lesgo
Version:
Core framework for lesgo node.js serverless framework.
172 lines (171 loc) • 5.26 kB
JavaScript
import LesgoException from '../exceptions/LesgoException';
import { getCurrentDatetime } from '../utils';
export default class LoggerService {
constructor(opts = {}) {
const defaultOptions = {
logger: 'lesgo-logger',
meta: {},
transports: [],
};
const options = Object.assign(Object.assign({}, defaultOptions), {
logger: opts.logger || defaultOptions.logger,
meta: Object.assign(
Object.assign({}, defaultOptions.meta),
opts.defaultMeta || {}
),
transports: opts.transports || [],
});
this.logger = options.logger;
this.meta = options.meta;
this.transports = options.transports;
this.logLevels = {
error: 0,
warn: 1,
notice: 2,
info: 3,
debug: 4,
};
}
log(level, message, extra = {}) {
if (level === undefined || !Object.keys(this.logLevels).includes(level)) {
throw new LesgoException('Invalid level provided in log()');
}
const structuredMessage = this.structureLogMessage(level, message, extra);
this.transports.map(transport => {
// @ts-ignore
return this[`${transport.logType}Logger`](level, structuredMessage);
});
}
error(message, extra = {}) {
this.log('error', message, extra);
}
warn(message, extra = {}) {
this.log('warn', message, extra);
}
info(message, extra = {}) {
this.log('info', message, extra);
}
debug(message, extra = {}) {
this.log('debug', message, extra);
}
notice(message, extra = {}) {
this.log('notice', message, extra);
}
addMeta(meta = {}) {
this.meta = Object.assign(Object.assign({}, this.meta), meta);
}
consoleLogger(level, message) {
if (!this.checkIsLogRequired('console', level)) return null;
const refinedMessage = this.refineMessagePerTransport('console', message);
const consoleFunc = level === 'notice' ? 'log' : level;
const sanitizedMessage = this.sanitizeForJson(refinedMessage);
return console[consoleFunc](JSON.stringify(sanitizedMessage));
}
checkIsLogRequired(transportName, level) {
const transport = this.getTransportByName(transportName);
if (transport === undefined) {
return false;
}
const transportLevel = transport.level;
if (this.logLevels[transportLevel] < this.logLevels[level]) {
return false;
}
return true;
}
structureLogMessage(level, message, extra) {
const structuredMessage = {
level,
message,
logger: this.logger,
extra: Object.assign(Object.assign({}, this.meta), extra),
};
return structuredMessage;
}
refineMessagePerTransport(transportName, message) {
const transport = this.getTransportByName(transportName);
const refinedMessage = message;
if (transport === undefined) {
return refinedMessage;
}
if (transport.config !== undefined) {
if (transport.config.meta !== undefined) {
refinedMessage.extra = Object.assign(
Object.assign({}, refinedMessage.extra),
transport.config.meta
);
}
if (transport.config.tags !== undefined) {
refinedMessage.tags = transport.config.tags;
}
if (
refinedMessage.tags !== undefined &&
refinedMessage.extra.tags !== undefined
) {
refinedMessage.tags = Object.assign(
Object.assign({}, refinedMessage.tags),
refinedMessage.extra.tags
);
delete refinedMessage.extra.tags;
}
if (transport.config.getCreatedAt) {
refinedMessage.created = getCurrentDatetime();
}
}
return refinedMessage;
}
getTransportByName(transportName) {
return this.transports.find(
transport => transport.logType === transportName
);
}
/**
* Sanitizes an object for JSON serialization by:
* - Replacing functions with a string representation (similar to console.log behavior)
* - Handling circular references
* - Preserving other values
*/
sanitizeForJson(obj, seen = new WeakSet()) {
// Handle null and undefined
if (obj === null || obj === undefined) {
return obj;
}
// Handle functions - replace with string representation like console.log does
if (typeof obj === 'function') {
const funcName = obj.name || 'anonymous';
return `[Function: ${funcName}]`;
}
// Handle primitives
if (typeof obj !== 'object') {
return obj;
}
// Handle circular references
if (seen.has(obj)) {
return '[Circular]';
}
// Handle Date objects
if (obj instanceof Date) {
return obj.toISOString();
}
// Handle arrays
if (Array.isArray(obj)) {
seen.add(obj);
return obj.map(item => this.sanitizeForJson(item, seen));
}
// Handle objects
seen.add(obj);
const sanitized = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
try {
sanitized[key] = this.sanitizeForJson(obj[key], seen);
} catch (error) {
// If we can't serialize a property, replace it with error message
sanitized[key] = `[Error: ${
error instanceof Error ? error.message : 'Unknown error'
}]`;
}
}
}
return sanitized;
}
}