Merge pull request #149 from mkielar/master
Clustered appender for log4js.
This commit is contained in:
commit
49892f35d3
118
lib/appenders/clustered.js
Executable file
118
lib/appenders/clustered.js
Executable file
@ -0,0 +1,118 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var cluster = require('cluster');
|
||||||
|
var log4js = require('../log4js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a loggingEvent object, returns string representation of it.
|
||||||
|
*/
|
||||||
|
function serializeLoggingEvent(loggingEvent) {
|
||||||
|
return JSON.stringify(loggingEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a string, returns an object with
|
||||||
|
* the correct log properties.
|
||||||
|
*
|
||||||
|
* This method has been "borrowed" from the `multiprocess` appender
|
||||||
|
* by `nomiddlename` (https://github.com/nomiddlename/log4js-node/blob/master/lib/appenders/multiprocess.js)
|
||||||
|
*
|
||||||
|
* Apparently, node.js serializes everything to strings when using `process.send()`,
|
||||||
|
* so we need smart deserialization that will recreate log date and level for further processing by log4js internals.
|
||||||
|
*/
|
||||||
|
function deserializeLoggingEvent(loggingEventString) {
|
||||||
|
|
||||||
|
var loggingEvent;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
loggingEvent = JSON.parse(loggingEventString);
|
||||||
|
loggingEvent.startTime = new Date(loggingEvent.startTime);
|
||||||
|
loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
// JSON.parse failed, just log the contents probably a naughty.
|
||||||
|
loggingEvent = {
|
||||||
|
startTime: new Date(),
|
||||||
|
categoryName: 'log4js',
|
||||||
|
level: log4js.levels.ERROR,
|
||||||
|
data: [ 'Unable to parse log:', loggingEventString ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return loggingEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an appender.
|
||||||
|
*
|
||||||
|
* If the current process is a master (`cluster.isMaster`), then this will be a "master appender".
|
||||||
|
* Otherwise this will be a worker appender, that just sends loggingEvents to the master process.
|
||||||
|
*
|
||||||
|
* If you are using this method directly, make sure to provide it with `config.actualAppenders` array
|
||||||
|
* of actual appender instances.
|
||||||
|
*
|
||||||
|
* Or better use `configure(config, options)`
|
||||||
|
*/
|
||||||
|
function createAppender(config) {
|
||||||
|
|
||||||
|
if (cluster.isMaster) {
|
||||||
|
|
||||||
|
var masterAppender = function(loggingEvent) {
|
||||||
|
|
||||||
|
if (config.actualAppenders) {
|
||||||
|
var size = config.actualAppenders.length;
|
||||||
|
for(var i = 0; i < size; i++) {
|
||||||
|
config.actualAppenders[i](loggingEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen on new workers
|
||||||
|
cluster.on('fork', function(worker) {
|
||||||
|
|
||||||
|
worker.on('message', function(message) {
|
||||||
|
if (message.type && message.type === '::log-message') {
|
||||||
|
// console.log("master : " + cluster.isMaster + " received message: " + JSON.stringify(message.event));
|
||||||
|
|
||||||
|
var loggingEvent = deserializeLoggingEvent(message.event);
|
||||||
|
masterAppender(loggingEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return masterAppender;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return function(loggingEvent) {
|
||||||
|
// If inside the worker process, then send the logger event to master.
|
||||||
|
if (cluster.isWorker) {
|
||||||
|
// console.log("worker " + cluster.worker.id + " is sending message");
|
||||||
|
process.send({ type: '::log-message', event: serializeLoggingEvent(loggingEvent)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function configure(config, options) {
|
||||||
|
|
||||||
|
if (config.appenders && cluster.isMaster) {
|
||||||
|
|
||||||
|
var size = config.appenders.length;
|
||||||
|
config.actualAppenders = new Array(size);
|
||||||
|
|
||||||
|
for(var i = 0; i < size; i++) {
|
||||||
|
|
||||||
|
log4js.loadAppender(config.appenders[i].type);
|
||||||
|
config.actualAppenders[i] = log4js.appenderMakers[config.appenders[i].type](config.appenders[i], options);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createAppender(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.appender = createAppender;
|
||||||
|
exports.configure = configure;
|
116
test/clusteredAppender-test.js
Executable file
116
test/clusteredAppender-test.js
Executable file
@ -0,0 +1,116 @@
|
|||||||
|
"use strict";
|
||||||
|
var assert = require('assert');
|
||||||
|
var vows = require('vows');
|
||||||
|
var layouts = require('../lib/layouts');
|
||||||
|
var sandbox = require('sandboxed-module');
|
||||||
|
var LoggingEvent = require('../lib/logger').LoggingEvent;
|
||||||
|
var cluster = require('cluster');
|
||||||
|
|
||||||
|
vows.describe('log4js cluster appender').addBatch({
|
||||||
|
'when in master mode': {
|
||||||
|
topic: function() {
|
||||||
|
|
||||||
|
var registeredClusterEvents = [];
|
||||||
|
var loggingEvents = [];
|
||||||
|
|
||||||
|
// Fake cluster module, so no cluster listeners be really added
|
||||||
|
var fakeCluster = {
|
||||||
|
|
||||||
|
on: function(event, callback) {
|
||||||
|
registeredClusterEvents.push(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
isMaster: true,
|
||||||
|
isWorker: false,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var fakeActualAppender = function(loggingEvent) {
|
||||||
|
loggingEvents.push(loggingEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load appender and fake modules in it
|
||||||
|
var appenderModule = sandbox.require('../lib/appenders/clustered', {
|
||||||
|
requires: {
|
||||||
|
'cluster': fakeCluster,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var masterAppender = appenderModule.appender({
|
||||||
|
actualAppenders: [ fakeActualAppender ]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Actual test - log message using masterAppender
|
||||||
|
masterAppender(new LoggingEvent('wovs', 'Info', ['masterAppender test']));
|
||||||
|
|
||||||
|
var returnValue = {
|
||||||
|
registeredClusterEvents: registeredClusterEvents,
|
||||||
|
loggingEvents: loggingEvents,
|
||||||
|
};
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
"should register 'fork' event listener on 'cluster'": function(topic) {
|
||||||
|
assert.equal(topic.registeredClusterEvents[0], 'fork');
|
||||||
|
},
|
||||||
|
|
||||||
|
"should log using actual appender": function(topic) {
|
||||||
|
assert.equal(topic.loggingEvents[0].data[0], 'masterAppender test');
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
'when in worker mode': {
|
||||||
|
|
||||||
|
topic: function() {
|
||||||
|
|
||||||
|
var registeredProcessEvents = [];
|
||||||
|
|
||||||
|
// Fake cluster module, to fake we're inside a worker process
|
||||||
|
var fakeCluster = {
|
||||||
|
|
||||||
|
isMaster: false,
|
||||||
|
isWorker: true,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
var fakeProcess = {
|
||||||
|
|
||||||
|
send: function(data) {
|
||||||
|
registeredProcessEvents.push(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load appender and fake modules in it
|
||||||
|
var appenderModule = sandbox.require('../lib/appenders/clustered', {
|
||||||
|
requires: {
|
||||||
|
'cluster': fakeCluster,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
'process': fakeProcess,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var workerAppender = appenderModule.appender();
|
||||||
|
|
||||||
|
// Actual test - log message using masterAppender
|
||||||
|
workerAppender(new LoggingEvent('wovs', 'Info', ['workerAppender test']));
|
||||||
|
|
||||||
|
var returnValue = {
|
||||||
|
registeredProcessEvents: registeredProcessEvents,
|
||||||
|
};
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"worker appender should call process.send" : function(topic) {
|
||||||
|
assert.equal(topic.registeredProcessEvents[0].type, '::log-message');
|
||||||
|
assert.equal(JSON.parse(topic.registeredProcessEvents[0].event).data[0], "workerAppender test");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}).exportTo(module);
|
Loading…
Reference in New Issue
Block a user