nightingale-logger
Version:
Logger for browser and node
507 lines (469 loc) • 11.9 kB
JavaScript
import * as util from 'node:util';
import { Level } from 'nightingale-levels';
export { Level } from 'nightingale-levels';
const globalOrWindow = typeof globalThis !== "undefined" ? globalThis : globalThis;
if (!globalOrWindow.__NIGHTINGALE_GET_CONFIG_FOR_LOGGER) {
globalOrWindow.__NIGHTINGALE_GET_CONFIG_FOR_LOGGER = () => ({
handlers: [],
processors: []
});
}
if (!globalOrWindow.__NIGHTINGALE_GET_CONFIG_FOR_LOGGER_RECORD) {
globalOrWindow.__NIGHTINGALE_GET_CONFIG_FOR_LOGGER_RECORD = (key, level) => {
const {
handlers,
processors
} = globalOrWindow.__NIGHTINGALE_GET_CONFIG_FOR_LOGGER(key);
return {
handlers: handlers.filter(handler => level >= handler.minLevel && (!handler.isHandling || handler.isHandling(level, key))),
processors
};
};
}
function getConfigForLoggerRecord(key, recordLevel) {
return globalOrWindow.__NIGHTINGALE_GET_CONFIG_FOR_LOGGER_RECORD(key, recordLevel);
}
function isError(messageOrError) {
return messageOrError instanceof Error;
}
/**
* Interface that allows you to log records.
* This records are treated by handlers
*/
class Logger {
/**
* Create a new Logger
*
* @param {string} key
* @param {string} [displayName]
*/
constructor(key, displayName) {
this.key = key;
this.displayName = displayName;
if (process.env.NODE_ENV !== "production" && key.includes(".")) {
throw new Error(`nightingale: \`.\` in key is no longer supported, use \`:\` instead (key: ${key})`);
}
}
/** @private */
getHandlersAndProcessors(recordLevel) {
return getConfigForLoggerRecord(this.key, recordLevel);
}
/** @private */
getConfig() {
return globalOrWindow.__NIGHTINGALE_GET_CONFIG_FOR_LOGGER(this.key);
}
/**
* Create a child logger
*/
child(childSuffixKey, childDisplayName) {
return new Logger(`${this.key}:${childSuffixKey}`, childDisplayName);
}
/**
* Create a new Logger with the same key a this attached context
*
* @example
* ```typescript
* const loggerMyService = new Logger('app:myService');
* function someAction(arg1) {
* const logger = loggerMyService.context({ arg1 });
* logger.enter(someAction);
* // do stuff
* logger.info('info');
* // do stuff
* logger.exit(someAction);
* }
* ```
*
*/
context(context) {
const logger = new Logger(this.key);
logger.setContext(context);
return logger;
}
/**
* Get the context of this logger
*/
getContextObject() {
return this.contextObject;
}
/**
* Set the context of this logger
*
* @param {Object} context
*/
setContext(context) {
this.contextObject = context;
}
/**
* Extends existing context of this logger
*/
extendsContext(extendedContext) {
if (this.contextObject === undefined) {
throw new Error("Cannot extends context that does not exists. Use setContext(context) first.");
}
Object.assign(this.contextObject, extendedContext);
}
/**
* Handle a record
*
* Use this only if you know what you are doing.
*/
addRecord(record) {
const {
handlers,
processors
} = this.getHandlersAndProcessors(record.level);
if (handlers.length === 0) {
if (record.level > Level.ERROR) {
// eslint-disable-next-line no-console
console.log("[nightingale] no logger for > error level.", {
key: record.key,
message: record.message
});
}
return;
}
if (processors) {
processors.forEach(process => {
process(record, record.context);
});
}
handlers.some(handler => handler.handle(record) === false);
}
/**
* Log a message
*/
log(messageOrError, metadata, level = Level.INFO, options) {
const isMessageError = isError(messageOrError);
const message = isMessageError ? `${messageOrError.name}: ${messageOrError.message}` : messageOrError;
const extendedMetadata = isMessageError && !(metadata && Object.hasOwn(metadata, "error")) ? {
...metadata,
error: messageOrError
} : metadata;
const context = extendedMetadata?.context;
if (extendedMetadata) {
delete extendedMetadata.context;
}
const record = {
level,
key: this.key,
displayName: this.displayName,
datetime: new Date(),
message,
context: context || this.contextObject,
metadata: extendedMetadata,
extra: {},
...options
};
this.addRecord(record);
}
/**
* Log a trace message
*/
trace(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.TRACE, {
metadataStyles
});
}
/**
* Log a debug message
*/
debug(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.DEBUG, {
metadataStyles
});
}
/**
* Notice an info message
*/
notice(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.NOTICE, {
metadataStyles
});
}
/**
* Log an info message
*/
info(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.INFO, {
metadataStyles
});
}
/**
* Log a warn message
*/
warn(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.WARN, {
metadataStyles
});
}
/**
* Log an error message
*
* @example
* ```typescript
* const logger = new Logger('something');
* try {
* throw new Error('Always throws here');
* } catch (error) {
* logger.error('caught error', { error });
* }
* ```
*/
error(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.ERROR, {
metadataStyles
});
}
/**
* Log an critical message
*/
critical(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.CRITICAL, {
metadataStyles
});
}
/**
* Log a fatal message
*
* @example
* ```typescript
* const logger = new Logger('something');
* try {
* throw new Error('Always throws here');
* } catch (error) {
* logger.error('caught error', { error });
* process.exit(1);
* }
*/
fatal(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.FATAL, {
metadataStyles
});
}
/**
* Log an alert message
*/
alert(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.ALERT, {
metadataStyles
});
}
/**
* Log an inspected value
*/
inspectValue(value, metadata, metadataStyles) {
{
// Note: inspect is a special function for node:
// https://github.com/nodejs/node/blob/a1bda1b4deb08dfb3e06cb778f0db40023b18318/lib/util.js#L210
const inspectedValue = util.inspect(value, {
depth: 6
});
this.log(inspectedValue, metadata, Level.DEBUG, {
metadataStyles,
styles: ["gray"]
});
}
}
/**
* Log a debugged var
*/
inspectVar(varName, varValue, metadata, metadataStyles) {
{
const inspectedValue = util.inspect(varValue, {
depth: 6
});
this.log(`${varName} = ${inspectedValue}`, metadata, Level.DEBUG, {
metadataStyles,
styles: ["cyan"]
});
}
}
/**
* Alias for infoSuccess
*/
success(message, metadata, metadataStyles) {
this.infoSuccess(message, metadata, metadataStyles);
}
/**
* Log an info success message
*/
infoSuccess(message, metadata, metadataStyles) {
this.log(message, metadata, Level.INFO, {
metadataStyles,
symbol: "✔",
styles: ["green", "bold"]
});
}
/**
* Log an debug success message
*/
debugSuccess(message, metadata, metadataStyles) {
this.log(message, metadata, Level.DEBUG, {
metadataStyles,
symbol: "✔",
styles: ["green"]
});
}
/**
* Alias for infoFail
*/
fail(messageOrError, metadata, metadataStyles) {
this.infoFail(messageOrError, metadata, metadataStyles);
}
/**
* Log an info fail message
*/
infoFail(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.INFO, {
metadataStyles,
symbol: "✖",
styles: ["red", "bold"]
});
}
/**
* Log an debug fail message
*/
debugFail(messageOrError, metadata, metadataStyles) {
this.log(messageOrError, metadata, Level.DEBUG, {
metadataStyles,
symbol: "✖",
styles: ["red"]
});
}
/**
* @returns {number} time to pass to timeEnd
*/
time(message, metadata, metadataStyles, level = Level.DEBUG) {
if (message) {
this.log(message, metadata, level, {
metadataStyles
});
}
return Date.now();
}
infoTime(message, metadata, metadataStyles) {
return this.time(message, metadata, metadataStyles, Level.INFO);
}
/**
* Finds difference between when this method
* was called and when the respective time method
* was called, then logs out the difference
* and deletes the original record
*/
timeEnd(startTime, message, metadata, metadataStyles, level = Level.DEBUG, options) {
const now = Date.now();
const diffTime = now - startTime;
let readableTime;
if (diffTime < 1000) {
readableTime = `${diffTime}ms`;
} else {
const seconds = diffTime > 1000 ? Math.floor(diffTime / 1000) : 0;
readableTime = `${seconds ? `${seconds}s and ` : ""}${diffTime - seconds * 1000}ms`;
}
const extendedMetadata = {
...metadata,
readableTime,
timeMs: diffTime
};
this.log(message, extendedMetadata, level, {
...options,
metadataStyles
});
}
/**
* Like timeEnd, but with INFO level
*/
infoTimeEnd(time, message, metadata, metadataStyles) {
this.timeEnd(time, message, metadata, metadataStyles, Level.INFO);
}
/**
* Like timeEnd, but with INFO level
*/
infoSuccessTimeEnd(time, message, metadata, metadataStyles) {
this.timeEnd(time, message, metadata, metadataStyles, Level.INFO, {
symbol: "✔",
styles: ["green", "bold"]
});
}
/**
* Log an enter in a function
*
* @example
* ```typescript
* class A {
* method(arg1) {
* logger.enter(method, { arg1 });
* // Do your stuff
* }
* }
* ```
*
*/
enter(fn, metadata, metadataStyles) {
const extendedMetadata = {
...metadata,
functionName: fn.name
};
this.log("enter", extendedMetadata, Level.TRACE, {
metadataStyles
});
}
/**
* Log an exit in a function
*
* @example
* ```typescript
* const logger = new Logger('myNamespace:A');
* class A {
* method(arg1) {
* // Do your stuff
* logger.exit(method, { arg1 });
* }
* }
* ```
*/
exit(fn, metadata, metadataStyles) {
const extendedMetadata = {
...metadata,
functionName: fn.name
};
this.log("exit", extendedMetadata, Level.TRACE, {
metadataStyles
});
}
/**
* Wrap around a function to log enter and exit of a function
*
* @example
* ```typescript
* const logger = new Logger('myNamespace:A');
* class A {
* method() {
* logger.wrap(method, () => {
* // Do your stuff
* });
* }
* }
* ```
*/
wrap(fn, option1, option2, callback) {
let metadata;
let metadataStyles;
if (typeof option1 === "function") {
callback = option1;
} else {
metadata = option1;
if (typeof option2 === "function") {
callback = option2;
} else {
metadataStyles = option2;
}
}
this.enter(fn, metadata, metadataStyles);
callback();
this.exit(fn);
}
}
export { Logger };
//# sourceMappingURL=index-node20.mjs.map