moved cluster support into core, removed clustered appender, multiprocess appender

This commit is contained in:
Gareth Jones
2013-08-24 20:46:10 +10:00
parent d8cf8cb2dc
commit eabcaf8aef
5 changed files with 185 additions and 783 deletions

View File

@@ -1,116 +1,145 @@
"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');
var should = require('should')
, sandbox = require('sandboxed-module');
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 ]
});
describe('log4js in a cluster', function() {
describe('when in master mode', function() {
// 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();
var log4js
, clusterOnFork = false
, workerCb
, events = []
, worker = {
on: function(evt, cb) {
evt.should.eql('message');
this.cb = cb;
}
};
// 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");
}
}
before(function() {
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'cluster': {
isMaster: true,
on: function(evt, cb) {
evt.should.eql('fork');
clusterOnFork = true;
cb(worker);
}
},
'./appenders/console': {
configure: function() {
return function(event) {
events.push(event);
};
}
}
}
}
);
});
}).exportTo(module);
it('should listen for fork events', function() {
clusterOnFork.should.be.true;
});
it('should listen for messages from workers', function() {
//workerCb was created in a different context to the test
//(thanks to sandbox.require), so doesn't pick up the should prototype
(typeof worker.cb).should.eql('function');
});
it('should log valid ::log4js-message events', function() {
worker.cb({
type: '::log4js-message',
event: JSON.stringify({
startTime: '2010-10-10 18:54:06',
category: 'cheese',
level: { levelStr: 'DEBUG' },
data: [ "blah" ]
})
});
events.should.have.length(1);
events[0].data[0].should.eql("blah");
events[0].category.should.eql('cheese');
//startTime was created in a different context to the test
//(thanks to sandbox.require), so instanceof doesn't think
//it's a Date.
events[0].startTime.constructor.name.should.eql('Date');
events[0].level.toString().should.eql('DEBUG');
});
it('should handle invalid ::log4js-message events', function() {
worker.cb({
type: '::log4js-message',
event: "biscuits"
});
worker.cb({
type: '::log4js-message',
event: JSON.stringify({
startTime: 'whatever'
})
});
events.should.have.length(3);
events[1].data[0].should.eql('Unable to parse log:');
events[1].data[1].should.eql('biscuits');
events[1].category.should.eql('log4js');
events[1].level.toString().should.eql('ERROR');
events[2].data[0].should.eql('Unable to parse log:');
events[2].data[1].should.eql(JSON.stringify({ startTime: 'whatever'}));
});
it('should ignore other events', function() {
worker.cb({
type: "::blah-blah",
event: "blah"
});
events.should.have.length(3);
});
});
describe('when in worker mode', function() {
var log4js, events = [];
before(function() {
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'cluster': {
isMaster: false,
on: function() {}
}
},
globals: {
'process': {
'send': function(event) {
events.push(event);
}
}
}
}
);
log4js.getLogger('test').debug("just testing");
});
it('should emit ::log4js-message events', function() {
events.should.have.length(1);
events[0].type.should.eql('::log4js-message');
events[0].event.should.be.a('string');
var evt = JSON.parse(events[0].event);
evt.category.should.eql('test');
evt.level.levelStr.should.eql('DEBUG');
evt.data[0].should.eql('just testing');
});
});
});

View File

@@ -1,303 +0,0 @@
"use strict";
var vows = require('vows')
, sandbox = require('sandboxed-module')
, assert = require('assert')
;
function makeFakeNet() {
return {
logEvents: [],
data: [],
cbs: {},
createConnectionCalled: 0,
fakeAppender: function(logEvent) {
this.logEvents.push(logEvent);
},
createConnection: function(port, host) {
var fakeNet = this;
this.port = port;
this.host = host;
this.createConnectionCalled += 1;
return {
on: function(evt, cb) {
fakeNet.cbs[evt] = cb;
},
write: function(data, encoding) {
fakeNet.data.push(data);
fakeNet.encoding = encoding;
},
end: function() {
fakeNet.closeCalled = true;
}
};
},
createServer: function(cb) {
var fakeNet = this;
cb({
remoteAddress: '1.2.3.4',
remotePort: '1234',
setEncoding: function(encoding) {
fakeNet.encoding = encoding;
},
on: function(event, cb) {
fakeNet.cbs[event] = cb;
}
});
return {
listen: function(port, host) {
fakeNet.port = port;
fakeNet.host = host;
}
};
}
};
}
vows.describe('Multiprocess Appender').addBatch({
'worker': {
topic: function() {
var fakeNet = makeFakeNet(),
appender = sandbox.require(
'../lib/appenders/multiprocess',
{
requires: {
'net': fakeNet
}
}
).appender({ mode: 'worker', loggerPort: 1234, loggerHost: 'pants' });
//don't need a proper log event for the worker tests
appender('before connect');
fakeNet.cbs.connect();
appender('after connect');
fakeNet.cbs.close(true);
appender('after error, before connect');
fakeNet.cbs.connect();
appender('after error, after connect');
return fakeNet;
},
'should open a socket to the loggerPort and loggerHost': function(net) {
assert.equal(net.port, 1234);
assert.equal(net.host, 'pants');
},
'should buffer messages written before socket is connected': function(net) {
assert.equal(net.data[0], JSON.stringify('before connect'));
},
'should write log messages to socket as json strings with a terminator string': function(net) {
assert.equal(net.data[0], JSON.stringify('before connect'));
assert.equal(net.data[1], '__LOG4JS__');
assert.equal(net.data[2], JSON.stringify('after connect'));
assert.equal(net.data[3], '__LOG4JS__');
assert.equal(net.encoding, 'utf8');
},
'should attempt to re-open the socket on error': function(net) {
assert.equal(net.data[4], JSON.stringify('after error, before connect'));
assert.equal(net.data[5], '__LOG4JS__');
assert.equal(net.data[6], JSON.stringify('after error, after connect'));
assert.equal(net.data[7], '__LOG4JS__');
assert.equal(net.createConnectionCalled, 2);
}
},
'worker with timeout': {
topic: function() {
var fakeNet = makeFakeNet(),
appender = sandbox.require(
'../lib/appenders/multiprocess',
{
requires: {
'net': fakeNet
}
}
).appender({ mode: 'worker' });
//don't need a proper log event for the worker tests
appender('before connect');
fakeNet.cbs.connect();
appender('after connect');
fakeNet.cbs.timeout();
appender('after timeout, before close');
fakeNet.cbs.close();
appender('after close, before connect');
fakeNet.cbs.connect();
appender('after close, after connect');
return fakeNet;
},
'should attempt to re-open the socket': function(net) {
//skipping the __LOG4JS__ separators
assert.equal(net.data[0], JSON.stringify('before connect'));
assert.equal(net.data[2], JSON.stringify('after connect'));
assert.equal(net.data[4], JSON.stringify('after timeout, before close'));
assert.equal(net.data[6], JSON.stringify('after close, before connect'));
assert.equal(net.data[8], JSON.stringify('after close, after connect'));
assert.equal(net.createConnectionCalled, 2);
}
},
'worker defaults': {
topic: function() {
var fakeNet = makeFakeNet(),
appender = sandbox.require(
'../lib/appenders/multiprocess',
{
requires: {
'net': fakeNet
}
}
).appender({ mode: 'worker' });
return fakeNet;
},
'should open a socket to localhost:5000': function(net) {
assert.equal(net.port, 5000);
assert.equal(net.host, 'localhost');
}
},
'master': {
topic: function() {
var fakeNet = makeFakeNet(),
appender = sandbox.require(
'../lib/appenders/multiprocess',
{
requires: {
'net': fakeNet
}
}
).appender({ mode: 'master',
loggerHost: 'server',
loggerPort: 1234,
actualAppender: fakeNet.fakeAppender.bind(fakeNet)
});
appender('this should be sent to the actual appender directly');
return fakeNet;
},
'should listen for log messages on loggerPort and loggerHost': function(net) {
assert.equal(net.port, 1234);
assert.equal(net.host, 'server');
},
'should return the underlying appender': function(net) {
assert.equal(net.logEvents[0], 'this should be sent to the actual appender directly');
},
'when a client connects': {
topic: function(net) {
var logString = JSON.stringify(
{ level: { level: 10000, levelStr: 'DEBUG' }
, data: ['some debug']}
) + '__LOG4JS__';
net.cbs.data(
JSON.stringify(
{ level: { level: 40000, levelStr: 'ERROR' }
, data: ['an error message'] }
) + '__LOG4JS__'
);
net.cbs.data(logString.substring(0, 10));
net.cbs.data(logString.substring(10));
net.cbs.data(logString + logString + logString);
net.cbs.end(
JSON.stringify(
{ level: { level: 50000, levelStr: 'FATAL' }
, data: ["that's all folks"] }
) + '__LOG4JS__'
);
net.cbs.data('bad message__LOG4JS__');
return net;
},
'should parse log messages into log events and send to appender': function(net) {
assert.equal(net.logEvents[1].level.toString(), 'ERROR');
assert.equal(net.logEvents[1].data[0], 'an error message');
assert.equal(net.logEvents[1].remoteAddress, '1.2.3.4');
assert.equal(net.logEvents[1].remotePort, '1234');
},
'should parse log messages split into multiple chunks': function(net) {
assert.equal(net.logEvents[2].level.toString(), 'DEBUG');
assert.equal(net.logEvents[2].data[0], 'some debug');
assert.equal(net.logEvents[2].remoteAddress, '1.2.3.4');
assert.equal(net.logEvents[2].remotePort, '1234');
},
'should parse multiple log messages in a single chunk': function(net) {
assert.equal(net.logEvents[3].data[0], 'some debug');
assert.equal(net.logEvents[4].data[0], 'some debug');
assert.equal(net.logEvents[5].data[0], 'some debug');
},
'should handle log messages sent as part of end event': function(net) {
assert.equal(net.logEvents[6].data[0], "that's all folks");
},
'should handle unparseable log messages': function(net) {
assert.equal(net.logEvents[7].level.toString(), 'ERROR');
assert.equal(net.logEvents[7].categoryName, 'log4js');
assert.equal(net.logEvents[7].data[0], 'Unable to parse log:');
assert.equal(net.logEvents[7].data[1], 'bad message');
}
}
},
'master defaults': {
topic: function() {
var fakeNet = makeFakeNet(),
appender = sandbox.require(
'../lib/appenders/multiprocess',
{
requires: {
'net': fakeNet
}
}
).appender({ mode: 'master' });
return fakeNet;
},
'should listen for log messages on localhost:5000': function(net) {
assert.equal(net.port, 5000);
assert.equal(net.host, 'localhost');
}
}
}).addBatch({
'configure': {
topic: function() {
var results = {}
, fakeNet = makeFakeNet()
, appender = sandbox.require(
'../lib/appenders/multiprocess',
{
requires: {
'net': fakeNet,
'../log4js': {
loadAppender: function(app) {
results.appenderLoaded = app;
},
appenderMakers: {
'madeupappender': function(config, options) {
results.config = config;
results.options = options;
}
}
}
}
}
).configure(
{
mode: 'master',
appender: {
type: 'madeupappender',
cheese: 'gouda'
}
},
{ crackers: 'jacobs' }
);
return results;
},
'should load underlying appender for master': function(results) {
assert.equal(results.appenderLoaded, 'madeupappender');
},
'should pass config to underlying appender': function(results) {
assert.equal(results.config.cheese, 'gouda');
},
'should pass options to underlying appender': function(results) {
assert.equal(results.options.crackers, 'jacobs');
}
}
}).exportTo(module);