UNPKG

@athenna/logger

Version:

The Athenna logging solution. Log in stdout, files and buckets.

229 lines (228 loc) 6.2 kB
/** * @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); } }