Logging framework that integrates at the console.log level.
npm install @travetto/logInstall: @travetto/log
``bash
npm install @travetto/log
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.
. Additionally the ConsoleLogAppender can be swapped out for the FileLogAppender depending on the value of CommonLoggerConfig.output.Code: Standard Logging Config
`typescript
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
LogCommonSymbol.Code: Sample Common Formatter
`typescript
import { Injectable } from '@travetto/di';
import { type LogFormatter, LogCommonSymbol, type LogEvent } from '@travetto/log';@Injectable(LogCommonSymbol)
export class SampleFormatter implements LogFormatter {
format(event: LogEvent): string {
return
${event.timestamp} [${event.level}]#[${event.scope ?? 'unknown'}] ${event.message ?? 'NO MESSAGE'} ${(event.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
`typescript
export interface Logger {
log(event: LogEvent): unknown;
}
`Code: Log Event
`typescript
export interface LogEvent extends ConsoleEvent {
/**
* Log message
*/
message?: string;
}
`Code: Console Event
`typescript
export interface 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 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 dataCode: Custom Logger
`typescript
import { Injectable } from '@travetto/di';
import type { LogEvent, Logger } from '@travetto/log';@Injectable()
export class CustomLogger implements Logger {
log(event: LogEvent): void {
const headers = new Headers();
headers.set('Content-Type', 'application/json');
const body = JSON.stringify(event);
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 LogDecorator 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
`typescript
export interface LogDecorator {
decorate(event: LogEvent): LogEvent;
}
`Code: Custom Logger
`typescript
import os from 'node:os';import { Injectable } from '@travetto/di';
import type { LogDecorator, LogEvent } from '@travetto/log';
@Injectable()
export class CustomDecorator implements LogDecorator {
decorate(event: LogEvent): LogEvent {
event.args.push({
memory: process.memoryUsage,
hostname: os.hostname()
});
return event;
}
}
`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. 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
`typescript
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.