changed the way appenders are loaded, so that they don't need to include log4js as a direct dependency

This commit is contained in:
Gareth Jones
2013-09-15 14:37:01 +10:00
parent 245b2e3b1a
commit 491c2709e7
12 changed files with 350 additions and 185 deletions
+6 -5
View File
@@ -1,6 +1,7 @@
"use strict"; "use strict";
var layouts = require('../layouts') var consoleLog = console.log.bind(console);
, consoleLog = console.log.bind(console);
module.exports = function(layouts, levels) {
function consoleAppender (layout) { function consoleAppender (layout) {
layout = layout || layouts.colouredLayout; layout = layout || layouts.colouredLayout;
@@ -9,12 +10,12 @@ function consoleAppender (layout) {
}; };
} }
function configure(config) { return function configure(config) {
var layout; var layout;
if (config.layout) { if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout); layout = layouts.layout(config.layout.type, config.layout);
} }
return consoleAppender(layout); return consoleAppender(layout);
} };
exports.configure = configure; };
+4 -5
View File
@@ -1,6 +1,5 @@
"use strict"; "use strict";
var streams = require('streamroller') var streams = require('streamroller')
, layouts = require('../layouts')
, path = require('path') , path = require('path')
, os = require('os') , os = require('os')
, eol = os.EOL || '\n' , eol = os.EOL || '\n'
@@ -13,6 +12,7 @@ process.on('exit', function() {
}); });
}); });
module.exports = function(layouts, levels) {
/** /**
* File appender that rolls files according to a date pattern. * File appender that rolls files according to a date pattern.
* @filename base filename. * @filename base filename.
@@ -36,7 +36,7 @@ function appender(filename, pattern, alwaysIncludePattern, layout) {
} }
function configure(config, options) { return function configure(config) {
var layout; var layout;
if (config.layout) { if (config.layout) {
@@ -48,7 +48,6 @@ function configure(config, options) {
} }
return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout); return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout);
} };
exports.appender = appender; };
exports.configure = configure;
+6 -6
View File
@@ -1,6 +1,5 @@
"use strict"; "use strict";
var layouts = require('../layouts') var path = require('path')
, path = require('path')
, fs = require('fs') , fs = require('fs')
, streams = require('streamroller') , streams = require('streamroller')
, os = require('os') , os = require('os')
@@ -14,6 +13,8 @@ process.on('exit', function() {
}); });
}); });
module.exports = function(layouts, levels) {
/** /**
* File Appender writing the logs to a text file. Supports rolling of logs by size. * File Appender writing the logs to a text file. Supports rolling of logs by size.
* *
@@ -65,14 +66,13 @@ function fileAppender (file, layout, logSize, numBackups) {
}; };
} }
function configure(config) { return function configure(config) {
var layout; var layout;
if (config.layout) { if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout); layout = layouts.layout(config.layout.type, config.layout);
} }
return fileAppender(config.filename, layout, config.maxLogSize, config.backups); return fileAppender(config.filename, layout, config.maxLogSize, config.backups);
} };
exports.appender = fileAppender; };
exports.configure = configure;
+6 -6
View File
@@ -1,7 +1,7 @@
"use strict"; "use strict";
var levels = require('../levels') var debug = require('debug')('log4js:logLevelFilter');
, debug = require('debug')('log4js:logLevelFilter')
, log4js = require('../log4js'); module.exports = function(layouts, levels) {
function logLevelFilter(allowedLevels, appender) { function logLevelFilter(allowedLevels, appender) {
return function(logEvent) { return function(logEvent) {
@@ -13,7 +13,7 @@ function logLevelFilter(allowedLevels, appender) {
}; };
} }
function configure(config, appenderByName) { return function configure(config, appenderByName) {
if (!Array.isArray(config.allow)) { if (!Array.isArray(config.allow)) {
throw new Error("No allowed log levels specified."); throw new Error("No allowed log levels specified.");
} }
@@ -35,6 +35,6 @@ function configure(config, appenderByName) {
} }
return logLevelFilter(allowedLevels, appenderByName(config.appender)); return logLevelFilter(allowedLevels, appenderByName(config.appender));
} };
exports.configure = configure; };
+9 -4
View File
@@ -213,8 +213,9 @@ function validateCategories(cats) {
} }
function clearAppenders () { function clearAppenders () {
debug("clearing appenders"); debug("clearing appenders and appender makers");
appenders = {}; appenders = {};
appenderMakers = {};
} }
function appenderByName(name) { function appenderByName(name) {
@@ -230,7 +231,6 @@ function configureAppenders(appenderMap) {
Object.keys(appenderMap).forEach(function(appenderName) { Object.keys(appenderMap).forEach(function(appenderName) {
var appender, appenderConfig = appenderMap[appenderName]; var appender, appenderConfig = appenderMap[appenderName];
loadAppender(appenderConfig.type); loadAppender(appenderConfig.type);
appenderConfig.makers = appenderMakers;
try { try {
appenders[appenderName] = appenderMakers[appenderConfig.type]( appenders[appenderName] = appenderMakers[appenderConfig.type](
appenderConfig, appenderConfig,
@@ -247,21 +247,26 @@ function configureAppenders(appenderMap) {
function loadAppender(appender) { function loadAppender(appender) {
var appenderModule; var appenderModule;
if (!appenderMakers[appender]) {
debug("Loading appender ", appender);
try { try {
appenderModule = require('./appenders/' + appender); appenderModule = require('./appenders/' + appender);
} catch (e) { } catch (e) {
try { try {
debug("Appender ", appender, " is not a core log4js appender.");
appenderModule = require(appender); appenderModule = require(appender);
} catch (err) { } catch (err) {
throw new Error("Could not load appender of type '" + appender + "'."); throw new Error("Could not load appender of type '" + appender + "'.");
} }
} }
appenderMakers[appender] = appenderModule.configure.bind(appenderModule); appenderMakers[appender] = appenderModule(layouts, levels);
}
} }
module.exports = { module.exports = {
getLogger: getLogger, getLogger: getLogger,
configure: configure, configure: configure
}; };
//set ourselves up //set ourselves up
+3 -3
View File
@@ -30,12 +30,12 @@ describe('log4js in a cluster', function() {
cb(worker); cb(worker);
} }
}, },
'./appenders/console': { './appenders/console': function() {
configure: function() { return function() {
return function(event) { return function(event) {
events.push(event); events.push(event);
}; };
} };
} }
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@ describe('../lib/appenders/console', function() {
} }
} }
) )
, appender = appenderModule.configure( , appender = appenderModule(require('../lib/layouts'))(
{ layout: { type: "messagePassThrough" } } { layout: { type: "messagePassThrough" } }
); );
+4 -4
View File
@@ -17,7 +17,7 @@ describe('../lib/appenders/dateFile', function() {
var files = [], initialListeners; var files = [], initialListeners;
before(function() { before(function() {
var dateFileAppender = require('../lib/appenders/dateFile'), var dateFileAppender = require('../lib/appenders/dateFile')({ basicLayout: function() {} }),
count = 5, count = 5,
logfile; logfile;
@@ -25,7 +25,7 @@ describe('../lib/appenders/dateFile', function() {
while (count--) { while (count--) {
logfile = path.join(__dirname, 'datefa-default-test' + count + '.log'); logfile = path.join(__dirname, 'datefa-default-test' + count + '.log');
dateFileAppender.configure({ dateFileAppender({
filename: logfile filename: logfile
}); });
files.push(logfile); files.push(logfile);
@@ -69,10 +69,10 @@ describe('../lib/appenders/dateFile', function() {
} }
} }
} }
); )({ basicLayout: function() {} });
for (var i=0; i < 5; i += 1) { for (var i=0; i < 5; i += 1) {
dateFileAppender.configure({ dateFileAppender({
filename: 'test' + i filename: 'test' + i
}); });
} }
+4 -4
View File
@@ -75,9 +75,9 @@ describe('log4js fileAppender', function() {
} }
} }
} }
); )(require('../lib/layouts'));
for (var i=0; i < 5; i += 1) { for (var i=0; i < 5; i += 1) {
fileAppender.appender('test' + i, null, 100); fileAppender({ filename: 'test' + i, maxLogSize: 100 });
} }
openedFiles.should.not.be.empty; openedFiles.should.not.be.empty;
exitListener(); exitListener();
@@ -317,8 +317,8 @@ describe('log4js fileAppender', function() {
} }
} }
} }
); )(require('../lib/layouts'));
fileAppender.configure({ fileAppender({
filename: 'test1.log', filename: 'test1.log',
maxLogSize: 100 maxLogSize: 100
}); });
+111 -15
View File
@@ -178,10 +178,10 @@ describe('../lib/log4js', function() {
'../lib/log4js', '../lib/log4js',
{ {
requires: { requires: {
'cheese': { 'cheese': function() {
configure: function() { return function() {
return function(evt) { events.push(evt); }; return function(evt) { events.push(evt); };
} };
} }
} }
} }
@@ -201,6 +201,102 @@ describe('../lib/log4js', function() {
}); });
it('should only load third-party appenders once', function() {
var moduleCalled = 0
, log4js_sandbox = sandbox.require(
'../lib/log4js',
{
requires: {
'cheese': function() {
moduleCalled += 1;
return function() {
return function() {};
};
}
}
}
);
log4js_sandbox.configure({
appenders: {
"thing1": { type: "cheese" },
"thing2": { type: "cheese" }
},
categories: {
default: { level: "DEBUG", appenders: [ "thing1", "thing2" ] }
}
});
moduleCalled.should.eql(1);
});
it('should pass layouts and levels to appender modules', function() {
var layouts
, levels
, log4js_sandbox = sandbox.require(
'../lib/log4js',
{
requires: {
'cheese': function(arg1, arg2) {
layouts = arg1;
levels = arg2;
return function() {
return function() {};
};
}
}
}
);
log4js_sandbox.configure({
appenders: {
"thing": { type: "cheese" }
},
categories: {
"default": { level: "debug", appenders: [ "thing" ] }
}
});
layouts.should.have.property("basicLayout");
levels.should.have.property("toLevel");
});
it('should pass config and appenderByName to appender makers', function() {
var otherAppender = function() { /* I do nothing */ }
, config
, other
, log4js_sandbox = sandbox.require(
'../lib/log4js',
{
requires: {
'other': function() {
return function() {
return otherAppender;
};
},
'cheese': function() {
return function(arg1, arg2) {
config = arg1;
other = arg2("other");
return function() {};
};
}
}
}
);
log4js_sandbox.configure({
appenders: {
"other": { type: "other" },
"thing": { type: "cheese", something: "something" }
},
categories: {
default: { level: "debug", appenders: [ "other", "thing" ] }
}
});
other.should.equal(otherAppender);
config.should.have.property("something", "something");
});
it('should complain about unknown appenders', function() { it('should complain about unknown appenders', function() {
(function() { (function() {
log4js.configure({ log4js.configure({
@@ -221,10 +317,10 @@ describe('../lib/log4js', function() {
'../lib/log4js', '../lib/log4js',
{ {
requires: { requires: {
'cheese': { 'cheese': function() {
configure: function() { return function() {
return function(event) { events.push(event); }; return function(event) { events.push(event); };
} };
} }
} }
} }
@@ -244,10 +340,10 @@ describe('../lib/log4js', function() {
'../lib/log4js', '../lib/log4js',
{ {
requires: { requires: {
'cheese': { 'cheese': function() {
configure: function() { return function() {
return function(event) { events.push(event); }; return function(event) { events.push(event); };
} };
} }
} }
} }
@@ -273,10 +369,10 @@ describe('../lib/log4js', function() {
'../lib/log4js', '../lib/log4js',
{ {
requires: { requires: {
'cheese': { 'cheese': function() {
configure: function() { return function() {
return function(event) { events.push(event); }; return function(event) { events.push(event); };
} };
} }
} }
} }
@@ -301,10 +397,10 @@ describe('../lib/log4js', function() {
'../lib/log4js', '../lib/log4js',
{ {
requires: { requires: {
'./appenders/console': { './appenders/console': function() {
configure: function() { return function() {
return function(event) { events.push(event); }; return function(event) { events.push(event); };
} };
} }
} }
} }
+2 -2
View File
@@ -11,8 +11,8 @@ describe('log level filter', function() {
var log4js_sandboxed = sandbox.require( var log4js_sandboxed = sandbox.require(
'../lib/log4js', '../lib/log4js',
{ requires: { requires:
{ './appenders/console': { './appenders/console': function() {
{ configure: function() { return function(evt) { events.push(evt); }; } } return function() { return function(evt) { events.push(evt); }; }; }
} }
} }
); );
+64
View File
@@ -0,0 +1,64 @@
Writing Appenders For log4js
============================
Loading appenders
-----------------
log4js supports loading appender modules from outside its own code. The [log4js-gelf](http://github.com/nomiddlename/log4js-gelf), [log4js-smtp](http://github.com/nomiddlename/log4js-smtp), and [log4js-hookio](http://github.com/nomiddlename/log4js-hookio) appenders are examples of this. In the configuration for an appender, log4js will first attempt to `require` the module from `./lib/appenders/ + type` within log4js - if that fails, it will `require` just using the type. e.g.
log4js.configure({
appenders: {
"custom": { type: "log4js-gelf", hostname: "blah", port: 1234 }
},
categories: {
"default": { level: "debug", appenders: ["custom"] }
}
});
log4js will first attempt to `require('./appenders/' + log4js-gelf)`, this will fail. It will then attempt `require('log4js-gelf')`, which (assuming you have previously run `npm install log4js-gelf`) will pick up the gelf appender.
Writing your own custom appender
--------------------------------
This is easiest to explain with an example. Let's assume you want to write a [CouchDB](http://couchdb.apache.org) appender. CouchDB is a document database that you talk to via HTTP and JSON. Our log4js configuration is going to look something like this:
log4js.configure({
appenders: {
"couch": {
type: "log4js-couchdb",
url: "http://mycouchhost:5984",
db: "logs",
layout: {
type: "messagePassThrough"
}
}
},
categories: {
"default": { level: "debug", appenders: ["couch"] }
}
});
When processing this configuration, the first thing log4js will do is `require('log4js-couchdb')`. It expects this module to return a function that accepts two arguments
module.exports = function(layouts, levels) {
...
};
log4js will then call that function, passing in the `layouts` and `levels` sub-modules in case your appender might need to use them. Layouts contains functions which will format a log event as a string in various different ways. Levels contains the definitions of the log levels used by log4js - you might need this for mapping log4js levels to external definitions (the GELF appender does this). These are passed in so that appenders do not need to include a hard dependency on log4js (see below), and so that log4js does not need to expose these modules to the public API. The module function will only be called once per call to `log4js.configure`, even if there are multiple appenders of that type defined.
The module function should return another function, a configuration function, which will be called for each appender of that type defined in the config. That function should return an appender instance. For our CouchDB example, the calling process is roughly like this:
couchDbModule = require('log4js-couchdb');
appenderMaker = couchDbModule(layouts, levels);
appender = appenderMaker({
type: "log4js-couchdb",
url: "http://mycouchhost:5984",
db: "logs",
layout: {
type: "messagePassThrough"
}
}, appenderByName)
Note that in addition to our couchdb appender config, the appenderMaker function gets an extra argument: `appenderByName`, a function which returns an appender when passed its name. This is used by appenders that wrap other appenders. The `logLevelFilter` is an example of this use.
The `layout` portion of the config can be passed directly to `layouts.layout(config.layout)` to generate a layout function.
The appender function returned after processing your config should just take one argument: a log event. This function will be called for every log event that should be handled by your appender. In our case, with the config above, every log event of DEBUG level and above will be sent to our appender.