import type { Logger as IOTelLogger } from "@opentelemetry/api-logs"; import { SeverityNumber } from "@opentelemetry/api-logs"; import type { LoggerProvider } from "@opentelemetry/sdk-logs"; import type { Attributes } from "@opentelemetry/api"; import type { Logger, LogAttributes, LogLevel } from "./types.js"; import { SEVERITY_MAP } from "./types.js"; /** * Structured logger wrapping the OpenTelemetry Logs API. * * Provides an ergonomic interface (info/warn/error/etc.) over the * lower-level OTel LogRecord emit API, with child logger support * for adding persistent context (request ID, component name, etc.). */ export class AppLogger implements Logger { private otelLogger: IOTelLogger; private provider: LoggerProvider; private baseAttributes: LogAttributes; private minSeverity: SeverityNumber; constructor( otelLogger: IOTelLogger, provider: LoggerProvider, baseAttributes: LogAttributes = {}, minSeverity: SeverityNumber = SeverityNumber.INFO ) { this.otelLogger = otelLogger; this.provider = provider; this.baseAttributes = baseAttributes; this.minSeverity = minSeverity; } debug(message: string, attributes?: LogAttributes): void { this.emit(SeverityNumber.DEBUG, "debug", message, attributes); } info(message: string, attributes?: LogAttributes): void { this.emit(SeverityNumber.INFO, "info", message, attributes); } warn(message: string, attributes?: LogAttributes): void { this.emit(SeverityNumber.WARN, "warn", message, attributes); } error(message: string, attributes?: LogAttributes): void { this.emit(SeverityNumber.ERROR, "error", message, attributes); } fatal(message: string, attributes?: LogAttributes): void { this.emit(SeverityNumber.FATAL, "fatal", message, attributes); } child(attributes: LogAttributes): Logger { return new AppLogger( this.otelLogger, this.provider, { ...this.baseAttributes, ...attributes }, this.minSeverity ); } async shutdown(): Promise { await this.provider.shutdown(); } private emit( severityNumber: SeverityNumber, severityText: string, message: string, attributes?: LogAttributes ): void { if (severityNumber < this.minSeverity) { return; } this.otelLogger.emit({ severityNumber, severityText, body: message, attributes: { ...this.baseAttributes, ...attributes, } as Attributes, }); } } /** Resolve a LogLevel string to its OTel SeverityNumber. */ export function resolveLogLevel(level: LogLevel): SeverityNumber { return SEVERITY_MAP[level]; }