Giter Club home page Giter Club logo

tslog's Introduction

๐Ÿ“ tslog: Beautiful logging experience for TypeScript and JavaScript

lang: Typescript License: MIT npm version CI: GitHub codecov.io code style: prettier GitHub stars

Powerful, fast and expressive logging for TypeScript and JavaScript

tslog pretty output

Highlights

โšก Fast and powerful
๐Ÿชถ Lightweight and flexible
๐Ÿ— Universal: Works in Browsers and Node.js
๐Ÿ‘ฎโ€๏ธ Fully typed with TypeScript support (native source maps)
๐Ÿ—ƒ Pretty or JSON output
๐Ÿ“ Customizable log level
โญ•๏ธ Supports circular structures
๐Ÿฆธ Custom pluggable loggers
๐Ÿ’… Object and error interpolation
๐Ÿค“ Stack trace and pretty errors
๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Sub-logger with inheritance
๐Ÿ™Š Mask/hide secrets and keys
๐Ÿ“ฆ CJS & ESM with tree shaking support
โœ๏ธ Well documented and tested

Example

import { Logger, ILogObj } from "tslog";

const log: Logger<ILogObj> = new Logger();
log.silly("I am a silly log.");

Donations help me allocate more time for my open source work.

Install

npm install tslog

In order to run a native ES module in Node.js, you have to do two things:

  1. Set "type": "module" in package.json.
  2. For now, start with --experimental-specifier-resolution=node

Example package.json

{
  "name": "NAME",
  "version": "1.0.0",
  "main": "index.js",
  // here:
  "type": "module",
  "scripts": {
    "build": "tsc -p .",
    // and here:
    "start": "node --enable-source-maps --experimental-specifier-resolution=node index.js"
  },
  "dependencies": {
    "tslog": "^4"
  },
  "devDependencies": {
    "typescript": "^4"
  },
  "engines": {
    "node": ">=16"
  }
}

With this package.json you can simply build and run it:

npm run build
npm start

Otherwise:

ESM: Node.js with JavaScript:

node --enable-source-maps --experimental-specifier-resolution=node

CJS: Node.js with JavaScript:

node --enable-source-maps

ESM: Node.js with TypeScript and ts-node:

node --enable-source-maps --experimental-specifier-resolution=node --no-warnings --loader ts-node/esm

CJS: Node.js with TypeScript and ts-node:

node --enable-source-maps --no-warnings --loader ts-node/cjs

Browser:

<!doctype html>
<html lang="en">
<head>
<title>tslog example</title>
</head>
<body>
<h1>Example</h1>

<script src="tslog.js"></script>

<script>
  const logger = new tslog.Logger();
  logger.silly("I am a silly log.");
</script>

</body>
</html>

Enable TypeScript source map support:

This feature enables tslog to reference a correct line number in your TypeScript source code.

// tsconfig.json
{
  // ...
  compilerOptions: {
    // ...
    "inlineSourceMap": true,  // <!-- here
    // we recommend using a current ES version
    target: "es2020",
  },
}

Simple example

import { Logger } from "tslog";

const logger = new Logger({ name: "myLogger" });
logger.silly("I am a silly log.");
logger.trace("I am a trace log.");
logger.debug("I am a debug log.");
logger.info("I am an info log.");
logger.warn("I am a warn log with a json object:", { foo: "bar" });
logger.error("I am an error log.");
logger.fatal(new Error("I am a pretty Error with a stacktrace."));

All Features

  • Universal: Works in browsers and Node.js
  • Tested: 100% code coverage, CI
  • Super customizable: Every aspect can be overwritten
  • Fully typed: Written in TypeScript, with native TypeScript support
  • Default log level: silly, trace, debug, info, warn, error, fatal (different colors)
  • Customizable log level: BaseLogger with configurable log level
  • Pretty & JSON output: Structured/pretty, JSON or suppressed output
  • Attachable transports: Send logs to an external log aggregation services, file system, database, or email/slack/sms/you name it...
  • Minimum log level per output: minLevel level can be set individually per transport
  • Native source maps lookup: Shows exact position also in TypeScript code (compile-to-JS), one click to IDE position
  • Pretty Error: Errors and stack traces printed in a structured way and fully accessible through JSON (e.g. external Log services)
  • ES Modules: import syntax with (tree-shaking)
  • Object/JSON highlighting: Nicely prints out objects
  • Instance Name: (Server-side only) Logs capture instance name (default host name) making it easy to distinguish logs coming from different instances
  • Named Logger: Logger can be named (e.g. useful for packages/modules and monorepos)
  • Sub-logger with inheritance: Powerful sub-loggers with settings inheritance, also at runtime
  • Secrets masking: Prevent passwords and secrets from sneaking into log files by masking them
  • Short paths: Paths are relative to the root of the application folder
  • Prefixes: Prefix log messages and bequeath prefixes to child loggers

API documentation

tslog >= v4 is a major rewrite and introduces breaking changes.
Please, follow this documentation when migrating.

Lifecycle of a log message

Every incoming log message runs through a number of steps before being displayed or handed over to a "transport". Every step can be overwritten and adjusted.

tslog pretty output

  • log message Log message comes in through the BaseLogger.log() method
  • mask If masking is configured, log message gets recursively masked
  • toLogObj Log message gets transformed into a log object: A default typed log object can be passed to constructor as a second parameter and will be cloned and enriched with the incoming log parameters. Error properties will be handled accordingly. If there is only one log property, and it's an object, both objects (cloned default logObj as well as the log property object) will be merged. If there are more than one, they will be put into properties called "0", "1", ... and so on. Alternatively, log message properties can be put into a property with a name configured with the argumentsArrayName setting.
  • addMetaToLogObj Additional meta information, like date, runtime and source code position of the log will be gathered and added to the _meta property or any other one configured with the setting metaProperty.
  • format In case of "pretty" configuration, a log object will be formatted based on the templates configured in settings. Meta will be formatted by the method _prettyFormatLogObjMeta and the actual log payload will be formatted by prettyFormatLogObj. Both steps can be overwritten with the settings formatMeta and formatLogObj.
  • transport Last step is to "transport" a log message to every attached transport from the setting attachedTransports. Last step is the actual transport, either JSON (transportJSON), formatted (transportFormatted) or omitted, if its set to "hidden". Both default transports can also be overwritten by the corresponding setting.

โ—Performance

By default, tslog is optimized for the best developer experience and includes some default settings that may impact performance in production environments. To ensure optimal performance in production, we recommend modifying these settings, such as hideLogPositionForProduction(s. below), as needed.

Default log level

tslog comes with default log level 0: silly, 1: trace, 2: debug, 3: info, 4: warn, 5: error, 6: fatal.

Tip: Each logging method has a return type, which is a JSON representation of the log message (ILogObj).

import { Logger } from "tslog";

const log = new Logger();
log.silly("I am a silly log.");
log.trace("I am a trace log.");
log.debug("I am a debug log.");
log.info("I am an info log.");
log.warn("I am a warn log with a json object:", { foo: "bar" });
log.error("I am an error log.");
log.fatal(new Error("I am a pretty Error with a stacktrace."));

Custom log level

In addition to the default log level, custom log level can be defined in the same way tslog does it under the hood, by extending the BaseLogger and utilizing the log method. log method expects the following parameters:

  • logLevelId - Log level ID e.g. 0
  • logLevelName - Log level name e.g. silly
  • args - Multiple log attributes that should be logged.

Tip: Also the generic logging method (log()) returns a JSON representation of the log message (ILogObject).

import { BaseLogger, ILogObjMeta, ISettingsParam, ILogObj } from "./BaseLogger";

export class CustomLogger<LogObj> extends BaseLogger<LogObj> {
  constructor(settings?: ISettingsParam<LogObj>, logObj?: LogObj) {
    super(settings, logObj, 5);
  }

  /**
   * Logs a _CUSTOM_ message.
   * @param args  - Multiple log attributes that should be logged.
   * @return LogObject with meta property, when log level is >= minLevel
   */
  public custom(...args: unknown[]): LogObj & ILogObjMeta | undefined {
    return super.log(8, "CUSTOM", ...args);
  }

}

Sub-logger

Each tslog-Logger instance can create sub-loggers and bequeath its settings to a child. It is also possible to overwrite the LogObj when creating a child.
Sub-loggers are a powerful feature when building a modular application and due to its inheritance make it easy to configure the entire application.

Use getSubLogger() to create a child logger based on the current instance.

Example:

const mainLogger = new Logger({ type: "pretty", name: "MainLogger" });
mainLogger.silly("foo bar");

const firstSubLogger = mainLogger.getSubLogger({ name: "FirstSubLogger" });
firstSubLogger.silly("foo bar 1");

Sub-logger with LogObj

You can also overwrite the LogObj(s. below), when you create a sub-logger:

const mainLogObj = { main: true, sub: false };
const mainLogger = new Logger({ type: "pretty", name: "MainLogger" }, mainLogObj);
mainLogger.silly("foo bar");

const subLogObj = { main: false, sub: true };
const firstSubLogger = mainLogger.getSubLogger({ name: "FirstSubLogger" }, subLogObj);
firstSubLogger.silly("foo bar 1");

Settings

tslog is highly customizable and pretty much every aspect can be either configured or overwritten. A settings object is the first parameter passed to the tslog constructor:

const logger = new Logger<ILogObj>({ /* SETTINGS */ }, defaultLogObject);
Changing settings at runtime

settings is a public property and can also be changed on runtime.

Example on changing minLevel on runtime:

    const logger = new Logger({
      minLevel: 1
    });
    
    // visible
    logger.log(1, "level_one", "LOG1");
    // visible
    logger.log(2, "level_two", "LOG2");
    
    // change minLevel to 2
    logger.settings.minLevel = 2;

    // hidden
    logger.log(1, "level_one", "LOG3");
    // visible
    logger.log(2, "level_two", "LOG4");

Type: pretty, json, hidden

  • pretty Default setting prints out a formatted structured "pretty" log entry.
  • json prints out a JSON formatted log entry.
  • hidden suppresses any output whatsoever and can be used with attached loggers for example.

Hint: Each JSON log is printed in one line, making it easily parsable by external services.

// pretty output
const defaultPrettyLogger = new Logger();

// also pretty output
const prettyLogger = new Logger({type: "pretty"});

// JSON output
const jsonLogger = new Logger({type: "json"});

// hidden output
const hiddenLogger = new Logger({type: "hidden"});

name

Each logger has an optional name. You can find the name of the logger responsible for a log inside the Meta-object or printed in pretty mode. Names get also inherited to sub-loggers and can be found inside the Meta-object parentNames as well as printed out with a separator (e.g. :) in pretty mode.

Simple name example:

new Logger({ name: "myLogger" });

Sub-loggers with an inherited name:

const mainLogger = new Logger({ type: "pretty", name: "MainLogger" });
mainLogger.silly("foo bar");

const firstSubLogger = mainLogger.getSubLogger({ name: "FirstSubLogger" });
firstSubLogger.silly("foo bar 1");

const secondSubLogger = firstSubLogger.getSubLogger({ name: "SecondSubLogger" });
secondSubLogger.silly("foo bar 2");

Output:

2022-11-17 10:45:47.705 SILLY   [/examples/nodejs/index2.ts:51 MainLogger]    foo bar
2022-11-17 10:45:47.706 SILLY   [/examples/nodejs/index2.ts:54 MainLogger:FirstSubLogger ]    foo bar 1
2022-11-17 10:45:47.706 SILLY   [/examples/nodejs/index2.ts:57 MainLogger:FirstSubLogger:SecondSubLogger]   foo bar 2

minLevel

You can ignore every log message from being processed until a certain severity. Default severities are: 0: silly, 1: trace, 2: debug, 3: info, 4: warn, 5: error, 6: fatal

const suppressSilly = new Logger({ minLevel: 1 });
suppressSilly.silly("Will be hidden");
suppressSilly.trace("Will be visible");

argumentsArrayName

tslog < 4 wrote all parameters into an arguments array. In tslog >= 4 the main object becomes home for all log parameters, and they get merged with the default logObj. If you still want to put them into a separated parameter, you can do so by defining the argumentsArrayName.

const logger = new Logger({
  type: "json",
  argumentsArrayName: "argumentsArray",
});
const logMsg = logger.silly("Test1", "Test2");

//logMsg : {
// argumentsArray: [ 'Test1', 'Test2' ],
//   _meta: {
//       [...]
//     }
//   }
// }

hideLogPositionForProduction (default: false)

By default, tslog gathers and includes the log code position in the meta information of a logObj o improve the developer experience. However, this can significantly impact performance and slow down execution times in production. To improve performance, you can disable this functionality by setting the option hideLogPositionForProduction to true.

Pretty templates and styles (color settings)

Enables you to overwrite the looks of a formatted "pretty" log message by providing a template string. Following settings are available for styling:

  • Templates:

    • prettyLogTemplate: template string for log messages. Possible placeholders:
      • {{yyyy}}: year
      • {{mm}}: month
      • {{dd}}: day
      • {{hh}}: hour
      • {{MM}}: minute
      • {{ss}}: seconds
      • {{ms}}: milliseconds
      • {{dateIsoStr}}: Shortcut for {{yyyy}}.{{mm}}.{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}}
      • {{rawIsoStr}}: Renders the date and time in ISO format (e.g.: YYYY-MM-DDTHH:mm:ss.SSSZ)
      • {{logLevelName}}: name of the log level
      • {{name}}: optional name of the current logger and his parents (e.g. "ParentLogger:ThisLogger")
      • {{nameWithDelimiterPrefix}}: optional name of the current logger (s. above) with a delimiter in the beginning
      • {{nameWithDelimiterSuffix}}: optional name of the current logger (s. above) with a delimiter at the end
      • {{fullFilePath}}: a full path starting from / root
      • {{filePathWithLine}}: a full path below the project path with line number
      • {{fileNameWithLine}}: file name with line number
    • prettyErrorTemplate: template string for error message. Possible placeholders:
      • {{errorName}}: name of the error
      • {{errorMessage}}: error message
      • {{errorStack}}: Placeholder for all stack lines defined by prettyErrorStackTemplate
    • prettyErrorStackTemplate: template string for error stack trace lines. Possible placeholders:
      • {{fileName}}: name of the file
      • {{fileNameWithLine}}: file name with line number
      • {{filePathWithLine}}: a full path below the project path with a line number
      • {{method}}: optional name of the invoking method
    • prettyErrorParentNamesSeparator: separator to be used when joining names ot the parent logger, and the current one (default: :)
    • prettyErrorLoggerNameDelimiter: if a logger name is set this delimiter will be added afterwards
    • prettyInspectOptions: Available options

    Customizing template tokens

    It's possible to add user defined tokes, by overwriting the addPlaceholders in the settings.overwrite. this callback allows to add or overwrite tokens in the placeholderValues. for example, to add the token: {{custom}};

    const logger = new Logger({
      type: "pretty",
      prettyLogTemplate: "{{custom}} ",
      overwrite: {
        addPlaceholders: (logObjMeta: IMeta, placeholderValues: Record<string, string>) => {
          placeholderValues["custom"] = "test";
        },
      },
    });

    this would yield in the token {{custom}} being replaced with "test"

  • Styling:

    • stylePrettyLogs: defines whether logs should be styled and colorized
    • prettyLogStyles: provides colors and styles for different placeholders and can also be dependent on the value (e.g. log level)
      • Level 1: template placeholder (defines a style for a certain template placeholder, s. above, without brackets).
      • Level 2: Either a string with one style (e.g. white), or an array of styles (e.g. ["bold", "white"]), or a nested object with key being a value.
      • Level 3: Optional nested style based on placeholder values. Key is the value of the template placeholder and value is either a string of a style, or an array of styles (s. above), e.g. { SILLY: ["bold", "white"] } which means: value "SILLY" should get a style of "bold" and "white". * means any value other than the defined.
    • prettyInspectOptions: When a (potentially nested) object is printed out in Node.js, we use util.formatWithOptions under the hood. With prettyInspectOptions you can define the output. Possible values
  • Time zone support:

    • prettyLogTimeZone: Set timezone of pretty log messages to either UTC (default) or local (based on your server/browser configuration)

Log meta information

tslog collects meta information for every log, like runtime, code position etc. The meta information collected depends on the runtime (browser or Node.js) and is accessible through the LogObj. You can define the property containing this meta information with metaProperty, which is "_meta" by default.

Pretty templates and styles (color settings)

const logger = new Logger({
  prettyLogTemplate: "{{yyyy}}.{{mm}}.{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}}\t{{logLevelName}}\t[{{filePathWithLine}}{{name}}]\t",
  prettyErrorTemplate: "\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}",
  prettyErrorStackTemplate: "  โ€ข {{fileName}}\t{{method}}\n\t{{filePathWithLine}}",
  prettyErrorParentNamesSeparator: ":",
  prettyErrorLoggerNameDelimiter: "\t",
  stylePrettyLogs: true,
  prettyLogTimeZone: "UTC",
  prettyLogStyles: {
    logLevelName: {
      "*": ["bold", "black", "bgWhiteBright", "dim"],
      SILLY: ["bold", "white"],
      TRACE: ["bold", "whiteBright"],
      DEBUG: ["bold", "green"],
      INFO: ["bold", "blue"],
      WARN: ["bold", "yellow"],
      ERROR: ["bold", "red"],
      FATAL: ["bold", "redBright"],
    },
    dateIsoStr: "white",
    filePathWithLine: "white",
    name: ["white", "bold"],
    nameWithDelimiterPrefix: ["white", "bold"],
    nameWithDelimiterSuffix: ["white", "bold"],
    errorName: ["bold", "bgRedBright", "whiteBright"],
    fileName: ["yellow"],
    fileNameWithLine: "white",
  },
});

Masking secrets in logs

One of the most common ways of a password/secret breach is through log files. Given the central position of tslog as the collecting hub of all application logs, it's only natural to use it as a filter. There are multiple ways of masking secrets, before they get exposed:

  • maskPlaceholder: Placeholder to replaced masked secrets with, Default: [***]
  • maskValuesOfKeys: Array of keys to replace the values with the placeholder (maskPlaceholder). Default: ["password"]
  • maskValuesOfKeysCaseInsensitive: Should the keys be matched case-insensitive (e.g. "password" would replace "password" as well as "Password", and "PASSWORD"). Default: false
  • maskValuesRegEx: For even more flexibility, you can also replace strings and object values with a RegEx.

Prefixing logs

Prefix every log message with an array of additional attributes.
Prefixes propagate to sub-loggers and can help to follow a chain of promises.
In addition to AsyncLocalStorage, prefixes can help further distinguish different parts of a request.

Hint: A good example could be a GraphQL request, that by design could consist of multiple queries and/or mutations.

Example:

const logger = new Logger({
  prefix: ["main-prefix", "parent-prefix"],
});
logger.info("MainLogger message");
// Output:
// main-prefix parent-prefix MainLogger message

const childLogger = logger.getSubLogger({
  prefix: ["child1-prefix"],
});
childLogger.info("child1 message");
// Output:
// main-prefix parent-prefix child1-prefix MainLogger message

const grandchildLogger = childLogger.getSubLogger({
  prefix: ["grandchild1-prefix"],
});
grandchildLogger.silly("grandchild1 message");
// Output:
// main-prefix parent-prefix child1-prefix grandchild1-prefix grandchild1 message

Attach additional transports

tslog focuses on the one thing it does well: capturing logs. Therefore, there is no built-in file system logging, log rotation, or similar. Per default all logs go to console, which can be overwritten (s. below).

However, you can easily attach as many transports as you wish, enabling you to do fancy stuff like sending messages to Slack or Telegram in case of an urgent error or forwarding them to a log aggregator service.

Attached transports are also inherited by sub-loggers.

Simple transport example

Here is a very simple implementation used in our jest tests. This example will suppress logs from being sent to console (type: "hidden") and will instead collect them in an array.

const transports: any[] = [];
const logger = new Logger({ type: "hidden" });

logger.attachTransport((logObj) => {
  transports.push(logObj);
});

const logMsg = logger.info("Log message");
Storing logs in a file

Here is an example of how to store all logs in a file.

import { Logger } from "tslog";
import { appendFileSync } from "fs";

const logger = new Logger();
logger.attachTransport((logObj) => {
  appendFileSync("logs.txt", JSON.stringify(logObj) + "\n");
});

logger.debug("I am a debug log.");
logger.info("I am an info log.");
logger.warn("I am a warn log with a json object:", { foo: "bar" });
Storing logs in a file system with rotating files

If you want to limit the file size of the stored logs, a good practice is to use file rotation, where old logs will be deleted automatically. There is a great library called rotating-file-stream solving this problem for us and even adding features like compression, file size limit etc.

  1. First you need to install this library:
  npm i rotating-file-stream
  1. Combine it with tslog:
import { Logger } from "tslog";
import { createStream } from "rotating-file-stream";

const stream = createStream("tslog.log", {
  size: "10M", // rotate every 10 MegaBytes written
  interval: "1d", // rotate daily
  compress: "gzip", // compress rotated files
});

const logger = new Logger();
logger.attachTransport((logObj) => {
  stream.write(JSON.stringify(logObject) + "\n");
});

logger.debug("I am a debug log.");
logger.info("I am an info log.");
logger.warn("I am a warn log with a json object:", { foo: "bar" });

Overwriting default behavior

One of the key advantages of tslog >= 4 is that you can overwrite pretty much every aspect of the log processing described in "Lifecycle of a log message".

For every log:

    const logger = new Logger({
  overwrite: {
    mask: (args: unknown[]): unknown[] => {
      // mask and return an array of log attributes for further processing
    },
    toLogObj: (args: unknown[], clonesLogObj?: LogObj): unknown => {
      // convert the log attributes to a LogObj and return it
    },
    addMeta: (logObj: any, logLevelId: number, logLevelName: string) => {
      // add meta information to the LogObj and return it
    }

  },
});

For pretty logs:

    const logger = new Logger({
      type: "pretty",
      overwrite: {
        formatMeta: (meta?: IMeta) => {
          // format LogObj meta object to a string and return it
        },
        formatLogObj: <LogObj>(maskedArgs: unknown[], settings: ISettings<LogObj>) => {
            // format LogObj attributes to a string and return it
        },
        transportFormatted: (logMetaMarkup: string, logArgs: unknown[], logErrors: string[], settings: unknown) => {
          // overwrite the default transport for formatted (e.g. pretty) log levels. e.g. replace console with StdOut, write to file etc.
        },
      },
    });

For JSON logs (no formatting happens here):

    const logger = new Logger({
      type: "pretty",
      overwrite: {
        transportJSON: (logObjWithMeta: any) => {
          // transport the LogObj to console, StdOut, a file or an external service
        },
      },
    });

Defining and accessing logObj

As described in "Lifecycle of a log message", every log message goes through some lifecycle steps and becomes an object representation of the log with the name logObj. A default logObj can be passed to the tslog constructor and will be cloned and merged into the log message. This makes tslog >= 4 highly configurable and easy to integrate into any 3rd party service. The entire logObj will be printed out in JSON mode and also returned by every log method.

Tip: All properties of the default LogObj containing function calls will be executed for every log message making use cases possible like requestId (s. below).

interface ILogObj {
    foo: string;
}

const defaultLogObject: ILogObj = {
  foo: "bar",
};

const logger = new Logger<ILogObj>({ type: "json" }, defaultLogObject);
const logMsg = logger.info("Test");

// logMsg: {
//  '0': 'Test',
//  foo: 'bar',
//  _meta: {
//    runtime: 'Nodejs',
//    hostname: 'Eugenes-MBP.local',
//    date: 2022-10-23T10:51:08.857Z,
//    logLevelId: 3,
//    logLevelName: 'INFO',
//    path: {
//      fullFilePath: 'file:///[...]/tslog/examples/nodejs/index.ts:113:23',
//      fileName: 'index.ts',
//      fileColumn: '23',
//      fileLine: '113',
//      filePath: '/examples/nodejs/index.ts',
//      filePathWithLine: '/examples/nodejs/index.ts:113'
//    }
//  }
//}

Backwards compatibility

tslog follows a semantic release policy. A major version change indicates breaking changes.

tslog >=4 is less limiting when it comes to configuration. There are many use cases (especially when it comes to integration with 3rd party services) that now can be achieved elegantly and were not possible before.

RequestID: Mark a request (e.g. HTTP) call with AsyncLocalStorage and tslog

Node.js 13.10 introduced a new feature called AsyncLocalStorage.

** Keep track of all subsequent calls and promises originated from a single request (e.g. HTTP).**

In a real world application a call to an API would lead to many logs produced across the entire application. When debugging it can be quite handy to be able to group all logs based on a unique identifier, e.g. requestId.

Some providers (e.g. Heroku) already set a X-Request-ID header, which we are going to use or fallback to a short ID generated by nanoid.

In this example every subsequent logger is a sub-logger of the main logger and thus inherits all of its settings making requestId available throughout the entire application without any further ado.

tslog works with any API framework (like Express, Koa, Hapi and so on), but in this example we are using Koa.

  import { AsyncLocalStorage } from "async_hooks";
  import Koa from "koa";
  import { customAlphabet } from "nanoid";
  import { Logger } from "tslog";

  interface ILogObj {
    requestId?: string | (() => string | undefined);
  }

  const asyncLocalStorage: AsyncLocalStorage<{ requestId: string }> = new AsyncLocalStorage();

  const defaultLogObject: ILogObj = {
    requestId: () => asyncLocalStorage.getStore()?.requestId,
  };

  const logger = new Logger<ILogObj>({ type: "json" }, defaultLogObject);
  export { logger };

  logger.info("Test log without requestId");

  const koaApp = new Koa();

  /** START AsyncLocalStorage requestId middleware **/
  koaApp.use(async (ctx: Koa.Context, next: Koa.Next) => {
    // use x-request-id or fallback to a nanoid
    const requestId: string = (ctx.request.headers["x-request-id"] as string) ?? customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 6)();
    // every other Koa middleware will run within the AsyncLocalStorage context
    await asyncLocalStorage.run({ requestId }, async () => {
      return next();
    });
  });
  /** END AsyncLocalStorage requestId middleware **/

  // example middleware
  koaApp.use(async (ctx: Koa.Context, next) => {

    // log request
    logger.silly({ originalUrl: ctx.originalUrl, status: ctx.response.status, message: ctx.response.message });

    // also works with a sub-logger
    const subLogger = logger.getSubLogger();
    subLogger.info("Log containing requestId"); // <-- will contain a requestId

    return await next();
  });

  koaApp.listen(3000);

  logger.info("Server running on port 3000");

tslog's People

Contributors

ariesclark avatar barakd avatar beanaroo avatar coinwalletdev avatar dependabot[bot] avatar ejose19 avatar jonahsnider avatar kevintechie avatar kwonoj avatar lorefnon avatar markayers avatar mueckema avatar nini avatar nyrox avatar olafbuitelaar avatar quinnturner avatar sachaw avatar terehov avatar thomaschampagne avatar tomkeyte avatar withmask avatar zhen-dot 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  avatar  avatar  avatar  avatar  avatar  avatar

tslog's Issues

Bug: Passing Buffer object causes an error

Describe the bug

Version: 3.1.0

When a Buffer object is passed to logging methods, an error occurs.

To Reproduce

Run the code below:

import { Logger } from 'tslog'
const logger = new Logger()
const buffer = Buffer.from('foo')
logger.info(buffer)

Then, an error as below is thrown.

/srv # yarn ts-node reproduce-error.ts
yarn run v1.22.5
$ /srv/node_modules/.bin/ts-node reproduce-error.ts
2021-02-01 06:28:15.593  INFO [reproduce-error.ts:4 Object.<anonymous>] TypeError: Method get TypedArray.prototype.length called on incompatible receiver [object Object]
    at Buffer.get length [as length] (<anonymous>)
    at Buffer.inspect (buffer.js:826:39)
    at internal/per_context/primordials.js:23:32
    at formatValue (internal/util/inspect.js:764:19)
    at Object.inspect (internal/util/inspect.js:326:10)
    at Logger._inspectAndHideSensitive (/srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:885:26)
    at /srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:652:18
    at Array.forEach (<anonymous>)
    at Logger.printPrettyLog (/srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:632:30)
    at Logger._handleLog (/srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:348:14)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Expected behavior

I think a log similar to the output from console.log should be printed as below.

<Buffer 66 6f 6f>

Node.js Version
v14.13.1

OS incl. Version
Alpine Linux (node:14-alpine docker image)

exception when loging errorr: [BUG]

Describe the bug
when trying to log error object an exception is thrown

To Reproduce
Steps to reproduce the behavior:
write in the code the following code:
logger.error(new Error())

Expected behavior
should work

Additional context
TypeError: stack.reduce is not a function
at Function.getCallSites (/my-app/node_modules/tslog/src/LoggerHelper.ts:57:15)
at Logger._buildErrorObject (/my-app/node_modules/tslog/src/index.ts:451:60)
at /my-app/node_modules/tslog/src/index.ts:428:16
at Array.forEach ()
at Logger._buildLogObject (/my-app/node_modules/tslog/src/index.ts:425:28)
at Logger._handleLog (/my-app/node_modules/tslog/src/index.ts:351:40)
at Logger.error (/my-app/node_modules/tslog/src/index.ts:304:28)

this happend because error.stack is string and not array

const stack: NodeJS.CallSite[] =
error == null
? ((new Error().stack as unknown) as NodeJS.CallSite[]).slice(1)
: ((error.stack as unknown) as NodeJS.CallSite[]);

Feature Request: Allow custom delimiters

Description / Use Case for a Feature
The use case is for formatting the logs with more customization.

Right now my logs look like:
image

I would like to be able to not have all the spaces between the meta information but still have the colored output with log levels and time. Is that possible or would it be a feature request?

Feature Request: React-Native

I try to use your library in my react-native project.

After yarn add tslog and use it in project, I have an errors like:

Unable to resolve module `os` from `node_modules/tslog/dist/index.js`

I suppose, that this os module is node based dependency. In react-native we can not access to node features. Can you adapt your library to use it specific environment, like react native?

Feature Request: [Feature] tree-shakable dist output support

Description / Use Case for a Feature

This is feature request to support module field in package.json allows bundler can pick up tree-shakable code. Whole size of tslog itself is relatively small enough, but I hope tslog could expose import paths to skip few deps like source-map as well to squeeze size: it may possible by having export site in top level export (index.ts), but should be esm syntax to allow tree shaking.

Below's size composition for tslog which source-map takes more than half.
image

I do not have a strong opinion if it should be true esm or cjs output with esm syntax only - but given native esm support is not wide enough yet, native esm could wait longer.

Happy to try PR if you don't mind.

Bug: Doesn't mask password keys when type is `json`

Describe the bug
It doesn't mask password when type is json

To Reproduce
tslog is v2.10.0

When type is pretty, it masks password key

const log = new Logger()
log.info({ name: 'name1', password: 'secretPassword' })

output:

0|worker  | 2020-11-03 15:42:45.388.000
0|worker  |  INFO
0|worker  | [src/util.js:25]
0|worker  |
0|worker  | {
0|worker  |   name: 'name1',
0|worker  |   password: '[***]'
0|worker  | }

It works as expected and the password was masked.

But when type is json

const log = new Logger({type: 'json'})
log.info({ name: 'name1', password: 'secretPassword' })

Output:

0|worker  | {"instanceName":"localhost","hostname":"localhost","date":"2020-11-03T15:50:11.733Z","logLevel":"info","logLevelId":3,"filePath":"src/util.js","fullFilePath":"/src/util.js","fileName":"util.js","lineNumber":25,"columnNumber":5,"isConstructor":false,"functionName":null,"typeName":"Object","methodName":null,"argumentsArray":["{ name: 'name1', password: 'secretPassword' }"]}

Password value wasn't masked in this case

Expected behavior
Password value should be masked for type json

Node.js Version
v10.23.0

Bug: TypeError: errorCallSites.splice is not a function

Describe the bug
Got TypeError: errorCallSites.splice is not a function while trying to log uncaughtException.

To Reproduce

import { Logger } from "tslog";

const logger = new Logger();

function logError(err: any) {
    console.error(err);  // GOOD -> TypeError: Cannot read property 'id' of undefined
    logger.error(err); // !ERROR HERE -> TypeError: errorCallSites.splice is not a function
}

process.on("uncaughtException", logError);

let obj: any;
const id = obj.id; // generating uncaught exception

Expected behavior
Log error TypeError: Cannot read property 'id' of undefined

Additional context
Node.JS 12.18.0
typescript ^3.9.6
tslog ^2.6.0

Feature Request: Defaults / Formatter

Description / Use Case for a Feature

I'd really like to see a way to handle defaults with tslog. I appreciate requestId, hostname etc. but I also need to add a few more parameters as I intend to use your logger with Google's Stackdriver, which needs some additional values e.g. for labels, a service context and so on.

I can imagine that there are more use cases for custom defaults that will always be logged. Right now I'm using a transport where I attach that information. But this leads to the output deviating from what is actually being logged by tslog when not using the transport.

Maybe introducing formatters like other logging libraries do could also solve that problem by letting us mutate the arguments before they are sent to either the default transport or a custom one.

Bug: Can not run debug a script with tslog in vscode

Describe the bug

I'm not sure this is a issue in this lib or if the issue is with vscode debugger. But when I debug a script with tslog in vscode I get the following error:

Process exited with code 1
Uncaught Error: write EPIPE
Emitted 'error' event at: <node_internals>/internal/errors.js:463

To Reproduce

Steps to reproduce the behavior:

  1. Create a new project
$ mkdir tslog-test
$ cd tslog-test
$ npm init -y
$ npm install tslog
  1. Create a new javascript file and name it test.js:
const { Logger } = require('tslog');

const logger = new Logger();

logger.error('Hello');
console.log('The end');
  1. Create a launch.json file by following the GUI guide or create a new file and place it in .vscode/launch.json:
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "program": "${workspaceFolder}/test.js"
    }
  ]
}
  1. Open the debug panel in vscode och click on "Launch Program" and see the error in "DEBUG CONSOLE".

However, if I execute the script without a debugger it works fine:

$ node test.js
2020-09-16 10:46:16.834	 ERROR 	[test.js:5]  	  	Hello
The end

And it works also fine with nodes builtin debugger:

$ node inspect test.js
< Debugger listening on ws://127.0.0.1:9229/df1e3c02-dc43-40af-83f5-0cd1bc9c534f
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in test.js:1
> 1 const { Logger } = require('tslog');
  2
  3 const logger = new Logger();
debug> c
< 2020-09-16 10:47:29.536
<  ERROR
< [test.js:5]
< Hello
< The end
< Waiting for the debugger to disconnect...
debug>

Expected behavior
I expected to be able to use VScodes debugger with tslog.

Node.js Version
e.g. 12.8.0

OS incl. Version
macOS 10.15.6

Europe/Paris timezone bug

Hello, there is a slight problem with the display of the time for the Europe/Paris timezone, instead of 00h00 it changes to 24h00
Sans titre

Bug: Masking the password only if it is not the last key-value pair.

Describe the bug
TSLog masks password only if it is not the last value in the json.

To Reproduce
Steps to reproduce the behavior:

  1. mkdir tslogBugReplace
  2. cd tslogBugReplace
  3. npm init -y
  4. npm i tslog
  5. Open directory with your favourite text editor and create index.js
  6. Run the following code:
const { Logger } = require('tslog');
let log = new Logger();

log.info({ password: 'topsecret', user: 'test'});
// password is masked.

log.info({ user: 'test', password: 'topsecret' });
// password is not masked!

log.info({ user: 'test', password: 'topsecret', iWillMask: 'Because I exist' });
// Whew! It's masked again.
// Hint: util.inspect() does not add a comma after the last key in json.

// In the following example:
// Keys do not include password. 
// Only values have the keyword 'password' (case insensitive).
log.info({ 
  errorCode: 'WrongPasswordOrEmail', 
  message: 'Email or Password is wrong! Password, password, password!',
  anyKey: 'any value' // removing this key will result in unmasked message.
});
  1. node index.js or you can also see the screenshot below.

Expected behavior
Password should be masked regardless of the comma at the end when it was serialized for replacement with the mask string.

Screenshots

Additional context
I have seen the code for replacing the value of the key is using inspect from util library. A simple regex matching for every key-value pair is a bit hard to write. I am currently working on a depth first search approach, where each property is visited and modified then copied to a clone until a cycle is encountered. Finally I would call util.inpect() on the clone as well, and modify the result for pretty printing.

Bug: JSON type returning hostname and instance name when supposed to be hidden by default

Describe the bug
The json type is returning the full logObject regardless of the values provided to be hidden/displayed in the settings.

To Reproduce
Steps to reproduce the behavior:

  1. Create new Logger with settings param {type: 'json'}
  2. Pass a string to Logger instance to see the output.

Expected behavior
The log output when formatted as json should not display the values that are supposed to be hidden by default. Ie. hostname, instancename.

Node.js Version
14.16.0

OS incl. Version
macOS 10.15.7

Error when trying to access relevant call sites

I am currently trying to integrate tslog in a nextjs api function. I stumbled upon an error where relevantCallSites turned out to be an array of 3 elements. Since the default for ignoreStackLevels seems to be 3, it makes sense that undefined cannot be evaluated.

I haven't yet tracked down why my callsites only have a length of 3 but in any case, it'd be better to check if relevantCallSites.length > 0 and not pass it down in case there is no CallSite left.

const stackFrame: NodeJS.CallSite = this._callSiteWrapper(
relevantCallSites[0]
);

Bug: [BUG] Unhandled exception in `_maskValuesOfKeys`

Describe the bug
A clear and concise description of what the bug is.

With latest release, there are some unhandled exception being thrown like below:

exceptionStack: 'TypeError: Cannot set property devToolsWebContents of #<BrowserWindow> which has only a getter\n' +
    '    at C:\\github\\apps\\dist\\main.bundle.js:147454:30\n' +
    '    at Array.forEach (<anonymous>)\n' +
    '    at Function.traverseObjectRecursively (C:\\github\\apps\\dist\\main.bundle.js:147445:26)\n' +
    '    at Function.logObjectMaskValuesOfKeys (C:\\github\\apps\\dist\\main.bundle.js:147467:29)\n' +
    '    at Logger._maskValuesOfKeys (C:\\github\\apps\\dist\\main.bundle.js:148020:44)\n' +
    '    at Logger._inspectAndHideSensitive (C:\\github\\apps\\dist\\main.bundle.js:148013:35)\n' +
    '    at C:\\github\\apps\\dist\\main.bundle.js:147911:26\n' +
    '    at Array.forEach (<anonymous>)\n' +
    '    at Logger.printPrettyLog (C:\\github\\apps\\dist\\main.bundle.js:147899:34)\n' +
    '    at Object.writer (C:\\github\\apps\\dist\\main.bundle.js:161187:21)',

looks like introduced with a80f4a4. Assuming traverse tries to mutate given object payload somehow.

To Reproduce
Steps to reproduce the behavior:

clone https://github.com/kwonoj/tslog-getter-exception, npm ci && npm start

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context

Though this repro is written in electron, I suspect this could happen in plain node.js usecases. Electron's picked up as it was quickest way to find object have setter restriction while I work with.

Node.js Version

OS incl. Version

Bug: maskValuesOfKeys option mutates object properties!

To Reproduce

import { Logger } from "tslog";

const logger = new Logger();

const password = "123";
const someObj = {
    password: "123"
};

logger.info(someObj);
logger.info(password);

console.log(password === "123"); // true
console.log(someObj.password === "123"); // false

Additional context
v2.11.1

Node.js Version
v14.15.1

How to disable short path in log message?

Thanks for such an awesome library! I've been using it in next.js project, but unfortunately, I was not able to get helpful "short paths" due to the way how next.js works

Can I disable them? I looked at the available settings, but haven't found one.

Thanks!

image

Bug: Logs are blank in Sentry breadcrumbs

Describe the bug

Here's what tslog looks like in Sentry breadcrumbs with pretty logging:

image

This is captured to sentry in Node like this:

  } catch (error) {
    Sentry.captureException(error)
    throw error
  }

Not sure what the core issue is ๐Ÿคท

Expected behavior
Pretty logs would show up correctly

Bug: Logging URL objects fails

Describe the bug
TSlog version: 3.1.1

Similar to #86.
When logging a URL object, an error occurs.

root@88f15f987cd8:/srv# yarn ts-node src/tslogdebug.ts
yarn run v1.22.5
$ /srv/node_modules/.bin/ts-node src/tslogdebug.ts
2021-03-09 18:30:36.039  INFO [src/tslogdebug.ts:5 Object.<anonymous>] TypeError: Cannot convert undefined or null to object
    at getPrototypeOf (<anonymous>)
    at URL.[nodejs.util.inspect.custom] (internal/url.js:357:9)
    at internal/per_context/primordials.js:23:32
    at formatValue (internal/util/inspect.js:783:19)
    at Object.inspect (internal/util/inspect.js:337:10)
    at Logger._inspectAndHideSensitive (/srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:897:26)
    at /srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:664:18
    at Array.forEach (<anonymous>)
    at Logger.printPrettyLog (/srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:632:30)
    at Logger._handleLog (/srv/node_modules/tslog/src/LoggerWithoutCallSite.ts:348:14)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

To Reproduce
Steps to reproduce the behavior:
Run the code below.

import { Logger } from "tslog";

const logger = new Logger();
const url = new URL("http://example.com");
logger.info(url);

Additional context
Add any other context about the problem here.

Node.js Version
v14.16.0 (node:14.16.0-buster docker image)

OS incl. Version
Debian (buster) docker image on Debian (buster) host.

Not a bug: Color pallet

It's not a bug but some recommendations are needed. I'm using iTerm + Oh-My-Zsh with Powerlevel10k and can't get those nice colors you have in the screenshot any how.

Can you recommend what was used to get shiny and beautiful looking logs?

Here is what i see:
image

Bug: Instantiating logger breaks console.error()

Describe the bug
We are using tslog inside Blitz.js. It's really great, but noticed that it's causing a regression because it breaks the default behavior of console.error.

Before instantiating a tslog logger, console.error(new Error("Hello")) prints the following:

Error: Hello
    at Test (webpack-internal:///./pages/ssr.tsx:92:17)
    ...redacted

But after instantiating a logger, console.error(new Error("Hello")) prints this:

[Error: Hello]

As you can see, this is very bad because you can't tell where the error is from.

In this case, third-party code is using console.error, so it's not something we can change.

To Reproduce
Steps to reproduce the behavior:

  1. Run console.error(new Error("Hello"))
  2. See error with stack trace
  3. Instantiate logger: new Logger()
  4. Run console.error(new Error("Hello"))
  5. See there is no stack trace.

Node.js Version
Node v12

OS incl. Version

  System:
    OS: macOS 10.15.7
    CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
    Memory: 5.43 GB / 64.00 GB
    Shell: 3.1.2 - /usr/local/bin/fish

GraphQL JSON Parse error: Unrecognized token '<' error when using TSLog library

Hello,

I am currently running into an issue where upon adding a log using this library, my graphql query errors stop working and return the following error: JSON Parse error: Unrecognized token '<'. This only affects when i am expecting graphql to return an error, i am able to get data when i am expecting data. I am using apollo-server and graphql with this library.
I added the following parameters:
export const logger = new Logger({ minLevel: "debug", displayDateTime: true, dateTimeTimezone: "America/New_York", name: "console", overwriteConsole: true });

If more information is required please let me know. Thank you for your help!

How does this compare to pino, winston and co?

Hi there,

I came across tslog in search of a good logging library and it looks promising!
I'm currently mostly using pino but having some issues with it's API and other things.

I was wondering if you could give a quick summary on how this logging library compares to X, what you did differently, why and what's the overall philosophy of it?
I'm sort of tasked with defining some best practices wrt to logging and observability at my company.

When I say X, I mostly mean:

  • pino
  • winston
    i.e. the 2 most popular libs

Some features that I'm looking for usually:

  • json logging support (must have - I see tslog has it)
  • support for child loggers
  • support to change the log level globally at runtime (winston has that, pino doesn't)

Appreciate your thoughts!

tslog shouldn't modify global util.inspect styles by default

Describe the bug
Currently, tslog overwrites util.inspect styles by its own:

prettyInspectHighlightStyles: {
special: "cyan",
number: "green",
bigint: "green",
boolean: "yellow",
undefined: "red",
null: "red",
string: "red",
symbol: "green",
date: "magenta",
name: "white",
regexp: "red",
module: "underline",

tslog/src/LoggerHelper.ts

Lines 136 to 144 in 2760b41

public static setUtilsInspectStyles(
utilsInspectStyles: IHighlightStyles
): void {
Object.entries(utilsInspectStyles).forEach(
([symbol, color]: [string, TUtilsInspectColors]) => {
inspect.styles[symbol] = color;
}
);
}

which will affect other consumers of util.inspect, this shouldn't be enabled by default and instead by provided as an export (ie: CustomHighlightStyles that must be explicitly be used as:

import { Logger, CustomHighlightStyles } from "tslog";

const log = new Logger({ prettyInspectHighlightStyles: CustomHighlightStyles });

To Reproduce
Steps to reproduce the behavior:

  1. Create a logger with default options
  2. Use console.log(inspect({ foo: "bar" }, { colors: true }))
  3. Notice how the colors changed implicitly

Expected behavior
Global shouldn't be modified implicitly

Screenshots
N/A

Additional context
I'm aware util.inspect doesn't provide a way to create a custom inspect function (ie: new Inspect()), so in case this library needs to modify global inspect at least it should be explicitly (and with a docs warning).

Node.js Version
16.3.0

OS incl. Version
Archlinux

Bug: About maskStrings parameter

Describe the bug
When maskStrings parameter is used tslog is masking the keys as well and also adding an extra colon. Also the maskStrings works case sensitive unlike maskValuesOfKeys. See the screenshot below.

To Reproduce
Steps to reproduce the behavior:

  1. mkdir tslogBugMaskStrings
  2. cd tslogBugMaskStrings
  3. npm init -y
  4. npm i tslog
  5. Create a js file called index.js, touch index.js perhaps
  6. run the following code:
const { Logger } = require('tslog');
let log = new Logger({
  maskStrings: ['password'],
  maskValuesOfKeys: ['password', 'password_hash']
});

log.info({ 
  password: 'password',
  password_hash: 'hush-hush-hush',
  paSSword_column: "pass Pass password."
});

Expected behavior
There should be one colon after the key and if it is not intended, keys should not be masked.

Screenshots

Additional context
I added this as a separate issue to keep things cleaner with issue #37

Bug: Error thrown of setting a property name that only has a getter

Describe the bug
In some applications, ts-log will report the error (this is with ts-node, same errors produces in normal JavaScript!):

 TypeError  Cannot set property name of  which has only a getter
error stack:
โ€ข LoggerWithoutCallSite.ts:463 _buildErrorObject
    node_modules\tslog\src\LoggerWithoutCallSite.ts:463:21

โ€ข LoggerWithoutCallSite.ts:424 <anonymous>
    node_modules\tslog\src\LoggerWithoutCallSite.ts:424:16

โ€ข LoggerWithoutCallSite.ts:421 _buildLogObject
    node_modules\tslog\src\LoggerWithoutCallSite.ts:421:28

โ€ข LoggerWithoutCallSite.ts:332 _handleLog
    node_modules\tslog\src\LoggerWithoutCallSite.ts:332:40

โ€ข LoggerWithoutCallSite.ts:289 fatal
    node_modules\tslog\src\LoggerWithoutCallSite.ts:289:28

โ€ข main.ts:54 <anonymous>
    main.ts:54:60

โ€ข node:events:327 emit
    node:events:327:20

โ€ข node:domain:486 EventEmitter.emit
    node:domain:486:12

โ€ข source-map-support.js:495 process.emit
    C:\Users\<name>\AppData\Roaming\npm\node_modules\ts-node\node_modules\source-map-support\source-map-support.js:495:21

To Reproduce
This happens when you do:

const { Logger } = require('tslog');
const logger = new Logger();

class CustomError extends Error {
  get name() {
    return 'CustomError';
  }
}

try {
  throw new CustomError();
} catch(ex) {
  logger.fatal(ex);
  // logger.error(ex); also causes it!
}

Expected behavior
Shouldn't override setters (it seems?) and the error should be printed.

Node.js Version
v15.0.1

OS incl. Version
Windows 10 Pro (build 2004, 10.0.19041)

Bug: stack trace error

Describe the bug
After updating to 2.6.1 I got error in bluebird

/node_modules/bluebird/js/release/debuggability.js:768 var lastStackLines = (lastLineError.stack || "").split("\n");

seems like tslog converts error.stack into array

To Reproduce

const { Logger } = require("tslog");
const promise = require("bluebird");

const logger = new Logger();

async function run() {
    try {
        await promise.any([Promise.resolve()]);
    } catch (err) {
        logger.error(err);
    }
}
run();

Additional context
Node.JS 12.18.0
tslog ^2.6.1

Seems related #33

[Question]: disable all colors in `prettyPrintLog`

Description / Use Case for a Feature

If I read correctly, it looks like few cases colors are always applied, such as dateTime or logLevels:

I'm curious if there's way to disable all colors entirely when using prettyPrintLog.

Context: I'm trying to write pretty printed output into files, looks like

function transport(logObject): {
  logger.prettyPrintLog(fileWriteStream, logObject)
}

and it always ends up printing out color code in log output like

๏ฟฝ[90m10/28/2020, 22:54:32:930.000	๏ฟฝ[39m๏ฟฝ[1m๏ฟฝ[0m INFO ๏ฟฝ[0m๏ฟฝ[22m	

Having colors in stdout is still preferred - hope if there's way to have strings without color only for certain cases. Originally I thought creating child logger turns off all colors would allow to use prettyPrintLog without colors, but seems like there's no such option unless I miss something.

tslog should not modify the Error prototype

https://github.com/watson/error-callsites/blob/1d6e6cc416b56d535c608ed77980867bb7b345c9/index.js#L8-L21

Object.defineProperty(Error, "prepareStackTrace", {
configurable: true,
enumerable: true,
get: function () {
return prepareStackTrace;
},
set: function (fn?: (err: Error, stackTraces: NodeJS.CallSite[]) => string) {
// Don't set `lastPrepareStackTrace` to ourselves. If we did, we'd end up
// throwing a RangeError (Maximum call stack size exceeded).
lastPrepareStackTrace =
fn === prepareStackTrace
? fallback
: (fn as (err: Error, stackTraces: NodeJS.CallSite[]) => string);
},
});

tslog should not modify the global Error prototype. If modifying the prototype is necessary for certain features it should be explicitly enabled with an option.

This can cause issues in otherwise working code. For example, in a working API server, introducing tslog caused error handling code to crash the app instead of responding to an HTTP request.

Question: Rename Silly to Step via Config?

Is it possible via configuration to rename the "silly" level to "step"?

I'm designing an automation framework that will be using your logging library (great work btw!). I'd like to use the silly level as a way to report manual testing steps. So it would be ideal if the logs reported "step" instead of "silly".

Feature Request: Isomorphic logging (browser support)

Description / Use Case for a Feature

As I've said before, I'm really loving tslog in Blitz apps! But there's one thing I really wish tslog supported, and that is logging in the browser.

Currently I have to be constantly making a cognitive choice between using tslog or console.log based on if I'm in a server or browser context. And this is often since Blitz is a fullstack monolith.

Possible implementation

Provide a separate browser bundle (via package.json browser field) that has a minimal logger without any node.js dependencies. Should be a small as possible for bundle size concerns. Ideally it would accept all the same options, but some of the options would be no-op in the browser.

Cannot run with node once compiled

node version: v12.13.1
os: MacOS
typescript: 3.9.6
tslog: 2.6.1

I have no issue running this with ts-node, but when I actually run the tsc on my project and do a node ./dist/index.js I get the following error. I have tried removing it and everything works as expected, but as soon as I even just instantiate the new Logger() I get the error below.

/Users/****/source/*****/node_modules/@babel/template/lib/builder.js:71
      rootStack = error.stack.split("\n").slice(3).join("\n");

tsconfig.json

  "compilerOptions": {
    "target": "es5",
    "lib": ["esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": false,
    "strict": true,
    "noErrorTruncation": true,
    "rootDir": "src",
    "outDir": "dist",
    "sourceMap": true
  },
  "include": ["./src/**/*"],
  "exclude": [
    "node_modules"
  ]
}```

Bug: TypeError: Cannot read property '0' of undefined

Describe the bug
2020-06-04 15:13:57.858 INFO [test.ts:5] TypeError: Cannot read property '0' of undefined
at stylizeWithColor (internal/util/inspect.js:340:27)
at formatPrimitive (internal/util/inspect.js:1120:12)
at formatValue (internal/util/inspect.js:533:12)
at formatProperty (internal/util/inspect.js:1406:11)
at formatRaw (internal/util/inspect.js:785:9)
at formatValue (internal/util/inspect.js:591:10)
at Object.inspect (internal/util/inspect.js:221:10)
at ../node_modules/tslog/src/index.ts:435:26
at Array.forEach ()
at Logger._printPrettyLog (../node_modules/tslog/src/index.ts:430:30)

To Reproduce

import { Logger } from "tslog";

const logger = new Logger();

logger.info({ foo: "bar" });

Expected behavior
Log object

Context
Node v12.14.0
ts-node v8.10.2
tslog v2.1.0

Feature Request: [Feature] Consistent formatting + timestamp customization for floating point

Description / Use Case for a Feature

  1. Space / tab mixed formatting:

image

If logger contains different display configs across log object as above - for example displayLogname: true then some other properties are not set - properties are substituted as space then each separator uses tab. This mixes spaces / tab for output, as well as it'll have numbers of tab instead of single tab separator.

Curious if it's possible logger do not emit separator for property not to display and have consistent (preferably single tab) separator?

  1. floating point customization

It looks like ms point is fixed to 3 always, and it's usually not very much helpful as most of log may need ms only. Will it be possible to allow customization for this as well?

Bug: TypeError: Cannot read property 'isNative' of undefined

Describe the bug
I get an error: [TypeError: Cannot read property 'isNative' of undefined] when using the library

To Reproduce

In electron environment:
console.log('quicktest')
try {
const log: Logger = new Logger()
log.silly('I am a silly log.')
} catch (e) {
console.log(e)
}
console.log('quicktest')

get output:

quicktest
[TypeError: Cannot read property 'isNative' of undefined]
quicktest

Expected behavior
Not getting an error

Additional context
I am using electron, and running this in the main process

Bug: Cannot convert undefined or null to object

Describe the bug

These both fail as of 3.0.3. They worked fine in 2.9.1

To Reproduce

logger.info("A", null)
logger.info("B", undefined)

Expected behavior
Logger should accept all values, especially null and undefined.

Node.js Version
12

OS incl. Version
System:
OS: macOS 11.1
CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Memory: 1.43 GB / 64.00 GB
Shell: 3.1.2 - /usr/local/bin/fish

Bug: Absolute paths using ES modules in Node

Describe the bug
In the module description, it says

Paths are relative to the root of the application folder

Using node ES modules in an express project, the paths are not relative, but absolute from the system root.

To Reproduce
The logger module file is below

import { Logger } from 'tslog'

const logger = new Logger({
  name: 'log',
  displayLoggerName: false,
  printLogMessageInNewLine: true,
  overwriteConsole: true,
})

export default logger

Using it like the following:

import logger from './utils/logger.mjs'

logger.info(`Connecting to ${config.PORT}`)

const err = new Error('Test Error')
logger.prettyError(err)

The output is

2021-04-18 06:56:20.482  INFO [file:///home/vanntile/src/tests/nn/src/app.mjs:12 undefined.<anonymous>]
Connecting to 8080 

 Error  Test Error
error stack:
โ€ข app.mjs:24 <anonymous>
    file:/home/vanntile/src/tests/nn/src/app.mjs:24:13

Expected behavior
The paths being relative from the project root.

Additional context
This happens both when running with node and nodemon.

Node.js Version
14.15.3

npm Version
7.9.0

OS incl. Version
Ubuntu 20.04

Feature Request: Simpler transport

Description / Use Case for a Feature

Because the severity of a log is included with the logObject, there should be a simpler transport implementation option similar to the IStd interface for prettyprint.

export interface TSimpleTransport {
  log: (logObject: ILogObject) => void
}

And this type added as an overload to attachTransport

This would allow a transport logger to take out all the per-severity boilerplate and still support minLevel (because the logger simply wouldn't call the transport unless minlevel is reached). It would also remain backwards compatible if new severity types are added to the interface (which would otherwise be breaking for transports with the current implementation)

Simple implementation:

class ConsoleLogTransport implements TSimpleTransport {
  //Special processing for severity differences can go here
  log: (logObject: ILogObject) => {
    console.log(logObject.argumentsArray.ToString())
  }
}

An example existing transport implementation that could have about 10 lines cut out of it if this is implemented:

class DebugConsoleOutput implements IStd {
    write(message: string) {console.log(message)}
}

/**
 * Writes TSLog Pretty Print messages to the vscode debug console. It requires the logger during construction to import
 * its pretty print preferences
 *
 * @class DebugConsoleTransport
 * @implements {TTransportLogger<(ILogObject) => void>}
 */
class DebugConsoleTransport implements TTransportLogger<(logObject: ILogObject) => void> {
    private readonly debugConsoleOutput = new DebugConsoleOutput()
    constructor(
        private readonly logger: Logger
    ){}
    log(logObject: ILogObject): void {
        this.logger.printPrettyLog(this.debugConsoleOutput, logObject)
    }
    silly = this.log
    debug = this.log
    trace = this.log
    info = this.log
    warn = this.log
    error = this.log
    fatal = this.log
}

json loging api: [Feature]

when log output is json add option to to log json object
for example
logger.info({userId: 123})

expected output:
{
//...
userId: 123
}

current output:
{
//...
argumentsArray:[{userId: 123}]

Feature Request: [Feature] [Question] masking dynamic values, such as regex pattern matching

Description / Use Case for a Feature

I may misunderstand docs and this is already supported, labeled question to clarify it.

There are some cases we would like to traverse all object payload recursively to detect a certain patterns of strings matching regex, such as prefix-based auth token and vice versa. Is there way to specify it via current mask* configuration values?

Bug: Logging rror causes "Converting circular structure to JSON"

Describe the bug
When I pass an error object which has a circular structure to logger.info or any other logging method, I get the following error:

TypeError: Converting circular structure to JSON

To Reproduce
Execute the following code:

import { Logger } from 'tslog';

const logger = new Logger();

const request = { url: 'http://example.com/does-not-exist', originalRequest: {} };
request.originalRequest = request;

const error = new Error('Failed to fetch the resource');
error.request = request;

logger.warn('The request failed', error);

To see this happening in a more realistic context, install got and run the following code:

import { Logger } from 'tslog';
import got from 'got';

const logger = new Logger();

async function getResponse() {
  try {
    const response = await got.get('http://example.com/does-not-exist');
    return response;
  } catch (error) {
    logger.warn('Failed to fetch the data', error);  
  }
};

getResponse();

Expected behavior
tslog stringifies the error object in such a way that it does not throw an error.

Screenshots
Output from the 2nd example above:

(node:15937) UnhandledPromiseRejectionWarning: TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Socket'
    |     property '_httpMessage' -> object with constructor 'ClientRequest'
    --- property 'socket' closes the circle
    at JSON.stringify (<anonymous>)
    at Logger._buildErrorObject (/tmp/tmp.5tNebHIiKR/node_modules/tslog/src/index.ts:466:55)
    at /tmp/tmp.5tNebHIiKR/node_modules/tslog/src/index.ts:432:16
    at Array.forEach (<anonymous>)
    at Logger._buildLogObject (/tmp/tmp.5tNebHIiKR/node_modules/tslog/src/index.ts:429:28)
    at Logger._handleLog (/tmp/tmp.5tNebHIiKR/node_modules/tslog/src/index.ts:353:40)
    at Logger.warn (/tmp/tmp.5tNebHIiKR/node_modules/tslog/src/index.ts:298:28)
    at getResponse (/tmp/tmp.5tNebHIiKR/bug.ts:11:12)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)

Additional context
N/a

Node.js Version
v12.18.2

OS incl. Version
Linux Mint 19.1

Bug: Arrays and Dates stopped displaying correctly

Describe the bug

After upgrading from 2.9.1 to 3.0.5, there is a regression in array and date logging.

Before:

2021-01-07 22:21:30.784.000	 DEBUG 	[test.js:5]
{
  createdAt: 2021-01-07T22:21:30.782Z,
  students: [
    {
      id: 1
    },
    {
      id: 2
    }
  ]
}

After:

Notice how Date is now Date {} instead of a string and that array has object syntax.

2021-01-07 22:20:31.495  DEBUG [test.js:5 Object.<anonymous>]
{
  createdAt: Date {},
  students: Array {
    '0': {
      id: 1
    },
    '1': {
      id: 2
    },
    length: 2
  }
}

Also, sometimes I'm seeing [Circular] in place of a Date, but not sure how to reproduce that.

To Reproduce

Run this with 2.9.1 and 3.0.5

var tslog = require("tslog")

const log = new tslog.Logger()

log.debug({
  createdAt: new Date(),
  students: [{ id: 1 }, { id: 2 }],
})

Expected behavior

Same as 2.9.1

Env

System:
OS: macOS 11.1
CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Memory: 1.44 GB / 64.00 GB
Shell: 3.1.2 - /usr/local/bin/fish

[Question] displayFilePath/displayFunctionName in a "facade" class

Hello, I have a class LoggerService:

import { Logger } from 'tslog'

class LoggerService {
  private logger: Logger

  construtor() {
    this.logger = new Logger()
  }

  error(...args) {
    this.logger.error(args)
  }
}

Now if I call error() somewhere in another file:

// app.ts
startApp() {
  this.loggerService.error('something is wrong')
}

The settings displayFilePath and displayFunctionName shows:

2020-10-24 16:18:18.210  ERROR  [src/core/logger/logger.service.ts:105 LoggerService.error]             something is wrong 

I'd like the log to show:

2020-10-24 16:18:18.210  ERROR  [src/app.ts:35 app.startApp]             something is wrong 

So my question is can I do something to say "take the file name and function name from the parent class App instead of LoggerService?

For the stack, I can remove the first item of the array, I wonder if something similar could be done.

Attached transports do not respect individual minLevel

Describe the bug
When transport's minLevel is different from stdoutput, the log is not generated on transports with minLevel less than stdoutput.

To Reproduce
Steps to reproduce the behavior:

  1. Run the following code:
import { ILogObject, Logger } from 'tslog';
import { appendFileSync }  from 'fs';

function logToTransport(logObject: ILogObject) {
  appendFileSync('logs.txt', JSON.stringify(logObject) + '\n');
}

const logger: Logger = new Logger({ minLevel: 'info' });
logger.attachTransport(
  {
    silly: logToTransport,
    debug: logToTransport,
    trace: logToTransport,
    info: logToTransport,
    warn: logToTransport,
    error: logToTransport,
    fatal: logToTransport,
  },
  'debug'
);

logger.info("I am an info log.");
logger.warn("I am a warn log with a json object:", { foo: "bar" });
logger.debug("I am a debug log.");
  1. The "info" log is generated on the stdoutput and attached transport.
  2. The "warn" log is generated on the stdoutput and attached transport.
  3. The "debug" log is not generated on the attached transport.

Expected behavior
The "debug" log should be generated in the attached transport with minLevel is debug.

Additional context
To resolve, I believe that is need to change the code highlighted in the image below from this.settings.minLevel to transport.minLevel.

File: tslog/src/index.ts

tempsnip

Bug: `Cannot read property 'isError' of null`

Describe the bug

tslog version: 3.1.0

Unfortunately I don't know how to reproduce this but I periodically get an error thrown by tslog of Cannot read property 'isError' of null while attempting to log a message with a JS object. I see this usually in production. Although I think I've also seen it in local dev.

Expected behavior

Should not error

Additional context

import { Logger } from "tslog"

export const logger = new Logger({
  type: clientEnv.isProduction ? "json" : "pretty",
  dateTimePattern: "hour:minute:second.millisecond",
  displayFunctionName: false,
  displayFilePath: "hidden",
  dateTimeTimezone: clientEnv.isProduction
    ? "utc"
    : Intl.DateTimeFormat().resolvedOptions().timeZone,
  prettyInspectHighlightStyles: { name: "yellow", number: "blue", bigint: "blue", boolean: "blue" },
  maskValuesOfKeys: ["password", "passwordConfirmation"],
  exposeErrorCodeFrame: !clientEnv.isProduction,
})


// And then I'm also using a child logger
const log = logger.getChildLogger({ prefix: ["submitGroupStep1()"] })

Node.js Version
12

Bug: Method names for async functions not showing

Describe the bug
When a log is written from an async function it does not have a method name associated with it in the output. This might be an issue with how async/await works - or how I implement so not sure this is a tslog issue or not?

2020-11-18 02:31:14.309.000      INFO   [src\main.ts:5 testSync]                testSync - Yep    
2020-11-18 02:31:14.338.000      INFO   [src\main.ts:8]                 testAsync - Nope

To Reproduce

import { Logger } from 'tslog';
const logger = new Logger();

const testSync = () => {
  logger.info('testSync - Yep');
};
const testAsync = async () => {
  logger.info('testAsync - Nope');
};

(async () => {
  testSync();
  await testAsync();
})();

Expected behavior
I would expect the output to show [src\main.ts:8 testAsync] not just [src\main.ts:8].

Screenshots
image

Additional context
Add any other context about the problem here.

Node.js Version
v15.2.1

OS incl. Version
Windows 10

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.