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'); } } }).exportTo(module);