@travetto/log
v5.0.15
Published
Logging framework that integrates at the console.log level.
Downloads
451
Readme
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 Runtime 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 {
log(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: 'info' | 'warn' | 'debug' | 'error';
/** 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 emptycontext
- 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 {
log(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 JSONTRV_LOG_OUTPUT
- This determines whether or not the logging goes to the console or if it is written to a fileTRV_LOG_PLAIN
- Allows for an override of whether or not to log colored output, this defaults to values provided by the Terminal in response toFORCE_COLOR
andNO_COLOR
TRV_LOG_TIME
- This represents what level of time logging is desired, the default isms
which is millisecond output. A value ofs
allows for second level logging, andfalse
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.