mirror of
https://github.com/log4js-node/log4js-node.git
synced 2025-12-08 19:26:01 +00:00
293 lines
8.6 KiB
JavaScript
293 lines
8.6 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* @fileoverview log4js is a library to log in JavaScript in similar manner
|
|
* than in log4j for Java (but not really).
|
|
*
|
|
* <h3>Example:</h3>
|
|
* <pre>
|
|
* const logging = require('log4js');
|
|
* const log = logging.getLogger('some-category');
|
|
*
|
|
* //call the log
|
|
* log.trace('trace me' );
|
|
* </pre>
|
|
*
|
|
* NOTE: the authors below are the original browser-based log4js authors
|
|
* don't try to contact them about bugs in this version :)
|
|
* @version 1.0
|
|
* @author Stephan Strittmatter - http://jroller.com/page/stritti
|
|
* @author Seth Chisamore - http://www.chisamore.com
|
|
* @since 2005-05-20
|
|
* @static
|
|
* Website: http://log4js.berlios.de
|
|
*/
|
|
const debug = require('debug')('log4js:main');
|
|
const fs = require('fs');
|
|
const CircularJSON = require('circular-json');
|
|
const Configuration = require('./configuration');
|
|
const connectModule = require('./connect-logger');
|
|
const logger = require('./logger');
|
|
const layouts = require('./layouts');
|
|
const levels = require('./levels');
|
|
|
|
let cluster;
|
|
try {
|
|
cluster = require('cluster'); // eslint-disable-line global-require
|
|
} catch (e) {
|
|
debug('Clustering support disabled because require(cluster) threw an error: ', e);
|
|
}
|
|
|
|
const defaultConfig = {
|
|
appenders: {
|
|
stdout: { type: 'stdout' }
|
|
},
|
|
categories: {
|
|
default: { appenders: ['stdout'], level: 'OFF' }
|
|
}
|
|
};
|
|
|
|
let Logger;
|
|
let LoggingEvent;
|
|
let config;
|
|
let enabled = false;
|
|
|
|
function configForCategory(category) {
|
|
debug(`configForCategory: searching for config for ${category}`);
|
|
if (config.categories.has(category)) {
|
|
debug(`configForCategory: ${category} exists in config, returning it`);
|
|
return config.categories.get(category);
|
|
}
|
|
if (category.indexOf('.') > 0) {
|
|
debug(`configForCategory: ${category} has hierarchy, searching for parents`);
|
|
return configForCategory(category.substring(0, category.lastIndexOf('.')));
|
|
}
|
|
debug('configForCategory: returning config for default category');
|
|
return configForCategory('default');
|
|
}
|
|
|
|
function appendersForCategory(category) {
|
|
return configForCategory(category).appenders;
|
|
}
|
|
|
|
function levelForCategory(category) {
|
|
return configForCategory(category).level;
|
|
}
|
|
|
|
function setLevelForCategory(category, level) {
|
|
let categoryConfig = config.categories.get(category);
|
|
debug(`setLevelForCategory: found ${categoryConfig} for ${category}`);
|
|
if (!categoryConfig) {
|
|
const sourceCategoryConfig = configForCategory(category);
|
|
debug('setLevelForCategory: no config found for category, ' +
|
|
`found ${sourceCategoryConfig} for parents of ${category}`);
|
|
categoryConfig = { appenders: sourceCategoryConfig.appenders };
|
|
}
|
|
categoryConfig.level = level;
|
|
config.categories.set(category, categoryConfig);
|
|
}
|
|
|
|
function serialise(logEvent) {
|
|
// JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
|
|
// The following allows us to serialize errors correctly.
|
|
// Validate that we really are in this case
|
|
try {
|
|
const logData = logEvent.data.map((e) => {
|
|
if (e && e.stack && CircularJSON.stringify(e) === '{}') {
|
|
e = { message: e.message, stack: e.stack };
|
|
}
|
|
return e;
|
|
});
|
|
logEvent.data = logData;
|
|
return CircularJSON.stringify(logEvent);
|
|
} catch (e) {
|
|
return serialise(new LoggingEvent(
|
|
'log4js',
|
|
config.levels.ERROR,
|
|
['Unable to serialise log event due to :', e]
|
|
));
|
|
}
|
|
}
|
|
|
|
function deserialise(serialised) {
|
|
let event;
|
|
try {
|
|
event = CircularJSON.parse(serialised);
|
|
event.startTime = new Date(event.startTime);
|
|
event.level = config.levels.getLevel(event.level.levelStr);
|
|
event.data = event.data.map((e) => {
|
|
if (e && e.stack) {
|
|
const fakeError = new Error(e.message);
|
|
fakeError.stack = e.stack;
|
|
e = fakeError;
|
|
}
|
|
return e;
|
|
});
|
|
} catch (e) {
|
|
event = new LoggingEvent(
|
|
'log4js',
|
|
config.levels.ERROR,
|
|
['Unable to parse log:', serialised, 'because: ', e]
|
|
);
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
function sendLogEventToAppender(logEvent) {
|
|
if (!enabled) return;
|
|
debug('Received log event ', logEvent);
|
|
const appenders = appendersForCategory(logEvent.categoryName);
|
|
appenders.forEach((appender) => {
|
|
appender(logEvent);
|
|
});
|
|
}
|
|
|
|
function workerDispatch(logEvent) {
|
|
debug(`sending message to master from worker ${process.pid}`);
|
|
process.send({ topic: 'log4js:message', data: serialise(logEvent) });
|
|
}
|
|
|
|
function isPM2Master() {
|
|
return config.pm2 && process.env[config.pm2InstanceVar] === '0';
|
|
}
|
|
|
|
function isMaster() {
|
|
return config.disableClustering || cluster.isMaster || isPM2Master();
|
|
}
|
|
|
|
/**
|
|
* Get a logger instance.
|
|
* @static
|
|
* @param loggerCategoryName
|
|
* @return {Logger} instance of logger for the category
|
|
*/
|
|
function getLogger(category) {
|
|
if (!enabled) {
|
|
configure(process.env.LOG4JS_CONFIG || defaultConfig);
|
|
}
|
|
|
|
const cat = category || 'default';
|
|
debug(`creating logger as ${isMaster() ? 'master' : 'worker'}`);
|
|
return new Logger((isMaster() ? sendLogEventToAppender : workerDispatch), cat);
|
|
}
|
|
|
|
function loadConfigurationFile(filename) {
|
|
if (filename) {
|
|
debug(`Loading configuration from ${filename}`);
|
|
return JSON.parse(fs.readFileSync(filename, 'utf8'));
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
// in a multi-process node environment, worker loggers will use
|
|
// process.send
|
|
const receiver = (worker, message) => {
|
|
// prior to node v6, the worker parameter was not passed (args were message, handle)
|
|
debug('cluster message received from worker ', worker, ': ', message);
|
|
if (worker.topic && worker.data) {
|
|
message = worker;
|
|
worker = undefined;
|
|
}
|
|
if (message && message.topic && message.topic === 'log4js:message') {
|
|
debug('received message: ', message.data);
|
|
sendLogEventToAppender(deserialise(message.data));
|
|
}
|
|
};
|
|
|
|
function configure(configurationFileOrObject) {
|
|
let configObject = configurationFileOrObject;
|
|
|
|
if (typeof configObject === 'string') {
|
|
configObject = loadConfigurationFile(configurationFileOrObject);
|
|
}
|
|
debug(`Configuration is ${configObject}`);
|
|
config = new Configuration(configObject);
|
|
module.exports.levels = config.levels;
|
|
const loggerModule = logger(config.levels, levelForCategory, setLevelForCategory);
|
|
Logger = loggerModule.Logger;
|
|
LoggingEvent = loggerModule.LoggingEvent;
|
|
module.exports.connectLogger = connectModule(config.levels).connectLogger;
|
|
|
|
// just in case configure is called after shutdown
|
|
process.removeListener('message', receiver);
|
|
if (cluster) {
|
|
cluster.removeListener('message', receiver);
|
|
}
|
|
if (config.disableClustering) {
|
|
debug('Not listening for cluster messages, because clustering disabled.');
|
|
} else if (isPM2Master()) {
|
|
// PM2 cluster support
|
|
// PM2 runs everything as workers - install pm2-intercom for this to work.
|
|
// we only want one of the app instances to write logs
|
|
debug('listening for PM2 broadcast messages');
|
|
process.on('message', receiver);
|
|
} else if (cluster.isMaster) {
|
|
debug('listening for cluster messages');
|
|
cluster.on('message', receiver);
|
|
} else {
|
|
debug('not listening for messages, because we are not a master process');
|
|
}
|
|
|
|
enabled = true;
|
|
}
|
|
|
|
/**
|
|
* Shutdown all log appenders. This will first disable all writing to appenders
|
|
* and then call the shutdown function each appender.
|
|
*
|
|
* @params {Function} cb - The callback to be invoked once all appenders have
|
|
* shutdown. If an error occurs, the callback will be given the error object
|
|
* as the first argument.
|
|
*/
|
|
function shutdown(cb) {
|
|
debug('Shutdown called. Disabling all log writing.');
|
|
// First, disable all writing to appenders. This prevents appenders from
|
|
// not being able to be drained because of run-away log writes.
|
|
enabled = false;
|
|
|
|
// Call each of the shutdown functions in parallel
|
|
const appenders = Array.from(config.appenders.values());
|
|
const shutdownFunctions = appenders.reduceRight((accum, next) => (next.shutdown ? accum + 1 : accum), 0);
|
|
let completed = 0;
|
|
let error;
|
|
|
|
debug(`Found ${shutdownFunctions} appenders with shutdown functions.`);
|
|
function complete(err) {
|
|
error = error || err;
|
|
completed += 1;
|
|
debug(`Appender shutdowns complete: ${completed} / ${shutdownFunctions}`);
|
|
if (completed >= shutdownFunctions) {
|
|
debug('All shutdown functions completed.');
|
|
cb(error);
|
|
}
|
|
}
|
|
|
|
if (shutdownFunctions === 0) {
|
|
debug('No appenders with shutdown functions found.');
|
|
return cb();
|
|
}
|
|
|
|
appenders.filter(a => a.shutdown).forEach(a => a.shutdown(complete));
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @name log4js
|
|
* @namespace Log4js
|
|
* @property getLogger
|
|
* @property configure
|
|
* @property shutdown
|
|
*/
|
|
const log4js = {
|
|
getLogger,
|
|
configure,
|
|
shutdown,
|
|
connectLogger: connectModule(levels()).connectLogger,
|
|
levels: levels(),
|
|
addLayout: layouts.addLayout
|
|
};
|
|
|
|
module.exports = log4js;
|