@travetto/log

4.0.0 • Public • Published

Logging

Logging framework that integrates at the console.log level.

Install: @travetto/log

npm install @travetto/log

# or

yarn add @travetto/log

This module provides logging functionality, building upon ConsoleManager in the Base module. This is all ultimately built upon console operations. The logging infrastructure is built upon the Dependency Injection system, and so new loggers can be created that rely upon dependency injected services and sources.

Extending the Common Logger

By default, the system ships with the CommonLogger, and by default will leverage the LineLogFormatter and the ConsoleLogAppender. The configuration CommonLoggerConfig provides two configuration variables that allows for switching out LineLogFormatter for the JsonLogFormatter, depending on the value of CommonLoggerConfig.format. Additionally the ConsoleLogAppender can be swapped out for the FileLogAppender depending on the value of CommonLoggerConfig.output.

Code: Standard Logging Config

export class CommonLoggerConfig {
  @EnvVar('TRV_LOG_FORMAT')
  format: 'line' | 'json' = 'line';

  @EnvVar('TRV_LOG_OUTPUT')
  output: 'console' | 'file' | string = 'console';
}

In addition to these simple overrides, the CommonLogger can be extended by providing an implementation of either a LogFormatter or LogAppender, with the declared symbol of LogCommonⲐ.

Code: Sample Common Formatter

import { Injectable } from '@travetto/di';
import { LogFormatter, LogCommonⲐ, LogEvent } from '@travetto/log';

@Injectable(LogCommonⲐ)
export class SampleFormatter implements LogFormatter {

  format(e: LogEvent): string {
    return `${e.timestamp} [${e.level}]#[${e.scope ?? 'unknown'}] ${e.message ?? 'NO MESSAGE'} ${(e.args ?? []).join(' ')}`;
  }
}

As you can see, implementing LogFormatter/LogAppender with the appropriate symbol is all that is necessary to customize the general logging functionality.

Creating a Logger

The default pattern for logging is to create a Logger which simply consumes a logging event. The method is not asynchronous as ensuring the ordering of append calls will be the responsibility of the logger. The default logger uses console.log and that is synchronous by default.

Code: Logger Shape

export interface Logger {
  onLog(ev: LogEvent): unknown;
}

Code: Log Event

export interface LogEvent extends ConsoleEvent {
  /**
   * Log message
   */
  message?: string;
}

Code: Console Event

export type ConsoleEvent = {
  /** Time of event */
  timestamp: Date;
  /** The level of the console event */
  level: LogLevel;
  /** The source file of the event */
  source: string;
  /** The line number the console event was triggered from */
  line: number;
  /** The module name for the source file */
  module: string;
  /** The module path  for the source file*/
  modulePath: string;
  /** The computed scope for the console. statement.  */
  scope?: string;
  /** Arguments passed to the console call*/
  args: unknown[];
};

The LogEvent is an extension of the ConsoleEvent with the addition of two fields:

  • message - This is the primary argument passed to the console statement, if it happens to be a string, otherwise the field is left empty
  • context - This is the final argument passed to the console statement, if it happens to be a simple object. This is useful for external loggers that allow for searching/querying by complex data

Code: Custom Logger

import { Injectable } from '@travetto/di';
import { LogEvent, Logger } from '@travetto/log';

@Injectable()
export class CustomLogger implements Logger {
  onLog(ev: LogEvent): void {
    const headers = new Headers();
    headers.set('Content-Type', 'application/json');
    const body = JSON.stringify(ev);
    fetch('http://localhost:8080/log', { method: 'POST', headers, body, });
  }
}

Creating a Decorator

In addition to being able to control the entire logging experience, there are also scenarios in which the caller may want to only add information to the log event, without affecting control of the formatting or appending. The Logger is an interface that provides a contract that allows transforming the LogEvent data. A common scenario for this would be to add additional metadata data (e.g. server name, ip, code revision, CPU usage, memory usage, etc) into the log messages.

Code: Log Decorator Shape

export interface LogDecorator {
  decorate(ev: LogEvent): LogEvent;
}

Code: Custom Logger

import os from 'node:os';

import { Injectable } from '@travetto/di';
import { LogDecorator, LogEvent } from '@travetto/log';

@Injectable()
export class CustomDecorator implements LogDecorator {
  decorate(ev: LogEvent): LogEvent {

    // Add memory usage, and hostname
    Object.assign(ev.context ??= {}, {
      memory: process.memoryUsage,
      hostname: os.hostname()
    });

    return ev;
  }
}

Logging to External Systems

By default the logging functionality logs messages directly to the console, relying on the util.inspect method, as is the standard behavior. When building distributed systems, with multiple separate logs, it is useful to rely on structured logging for common consumption. The framework supports logging as JSON, which is easily consumable by services like elasticsearch or AWS Cloudwatch if running as a lambda or in a docker container.

The main caveat that comes with this, is that not all objects can be converted to JSON (specifically circular dependencies, and unsupported types). That end, the framework recommends logging with the following format, message: string   context: Record<string, Primitive>. Here context can be recursive, but the general idea is to only pass in known data structures that will not break the JSON production.

Environment Configuration

Code: Standard Logging Config

export class CommonLoggerConfig {
  @EnvVar('TRV_LOG_FORMAT')
  format: 'line' | 'json' = 'line';

  @EnvVar('TRV_LOG_OUTPUT')
  output: 'console' | 'file' | string = 'console';
}

The following environment variables have control over the default logging config:

  • TRV_LOG_FORMAT - This determines whether or not the output is standard text lines, or is it output as a single line of JSON
  • TRV_LOG_OUTPUT - This determines whether or not the logging goes to the console or if it is written to a file
  • TRV_LOG_PLAIN - Allows for an override of whether or not to log colored output, this defaults to values provided by the Terminal in response to FORCE_COLOR and NO_COLOR
  • TRV_LOG_TIME - This represents what level of time logging is desired, the default is ms which is millisecond output. A value of s allows for second level logging, and false will disable the output. When ingesting the content into another logging, its generally desirable to suppress the initial time output as most other loggers will append as needed.

Package Sidebar

Install

npm i @travetto/log

Homepage

travetto.io

Weekly Downloads

554

Version

4.0.0

License

MIT

Unpacked Size

24 kB

Total Files

15

Last publish

Collaborators

  • arcsine