@athenna/logger
Version:
The Athenna logging solution. Log in stdout, files and buckets.
229 lines (228 loc) • 6.2 kB
JavaScript
/**
* @athenna/logger
*
* (c) João Lenon <lenon@athenna.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import { hostname } from 'node:os';
import { Is, Color, Module } from '@athenna/common';
const otelApi = await Module.safeImport('@opentelemetry/api');
export class Formatter {
constructor() {
/**
* Holds the configuration object of formatter.
*/
this.configs = {};
}
/**
* Creates a new instance of Formatter.
*/
config(configs) {
this.configs = configs;
return this;
}
/**
* Create the PID for formatter.
*/
pid() {
return process.pid.toString();
}
/**
* Create the hostname for formatter.
*/
hostname() {
return hostname();
}
/**
* Get the level without any color or format.
*/
level() {
return this.configs.level;
}
/**
* Get the trace id for formatter.
*/
traceId() {
if (otelApi?.trace?.getActiveSpan()?.spanContext().traceId) {
return otelApi?.trace?.getActiveSpan()?.spanContext().traceId;
}
return null;
}
/**
* Get the span id for formatter.
*/
spanId() {
return otelApi?.trace?.getActiveSpan()?.spanContext().spanId || null;
}
/**
* Resolve configured context bindings using the active OpenTelemetry context.
*/
contextBindings(activeContext) {
if (!otelApi) {
throw new Error('The package @opentelemetry/api is not installed');
}
if (!activeContext) {
activeContext = otelApi.context.active();
}
const contextBindings = this.configs.contextBindings || [];
const resolved = {};
for (const binding of contextBindings) {
const value = binding.resolve(activeContext);
if (Is.Undefined(value)) {
continue;
}
resolved[binding.field] = value;
}
return resolved;
}
/**
* Create the timestamp for formatter.
*/
timestamp() {
const localeStringOptions = {
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
day: '2-digit',
month: '2-digit'
};
return new Date(Date.now()).toLocaleString(undefined, localeStringOptions);
}
/**
* Get the circular replacer function to be used in
* JSON.stringify().
*/
getCircularReplacer() {
const ancestors = [];
return function (key, value) {
if (!Is.Object(value) || value === null) {
return value;
}
while (ancestors.length > 0 && ancestors.at(-1) !== this) {
ancestors.pop();
}
if (ancestors.includes(value)) {
return '[Circular]';
}
ancestors.push(value);
return value;
};
}
/**
* Transform the message to string.
*/
toString(message) {
if (Is.String(message)) {
return message;
}
if (Is.Object(message)) {
message = JSON.stringify(message, this.getCircularReplacer());
}
return `${message}`;
}
/**
* Clean the message removing colors if clean
* option is true. If force is true, then colors
* will be removed even if configs clean option
* is false.
*/
clean(message, force = false) {
if (this.configs.clean || force) {
return Color.removeColors(message);
}
return message;
}
/**
* Apply all colors necessary to message.
*/
applyColors(message) {
message = this.toString(message);
return this.applyColorsByChalk(this.applyColorsByLevel(message));
}
/**
* Apply colors in message.
*/
applyColorsByChalk(message) {
if (!this.configs.chalk) {
return message;
}
return this.configs.chalk(message);
}
/**
* Apply colors in message by level.
*/
applyColorsByLevel(message) {
const level = this.configs.level;
return this.paintMessageByLevel(level, message);
}
/**
* Create the cli level string.
*/
cliLevel() {
const level = this.configs.level;
if (!Color[level]) {
return Color.bold(`[ ${level} ]`);
}
return Color[level].bold(`[ ${level} ]`);
}
/**
* Create the simple level string.
*/
simpleLevel() {
const level = this.configs.level;
if (!Color[level]) {
return Color.bold(`[${level.toUpperCase()}]`);
}
return Color[level].bold(`[${level.toUpperCase()}]`);
}
/**
* Create the message level emoji string.
*/
messageLevel() {
const level = this.configs.level;
return this.getEmojiByLevel(level, this.configs.customEmoji);
}
/**
* Get the emoji by level.
*/
getEmojiByLevel(level, customEmoji) {
if (customEmoji) {
return customEmoji;
}
const levelEmojis = {
trace: '\u{1F43E}',
debug: '\u{1F50E}',
info: '\u{2139}',
success: '\u{2705}',
warn: '\u{26A0}',
error: '\u{274C}',
fatal: '\u{1F6D1}'
};
if (!levelEmojis[level.toLowerCase()]) {
return '';
}
return levelEmojis[level.toLowerCase()];
}
/**
* Paint the message by level.
*/
paintMessageByLevel(level, message) {
const levelLower = level.toLowerCase();
const levelColors = {
trace: Color.trace,
debug: Color.debug,
info: Color.info,
success: Color.success,
warn: Color.warn,
error: Color.error,
fatal: Color.fatal
};
if (!levelColors[levelLower]) {
return message;
}
return levelColors[levelLower](message);
}
}