3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 * @fileoverview log4js is a library to log in JavaScript in similar manner
18 * than in log4j for Java. The API should be nearly the same.
22 * var logging = require('log4js');
23 * //add an appender that logs all messages to stdout.
24 * logging.addAppender(logging.consoleAppender());
25 * //add an appender that logs "some-category" to a file
26 * logging.addAppender(logging.fileAppender("file.log"), "some-category");
28 * var log = logging.getLogger("some-category");
29 * log.setLevel(logging.levels.TRACE); //set the Level
34 * log.trace("trace me" );
37 * NOTE: the authors below are the original browser-based log4js authors
38 * don't try to contact them about bugs in this version :)
40 * @author Stephan Strittmatter - http://jroller.com/page/stritti
41 * @author Seth Chisamore - http://www.chisamore.com
44 * Website: http://log4js.berlios.de
46 var events = require('events')
48 , path = require('path')
49 , util = require('util')
50 , layouts = require('./layouts')
51 , levels = require('./levels')
52 , loggerModule = require('./logger')
53 , LoggingEvent = loggerModule.LoggingEvent
54 , Logger = loggerModule.Logger
55 , ALL_CATEGORIES = '[all]'
59 , appenderShutdowns = {}
67 require('./appenders/console');
69 function hasLogger(logger) {
70 return loggers.hasOwnProperty(logger);
73 levels.forName = function(levelStr, levelVal) {
75 if (typeof levelStr === "string" && typeof levelVal === "number") {
76 var levelUpper = levelStr.toUpperCase();
77 level = new levels.Level(levelVal, levelUpper);
78 loggerModule.addLevelMethods(level);
83 levels.getLevel = function(levelStr) {
85 if (typeof levelStr === "string") {
86 var levelUpper = levelStr.toUpperCase();
87 level = levels.toLevel(levelStr);
92 function getBufferedLogger(categoryName) {
93 var base_logger = getLogger(categoryName);
96 logger.target = base_logger;
97 logger.flush = function () {
98 for (var i = 0; i < logger.temp.length; i++) {
99 var log = logger.temp[i];
100 logger.target[log.level](log.message);
101 delete logger.temp[i];
104 logger.trace = function (message) { logger.temp.push({level: 'trace', message: message}); };
105 logger.debug = function (message) { logger.temp.push({level: 'debug', message: message}); };
106 logger.info = function (message) { logger.temp.push({level: 'info', message: message}); };
107 logger.warn = function (message) { logger.temp.push({level: 'warn', message: message}); };
108 logger.error = function (message) { logger.temp.push({level: 'error', message: message}); };
109 logger.fatal = function (message) { logger.temp.push({level: 'fatal', message: message}); };
114 function normalizeCategory (category) {
115 return category + '.';
118 function doesLevelEntryContainsLogger (levelCategory, loggerCategory) {
119 var normalizedLevelCategory = normalizeCategory(levelCategory);
120 var normalizedLoggerCategory = normalizeCategory(loggerCategory);
121 return normalizedLoggerCategory.substring(0, normalizedLevelCategory.length) == normalizedLevelCategory; //jshint ignore:line
124 function doesAppenderContainsLogger (appenderCategory, loggerCategory) {
125 var normalizedAppenderCategory = normalizeCategory(appenderCategory);
126 var normalizedLoggerCategory = normalizeCategory(loggerCategory);
127 return normalizedLoggerCategory.substring(0, normalizedAppenderCategory.length) == normalizedAppenderCategory; //jshint ignore:line
132 * Get a logger instance. Instance is cached on categoryName level.
133 * @param {String} categoryName name of category to log to.
134 * @return {Logger} instance of logger for the category
137 function getLogger (loggerCategoryName) {
139 // Use default logger if categoryName is not specified or invalid
140 if (typeof loggerCategoryName !== "string") {
141 loggerCategoryName = Logger.DEFAULT_CATEGORY;
144 if (!hasLogger(loggerCategoryName)) {
149 // If there's a "levels" entry in the configuration
151 // Goes through the categories in the levels configuration entry,
152 // starting with the "higher" ones.
153 var keys = Object.keys(levels.config).sort();
154 for (var idx = 0; idx < keys.length; idx++) {
155 var levelCategory = keys[idx];
156 if (doesLevelEntryContainsLogger(levelCategory, loggerCategoryName)) {
157 // level for the logger
158 level = levels.config[levelCategory];
164 // Create the logger for this name if it doesn't already exist
165 loggers[loggerCategoryName] = new Logger(loggerCategoryName, level);
169 for(var appenderCategory in appenders) {
170 if (doesAppenderContainsLogger(appenderCategory, loggerCategoryName)) {
171 appenderList = appenders[appenderCategory];
172 appenderList.forEach(function(appender) {
173 loggers[loggerCategoryName].addListener("log", appender);
179 if (appenders[ALL_CATEGORIES]) {
180 appenderList = appenders[ALL_CATEGORIES];
181 appenderList.forEach(function(appender) {
182 loggers[loggerCategoryName].addListener("log", appender);
187 return loggers[loggerCategoryName];
191 * args are appender, then zero or more categories
193 function addAppender () {
194 var args = Array.prototype.slice.call(arguments);
195 var appender = args.shift();
196 if (args.length === 0 || args[0] === undefined) {
197 args = [ ALL_CATEGORIES ];
199 //argument may already be an array
200 if (Array.isArray(args[0])) {
204 args.forEach(function(appenderCategory) {
205 addAppenderToCategory(appender, appenderCategory);
207 if (appenderCategory === ALL_CATEGORIES) {
208 addAppenderToAllLoggers(appender);
211 for(var loggerCategory in loggers) {
212 if (doesAppenderContainsLogger(appenderCategory,loggerCategory)) {
213 loggers[loggerCategory].addListener("log", appender);
221 function addAppenderToAllLoggers(appender) {
222 for (var logger in loggers) {
223 if (hasLogger(logger)) {
224 loggers[logger].addListener("log", appender);
229 function addAppenderToCategory(appender, category) {
230 if (!appenders[category]) {
231 appenders[category] = [];
233 appenders[category].push(appender);
236 function clearAppenders () {
238 for (var logger in loggers) {
239 if (hasLogger(logger)) {
240 loggers[logger].removeAllListeners("log");
245 function configureAppenders(appenderList, options) {
248 appenderList.forEach(function(appenderConfig) {
249 loadAppender(appenderConfig.type);
251 appenderConfig.makers = appenderMakers;
253 appender = appenderMakers[appenderConfig.type](appenderConfig, options);
254 addAppender(appender, appenderConfig.category);
256 throw new Error("log4js configuration problem for " + util.inspect(appenderConfig), e);
262 function configureLevels(_levels) {
263 levels.config = _levels; // Keep it so we can create loggers later using this cfg
265 var keys = Object.keys(levels.config).sort();
266 for (var idx in keys) {
267 var category = keys[idx];
268 if(category === ALL_CATEGORIES) {
269 setGlobalLogLevel(_levels[category]);
272 for(var loggerCategory in loggers) {
273 if (doesLevelEntryContainsLogger(category, loggerCategory)) {
274 loggers[loggerCategory].setLevel(_levels[category]);
282 function setGlobalLogLevel(level) {
283 Logger.prototype.level = levels.toLevel(level, levels.TRACE);
287 * Get the default logger instance.
288 * @return {Logger} instance of default logger
291 function getDefaultLogger () {
292 return getLogger(Logger.DEFAULT_CATEGORY);
295 var configState = {};
297 function loadConfigurationFile(filename) {
299 return JSON.parse(fs.readFileSync(filename, "utf8"));
304 function configureOnceOff(config, options) {
307 configureLevels(config.levels);
308 configureAppenders(config.appenders, options);
310 if (config.replaceConsole) {
317 "Problem reading log4js config " + util.inspect(config) +
318 ". Error was \"" + e.message + "\" (" + e.stack + ")"
324 function reloadConfiguration(options) {
325 var mtime = getMTime(configState.filename);
328 if (configState.lastMTime && (mtime.getTime() > configState.lastMTime.getTime())) {
329 configureOnceOff(loadConfigurationFile(configState.filename), options);
331 configState.lastMTime = mtime;
334 function getMTime(filename) {
337 mtime = fs.statSync(configState.filename).mtime;
339 getLogger('log4js').warn('Failed to load configuration file ' + filename);
344 function initReloadConfiguration(filename, options) {
345 if (configState.timerId) {
346 clearInterval(configState.timerId);
347 delete configState.timerId;
349 configState.filename = filename;
350 configState.lastMTime = getMTime(filename);
351 configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000, options);
354 function configure(configurationFileOrObject, options) {
355 var config = configurationFileOrObject;
356 config = config || process.env.LOG4JS_CONFIG;
357 options = options || {};
359 if (config === undefined || config === null || typeof(config) === 'string') {
360 if (options.reloadSecs) {
361 initReloadConfiguration(config, options);
363 config = loadConfigurationFile(config) || defaultConfig;
365 if (options.reloadSecs) {
366 getLogger('log4js').warn(
367 'Ignoring configuration reload parameter for "object" configuration.'
371 configureOnceOff(config, options);
374 var originalConsoleFunctions = {
376 debug: console.debug,
382 function replaceConsole(logger) {
383 function replaceWith(fn) {
385 fn.apply(logger, arguments);
388 logger = logger || getLogger("console");
389 ['log','debug','info','warn','error'].forEach(function (item) {
390 console[item] = replaceWith(item === 'log' ? logger.info : logger[item]);
394 function restoreConsole() {
395 ['log', 'debug', 'info', 'warn', 'error'].forEach(function (item) {
396 console[item] = originalConsoleFunctions[item];
401 * Load an appenderModule based on the provided appender filepath. Will first
402 * check if the appender path is a subpath of the log4js "lib/appenders" directory.
403 * If not, it will attempt to load the the appender as complete path.
405 * @param {string} appender The filepath for the appender.
406 * @returns {Object|null} The required appender or null if appender could not be loaded.
409 function requireAppender(appender) {
412 appenderModule = require('./appenders/' + appender);
414 appenderModule = require(appender);
416 return appenderModule;
420 * Load an appender. Provided the appender path to be loaded. If appenderModule is defined,
421 * it will be used in place of requiring the appender module.
423 * @param {string} appender The path to the appender module.
424 * @param {Object|void} [appenderModule] The pre-required appender module. When provided,
425 * instead of requiring the appender by its path, this object will be used.
429 function loadAppender(appender, appenderModule) {
430 appenderModule = appenderModule || requireAppender(appender);
432 if (!appenderModule) {
433 throw new Error("Invalid log4js appender: " + util.inspect(appender));
436 module.exports.appenders[appender] = appenderModule.appender.bind(appenderModule);
437 if (appenderModule.shutdown) {
438 appenderShutdowns[appender] = appenderModule.shutdown.bind(appenderModule);
440 appenderMakers[appender] = appenderModule.configure.bind(appenderModule);
444 * Shutdown all log appenders. This will first disable all writing to appenders
445 * and then call the shutdown function each appender.
447 * @params {Function} cb - The callback to be invoked once all appenders have
448 * shutdown. If an error occurs, the callback will be given the error object
449 * as the first argument.
452 function shutdown(cb) {
453 // First, disable all writing to appenders. This prevents appenders from
454 // not being able to be drained because of run-away log writes.
455 loggerModule.disableAllLogWrites();
457 // Call each of the shutdown functions in parallel
460 var shutdownFcts = [];
461 var complete = function(err) {
462 error = error || err;
464 if (completed >= shutdownFcts.length) {
468 for (var category in appenderShutdowns) {
469 if (appenderShutdowns.hasOwnProperty(category)) {
470 shutdownFcts.push(appenderShutdowns[category]);
473 if (!shutdownFcts.length) {
476 shutdownFcts.forEach(function(shutdownFct) { shutdownFct(complete); });
480 getBufferedLogger: getBufferedLogger,
481 getLogger: getLogger,
482 getDefaultLogger: getDefaultLogger,
483 hasLogger: hasLogger,
485 addAppender: addAppender,
486 loadAppender: loadAppender,
487 clearAppenders: clearAppenders,
488 configure: configure,
491 replaceConsole: replaceConsole,
492 restoreConsole: restoreConsole,
495 setGlobalLogLevel: setGlobalLogLevel,
499 appenderMakers: appenderMakers,
500 connectLogger: require('./connect-logger').connectLogger