An open API service indexing awesome lists of open source software.

https://github.com/pabra/logger

A small and simple but extendable logger for typescript/javascript in browser and Node.js.
https://github.com/pabra/logger

browser logger logging nodejs syslog typescript

Last synced: about 1 year ago
JSON representation

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

Awesome Lists containing this project

README

          

# @pabra/logger

[![npm version](https://img.shields.io/npm/v/%40pabra%2Flogger?label=version&logo=npm)](https://www.npmjs.com/package/%40pabra%2Flogger)
[![npm bundle size (scoped)](https://img.shields.io/bundlephobia/min/@pabra/logger?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACSUlEQVRYw%2B2XP2gTcRTH459BEKSiQkH8%2FcmlaUpJEESECpKh0IKiU2b%2FDNKxDkrp0rjZUXBSHERcBEEX7eRQhVKcBBfrIgriYDEoorVJzs%2FL%2FdpexbSXXq4g3MHjLne%2F3%2Ft%2B3vu9y%2B9dJhPjsNb2G5O9YUzBZLbrKJVKexG9qLV9aYz9zPW01mbRGPM0m82eqVarOxMRRuAEorc5f0X4CdGfrVQqu4Jn5T3cP8%2F9eew9UBO5XO5QbNF8Pn8QZ1eI8A3i78SxMYO9G83xPO8YMHcBqXF%2BwO%2BhjkQlhUwcwcHDwIm9R7SnOs%2FY0R6Ax5m%2FgL3mekyWb6OC0gy6TqQfsVcygTTui5tF3%2Fd3UBvDBPVIlg%2Fft7TuG1g3iBT7QSHZmwwuJlXALOlhCVKKVzRXimvKATSgewHA8aQAqIcjStnHaNVFU2DCAA7CNLEvwFzt1hIENSWFbBpKtTT8dgBhE5A6xDNbKcJisbhfKe8aPmqhwNZpbAawCqKUXnZZmYz4Gsob9NsJt%2FUdFSBsDef4OfPOlcvl3Wt%2FRN4FAN%2FyfOlf0XYLIGw%2FmVsLXivzg%2BtfnfqICxDbUoAUIAVIAVKAFoDriGrBBmKXkxalH5BmpEng3%2F5qy%2FQQe%2F%2Bs61bqUXe06Nba0ptBh913uu1%2BLr0%2BgyfJxqcuZKXhWj12Sn2%2F428F%2BsO86%2BGW1pxFj5Z0f6BBudyVhhKnl4BZ2CQrreVjfZ8JfCKdbaFQOKC1dweI70A1XXakqBYRnVr5XNuWA9FRQOaU6j%2BZ%2BV%2BPP5ZRWPOOMBtbAAAAAElFTkSuQmCC)](https://bundlephobia.com/package/%40pabra%2Flogger)
[![Codecov](https://img.shields.io/codecov/c/github/pabra/logger/master?logo=codecov)](https://codecov.io/gh/pabra/logger)
[![unit-tests](https://github.com/pabra/logger/actions/workflows/unit-tests.yml/badge.svg?branch=master)](https://github.com/pabra/logger/actions?query=branch%3Amaster+workflow%3Aunit-tests)
[![npm-publish](https://github.com/pabra/logger/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/pabra/logger/actions?query=workflow%3Anpm-publish)

## What

A JavaScript/TypeScript logger that implements [Syslog severitiy levels](https://en.wikipedia.org/wiki/Syslog#Severity_level).

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](#logger) consists of 3 parts:

- [Filter](#filter) (optional) - should a message be logged at all
- [Formatter](#formatter) - how to format log entries
- [Transporter](#transporter) - where to trasport log entries to

These are packed together into a [Handler](#handler).

## Install

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

## Getting Started

### Just Log

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

```typescript
// 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:

```console
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.

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

Results in the following console output:

```console
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.

```typescript
// 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:

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

### Selectively Logging for Dev / Prod

Set up a custom [Handler](#handler) to only show log messages starting at
'warning' level in production:

```typescript
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.

```typescript
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:

```console
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](https://webpack.js.org/plugins/environment-plugin/) might
help with that).

## Usage

### Logger

#### What is it

```typescript
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

```typescript
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`](#handler) |
| `handlers` |
Handler[]
| no | multiple [`Handler`](#handler)s |

#### How to use it

```typescript
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`](#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`](#logger) will pass the message
and data to each of it's [`Handler`](#handler)s.

### Handler

#### What is it

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

A [`Handler`](#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

```typescript
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`](#handler) if you don't pass one to `getLogger`. It mostly works like `console.log`. It doesn't has a [`Formatter`](#formatter) and just passes the raw data to console. |
| `getConsoleTextHandler` |
(
  level?: LogLevelName \| undefined,
) => Handler
| | This [`Handler`](#handler) will be best for human readability. |
| `getConsoleJsonHandler` |
(
  level?: LogLevelName \| undefined,
) => Handler
| | This [`Handler`](#handler) will be best for machine readability as it will be one big strigified JSON line. |

#### How to make it

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

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

### Filter

#### What is it

```typescript
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`](#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`](#filter) provided in a [`Handler`](#handler), every
log entry gets handled. So no [`Filter`](#filter) behaves the same as a
[`Filter`](#filter) that's always returning `true`.

#### How to get it

```typescript
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](https://en.wikipedia.org/wiki/Syslog#Severity_level)). |

#### How to make it

A [`Filter`](#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`](#handler) that should only handle `error` log
entries, your [`Filter`](#filter) could look like this:

```typescript
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

```typescript
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`](#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

```typescript
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`](#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`](#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`](#formatter) (for the sake of simplicity ignores
data) could look like this:

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

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

### Transporter

#### What is it

```typescript
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`](#transporter) "transports" the formatted message to its
destination. That might be the `console`, a file, some http endpoint, etc.

#### How to get it

```typescript
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`](#handler) (`getConsoleRawDataHandler` - used if no [`Handler`](#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`](#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`](#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:

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

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