Giter Site home page Giter Site logo

rescript-logger's Introduction

rescript-logger

npm version license

Logging implementation for ReScript.

rescript-logger

Features

  • Zero runtime in production builds.
  • Multiple logging levels.
  • Customizable verbosity.
  • [@log] helper.
  • @rescript/react integration.
  • Custom loggers.
  • Logging in libraries.

ShakaCode

If you are looking for help with the development and optimization of your project, ShakaCode can help you to take the reliability and performance of your app to the next level.

If you are a developer interested in working on ReScript / TypeScript / Rust / Ruby on Rails projects, we're hiring!

Installation

Get the package:

# yarn
yarn add rescript-logger
# or npm
npm install --save rescript-logger

Then add it to bsconfig.json:

"bs-dependencies": [
  "rescript-logger"
],
"ppx-flags": ["rescript-logger/ppx"]

Usage

There are 5 log levels:

  • trace
  • debug
  • info
  • warn
  • error

You can log message of specific level using appropriate macros:

// ReScript
%log.info("Info message")
%log.error("Error message")

// Reason
[%log.info "Info message"];
[%log.error "Error message"];

Additional data

You can add data to log entry like this:

// ReScript
%log.info(
  "Info message"
  ("Foo", 42)
)
%log.info(
  "Info message"
  ("Foo", {x: 42})
  ("Bar", [1, 2, 3])
)

// Reason
[%log.info "Info message"; ("Foo", 42)];
[%log.info
  "Info message";
  ("Foo", {x: 42});
  ("Bar", [1, 2, 3]);
];

Currently, logger can accept up to 7 additional entries.

Verbosity customization

Output verbosity can be customized by providing specific log level and/or code locations.

Log level

You can set maximum log level via environment variable RES_LOG.

Let's say you want to log only warnings and errors. To make it happen, run your build like this:

RES_LOG=warn rescript build

Available RES_LOG values:

  • *: log everything
  • trace: basically, the same as *
  • debug: log everything except trace level messages
  • info: log everything except trace & debug level messages
  • warn: log warn & error messages only
  • error: log error messages only
  • off: don't log anything

If RES_LOG is set to off, nothing will be logged and none of the log entries will appear in your JS assets.

In case if RES_LOG environment variable is not set, log level warn will be used.

Also, see Usage in libraries.

Code location

If you want to focus on logging from specific part(s) of your code, you can use RES_LOG_ONLY environment variable.

For example, if you want to see logs only from module Test, run the build as following:

RES_LOG_ONLY=Test rescript build

You can pass submodules and functions to it as well. If you want to log from multiple locations, separate them by ,.

Consider the following source:

// Test.res
%log.warn("Top level message")

module Submodule1 = {
  %log.warn("Message from Submodule1")
}

module Submodule2 = {
  %log.warn("Message from Submodule2")

  let fn = () => %log.warn("Message from function within Submodule2")
  fn()
}

Here is what will be logged with different build configurations:

# build
RES_LOG_ONLY=Test rescript build

# output
WARNING  [Test] Top level message
WARNING  [Test.Submodule1] Message from Submodule1
WARNING  [Test.Submodule2] Message from Submodule2
WARNING  [Test.Submodule2.fn] Message from function within Submodule2

# build
RES_LOG_ONLY=Test.Submodule2 rescript build

# output
WARNING  [Test.Submodule2] Message from Submodule2
WARNING  [Test.Submodule2.fn] Message from function within Submodule2

# build
RES_LOG_ONLY=Test.Submodule1,Test.Submodule2.fn rescript build

# output
WARNING  [Test.Submodule1] Message from Submodule1
WARNING  [Test.Submodule2.fn] Message from function within Submodule2

[@log] helper

This helper can be placed in front of any switch expression with constructor patterns and it will inject debug expressions into each branch.

// ReScript
let _ =
  x =>
    @log
    switch x {
    | A => "A"
    | B(b) => b
    }

// Reason
let _ =
  x =>
    [@log]
    switch (x) {
    | A => "A"
    | B(b) => b
    }

Without a @log helper, an equivalent would be:

// ReScript
let _ =
  x =>
    switch (x) {
    | A =>
      %log.debug("A")
      "A"
    | B(b) =>
      %log.debug("B with payload" ("b", b))
      b
    }

// Reason
let _ =
  x =>
    switch (x) {
    | A =>
      [%log.debug "A"];
      "A";
    | B(b) =>
      [%log.debug "B with payload"; ("b", b)];
      b;
    }

You can pass optional custom namespace to helper like this: @log("MyNamespace").

[@log] helper works only for switch expressions with constructor patterns, for now. Let us know in the issues if you need to handle more cases.

@rescript/react integration

Using @log helper, you can log dispatched actions in your components.

Annotate reducer function like this:

// ReScript
let reducer =
  (state, action) =>
    @log
    switch action {
      ...
    }

// Reason
let reducer =
  (state, action) =>
    [@log]
    switch (action) {
      ...
    }

These entries are logged on the debug level so none of it will appear in your production builds.

Custom loggers

rescript-logger ships with 4 loggers:

  • ReScriptLogger.Browser (default)
  • ReScriptLogger.Node
  • ReScriptLogger.Edge (to use in edge environment, such as Cloudflare Workers)
  • ReScriptLogger.Universal (picks either Browser or Node logger at runtime depending on an environment, useful for apps with SSR)

And you can easily plug your own.

For example, in development, you want to log everything to console using default logger, but in production, you want to disable console logging and send error level events to bug tracker.

To implement your own logger, you need to create a module (e.g. BugTracker.re) and set the following environment variables for production build.

RES_LOG=error
RES_LOGGER=BugTracker

Considering that you want to log only error level messages, you need to create functions only for errors logging.

// BugTracker.res

let error = (loc, msg) => BugTrackerSDK.notify(`${msg} in ${loc.rootModule}`)

let error1 =
  (
    loc,
    msg,
    (label, payload),
  ) =>
    BugTrackerSDK.notify(
      `${msg} in ${loc.rootModule}`,
      [|(label, payload)|],
    );

let error2 =
  (
    loc,
    msg,
    (label1, payload1),
    (label2, payload2),
  ) =>
    BugTrackerSDK.notify(
      `${msg} in ${loc.rootModule}`,
      [|
        (label1, payload1),
        (label2, payload2),
      |],
    );

// Up to 7

The first argument loc is a ReScriptLogger.Location.t record. It's passed by PPX and contains the location data.

type t = {
  rootModule: string,
  subModulePath: list<string>,
  value: option<string>,
  fullPath: string,
  filePath: string,
}

If Test.Submodule.fn gets called, logger would receive the following location:

// Test.res
module Submodule = {
  let fn = () => %log.warn("Warn!")
}

// Location
{
  rootModule: "Test",
  subModulePath: list{"Submodule"},
  value: Some("fn"),
  fullPath: "Test.Submodule.fn",
  filePath: "/absolute/path/to/project/src/Test.res",
}

Note, you don't have to re-implement all functions from the default logger, only the ones you actually use. Don't worry to forget to implement something. If later on, you will attempt to use unimplemented method it will be compile time error.

Usage in libraries

If you develop a library and want to use rescript-logger during development process, you can do so without spamming output of consumers of your library.

rescript-logger/ppx accepts --lib flag:

"ppx-flags": [
  ["rescript-logger/ppx", "--lib=my-lib"]
]

Once this flag is passed, you need to provide special value of RES_LOG to log your entries:

RES_LOG=my-lib=* rescript build

If consumers of your lib would like to see log output from your lib, they can do so too by extending a value of RES_LOG variable:

RES_LOG=*,my-lib=error rescript build

Few more examples to illustrate how it works:

# log everything from application code only
RES_LOG=* rescript build

# log everything from application code
# log errors from `my-lib`
RES_LOG=*,my-lib=error rescript build

# log everything from application code
# log errors from `my-lib-1`
# log warnings and errors from `my-lib-2`
RES_LOG=*,my-lib-1=error,my-lib-2=warn rescript build

Caveats

Logging is disabled after file save
If you run bsb via editor integration, make sure editor picked up RES_LOG variable. E.g. if you use Atom run it like this:

RES_LOG=info atom .

If your editor is telling you, variables used in ppx are unused, you can either:

  1. prefix such variables with _
  2. or open editor with RES_LOG variable set to appropriate level.

Changing value of RES_LOG/RES_LOGGER/RES_LOG_ONLY doesn't make any effect
When you change a value of environment variable, rescript clean before the next build.

Developing

Repo consists of 2 parts:

  • ReScript lib: dependencies are managed by yarn
  • OCaml PPX: dependencies are managed either by nix (in development) or esy (in development and/or on CI)

Nix flow

Clone repo and either enter the Nix shell:

nix-shell

Or use direnv and create .envrc file in the root directory of the project with the folowing content:

use nix

Then install deps:

yarn install

Build loggers and ppx:

dune build
cd lib && yarn run build
cd ../examples && yarn run build

Devbox flow

Clone repo and either enter the Devbox shell:

devbox shell

Or use direnv and create .envrc file in the root directory of the project by running:

devbox generate direnv --print-envrc

Then install deps:

yarn install

Build loggers and ppx:

devbox run build
cd lib && yarn run build
cd ../examples && yarn run build

Esy flow

Clone repo and install deps:

esy install
yarn install

Build loggers and ppx:

esy build
cd lib && yarn run build
cd ../examples && yarn run build

Auto-formatting

Note, this project doesn't use auto-formatting in OCaml files (*.ml), so if you're intended to contribute, please, turn off auto-formatting in the editor while editing such files.

Supporters

JetBrains ScoutAPM
BrowserStack Rails Autoscale Honeybadger

The following companies support our open source projects, and ShakaCode uses their products!

rescript-logger's People

Contributors

alex35mil avatar anler avatar baransu avatar cmdumar avatar lewwadoo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

rescript-logger's Issues

Universal Logger Support?

Hi. Really cool library. I was wondering if it would be possible to support detecting whether logger is called from browser or from Node and offer a unified API to be used in code that is ran in both server and client.

Then there could be an API like this:

Logger.error(__MODULE__, "My Error Message");

A quick googling gave me this:
https://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js
Although possibly just checking if window exists might be enough.

ppx behavior when BS_LOG env var doesn't exist

In the current version, ppx relies on value of BS_LOG env var to figure if messages of specific level should be logged or not. And if BS_LOG doesn't exist, it logs nothing. I want to change this to some default level, most likely info, so in case if deploy pipeline didn't set it due to misconfiguration, info/warning/error messages are still dispatched (e.g. to bug tracker). Yay or nay? If yay, which level should be set by default?

Use of console.group prevents grouping of similar logs

At least Chrome can group repeating similar log messages. The use of console.group() and console.groupCollapsed() in BrowserLogger seems to prevent that feature, which is unfortunate especially for often repeating debug output.

Support for comas between arguments (so log calls with arguments can span one line, instead of several)

Hi,

Thanks for building rescript-logger, it looks pretty neat!

I noticed that %log.info("foo" ("bar", 42)) leads to the error This expression has type string. It is not a function..

Instead, we have to put all arguments of the macro on a separate line:

%log.info(
  "Login"
  ("loginUrl", loginUrl))

I think, that just adds unnecessary verbosity, and I'm wondering if "single-line-logging-with-arguments" can somehow be supported. For example, by allowing commas between arguments: %log.info("foo", ("bar", 42)).

My current workaround is, to use template strings, instead of the built-in 'argument' feature:

%log.info(`foo bar=${42 -> Js.String.make}`)

...however, that isn't perfect, either.

Question: ppx arm64 on linux? (needed for docker on M1/M2 Mac)

Hi, just upfront I'd like to say I really love this product, thanks for all the work you're putting into it.

Recently I started to dockerize an app, and realized that on an OSX M1/M2, I cannot compile the app with rescript inside a docker container.

The changelog says that in v4 the arm64 linux compiler is removed, so this is expected. By the way, I also fail to compile with v3 for different reason (complains about ninja.exe, I tried both in alpine and debian based containers and failed with both).

That said, I can live with this, as the most important thing is to compile on the target machine which is x64, and this is working. However for development and other purposes, it would be useful if I could build the docker image locally. So I wonder if this could be achieved somehow.

  • the arm64 linux target could be put back and supported again in a working way? All other ppx packages that my app depends on, work for me by the way.
  • maybe there is another way to successfully compile on arm64 linux? and if yes, maybe you can give me a hint how this is possible?

Thanks for your reply in advance!

Bug: @log does not work on cases prefixed with module name

All case which have a prefix won't be logged. In the generated js files the code for logging is missing. It took a while to find out but now i have a minimal example:

// RSLoggerBugExample.res
module Page = {
  type t =
    | Home
    | Profile
    | Welcome
}

@react.component
let make = (~page) =>
  @log
  switch page {
  | Page.Welcome => "Hello World"->React.string
  | Home => "Hello Home"->React.string
  | Page.Profile => "Hello Profile"->React.string
  }
// RSLoggerBugExample.bs.js
function RSLoggerBugExample(Props) {
  var page = Props.page;
  switch (page) {
    case /* Home */0 :
        Browser$ReScriptLogger.debug({
              rootModule: "RSLoggerBugExample",
              subModulePath: /* [] */0,
              value: "make",
              fullPath: "RSLoggerBugExample.make",
              filePath: "absolute path to the res file which a can not disable, but that is another issue :D"
            }, "Home");
        return "Hello Home";
    case /* Profile */1 :
        return "Hello Profile";
    case /* Welcome */2 :
        return "Hello World";
  }
}

I got this issue originally in the old reason-syntax. But I could reproduce this bug also in rescript. I'm using [email protected] & [email protected]

Feature Request: A way of turning off generation of absolute path in js files

I'm checking in .bs.js files in to version control and i don't want system specific info inside the repo, because it changes as e.g. a college recompiles the files and would have changed files to commit even though he didn't change anything. Right now we are rebuilding with RS_LOG=off before committing to work around this issue.

  Browser$ReScriptLogger.debug({
              rootModule: "SomeModule",
              subModulePath: /* [] */0,
              value: "make",
              fullPath: "SomeModule.make",
              filePath: "absolute_file_path/SomeModule.res" // <--- I want this to be relative
            }, "Home");

I researched a little and found that ppxlib seems to have a way for stripping a prefix (see file_path.ml#L3)
I thought maybe i could pass it like

  "ppx-flags": [
    ["rescript-logger/ppx", "-prefix=./"]
  ]

I know that is principally possible. graphql_ppx for example offers this (graphql_ppx#configuration)
I could be misunderstanding the ppxlib sources of course ๐Ÿ˜…
But i wasn't able to find out if or how i could pass a prefix argument. If that would work and you know how, please tell me.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.