flaschenpost

flaschenpost is a logger for cloud-based applications.

Usage no npm install needed!

<script type="module">
  import flaschenpost from 'https://cdn.skypack.dev/flaschenpost';
</script>

README

flaschenpost

flaschenpost is a logger for cloud-based applications.

flaschenpost

A /ˈflaʃənˌpɔst/ is a „message written on a scrap of paper, rolled-up and put in an empty bottle and set adrift on the ocean; traditionally, a method used by castaways to advertise their distress to the outside world”. (from Wiktionary)

Status

Category Status
Version npm
Dependencies David
Dev dependencies David
Build GitHub Actions
License GitHub

Installation

$ npm install flaschenpost

Quick start

First you need to add a reference to flaschenpost to your application:

const { flaschenpost } = require('flaschenpost');

If you use TypeScript, use the following code instead:

import { flaschenpost } from 'flaschenpost';

Then, call the getLogger function to get a logger for the current file. Ideally, this is only done once per file:

const logger = flaschenpost.getLogger();

With this logger you can now log messages, using its fatal, error, info, warn and debug functions. E.g., to log an info message, call the info function with an appropriate log message:

logger.info('Server started.');

From time to time you may want to also provide additional metadata for the log message. For this, hand over a metadata object as second parameter:

logger.info('Server started.', { port: 3000 });

Please note that the metadata parameter must be an object. If you want to use other data types as metadata, such as booleans, numbers or strings, you need to wrap them within an object.

If at any point in your application code you want to check wether debug logging is enabled (to e.g. measure times for more detailled logging), you can do so via the public readonly property isDebugMode on the logger instance:

if (logger.isDebugMode) {
  // Collect more data and log it.
}

Managing log levels

By default flaschenpost only logs fatal, error, warn and info messages, but not debug messages. To change this, set the LOG_LEVEL environment variable to the log level that you would like to log message up to. E.g., to enable logging for all log levels, set its value to debug:

$ export LOG_LEVEL=debug

If you only want to see log messages with levels fatal and error, set it to error. The same concept applies to all other log levels.

Setting the log level to debug may result in a huge amount of log messages, which may not be what you want. Hence you can limit which modules you would like to see debug output for by setting the LOG_DEBUG_MODULE_FILTER environment variable to the name or a comma-separated list of the names of the appropriate modules.

E.g., to only get debug log messages for modules foo and bar, use the following line:

$ export LOG_DEBUG_MODULE_FILTER=foo,bar

Formatting log messages

The core concept of flaschenpost is to always log to the standard output stream of a process, according to the 12 Factor Apps principles. However, it makes a difference if you use flaschenpost in an interactive shell session, or in a scripted environment.

If flaschenpost detects a TTY, you will get the log messages as human-readable output. If it doesn't detect a TTY, you will get them formatted as newline-separated JSON.

Sometimes it is necessary to override this default behavior, e.g. in tests. For this, set the LOG_FORMATTER environment variable to human or to json, to enforce one of the two styles:

$ LOG_FORMATTER=human

Faking log sources

Basically, when calling the getLogger function, flaschenpost automatically detects the file from which this call is done, and infers the appropriate file name. Sometimes, this is not desired, as you may want to manually set the file name used as source.

Therefore, you can provide a file path as a parameter to the getLogger function. Please note that this file path must be an absolute path, and that it must point to an existing file:

const logger = flaschenpost.getLogger('/.../app.js');

To provide a virtual file path that does not exist, pass an additional parameter defining the module the file belongs to:

const logger = flaschenpost.getLogger(
  '/virtual/path/.../app.js',
  {
    name: 'custom-package',
    version: '1.0.1'
  }
);

Using the flaschenpost middleware

flaschenpost can be used as a middleware for Express. For this, first load flaschenpost's getMiddleware function:

const { getMiddleware } = require('flaschenpost');

If you use TypeScript, use the following code instead:

import { getMiddleware } from 'flaschenpost';

Create an instance of the middleware by calling getMiddleware and register it via the usual way in your Express application:

app.use(getMiddleware());

Sometimes you may want to log when the request is received, not when the response was sent, e.g. for long-running connections. For these cases, provide the logOn option and set it to request:

app.use(getMiddleware({ logOn: 'request' }));

By default, the middleware uses info as log level. To change this, provide the logLevel option and set it to the desired value:

app.use(getMiddleware({ logLevel: 'warn' }));

Configuring flaschenpost programmatically

Sometimes, you may need to change the configuration of flaschenpost programmatically. To do this, first get the current configuration using the getConfiguration function:

const configuration = flaschenpost.getConfiguration();

The configuration object now has a number of functions (see section below) to adjust the configuration. E.g., if you want to change the hostname being used, call the withHostname function:

const updatedConfiguration = configuration.withHostname('localhost');

Please note that all of the functions on the configuration object do not mutate the configuration, but return a new instance instead!

Finally, set the new configuration using the configure function. Typically, because of the configuration object's immutability, you may want to do all of this in a single line:

flaschenpost.configure(
  flaschenpost.getConfiguration().
    withHighestEnabledLogLevel('debug').
    withHostname('localhost')
);

Setting application data

To set the name and version of the application, use the withApplication function:

const updatedConfiguration =
  configuration.withApplication({ name: 'foo', version: '1.0.0' });

Setting the debug module filter

To set the list of modules for which debug messages should be logged, use the withDebugModuleFilter function:

const updatedConfiguration =
  configuration.withDebugModuleFilter([ 'foo', 'bar' ]);

Setting the formatter

To set the formatter, use the withFormatter function. As parameter, provide a function that takes a LogEntry and returns a string:

const updatedConfiguration =
  configuration.withFormatter(logEntry => '...');

For details on this see the Formatter interface and the LogEntry class.

If you want to programmatically enforce human-readable or JSON output, you first have to load all builtin formatters:

const { formatters } = require('flaschenpost');

If you use TypeScript, use the following code instead:

import { formatters } from 'flaschenpost';

Then access the asHumanReadable or the asJson function and hand it over using the withFormatter function.

Setting the log level

To set the log level, use the withHighestEnabledLogLevel function:

const updatedConfiguration =
  configuration.withHighestEnabledLogLevel('debug');

Setting the hostname

To set the hostname, use the withHostname function:

const updatedConfiguration =
  configuration.withHostname('localhost');

Setting the log entry ID generator

To set the log entry ID generator, use the withLogEntryIdGenerator function. As parameter, provide a generator function that endlessly returns new IDs, either of type number or of type string:

const updatedConfiguration =
  configuration.withLogEntryIdGenerator(function * () {
    while (true) {
      const nextId = // ...

      yield nextId;
    }
  });

Running quality assurance

To run quality assurance for this module use roboter:

$ npx roboter