npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mu-ts/logger

v3.2.3

Published

Simple, lightweight and extendable logging.

Downloads

695

Readme

Node.js CI

@mu-ts/logger

Simple lightweight logging.

This logging framework aims to be a good enough logging framework where being lightweight is an asset. For example FaaS situations like AWS Lambda, Azure Functions, Google Functions, etc. There are a lot out there. A common problem is cold start, where bundle size has an impact.

  • Lightweight, under 10k.
  • All the basics;
    • current level checking (isDebug())
    • per level logging (debug('message'))
    • a generic (log('debug', myArgs, here))
    • start('label') and stop('label') for durations
  • Non strict logging signatures, throw anything you want as arguments error(new Error(), 'can', 'go in here', 'itsSomeWhatSmart()')
  • Data filtering, to redact sensitive data like credit cards, secrets or passwords.
  • Console based logging.
  • Outputs JSON for easier parsing.
  • Environment configuration for log level, default value and by named logger. Optional override if ! is present in front of class name. LOG_LEVEL=info;mylogger trace;!myOverride warn

Usage

Trying to keep it simple. Use LoggerService.named() to create a new logger. Provide either an object, with a name and level, or just a name and use the default level. NOTE: The latter option is probably the best so you can use environment variables to change it as needed.

import { Logger, LoggerService, inOut, duration} from '@mu-ts/logger';

process.env['LOG_LEVEL'] = 'info;X trace';

export class ClassX {
  private logger: Logger;

  constructor() {
    this.logger = LoggerService.named({ name:'ClassX', adornments: {'foo':'bar'} }); // or simply LoggerService.named('ClassX');
  }

  @duration() // What dis?
  @inOut() // And this?
  public doSync(some: string): string {
    this.logger.trace('my message is this', 'doSync()', some, { clazz: this.constructor.name });
    return 'x';
  }

  @duration() // What dis?
  @inOut() // And this?
  public async doASync(some: string): Promise<string> {
    try {
      this.logger.debug(some, 'my message is this');
      return 'x';
    } catch (error) {
      this.logger.error(error, 'Caught a thing!');
      throw error;
    }
  }
}

const x: ClassX = new ClassX();

const doAThing = async () => {
  x.doSync('Hello Viewer');
  x.doASync('Hello Longer Guy');
};

doAThing();

Adornments

Fancy word to say you can provide a set of static attributes that will be applied to every output statement. This can be useful when you need to add a static value to each log statement for a logger, or set of loggers.

NOTE: Adornments do not apply to decorators, at this time.

LOG_LEVEL

LOG_LEVEL is looked for on process.env, and when found, used to define default log levels when hard coded values are not provided for loggers. However, you can optionally override a hard coded value if you add a ! in front of the name of a specific named logger. In this case, it will override hard coded values and set it to the value defined.

For the name of each logger, any string is valid. All values are stored lowercase, so don't expect mixed cases to provide different loggers.

Decorators

We have two decorators that can help provide a bit more traceability to your logging, @inOut and @duration.

@inOut({level})

This decorator can be placed on a function to record each time it is executed. By default it logs at trace level, as the amount of logging can be quite verbose and detailed.

Synchronous Example
{
  at: 2019-12-07T20:17:41.992Z,
  clazz: 'ClassX',
  func: 'doSync()',
  msg: 'inOut -->',
  data: { args: [ 'Hello Viewer' ] },
  name: 'ClassX.inOut',
  level: 'debug'
}
{
  at: 2019-12-07T20:17:42.005Z,
  clazz: 'ClassX',
  func: 'doSync()',
  msg: 'inOut <--',
  data: { result: 'x' },
  name: 'ClassX.inOut',
  level: 'debug'
}
Asynchronous Functions

In the case of async functions, you will see 3 output statements as an additional one is added to record the fact the method returned a Promise, but did not yet resolve.

{
  at: 2019-12-07T20:17:42.007Z,
  clazz: 'ClassX',
  func: 'doASync()',
  msg: 'inOut -->',
  data: { args: [ 'Hello Longer Guy' ] },
  name: 'ClassX.inOut',
  level: 'debug'
}
{
  at: 2019-12-07T20:17:42.013Z,
  clazz: 'ClassX',
  func: 'doASync()',
  msg: 'inOut -- promisified function',
  data: { result: {} },
  name: 'ClassX.inOut',
  level: 'debug'
}
{
  at: 2019-12-07T20:17:42.016Z,
  clazz: 'ClassX',
  func: 'doASync()',
  msg: 'inOut <--',
  data: { result: 'x' },
  name: 'ClassX.inOut',
  level: 'debug'
}

@duration()

This decorator will tell you the amount of time it takes for your function to execute. If this is an async function, it will be the amount of time until the promise resolves, not the amount of time to return the promise.

Default implementation outputs values using console.timeEnd() the name is assembled together using the logger name, 'duration' and function name.

Example: X.duration.doASync: 0.366ms

But wait, that's not JSON! Yea, erring on the side of using out of the box behavior instead of building this out. It's easy to create memory leaks with this kind of behavior. When I do it, I want to take the time to do it right.

Levels

Pretty standard here, highest level is fatal, lowest (most detailed) is trace. Levels listed below from lowest to highest:

  • trace isTrace() trace(...params)
  • debug isDebug() debug(...params)
  • info isInfo() info(...params)
  • warn isWarn() warn(...params)
  • error isError() error(...params)
  • fatal isFatal() fatal(...params)

For the default logger, each of these levels will use a different output method to take advantage of some of the built in functionality of console. So trace, debug and info are all reported using console.log, warn is reported using console.warn and then error and fatal are reported using console.error.

Filters

Filters are executed against resulting statements to modify the output before it is printed to the console. They should be used sparingly since logging statements can happen quite frequently and get expensive.

NOTE: Statements clone data being output, to avoid filters manipulating your runtime data model. This can be a bit of a risk since JavaScript/Node uses 'pass by reference'.

While filters can be declared globally on the LoggerService, you can override or provide specific filters when calling LoggerService.named(). There is an optional 2nd argument that takes a list of filters.

Credit Card Filtering

Looks for numbers that 'look' like a credit card. Checks both data and msg of a LoggerStatement. Any value found that is suspected of being a credit card value is replaced with >>> REDACTED <<<.

import { LoggerService, CreditCardLoggerFilter } from '@mu-ts/logger';
LoggerService.registerFilter(new CreditCardLoggerFilter());

Sensitive Naming Filtering

This filter is untrusting of values and looks only at names of attributes. When any are found with a name that is suspect, its value is redacted.

import { LoggerService, SensitiveNameLoggerFilter } from '@mu-ts/logger';
LoggerService.registerFilter(new SensitiveNameLoggerFilter());