unnbound-logger-sdk
Version:
A structured logging library with TypeScript support using Pino. Provides consistent, well-typed logging with automatic logId, workflowId, traceId, and deploymentId tracking across operational contexts.
113 lines (112 loc) • 3.93 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = void 0;
const pino_1 = __importDefault(require("pino"));
const uuid_1 = require("uuid");
const storage_1 = require("./storage");
const encode_1 = require("./encode");
const internal_1 = require("./internal");
const levels = new Set(['debug', 'info', 'warn', 'error']);
const formatLevel = (level) => ({ level });
// TODO: Check if there is a better way to encode the log object.
const formatLog = (log, visited = new WeakSet()) => {
// Prevent infinite loops from circular references
if (visited.has(log))
return log;
visited.add(log);
// We can't encode frozen objects.
if (Object.isFrozen(log))
return log;
// We don't use Object.entries() for performance reasons
for (const key in log) {
const value = log[key];
// We don't want to encode undefined values.
if (value == undefined)
continue;
// We can't encode immutable properties.
if (!Object.getOwnPropertyDescriptor(log, key)?.writable)
continue;
if (typeof value === 'string') {
log[key] = (0, encode_1.encode)(value);
}
else if (Array.isArray(value)) {
log[key] = value.map((item) => typeof item === 'string'
? (0, encode_1.encode)(item)
: item && typeof item === 'object'
? formatLog(item, visited)
: item);
}
else if (typeof value === 'object') {
log[key] = formatLog(value, visited);
}
}
return log;
};
exports.logger = (0, pino_1.default)({
level: process.env.LOG_LEVEL ?? 'debug',
base: {
environment: process.env.ENVIRONMENT,
workflowId: process.env.UNNBOUND_WORKFLOW_ID,
serviceId: process.env.UNNBOUND_SERVICE_ID,
deploymentId: process.env.UNNBOUND_DEPLOYMENT_ID,
},
mixin: () => ({ ...storage_1.storage.getStore(), logId: (0, uuid_1.v4)() }),
serializers: {
req: pino_1.default.stdSerializers.req,
res: pino_1.default.stdSerializers.res,
err: pino_1.default.stdSerializers.err,
message: encode_1.encode,
},
// Let CloudWatch handle timestamps
timestamp: false,
// Change message field from 'msg' to 'message'
messageKey: 'message',
formatters: { level: formatLevel, log: formatLog },
hooks: {
logMethod(args, method) {
const firstArg = args[0];
if (!!firstArg && typeof firstArg === 'object') {
// If the log entry is considered hidden, don't log it
if ((0, internal_1.hidden)(firstArg))
return;
// Dynamic log level by allowing overwriting the log level
if ('level' in firstArg &&
typeof firstArg.level === 'string' &&
levels.has(firstArg.level)) {
method = this[firstArg.level];
firstArg.level = undefined;
}
}
method.apply(this, args);
},
},
redact: {
paths: [
'http.request.headers.authorization',
'http.response.headers.authorization',
'http.request.headers.Authorization',
'http.response.headers.Authorization',
'err.config',
],
remove: true,
},
});
process.on('uncaughtException', (err, origin) => {
try {
exports.logger.error({ err, origin }, `Uncaught exception.`);
}
catch {
// noop
}
});
process.on('unhandledRejection', (err, origin) => {
try {
exports.logger.error({ err, origin }, `Unhandled rejection.`);
}
catch {
// noop
}
});