@nivinjoseph/n-log
Version:
Logging framework
225 lines • 8.88 kB
JavaScript
import { ConfigurationManager } from "@nivinjoseph/n-config";
import { Exception } from "@nivinjoseph/n-exception";
import { SpanStatusCode, context, isSpanContextValid, trace } from "@opentelemetry/api";
import { LogDateTimeZone } from "./log-date-time-zone.js";
import { DateTime } from "luxon";
import { ensureExhaustiveCheck } from "@nivinjoseph/n-defensive";
/**
* Abstract base class that provides common logging functionality.
* Implements the Logger interface and provides shared functionality for all logger implementations.
* Handles common tasks like timestamp formatting, error message extraction, and trace injection.
*/
export class BaseLogger {
// eslint-disable-next-line @typescript-eslint/naming-convention
_UINT_MAX = 4294967296;
_source = "nodejs";
_service = ConfigurationManager.getConfig("package_name") ?? ConfigurationManager.getConfig("package.name") ?? "n-log";
_env = ConfigurationManager.getConfig("env")?.toLowerCase() ?? "dev";
_logDateTimeZone;
_useJsonFormat;
_logInjector;
_enableOtelToDatadogTraceConversion;
/**
* Gets the source identifier for logs (default: "nodejs")
*/
get source() { return this._source; }
/**
* Gets the service name for logs (default: package name or "n-log")
*/
get service() { return this._service; }
/**
* Gets the environment identifier for logs (default: "dev")
*/
get env() { return this._env; }
/**
* Gets whether JSON format is enabled for logs
*/
get useJsonFormat() { return this._useJsonFormat; }
/**
* Gets the log record injector function if configured
*/
get logInjector() { return this._logInjector; }
/**
* Creates a new instance of BaseLogger
* @param config - Optional configuration for the logger
* @param config.logDateTimeZone - The timezone to use for log timestamps (default: UTC)
* @param config.useJsonFormat - Whether to format logs as JSON (default: false)
* @param config.logInjector - Function to inject additional data into log records (only used when useJsonFormat is true)
* @param config.enableOtelToDatadogTraceConversion - Whether to enable OpenTelemetry to Datadog trace ID conversion
*/
constructor(config) {
// eslint-disable-next-line @typescript-eslint/unbound-method
const { logDateTimeZone, useJsonFormat, logInjector, enableOtelToDatadogTraceConversion } = config ?? {};
if (!logDateTimeZone || logDateTimeZone.isEmptyOrWhiteSpace() ||
![LogDateTimeZone.utc, LogDateTimeZone.local, LogDateTimeZone.est, LogDateTimeZone.pst].contains(logDateTimeZone)) {
this._logDateTimeZone = LogDateTimeZone.utc;
}
else {
this._logDateTimeZone = logDateTimeZone;
}
this._useJsonFormat = !!useJsonFormat;
this._logInjector = logInjector ?? null;
this._enableOtelToDatadogTraceConversion = !!enableOtelToDatadogTraceConversion;
}
/**
* Extracts an error message from an exception or error object
* @param exp - The exception or error to extract the message from
* @returns The extracted error message
*/
getErrorMessage(exp) {
// eslint-disable-next-line no-useless-assignment
let logMessage = "";
try {
if (exp instanceof Exception)
logMessage = exp.toString();
else if (exp instanceof Error)
logMessage = exp.stack;
else
logMessage = exp.toString();
}
catch (error) {
console.warn(error);
logMessage = "There was an error while attempting to log another error. Check earlier logs for a warning.";
}
return logMessage;
}
/**
* Gets the current date and time in the configured timezone
* @returns ISO formatted date-time string
*/
getDateTime() {
const value = DateTime.now();
const time = value.toUTC().toISO();
let dateTime;
switch (this._logDateTimeZone) {
case LogDateTimeZone.utc:
dateTime = time;
break;
case LogDateTimeZone.local:
dateTime = value.toISO();
break;
case LogDateTimeZone.est:
dateTime = value.setZone("America/New_York").toISO();
break;
case LogDateTimeZone.pst:
dateTime = value.setZone("America/Los_Angeles").toISO();
break;
default:
ensureExhaustiveCheck(this._logDateTimeZone);
}
return { dateTime, time };
}
/**
* Injects trace information into a log record
* @param log - The log record to inject trace information into
* @param isError - Whether this is an error log (affects span status)
*/
injectTrace(log, isError = false) {
const span = trace.getSpan(context.active());
if (span) {
const spanContext = span.spanContext();
if (isSpanContextValid(spanContext)) {
log["trace_id"] = spanContext.traceId;
log["span_id"] = spanContext.spanId;
log["trace_flags"] = `0${spanContext.traceFlags.toString(16)}`;
if (isError)
span.setStatus({
code: SpanStatusCode.ERROR,
message: log.message
});
if (this._enableOtelToDatadogTraceConversion) {
const traceIdEnd = spanContext.traceId.slice(spanContext.traceId.length / 2);
log["dd.trace_id"] = this._toNumberString(this._fromString(traceIdEnd, 16));
log["dd.span_id"] = this._toNumberString(this._fromString(spanContext.spanId, 16));
}
}
}
}
/**
* Converts a buffer to a number string with the specified radix
* @param buffer - The buffer to convert
* @param radix - The radix to use for conversion (default: 10)
* @returns The converted number string
*/
_toNumberString(buffer, radix) {
let high = this._readInt32(buffer, 0);
let low = this._readInt32(buffer, 4);
let str = "";
radix = radix ?? 10;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
while (1) {
const mod = (high % radix) * this._UINT_MAX + low;
high = Math.floor(high / radix);
low = Math.floor(mod / radix);
str = (mod % radix).toString(radix) + str;
if (!high && !low)
break;
}
return str;
}
/**
* Converts a numerical string to a buffer using the specified radix
* @param str - The string to convert
* @param radix - The radix to use for conversion
* @returns The converted buffer
*/
_fromString(str, radix) {
const buffer = new Uint8Array(8);
const len = str.length;
let pos = 0;
let high = 0;
let low = 0;
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
if (str[0] === "-")
pos++;
const sign = pos;
while (pos < len) {
const chr = parseInt(str[pos++], radix);
if (!(chr >= 0))
break; // NaN
low = low * radix + chr;
high = high * radix + Math.floor(low / this._UINT_MAX);
low %= this._UINT_MAX;
}
if (sign) {
high = ~high;
if (low) {
low = this._UINT_MAX - low;
}
else {
high++;
}
}
this._writeUInt32BE(buffer, high, 0);
this._writeUInt32BE(buffer, low, 4);
return buffer;
}
/**
* Writes an unsigned 32-bit integer to a buffer in big-endian format
* @param buffer - The buffer to write to
* @param value - The value to write
* @param offset - The offset in the buffer to write at
*/
_writeUInt32BE(buffer, value, offset) {
buffer[3 + offset] = value & 255;
value = value >> 8;
buffer[2 + offset] = value & 255;
value = value >> 8;
buffer[1 + offset] = value & 255;
value = value >> 8;
buffer[0 + offset] = value & 255;
}
/**
* Reads a 32-bit integer from a buffer
* @param buffer - The buffer to read from
* @param offset - The offset in the buffer to read from
* @returns The read integer value
*/
_readInt32(buffer, offset) {
return (buffer[offset + 0] * 16777216) +
(buffer[offset + 1] << 16) +
(buffer[offset + 2] << 8) +
buffer[offset + 3];
}
}
//# sourceMappingURL=base-logger.js.map