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

@pabra/logger

v1.2.0

Published

A small and simple but extendable logger for typescript/javascript in browser and Node.js.

Downloads

236

Readme

@pabra/logger

npm version npm bundle size (scoped) Codecov unit-tests npm-publish

What

A JavaScript/TypeScript logger that implements Syslog severitiy levels.

goals are:

  • be lightweight/small
  • can be used in browser and node.js
  • have as few as possible dependencies (currently just 1)
  • (almost) ready to use if you just want to use console.log and do not want to log debug messages in production
  • easily extendable
  • functional code and immutable data

A Logger consists of 3 parts:

  • Filter (optional) - should a message be logged at all
  • Formatter - how to format log entries
  • Transporter - where to trasport log entries to

These are packed together into a Handler.

Install

npm install --save @pabra/logger
# or
yarn add @pabra/logger

Getting Started

Just Log

This works in both, browser and node.js environments.

// import
import getLogger from '@pabra/logger';

// init and use root logger
const rootLogger = getLogger('myProject');

rootLogger.info("I'm using a simple logger now!");

Results in the following console output:

2020-08-13T13:55:32.327Z [myProject] INFORMATIONAL - I'm using a simple logger now!

Logging Data

Pass any additional data after the log message.

rootLogger.warning(
  'something unexpected happened',
  { some: ['data', true] },
  '23',
  42,
);

Results in the following console output:

2020-09-06T07:29:05.356Z [myProject] WARNING - something unexpected happened { some: [ 'data', true ] } 23 42

Child Logger

Call getLogger on your rootLogger to get a child logger.

// import
import getLogger from '@pabra/logger';

// init root logger
const rootLogger = getLogger('myProject');

// init and use child logger in your modules/components/etc.
const moduleLogger = rootLogger.getLogger('myModule');
moduleLogger.info('Logging from within a module!');

Results in the following console output:

2020-09-06T07:39:08.677Z [myProject.myModule] INFORMATIONAL - Logging from within a module!

Selectively Logging for Dev / Prod

Set up a custom Handler to only show log messages starting at 'warning' level in production:

import getLogger, { handlers } from '@pabra/logger';

const logLevel = process.env.NODE_ENV === 'development' ? undefined : 'warning';
const logHandler = handlers.getConsoleRawDataHandler(logLevel);
const rootLogger = getLogger('myProject', logHandler);
// in some module
const moduleLogger = rootLogger.getLogger('myModule');

Then, any log messages that are lower than "warning" will be ignored.

rootLogger.info("I'm using a simple logger now!");
moduleLogger.notice("I'm using a simple module logger now!");
rootLogger.err('No such table in db.');
moduleLogger.warning('User entered invalid user name.');

Will only show messages eqal or higher than 'warning' level:

2020-09-06T07:53:40.896Z [myProject] ERROR - No such table in db.
2020-09-06T07:53:40.896Z [myProject.myModule] WARNING - User entered invalid user name.

You should take care that process.env.NODE_ENV is properly set. This might also differ if you use it in node.js or browser (there is no global process in the browser - webpack EnvironmentPlugin might help with that).

Usage

Logger

What is it

type Logger = {
  emerg: (message: string, ...data: any[]) => void;
  alert: (message: string, ...data: any[]) => void;
  crit: (message: string, ...data: any[]) => void;
  err: (message: string, ...data: any[]) => void;
  warning: (message: string, ...data: any[]) => void;
  notice: (message: string, ...data: any[]) => void;
  info: (message: string, ...data: any[]) => void;
  debug: (message: string, ...data: any[]) => void;
  getLogger: GetLogger;
  getHandlers: () => Handlers;
};

How to get it

import getLogger from '@pabra/logger';

// get a main/root logger
const mainLogger = getLogger(loggerName); // default handler will be used
// or
const mainLogger = getLogger(loggerName, handler);
// or
const mainLogger = getLogger(loggerName, handlers);

// get a child/module logger
const moduleLogger = mainLogger.getLogger(loggerName); // parent's handlers will be used
// or
const moduleLogger = mainLogger.getLogger(loggerName, handler);
// or
const moduleLogger = mainLogger.getLogger(loggerName, handlers);

| object | type | required | description | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | ------------------------------- | | loggerName | string | yes | name of your logger | | handler | type Handler = {    readonly filter?: Filter | undefined;    readonly formatter: Formatter;    readonly transporter: Transporter;} | no | a single Handler | | handlers | Handler[] | no | multiple Handlers |

How to use it

moduleLogger.info(message, ...data);

| object | type | required | description | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | ------------------------------------- | | moduleLogger | type Logger = {  emerg: (message: string, ...data: any[]) => void;  alert: (message: string, ...data: any[]) => void;  crit: (message: string, ...data: any[]) => void;  err: (message: string, ...data: any[]) => void;  warning: (message: string, ...data: any[]) => void;  notice: (message: string, ...data: any[]) => void;  info: (message: string, ...data: any[]) => void;  debug: (message: string, ...data: any[]) => void;  getLogger: GetLogger;  getHandlers: () => Handlers;}; | | the actual Logger Object | | message | string | yes | a message to log | | data | any | no | some kind of data to log |

For each call of a log function the Logger will pass the message and data to each of it's Handlers.

Handler

What is it

type Handler = {
  readonly filter?: Filter;
  readonly formatter: Formatter;
  readonly transporter: Transporter;
};

A Handler keeps all 3 parts together that are needed to handle a log entry - hence the name. Whereas the filter is optional.

How to get it

import { handlers, Handler } from '@pabra/logger';

const myHandler: Handler = handlers.getConsoleTextHandler(logLevelName);
const myHandler: Handler = handlers.getConsoleRawDataHandler(logLevelName);
const myHandler: Handler = handlers.getConsoleJsonHandler(logLevelName);

| object | type | required | description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | handlers | {  getConsoleTextHandler,  getConsoleRawDataHandler,  getConsoleJsonHandler,} as const; | | an object of common handlers | | logLevelName | type LogLevelName =  | 'emerg'  | 'alert'  | 'crit'  | 'err'  | 'warning'  | 'notice'  | 'info'  | 'debug'; | no | The name of the maximal log level to handle (low log levels are more urgent than higher ones). If none is passed (or undefined) that Hanlder won't filter - means everything get's logged. | | getConsoleRawDataHandler | (  level?: LogLevelName | undefined,) => Handler | | This is the default Handler if you don't pass one to getLogger. It mostly works like console.log. It doesn't has a Formatter and just passes the raw data to console. | | getConsoleTextHandler | (  level?: LogLevelName | undefined,) => Handler | | This Handler will be best for human readability. | | getConsoleJsonHandler | (  level?: LogLevelName | undefined,) => Handler | | This Handler will be best for machine readability as it will be one big strigified JSON line. |

How to make it

import { Handler } from '@pabra/logger';

const myHandler: Handler = {
  filter: myFilter,
  formatter: myFormatter,
  transporter: myTransporter,
};

Filter

What is it

type Filter = (logger: InternalLogger, message: Message) => boolean;
type InternalLogger = {
  readonly name: string;
  readonly nameChain: string[];
  readonly handlers: Handler[];
};
interface Message {
  readonly raw: string;
  readonly data: any[];
  readonly level:
    | 'emerg'
    | 'alert'
    | 'crit'
    | 'err'
    | 'warning'
    | 'notice'
    | 'info'
    | 'debug';
}

The Filter function decides if a log entry should be handled at all. If it returns false the log entry handling immediately ends for this handler.

If there is no Filter provided in a Handler, every log entry gets handled. So no Filter behaves the same as a Filter that's always returning true.

How to get it

import { filters, Filter } from '@pabra/logger';

const myFilter: Filter = filters.getMaxLevelFilter(logLevelName);

| object | type | required | description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | filters | { getMaxLevelFilter } as const; | | an object of common filters | | logLevelName | type LogLevelName =  | 'emerg'  | 'alert'  | 'crit'  | 'err'  | 'warning'  | 'notice'  | 'info'  | 'debug'; | yes | The name of the maximal log level to handle (low log levels are more urgent than higher ones). | | getMaxLevelFilter | (  level: LogLevelName,) => Filter | | This Filter decides based on the severity of the log entry weather it should be logged/handled or not (low levels are more urgent - see Syslog severitiy levels). |

How to make it

A Filter is a function that gets the InternalLogger object and the Message object passed as arguments and needs to return a boolean.

If you want to have a Handler that should only handle error log entries, your Filter could look like this:

import { Filter } from '@pabra/logger';

const myFilter: Filter = (_logger, message) => message.level === 'err';

// or if you only want to handle log entries from your "auth" module
const myFilter: Filter = (logger, _message) => logger.name === 'auth';

Formatter

What is it

type Formatter = (logger: InternalLogger, message: Message) => string;
type InternalLogger = {
  readonly name: string;
  readonly nameChain: string[];
  readonly handlers: Handler[];
};
interface Message {
  readonly raw: string;
  readonly data: any[];
  readonly level:
    | 'emerg'
    | 'alert'
    | 'crit'
    | 'err'
    | 'warning'
    | 'notice'
    | 'info'
    | 'debug';
}

The Formatter function produces the formatted message (string) that finally appears in your log file/console/etc. It might add a time stamp and than somehow join the severity level/name, logger name, raw log message and log data into one string.

How to get it

import { formatters, Formatter } from '@pabra/logger';

const myFormatter: Formatter = formatters.jsonFormatter;
const myFormatter: Formatter = formatters.textFormatter;
const myFormatter: Formatter = formatters.textWithoutDataFormatter;
const myFormatter: Formatter = formatters.getJsonLengthFormatter(maxLength);
const myFormatter: Formatter = formatters.getTextLengthFormatter(maxLength);

| object | type | required | description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | formatters | {  textWithoutDataFormatter,  textFormatter,  jsonFormatter,  getTextLengthFormatter,  getJsonLengthFormatter,} as const; | | an object of common formatters | | maxLength | undefined | number | no | The maximum length of your formatted log message. If undefined or omitted the default is 1024^2 (1 MiB). It is there to prevent you from potentially sending huge data objects over the wire. Notice: if used with jsonFormatter the stringified data will end up truncated and not parseable anymore. | | jsonFormatter | Formatter | | Will return untruncated, stringified JSON like this: {  "name": "auth",  "nameChain": ["main", "auth"],  "time": "2020-08-16T08:23:43.395Z",  "level": "debug",  "levelValue": 7,  "levelServerity": "Debug",  "message": "failed to login",  "data": [{ "user": "bob" }]} Can handle instances of Error as data. | | textFormatter | Formatter | | Will return untruncated, text like this: 2020-08-16T08:45:08.297Z [main.auth] DEBUG - failed to login {"user":"bob"} Can handle instances of Error as data. | | textWithoutDataFormatter | Formatter | | This Formatter will just return the raw message without trying to serialize data. It's used for getConsoleRawDataHandler to be able to pass arbitrary objects like DOM Nodes or Events to the console which could not be serialized by JSON.stringify otherwise. | | getJsonLengthFormatter | (  maxLength?: number | undefined,) => Formatter | | Will return length limited jsonFormatter. | | getTextLengthFormatter | (  maxLength?: number | undefined,) => Formatter | | Will return length limited textFormatter. |

How to make it

A Formatter is a function that gets the InternalLogger object and the Message object passed as arguments and needs to return a string.

A very simple Formatter (for the sake of simplicity ignores data) could look like this:

import { Formatter } from '@pabra/logger';

const myFormatter: Formatter = (logger, message) =>
  `${new Date().toISOString()} [${logger.name}] ${message.level}: ${
    message.raw
  }`;

Transporter

What is it

type Transporter = (logger: InternalLogger, message: MessageFormatted) => void;
type InternalLogger = {
  readonly name: string;
  readonly nameChain: string[];
  readonly handlers: Handler[];
};
interface MessageFormatted {
  readonly raw: string;
  readonly data: DataArgs;
  readonly level: LogLevelName;
  readonly formatted: string;
}

The Transporter "transports" the formatted message to its destination. That might be the console, a file, some http endpoint, etc.

How to get it

import { transporters, Transporter } from '@pabra/logger';

const myTransporter: Transporter = transporters.consoleTransporter;
const myTransporter: Transporter = transporters.consoleWithoutDataTransporter;

| object | type | required | description | | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | transporters | {  consoleTransporter,  consoleWithoutDataTransporter,} as const | | an object of common transporters | | consoleTransporter | Transporter | | It passes the formated message and data to the console. It's used by the default Handler (getConsoleRawDataHandler - used if no Handler is passed to getLogger). It can be used if you want to pass arbitrary objects (like DOM Nodes, Events, etc.) to the console without having formatter dealt with them. | | consoleWithoutDataTransporter | Transporter | | It passes only the formatted message to the console. A Formatter should have taken care, that data became part of formatted message. It's used by getConsoleTextHandler and getConsoleJsonHandler. |

How to make it

A Transporter is a function that gets the InternalLogger object and the MessageFormatted object passed as arguments and needs to return nothing (viod).

A very simple Tranporter to POST to your logging server might look like:

import { Transporter } from '@pabra/logger';

const myTransporter: Transporter = (_logger, message) =>
  void fetch('https://example.com', {
    method: 'POST',
    body: message.formatted,
  });