Compare commits
33 Commits
v0.5.0
...
date-rolli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b889fe776 | ||
|
|
9ac61e37f4 | ||
|
|
185f343e68 | ||
|
|
be1272cd7c | ||
|
|
cbc1dd32f9 | ||
|
|
a6fb26efb1 | ||
|
|
012b0d5ed7 | ||
|
|
de72005e7e | ||
|
|
c6a0e58409 | ||
|
|
f832a2ba79 | ||
|
|
3f10b68c30 | ||
|
|
54c311842c | ||
|
|
f948b5f5cd | ||
|
|
54e420eb58 | ||
|
|
40ba24a55d | ||
|
|
e3a20a1746 | ||
|
|
7a02f39921 | ||
|
|
b6ba3bce00 | ||
|
|
638ce187bb | ||
|
|
3cbae96a97 | ||
|
|
a33e48cb07 | ||
|
|
df491c0b14 | ||
|
|
6ff1a2499f | ||
|
|
ce2d7df8df | ||
|
|
1b12265800 | ||
|
|
32e9045334 | ||
|
|
1aed671137 | ||
|
|
68b47dd51c | ||
|
|
8f9b4444f6 | ||
|
|
e49f7107fb | ||
|
|
077302c772 | ||
|
|
6f0dfa0c5f | ||
|
|
82a6bee331 |
@@ -1,4 +1,4 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 0.6
|
- 0.6
|
||||||
- 0.7
|
- 0.8
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -17,6 +17,16 @@ Out of the box it supports the following features:
|
|||||||
* configurable log message layout/patterns
|
* configurable log message layout/patterns
|
||||||
* different log levels for different log categories (make some parts of your app log as DEBUG, others only ERRORS, etc.)
|
* different log levels for different log categories (make some parts of your app log as DEBUG, others only ERRORS, etc.)
|
||||||
|
|
||||||
|
NOTE: from log4js 0.5 onwards you'll need to explicitly enable replacement of node's console.log functions. Do this either by calling `log4js.replaceConsole()` or configuring with an object or json file like this:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
appenders: [
|
||||||
|
{ type: "console" }
|
||||||
|
],
|
||||||
|
replaceConsole: true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## installation
|
## installation
|
||||||
|
|
||||||
@@ -35,12 +45,13 @@ By default, log4js outputs to stdout with the coloured layout (thanks to [masylu
|
|||||||
|
|
||||||
[2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages
|
[2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages
|
||||||
|
|
||||||
See example.js:
|
See example.js for a full example, but here's a snippet (also in fromreadme.js):
|
||||||
|
|
||||||
var log4js = require('log4js');
|
var log4js = require('log4js');
|
||||||
log4js.loadAppender('console');
|
//console log is loaded by default, so you won't normally need to do this
|
||||||
|
//log4js.loadAppender('console');
|
||||||
log4js.loadAppender('file');
|
log4js.loadAppender('file');
|
||||||
log4js.addAppender(log4js.appenders.console());
|
//log4js.addAppender(log4js.appenders.console());
|
||||||
log4js.addAppender(log4js.appenders.file('logs/cheese.log'), 'cheese');
|
log4js.addAppender(log4js.appenders.file('logs/cheese.log'), 'cheese');
|
||||||
|
|
||||||
var logger = log4js.getLogger('cheese');
|
var logger = log4js.getLogger('cheese');
|
||||||
@@ -99,7 +110,6 @@ If you have already defined an absolute path for one of the FileAppenders in the
|
|||||||
"filename": "relative/path/to/log_file.log",
|
"filename": "relative/path/to/log_file.log",
|
||||||
"maxLogSize": 20480,
|
"maxLogSize": 20480,
|
||||||
"backups": 3,
|
"backups": 3,
|
||||||
"pollInterval": 15,
|
|
||||||
"category": "relative-logger"
|
"category": "relative-logger"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -108,19 +118,18 @@ If you have already defined an absolute path for one of the FileAppenders in the
|
|||||||
"filename": "/absolute/path/to/log_file.log",
|
"filename": "/absolute/path/to/log_file.log",
|
||||||
"maxLogSize": 20480,
|
"maxLogSize": 20480,
|
||||||
"backups": 10,
|
"backups": 10,
|
||||||
"pollInterval": 15,
|
|
||||||
"category": "absolute-logger"
|
"category": "absolute-logger"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Documentation for most of the core appenders can be found on the [wiki](wiki/Appenders), otherwise take a look at the tests and the examples.
|
Documentation for most of the core appenders can be found on the [wiki](log4js-node/wiki/Appenders), otherwise take a look at the tests and the examples.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
See the [wiki](wiki). Improve the [wiki](wiki), please.
|
See the [wiki](log4js-node/wiki). Improve the [wiki](log4js-node/wiki), please.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Contributions welcome, but take a look at the [rules](wiki/Contributing) first.
|
Contributions welcome, but take a look at the [rules](log4js-node/wiki/Contributing) first.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
45
example-socket.js
Normal file
45
example-socket.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
var log4js = require('./lib/log4js')
|
||||||
|
, cluster = require('cluster')
|
||||||
|
, numCPUs = require('os').cpus().length
|
||||||
|
, i = 0;
|
||||||
|
|
||||||
|
if (cluster.isMaster) {
|
||||||
|
log4js.configure({
|
||||||
|
appenders: [
|
||||||
|
{
|
||||||
|
type: "multiprocess",
|
||||||
|
mode: "master",
|
||||||
|
appender: {
|
||||||
|
type: "console"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info("Master creating %d workers", numCPUs);
|
||||||
|
for (i=0; i < numCPUs; i++) {
|
||||||
|
cluster.fork();
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster.on('death', function(worker) {
|
||||||
|
console.info("Worker %d died.", worker.pid);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log4js.configure({
|
||||||
|
appenders: [
|
||||||
|
{
|
||||||
|
type: "multiprocess",
|
||||||
|
mode: "worker"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
var logger = log4js.getLogger('example-socket');
|
||||||
|
|
||||||
|
console.info("Worker %d started.", process.pid);
|
||||||
|
for (i=0; i < 1000; i++) {
|
||||||
|
logger.info("Worker %d - logging something %d", process.pid, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
40
example.js
40
example.js
@@ -1,12 +1,39 @@
|
|||||||
var log4js = require('./lib/log4js');
|
var log4js = require('./lib/log4js');
|
||||||
//log the cheese logger messages to a file, and the console ones as well.
|
//log the cheese logger messages to a file, and the console ones as well.
|
||||||
log4js.addAppender(log4js.fileAppender('cheese.log'), 'cheese', 'console');
|
log4js.configure({
|
||||||
|
appenders: [
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
filename: "cheese.log",
|
||||||
|
category: [ 'cheese','console' ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "console"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
replaceConsole: true
|
||||||
|
});
|
||||||
|
|
||||||
|
//to add an appender programmatically, and without clearing other appenders
|
||||||
|
//loadAppender is only necessary if you haven't already configured an appender of this type
|
||||||
|
log4js.loadAppender('file');
|
||||||
|
log4js.addAppender(log4js.appenders.file('pants.log'), 'pants');
|
||||||
|
//a custom logger outside of the log4js/lib/appenders directory can be accessed like so
|
||||||
|
//log4js.loadAppender('what/you/would/put/in/require');
|
||||||
|
//log4js.addAppender(log4js.appenders['what/you/would/put/in/require'](args));
|
||||||
|
//or through configure as:
|
||||||
|
//log4js.configure({
|
||||||
|
// appenders: [ { type: 'what/you/would/put/in/require', otherArgs: 'blah' } ]
|
||||||
|
//});
|
||||||
|
|
||||||
var logger = log4js.getLogger('cheese');
|
var logger = log4js.getLogger('cheese');
|
||||||
//only errors and above get logged.
|
//only errors and above get logged.
|
||||||
|
//you can also set this log level in the config object
|
||||||
|
//via the levels field.
|
||||||
logger.setLevel('ERROR');
|
logger.setLevel('ERROR');
|
||||||
|
|
||||||
//console logging methds have been replaced with log4js ones.
|
//console logging methods have been replaced with log4js ones.
|
||||||
|
//so this will get coloured output on console, and appear in cheese.log
|
||||||
console.error("AAArgh! Something went wrong", { some: "otherObject", useful_for: "debug purposes" });
|
console.error("AAArgh! Something went wrong", { some: "otherObject", useful_for: "debug purposes" });
|
||||||
|
|
||||||
//these will not appear (logging level beneath error)
|
//these will not appear (logging level beneath error)
|
||||||
@@ -18,5 +45,14 @@ logger.warn('Cheese is quite smelly.');
|
|||||||
logger.error('Cheese %s is too ripe!', "gouda");
|
logger.error('Cheese %s is too ripe!', "gouda");
|
||||||
logger.fatal('Cheese was breeding ground for listeria.');
|
logger.fatal('Cheese was breeding ground for listeria.');
|
||||||
|
|
||||||
|
//these don't end up in cheese.log, but will appear on the console
|
||||||
|
var anotherLogger = log4js.getLogger('another');
|
||||||
|
anotherLogger.debug("Just checking");
|
||||||
|
|
||||||
|
//one for pants.log
|
||||||
|
//will also go to console, since that's configured for all categories
|
||||||
|
var pantsLog = log4js.getLogger('pants');
|
||||||
|
pantsLog.debug("Something for pants");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
fromreadme.js
Normal file
19
fromreadme.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//remember to change the require to just 'log4js' if you've npm install'ed it
|
||||||
|
var log4js = require('./lib/log4js');
|
||||||
|
//by default the console appender is loaded
|
||||||
|
//log4js.loadAppender('console');
|
||||||
|
//you'd only need to add the console appender if you
|
||||||
|
//had previously called log4js.clearAppenders();
|
||||||
|
//log4js.addAppender(log4js.appenders.console());
|
||||||
|
log4js.loadAppender('file');
|
||||||
|
log4js.addAppender(log4js.appenders.file('cheese.log'), 'cheese');
|
||||||
|
|
||||||
|
var logger = log4js.getLogger('cheese');
|
||||||
|
logger.setLevel('ERROR');
|
||||||
|
|
||||||
|
logger.trace('Entering cheese testing');
|
||||||
|
logger.debug('Got cheese.');
|
||||||
|
logger.info('Cheese is Gouda.');
|
||||||
|
logger.warn('Cheese is quite smelly.');
|
||||||
|
logger.error('Cheese is too ripe!');
|
||||||
|
logger.fatal('Cheese was breeding ground for listeria.');
|
||||||
49
lib/appenders/dateFile.js
Normal file
49
lib/appenders/dateFile.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
var streams = require('../streams'),
|
||||||
|
layouts = require('../layouts'),
|
||||||
|
path = require('path'),
|
||||||
|
os = require('os'),
|
||||||
|
eol = os.EOL || '\n',
|
||||||
|
openFiles = [];
|
||||||
|
|
||||||
|
//close open files on process exit.
|
||||||
|
process.on('exit', function() {
|
||||||
|
openFiles.forEach(function (file) {
|
||||||
|
file.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File appender that rolls files according to a date pattern.
|
||||||
|
* @filename base filename.
|
||||||
|
* @pattern the format that will be added to the end of filename when rolling,
|
||||||
|
* also used to check when to roll files - defaults to '.yyyy-MM-dd'
|
||||||
|
* @layout layout function for log messages - defaults to basicLayout
|
||||||
|
*/
|
||||||
|
function appender(filename, pattern, layout) {
|
||||||
|
layout = layout || layouts.basicLayout;
|
||||||
|
|
||||||
|
var logFile = new streams.BufferedWriteStream(new streams.DateRollingFileStream(filename, pattern));
|
||||||
|
openFiles.push(logFile);
|
||||||
|
|
||||||
|
return function(logEvent) {
|
||||||
|
logFile.write(layout(logEvent) + eol, "utf8");
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function configure(config, options) {
|
||||||
|
var layout;
|
||||||
|
|
||||||
|
if (config.layout) {
|
||||||
|
layout = layouts.layout(config.layout.type, config.layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options && options.cwd && !config.absolute) {
|
||||||
|
config.filename = path.join(options.cwd, config.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return appender(config.filename, config.pattern, layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.appender = appender;
|
||||||
|
exports.configure = configure;
|
||||||
@@ -1,7 +1,17 @@
|
|||||||
var layouts = require('../layouts')
|
var layouts = require('../layouts')
|
||||||
, path = require('path')
|
, path = require('path')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
, streams = require('../streams');
|
, streams = require('../streams')
|
||||||
|
, os = require('os')
|
||||||
|
, eol = os.EOL || '\n'
|
||||||
|
, openFiles = [];
|
||||||
|
|
||||||
|
//close open files on process exit.
|
||||||
|
process.on('exit', function() {
|
||||||
|
openFiles.forEach(function (file) {
|
||||||
|
file.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@@ -40,13 +50,11 @@ function fileAppender (file, layout, logSize, numBackups) {
|
|||||||
|
|
||||||
var logFile = openTheStream(file, logSize, numBackups);
|
var logFile = openTheStream(file, logSize, numBackups);
|
||||||
|
|
||||||
//close the file on process exit.
|
// push file to the stack of open handlers
|
||||||
process.on('exit', function() {
|
openFiles.push(logFile);
|
||||||
logFile.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
return function(loggingEvent) {
|
return function(loggingEvent) {
|
||||||
logFile.write(layout(loggingEvent)+'\n', "utf8");
|
logFile.write(layout(loggingEvent) + eol, "utf8");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,75 +1,133 @@
|
|||||||
var log4js = require('../log4js');
|
var log4js = require('../log4js'),
|
||||||
var layouts = require('../layouts');
|
net = require('net'),
|
||||||
var net = require('net');
|
END_MSG = '__LOG4JS__';
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
var LogServer = function createLogServer(config) {
|
/**
|
||||||
var actualAppender = config.actualAppender;
|
* Creates a server, listening on config.loggerPort, config.loggerHost.
|
||||||
var server = net.createServer(function serverCreated(clientSocket) {
|
* Output goes to config.actualAppender (config.appender is used to
|
||||||
clientSocket.on('connect', function clientConnected() {
|
* set up that appender).
|
||||||
var logMessage = '';
|
*/
|
||||||
clientSocket.on('data', function chunkReceived(chunk) {
|
function logServer(config) {
|
||||||
logMessage += chunk;
|
|
||||||
});
|
/**
|
||||||
clientSocket.on('end', function gotChunks() {
|
* Takes a utf-8 string, returns an object with
|
||||||
|
* the correct log properties.
|
||||||
|
*/
|
||||||
|
function deserializeLoggingEvent(clientSocket, msg) {
|
||||||
|
var loggingEvent;
|
||||||
try {
|
try {
|
||||||
var loggingEvent = JSON.parse(logMessage);
|
loggingEvent = JSON.parse(msg);
|
||||||
deserializeLoggingEvent(loggingEvent);
|
loggingEvent.startTime = new Date(loggingEvent.startTime);
|
||||||
actualAppender(loggingEvent);
|
loggingEvent.level.toString = function levelToString() {
|
||||||
|
return loggingEvent.level.levelStr;
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// JSON.parse failed, just log the contents probably a naughty.
|
// JSON.parse failed, just log the contents probably a naughty.
|
||||||
actualAppender(createLoggingEvent('ERROR', 'Unable to parse log: ' + logMessage));
|
loggingEvent = {
|
||||||
|
startTime: new Date(),
|
||||||
|
categoryName: 'log4js',
|
||||||
|
level: { toString: function () {
|
||||||
|
return 'ERROR';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: [ 'Unable to parse log:', msg ]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
server.listen(config.loggerPort || 5000, config.loggerHost || 'localhost');
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLoggingEvent(level, message) {
|
loggingEvent.remoteAddress = clientSocket.remoteAddress;
|
||||||
return {
|
loggingEvent.remotePort = clientSocket.remotePort;
|
||||||
startTime: new Date(),
|
|
||||||
categoryName: 'log4js',
|
|
||||||
level: { toString: function () {
|
|
||||||
return level;
|
|
||||||
}},
|
|
||||||
data: [ message ]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function deserializeLoggingEvent(loggingEvent) {
|
return loggingEvent;
|
||||||
loggingEvent.startTime = new Date(loggingEvent.startTime);
|
}
|
||||||
loggingEvent.level.toString = function levelToString() {
|
|
||||||
return loggingEvent.level.levelStr;
|
var actualAppender = config.actualAppender,
|
||||||
};
|
server = net.createServer(function serverCreated(clientSocket) {
|
||||||
|
clientSocket.setEncoding('utf8');
|
||||||
|
clientSocket.on('connect', function clientConnected() {
|
||||||
|
var logMessage = '';
|
||||||
|
|
||||||
|
function logTheMessage(msg) {
|
||||||
|
if (logMessage.length > 0) {
|
||||||
|
actualAppender(deserializeLoggingEvent(clientSocket, msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chunkReceived(chunk) {
|
||||||
|
var event;
|
||||||
|
logMessage += chunk || '';
|
||||||
|
if (logMessage.indexOf(END_MSG) > -1) {
|
||||||
|
event = logMessage.substring(0, logMessage.indexOf(END_MSG));
|
||||||
|
logTheMessage(event);
|
||||||
|
logMessage = logMessage.substring(event.length + END_MSG.length) || '';
|
||||||
|
//check for more, maybe it was a big chunk
|
||||||
|
chunkReceived();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSocket.on('data', chunkReceived);
|
||||||
|
clientSocket.on('end', chunkReceived);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
server.listen(config.loggerPort || 5000, config.loggerHost || 'localhost');
|
||||||
|
|
||||||
|
return actualAppender;
|
||||||
}
|
}
|
||||||
|
|
||||||
function workerAppender(config) {
|
function workerAppender(config) {
|
||||||
return function log(loggingEvent) {
|
var canWrite = false,
|
||||||
var socket = net.createConnection(config.loggerPort || 5000, config.loggerHost || 'localhost');
|
buffer = [],
|
||||||
socket.on('connect', function socketConnected() {
|
socket;
|
||||||
socket.end(JSON.stringify(loggingEvent), 'utf8');
|
|
||||||
});
|
createSocket();
|
||||||
};
|
|
||||||
|
function createSocket() {
|
||||||
|
socket = net.createConnection(config.loggerPort || 5000, config.loggerHost || 'localhost');
|
||||||
|
socket.on('connect', function() {
|
||||||
|
emptyBuffer();
|
||||||
|
canWrite = true;
|
||||||
|
});
|
||||||
|
socket.on('timeout', socket.end.bind(socket));
|
||||||
|
//don't bother listening for 'error', 'close' gets called after that anyway
|
||||||
|
socket.on('close', createSocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyBuffer() {
|
||||||
|
var evt;
|
||||||
|
while ((evt = buffer.shift())) {
|
||||||
|
write(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(loggingEvent) {
|
||||||
|
socket.write(JSON.stringify(loggingEvent), 'utf8');
|
||||||
|
socket.write(END_MSG, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return function log(loggingEvent) {
|
||||||
|
if (canWrite) {
|
||||||
|
write(loggingEvent);
|
||||||
|
} else {
|
||||||
|
buffer.push(loggingEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAppender(config) {
|
function createAppender(config) {
|
||||||
if (config.mode === 'master') {
|
if (config.mode === 'master') {
|
||||||
var server = new LogServer(config);
|
return logServer(config);
|
||||||
return config.actualAppender;
|
} else {
|
||||||
} else {
|
return workerAppender(config);
|
||||||
return workerAppender(config);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function configure(config) {
|
function configure(config) {
|
||||||
var actualAppender;
|
var actualAppender;
|
||||||
if (config.appender && config.mode === 'master') {
|
if (config.appender && config.mode === 'master') {
|
||||||
log4js.loadAppender(config.appender.type);
|
log4js.loadAppender(config.appender.type);
|
||||||
actualAppender = log4js.appenderMakers[config.appender.type](config.appender);
|
actualAppender = log4js.appenderMakers[config.appender.type](config.appender);
|
||||||
config.actualAppender = actualAppender;
|
config.actualAppender = actualAppender;
|
||||||
}
|
}
|
||||||
return createAppender(config);
|
return createAppender(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.appender = createAppender;
|
exports.appender = createAppender;
|
||||||
|
|||||||
@@ -1,59 +1,57 @@
|
|||||||
var layouts = require("../layouts"),
|
var layouts = require("../layouts"),
|
||||||
mailer = require("nodemailer");
|
mailer = require("nodemailer"),
|
||||||
|
os = require('os');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMTP Appender. Sends logging events using SMTP protocol.
|
* SMTP Appender. Sends logging events using SMTP protocol.
|
||||||
* It can either send an email on each event or group several logging events gathered during specified interval.
|
* It can either send an email on each event or group several logging events gathered during specified interval.
|
||||||
*
|
*
|
||||||
* @param recipients comma separated list of email recipients
|
* @param config appender configuration data
|
||||||
* @param sender sender of all emails (defaults to SMTP user)
|
|
||||||
* @param subject subject of all email messages (defaults to first event's message)
|
|
||||||
* @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
|
* @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
|
||||||
* @param smtpConfig SMTP configuration for 'nodemailer'
|
|
||||||
* @param sendInterval the time in seconds between sending attempts (defaults to 0);
|
|
||||||
* all events are buffered and sent in one email during this time; if 0 than every event sends an email
|
* all events are buffered and sent in one email during this time; if 0 than every event sends an email
|
||||||
*/
|
*/
|
||||||
function smtpAppender(recipients, sender, subject, layout, smtpConfig, sendInterval) {
|
function smtpAppender(config, layout) {
|
||||||
sender = sender || smtpConfig.user;
|
|
||||||
layout = layout || layouts.basicLayout;
|
layout = layout || layouts.basicLayout;
|
||||||
subjectLayout = layouts.messagePassThroughLayout;
|
var subjectLayout = layouts.messagePassThroughLayout;
|
||||||
mailer.SMTP = smtpConfig;
|
var sendInterval = config.sendInterval*1000 || 0;
|
||||||
sendInterval = sendInterval*1000 || 0;
|
|
||||||
|
|
||||||
var logEventBuffer = [];
|
var logEventBuffer = [];
|
||||||
var sendTimer;
|
var sendTimer;
|
||||||
|
var transport = mailer.createTransport(config.transport, config[config.transport]);
|
||||||
|
|
||||||
function sendBuffer() {
|
function sendBuffer() {
|
||||||
if (logEventBuffer.length == 0)
|
if (logEventBuffer.length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var firstEvent = logEventBuffer[0];
|
var firstEvent = logEventBuffer[0];
|
||||||
var body = "";
|
var body = "";
|
||||||
while (logEventBuffer.length > 0) {
|
while (logEventBuffer.length > 0) {
|
||||||
body += layout(logEventBuffer.shift()) + "\n";
|
body += layout(logEventBuffer.shift()) + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = {
|
var msg = {
|
||||||
sender: sender,
|
to: config.recipients,
|
||||||
to: recipients,
|
subject: config.subject || subjectLayout(firstEvent),
|
||||||
subject: subject || subjectLayout(firstEvent),
|
text: body,
|
||||||
body: body
|
headers: {"Hostname": os.hostname()}
|
||||||
};
|
};
|
||||||
mailer.send_mail(msg, function(error, success) {
|
if (config.sender)
|
||||||
|
msg.from = config.sender;
|
||||||
|
transport.sendMail(msg, function(error, success) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("log4js.smtpAppender - Error happened ", error);
|
console.error("log4js.smtpAppender - Error happened ", error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleSend() {
|
function scheduleSend() {
|
||||||
if (!sendTimer)
|
if (!sendTimer)
|
||||||
sendTimer = setTimeout(function() {
|
sendTimer = setTimeout(function() {
|
||||||
sendTimer = null;
|
sendTimer = null;
|
||||||
sendBuffer();
|
sendBuffer();
|
||||||
}, sendInterval);
|
}, sendInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function(loggingEvent) {
|
return function(loggingEvent) {
|
||||||
logEventBuffer.push(loggingEvent);
|
logEventBuffer.push(loggingEvent);
|
||||||
if (sendInterval > 0)
|
if (sendInterval > 0)
|
||||||
@@ -68,8 +66,10 @@ function configure(config) {
|
|||||||
if (config.layout) {
|
if (config.layout) {
|
||||||
layout = layouts.layout(config.layout.type, config.layout);
|
layout = layouts.layout(config.layout.type, config.layout);
|
||||||
}
|
}
|
||||||
return smtpAppender(config.recipients, config.sender, config.subject, layout, config.smtp, config.sendInterval);
|
return smtpAppender(config, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.name = "smtp";
|
||||||
exports.appender = smtpAppender;
|
exports.appender = smtpAppender;
|
||||||
exports.configure = configure;
|
exports.configure = configure;
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,34 @@
|
|||||||
var dateFormat = require('./date_format')
|
var dateFormat = require('./date_format')
|
||||||
, util = require('util')
|
, os = require('os')
|
||||||
, replacementRegExp = /%[sdj]/g
|
, eol = os.EOL || '\n'
|
||||||
, layoutMakers = {
|
, util = require('util')
|
||||||
"messagePassThrough": function() { return messagePassThroughLayout; }
|
, replacementRegExp = /%[sdj]/g
|
||||||
, "basic": function() { return basicLayout; }
|
, layoutMakers = {
|
||||||
, "colored": function() { return colouredLayout; }
|
"messagePassThrough": function() { return messagePassThroughLayout; }
|
||||||
, "coloured": function() { return colouredLayout; }
|
, "basic": function() { return basicLayout; }
|
||||||
, "pattern": function (config) {
|
, "colored": function() { return colouredLayout; }
|
||||||
var pattern = config.pattern || undefined;
|
, "coloured": function() { return colouredLayout; }
|
||||||
return patternLayout(pattern);
|
, "pattern": function (config) {
|
||||||
}
|
var pattern = config.pattern || undefined;
|
||||||
}
|
return patternLayout(pattern);
|
||||||
, colours = {
|
}
|
||||||
ALL: "grey"
|
}
|
||||||
, TRACE: "blue"
|
, colours = {
|
||||||
, DEBUG: "cyan"
|
ALL: "grey"
|
||||||
, INFO: "green"
|
, TRACE: "blue"
|
||||||
, WARN: "yellow"
|
, DEBUG: "cyan"
|
||||||
, ERROR: "red"
|
, INFO: "green"
|
||||||
, FATAL: "magenta"
|
, WARN: "yellow"
|
||||||
, OFF: "grey"
|
, ERROR: "red"
|
||||||
};
|
, FATAL: "magenta"
|
||||||
|
, OFF: "grey"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
function formatLogData(logData) {
|
function formatLogData(logData) {
|
||||||
var output = ""
|
var output = ""
|
||||||
, data = Array.isArray(logData) ? logData.slice() : Array.prototype.slice.call(arguments)
|
, data = Array.isArray(logData) ? logData.slice() : Array.prototype.slice.call(arguments)
|
||||||
, format = data.shift();
|
, format = data.shift();
|
||||||
|
|
||||||
if (typeof format === "string") {
|
if (typeof format === "string") {
|
||||||
output = format.replace(replacementRegExp, function(match) {
|
output = format.replace(replacementRegExp, function(match) {
|
||||||
@@ -47,10 +49,9 @@ function formatLogData(logData) {
|
|||||||
if (output) {
|
if (output) {
|
||||||
output += ' ';
|
output += ' ';
|
||||||
}
|
}
|
||||||
|
output += util.inspect(item);
|
||||||
if (item && item.stack) {
|
if (item && item.stack) {
|
||||||
output += item.stack;
|
output += "\n" + item.stack;
|
||||||
} else {
|
|
||||||
output += util.inspect(item);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,7 +198,7 @@ function patternLayout (pattern) {
|
|||||||
replacement = formatLogData(loggingEvent.data);
|
replacement = formatLogData(loggingEvent.data);
|
||||||
break;
|
break;
|
||||||
case "n":
|
case "n":
|
||||||
replacement = "\n";
|
replacement = eol;
|
||||||
break;
|
break;
|
||||||
case "p":
|
case "p":
|
||||||
replacement = loggingEvent.level.toString();
|
replacement = loggingEvent.level.toString();
|
||||||
@@ -257,4 +258,4 @@ module.exports = {
|
|||||||
, layout: function(name, config) {
|
, layout: function(name, config) {
|
||||||
return layoutMakers[name] && layoutMakers[name](config);
|
return layoutMakers[name] && layoutMakers[name](config);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
268
lib/streams.js
268
lib/streams.js
@@ -1,268 +0,0 @@
|
|||||||
var util = require('util'),
|
|
||||||
fs = require('fs'),
|
|
||||||
path = require('path'),
|
|
||||||
events = require('events'),
|
|
||||||
async = require('async');
|
|
||||||
|
|
||||||
function debug(message) {
|
|
||||||
// util.debug(message);
|
|
||||||
// console.log(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function BufferedWriteStream(stream) {
|
|
||||||
var that = this;
|
|
||||||
this.stream = stream;
|
|
||||||
this.buffer = [];
|
|
||||||
this.canWrite = false;
|
|
||||||
this.bytes = 0;
|
|
||||||
|
|
||||||
this.stream.on("open", function() {
|
|
||||||
that.canWrite = true;
|
|
||||||
that.flushBuffer();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.stream.on("error", function (err) {
|
|
||||||
that.emit("error", err);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.stream.on("drain", function() {
|
|
||||||
that.canWrite = true;
|
|
||||||
that.flushBuffer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(BufferedWriteStream, events.EventEmitter);
|
|
||||||
|
|
||||||
Object.defineProperty(
|
|
||||||
BufferedWriteStream.prototype,
|
|
||||||
"fd",
|
|
||||||
{
|
|
||||||
get: function() { return this.stream.fd; },
|
|
||||||
set: function(newFd) {
|
|
||||||
this.stream.fd = newFd;
|
|
||||||
this.bytes = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.defineProperty(
|
|
||||||
BufferedWriteStream.prototype,
|
|
||||||
"bytesWritten",
|
|
||||||
{
|
|
||||||
get: function() { return this.bytes; }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
BufferedWriteStream.prototype.write = function(data, encoding) {
|
|
||||||
this.buffer.push({ data: data, encoding: encoding });
|
|
||||||
this.flushBuffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
BufferedWriteStream.prototype.end = function(data, encoding) {
|
|
||||||
if (data) {
|
|
||||||
this.buffer.push({ data: data, encoding: encoding });
|
|
||||||
}
|
|
||||||
this.flushBufferEvenIfCannotWrite();
|
|
||||||
};
|
|
||||||
|
|
||||||
BufferedWriteStream.prototype.writeToStream = function(toWrite) {
|
|
||||||
this.bytes += toWrite.data.length;
|
|
||||||
this.canWrite = this.stream.write(toWrite.data, toWrite.encoding);
|
|
||||||
};
|
|
||||||
|
|
||||||
BufferedWriteStream.prototype.flushBufferEvenIfCannotWrite = function() {
|
|
||||||
while (this.buffer.length > 0) {
|
|
||||||
this.writeToStream(this.buffer.shift());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
BufferedWriteStream.prototype.flushBuffer = function() {
|
|
||||||
while (this.buffer.length > 0 && this.canWrite) {
|
|
||||||
this.writeToStream(this.buffer.shift());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function BaseRollingFileStream(filename, options) {
|
|
||||||
this.filename = filename;
|
|
||||||
this.options = options || { encoding: 'utf8', mode: 0644, flags: 'a' };
|
|
||||||
this.rolling = false;
|
|
||||||
this.writesWhileRolling = [];
|
|
||||||
this.currentSize = 0;
|
|
||||||
|
|
||||||
function currentFileSize(file) {
|
|
||||||
var fileSize = 0;
|
|
||||||
try {
|
|
||||||
fileSize = fs.statSync(file).size;
|
|
||||||
} catch (e) {
|
|
||||||
// file does not exist
|
|
||||||
}
|
|
||||||
return fileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwErrorIfArgumentsAreNotValid() {
|
|
||||||
if (!filename) {
|
|
||||||
throw new Error("You must specify a filename");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throwErrorIfArgumentsAreNotValid();
|
|
||||||
|
|
||||||
BaseRollingFileStream.super_.call(this, this.filename, this.options);
|
|
||||||
this.currentSize = currentFileSize(this.filename);
|
|
||||||
}
|
|
||||||
util.inherits(BaseRollingFileStream, fs.FileWriteStream);
|
|
||||||
|
|
||||||
BaseRollingFileStream.prototype.initRolling = function() {
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
function emptyRollingQueue() {
|
|
||||||
debug("emptying the rolling queue");
|
|
||||||
var toWrite;
|
|
||||||
while ((toWrite = that.writesWhileRolling.shift())) {
|
|
||||||
BaseRollingFileStream.super_.prototype.write.call(that, toWrite.data, toWrite.encoding);
|
|
||||||
that.currentSize += toWrite.data.length;
|
|
||||||
if (that.shouldRoll()) {
|
|
||||||
that.flush();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
that.flush();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.rolling = true;
|
|
||||||
this.roll(this.filename, function() {
|
|
||||||
that.currentSize = 0;
|
|
||||||
that.rolling = emptyRollingQueue();
|
|
||||||
if (that.rolling) {
|
|
||||||
process.nextTick(function() { that.initRolling(); });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseRollingFileStream.prototype.write = function(data, encoding) {
|
|
||||||
if (this.rolling) {
|
|
||||||
this.writesWhileRolling.push({ data: data, encoding: encoding });
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
var canWrite = BaseRollingFileStream.super_.prototype.write.call(this, data, encoding);
|
|
||||||
this.currentSize += data.length;
|
|
||||||
debug('current size = ' + this.currentSize);
|
|
||||||
if (this.shouldRoll()) {
|
|
||||||
this.initRolling();
|
|
||||||
}
|
|
||||||
return canWrite;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseRollingFileStream.prototype.shouldRoll = function() {
|
|
||||||
return false; // default behaviour is never to roll
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseRollingFileStream.prototype.roll = function(filename, callback) {
|
|
||||||
callback(); // default behaviour is not to do anything
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function RollingFileStream (filename, size, backups, options) {
|
|
||||||
this.size = size;
|
|
||||||
this.backups = backups || 1;
|
|
||||||
|
|
||||||
function throwErrorIfArgumentsAreNotValid() {
|
|
||||||
if (!filename || !size || size <= 0) {
|
|
||||||
throw new Error("You must specify a filename and file size");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throwErrorIfArgumentsAreNotValid();
|
|
||||||
|
|
||||||
RollingFileStream.super_.call(this, filename, options);
|
|
||||||
}
|
|
||||||
util.inherits(RollingFileStream, BaseRollingFileStream);
|
|
||||||
|
|
||||||
RollingFileStream.prototype.shouldRoll = function() {
|
|
||||||
return this.currentSize >= this.size;
|
|
||||||
};
|
|
||||||
|
|
||||||
RollingFileStream.prototype.roll = function(filename, callback) {
|
|
||||||
var that = this,
|
|
||||||
nameMatcher = new RegExp('^' + path.basename(filename));
|
|
||||||
|
|
||||||
function justTheseFiles (item) {
|
|
||||||
return nameMatcher.test(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
function index(filename_) {
|
|
||||||
return parseInt(filename_.substring((path.basename(filename) + '.').length), 10) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function byIndex(a, b) {
|
|
||||||
if (index(a) > index(b)) {
|
|
||||||
return 1;
|
|
||||||
} else if (index(a) < index(b) ) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function increaseFileIndex (fileToRename, cb) {
|
|
||||||
var idx = index(fileToRename);
|
|
||||||
debug('Index of ' + fileToRename + ' is ' + idx);
|
|
||||||
if (idx < that.backups) {
|
|
||||||
//on windows, you can get a EEXIST error if you rename a file to an existing file
|
|
||||||
//so, we'll try to delete the file we're renaming to first
|
|
||||||
fs.unlink(filename + '.' + (idx+1), function (err) {
|
|
||||||
//ignore err: if we could not delete, it's most likely that it doesn't exist
|
|
||||||
debug('Renaming ' + fileToRename + ' -> ' + filename + '.' + (idx+1));
|
|
||||||
fs.rename(path.join(path.dirname(filename), fileToRename), filename + '.' + (idx + 1), cb);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renameTheFiles(cb) {
|
|
||||||
//roll the backups (rename file.n to file.n+1, where n <= numBackups)
|
|
||||||
debug("Renaming the old files");
|
|
||||||
fs.readdir(path.dirname(filename), function (err, files) {
|
|
||||||
async.forEachSeries(
|
|
||||||
files.filter(justTheseFiles).sort(byIndex).reverse(),
|
|
||||||
increaseFileIndex,
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openANewFile(cb) {
|
|
||||||
debug("Opening a new file");
|
|
||||||
fs.open(
|
|
||||||
filename,
|
|
||||||
that.options.flags,
|
|
||||||
that.options.mode,
|
|
||||||
function (err, fd) {
|
|
||||||
debug("opened new file");
|
|
||||||
var oldLogFileFD = that.fd;
|
|
||||||
that.fd = fd;
|
|
||||||
that.writable = true;
|
|
||||||
fs.close(oldLogFileFD, cb);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug("Starting roll");
|
|
||||||
debug("Queueing up data until we've finished rolling");
|
|
||||||
debug("Flushing underlying stream");
|
|
||||||
this.flush();
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
renameTheFiles,
|
|
||||||
openANewFile
|
|
||||||
], callback);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
exports.BaseRollingFileStream = BaseRollingFileStream;
|
|
||||||
exports.RollingFileStream = RollingFileStream;
|
|
||||||
exports.BufferedWriteStream = BufferedWriteStream;
|
|
||||||
99
lib/streams/BaseRollingFileStream.js
Normal file
99
lib/streams/BaseRollingFileStream.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
var fs = require('fs'),
|
||||||
|
util = require('util');
|
||||||
|
|
||||||
|
function debug(message) {
|
||||||
|
// console.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BaseRollingFileStream;
|
||||||
|
|
||||||
|
function BaseRollingFileStream(filename, options) {
|
||||||
|
|
||||||
|
debug("In BaseRollingFileStream");
|
||||||
|
this.filename = filename;
|
||||||
|
this.options = options || { encoding: 'utf8', mode: 0644, flags: 'a' };
|
||||||
|
this.rolling = false;
|
||||||
|
this.writesWhileRolling = [];
|
||||||
|
this.currentSize = 0;
|
||||||
|
this.rollBeforeWrite = false;
|
||||||
|
|
||||||
|
function currentFileSize(file) {
|
||||||
|
var fileSize = 0;
|
||||||
|
try {
|
||||||
|
fileSize = fs.statSync(file).size;
|
||||||
|
} catch (e) {
|
||||||
|
// file does not exist
|
||||||
|
}
|
||||||
|
return fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwErrorIfArgumentsAreNotValid() {
|
||||||
|
if (!filename) {
|
||||||
|
throw new Error("You must specify a filename");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throwErrorIfArgumentsAreNotValid();
|
||||||
|
debug("Calling BaseRollingFileStream.super");
|
||||||
|
BaseRollingFileStream.super_.call(this, this.filename, this.options);
|
||||||
|
this.currentSize = currentFileSize(this.filename);
|
||||||
|
}
|
||||||
|
util.inherits(BaseRollingFileStream, fs.FileWriteStream);
|
||||||
|
|
||||||
|
BaseRollingFileStream.prototype.initRolling = function() {
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
function emptyRollingQueue() {
|
||||||
|
debug("emptying the rolling queue");
|
||||||
|
var toWrite;
|
||||||
|
while ((toWrite = that.writesWhileRolling.shift())) {
|
||||||
|
BaseRollingFileStream.super_.prototype.write.call(that, toWrite.data, toWrite.encoding);
|
||||||
|
that.currentSize += toWrite.data.length;
|
||||||
|
if (that.shouldRoll()) {
|
||||||
|
that.flush();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
that.flush();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rolling = true;
|
||||||
|
this.roll(this.filename, function() {
|
||||||
|
that.currentSize = 0;
|
||||||
|
that.rolling = emptyRollingQueue();
|
||||||
|
if (that.rolling) {
|
||||||
|
process.nextTick(function() { that.initRolling(); });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseRollingFileStream.prototype.write = function(data, encoding) {
|
||||||
|
var canWrite = false;
|
||||||
|
if (this.rolling) {
|
||||||
|
this.writesWhileRolling.push({ data: data, encoding: encoding });
|
||||||
|
} else {
|
||||||
|
if (this.rollBeforeWrite && this.shouldRoll()) {
|
||||||
|
this.writesWhileRolling.push({ data: data, encoding: encoding });
|
||||||
|
this.initRolling();
|
||||||
|
} else {
|
||||||
|
canWrite = BaseRollingFileStream.super_.prototype.write.call(this, data, encoding);
|
||||||
|
this.currentSize += data.length;
|
||||||
|
debug('current size = ' + this.currentSize);
|
||||||
|
|
||||||
|
if (!this.rollBeforeWrite && this.shouldRoll()) {
|
||||||
|
this.initRolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return canWrite;
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseRollingFileStream.prototype.shouldRoll = function() {
|
||||||
|
return false; // default behaviour is never to roll
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseRollingFileStream.prototype.roll = function(filename, callback) {
|
||||||
|
callback(); // default behaviour is not to do anything
|
||||||
|
};
|
||||||
|
|
||||||
77
lib/streams/BufferedWriteStream.js
Normal file
77
lib/streams/BufferedWriteStream.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
var events = require('events'),
|
||||||
|
util = require('util');
|
||||||
|
|
||||||
|
module.exports = BufferedWriteStream;
|
||||||
|
|
||||||
|
function BufferedWriteStream(stream) {
|
||||||
|
var that = this;
|
||||||
|
this.stream = stream;
|
||||||
|
this.buffer = [];
|
||||||
|
this.canWrite = false;
|
||||||
|
this.bytes = 0;
|
||||||
|
|
||||||
|
this.stream.on("open", function() {
|
||||||
|
that.canWrite = true;
|
||||||
|
that.flushBuffer();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stream.on("error", function (err) {
|
||||||
|
that.emit("error", err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stream.on("drain", function() {
|
||||||
|
that.canWrite = true;
|
||||||
|
that.flushBuffer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(BufferedWriteStream, events.EventEmitter);
|
||||||
|
|
||||||
|
Object.defineProperty(
|
||||||
|
BufferedWriteStream.prototype,
|
||||||
|
"fd",
|
||||||
|
{
|
||||||
|
get: function() { return this.stream.fd; },
|
||||||
|
set: function(newFd) {
|
||||||
|
this.stream.fd = newFd;
|
||||||
|
this.bytes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.defineProperty(
|
||||||
|
BufferedWriteStream.prototype,
|
||||||
|
"bytesWritten",
|
||||||
|
{
|
||||||
|
get: function() { return this.bytes; }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BufferedWriteStream.prototype.write = function(data, encoding) {
|
||||||
|
this.buffer.push({ data: data, encoding: encoding });
|
||||||
|
this.flushBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
|
BufferedWriteStream.prototype.end = function(data, encoding) {
|
||||||
|
if (data) {
|
||||||
|
this.buffer.push({ data: data, encoding: encoding });
|
||||||
|
}
|
||||||
|
this.flushBufferEvenIfCannotWrite();
|
||||||
|
};
|
||||||
|
|
||||||
|
BufferedWriteStream.prototype.writeToStream = function(toWrite) {
|
||||||
|
this.bytes += toWrite.data.length;
|
||||||
|
this.canWrite = this.stream.write(toWrite.data, toWrite.encoding);
|
||||||
|
};
|
||||||
|
|
||||||
|
BufferedWriteStream.prototype.flushBufferEvenIfCannotWrite = function() {
|
||||||
|
while (this.buffer.length > 0) {
|
||||||
|
this.writeToStream(this.buffer.shift());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BufferedWriteStream.prototype.flushBuffer = function() {
|
||||||
|
while (this.buffer.length > 0 && this.canWrite) {
|
||||||
|
this.writeToStream(this.buffer.shift());
|
||||||
|
}
|
||||||
|
};
|
||||||
89
lib/streams/DateRollingFileStream.js
Normal file
89
lib/streams/DateRollingFileStream.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
var BaseRollingFileStream = require('./BaseRollingFileStream'),
|
||||||
|
format = require('../date_format'),
|
||||||
|
async = require('async'),
|
||||||
|
fs = require('fs'),
|
||||||
|
util = require('util');
|
||||||
|
|
||||||
|
module.exports = DateRollingFileStream;
|
||||||
|
|
||||||
|
function debug(message) {
|
||||||
|
// console.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DateRollingFileStream(filename, pattern, options, now) {
|
||||||
|
debug("Now is " + now);
|
||||||
|
if (pattern && typeof(pattern) === 'object') {
|
||||||
|
now = options;
|
||||||
|
options = pattern;
|
||||||
|
pattern = null;
|
||||||
|
}
|
||||||
|
this.pattern = pattern || '.yyyy-MM-dd';
|
||||||
|
this.now = now || Date.now;
|
||||||
|
this.lastTimeWeWroteSomething = format.asString(this.pattern, new Date(this.now()));
|
||||||
|
debug("this.now is " + this.now + ", now is " + now);
|
||||||
|
|
||||||
|
DateRollingFileStream.super_.call(this, filename, options);
|
||||||
|
this.rollBeforeWrite = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(DateRollingFileStream, BaseRollingFileStream);
|
||||||
|
|
||||||
|
DateRollingFileStream.prototype.shouldRoll = function() {
|
||||||
|
var lastTime = this.lastTimeWeWroteSomething,
|
||||||
|
thisTime = format.asString(this.pattern, new Date(this.now()));
|
||||||
|
|
||||||
|
debug("DateRollingFileStream.shouldRoll with now = " + this.now() + ", thisTime = " + thisTime + ", lastTime = " + lastTime);
|
||||||
|
|
||||||
|
this.lastTimeWeWroteSomething = thisTime;
|
||||||
|
this.previousTime = lastTime;
|
||||||
|
|
||||||
|
return thisTime !== lastTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
DateRollingFileStream.prototype.roll = function(filename, callback) {
|
||||||
|
var that = this,
|
||||||
|
newFilename = filename + this.previousTime;
|
||||||
|
|
||||||
|
debug("Starting roll");
|
||||||
|
debug("Queueing up data until we've finished rolling");
|
||||||
|
debug("Flushing underlying stream");
|
||||||
|
this.flush();
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
deleteAnyExistingFile,
|
||||||
|
renameTheCurrentFile,
|
||||||
|
openANewFile
|
||||||
|
], callback);
|
||||||
|
|
||||||
|
function deleteAnyExistingFile(cb) {
|
||||||
|
//on windows, you can get a EEXIST error if you rename a file to an existing file
|
||||||
|
//so, we'll try to delete the file we're renaming to first
|
||||||
|
fs.unlink(newFilename, function (err) {
|
||||||
|
//ignore err: if we could not delete, it's most likely that it doesn't exist
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameTheCurrentFile(cb) {
|
||||||
|
debug("Renaming the " + filename + " -> " + newFilename);
|
||||||
|
fs.rename(filename, newFilename, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openANewFile(cb) {
|
||||||
|
debug("Opening a new file");
|
||||||
|
fs.open(
|
||||||
|
filename,
|
||||||
|
that.options.flags,
|
||||||
|
that.options.mode,
|
||||||
|
function (err, fd) {
|
||||||
|
debug("opened new file");
|
||||||
|
var oldLogFileFD = that.fd;
|
||||||
|
that.fd = fd;
|
||||||
|
that.writable = true;
|
||||||
|
fs.close(oldLogFileFD, cb);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
110
lib/streams/RollingFileStream.js
Normal file
110
lib/streams/RollingFileStream.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
var BaseRollingFileStream = require('./BaseRollingFileStream'),
|
||||||
|
util = require('util'),
|
||||||
|
path = require('path'),
|
||||||
|
fs = require('fs'),
|
||||||
|
async = require('async');
|
||||||
|
|
||||||
|
function debug(message) {
|
||||||
|
// util.debug(message);
|
||||||
|
// console.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RollingFileStream;
|
||||||
|
|
||||||
|
function RollingFileStream (filename, size, backups, options) {
|
||||||
|
this.size = size;
|
||||||
|
this.backups = backups || 1;
|
||||||
|
|
||||||
|
function throwErrorIfArgumentsAreNotValid() {
|
||||||
|
if (!filename || !size || size <= 0) {
|
||||||
|
throw new Error("You must specify a filename and file size");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throwErrorIfArgumentsAreNotValid();
|
||||||
|
|
||||||
|
RollingFileStream.super_.call(this, filename, options);
|
||||||
|
}
|
||||||
|
util.inherits(RollingFileStream, BaseRollingFileStream);
|
||||||
|
|
||||||
|
RollingFileStream.prototype.shouldRoll = function() {
|
||||||
|
return this.currentSize >= this.size;
|
||||||
|
};
|
||||||
|
|
||||||
|
RollingFileStream.prototype.roll = function(filename, callback) {
|
||||||
|
var that = this,
|
||||||
|
nameMatcher = new RegExp('^' + path.basename(filename));
|
||||||
|
|
||||||
|
function justTheseFiles (item) {
|
||||||
|
return nameMatcher.test(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function index(filename_) {
|
||||||
|
return parseInt(filename_.substring((path.basename(filename) + '.').length), 10) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function byIndex(a, b) {
|
||||||
|
if (index(a) > index(b)) {
|
||||||
|
return 1;
|
||||||
|
} else if (index(a) < index(b) ) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function increaseFileIndex (fileToRename, cb) {
|
||||||
|
var idx = index(fileToRename);
|
||||||
|
debug('Index of ' + fileToRename + ' is ' + idx);
|
||||||
|
if (idx < that.backups) {
|
||||||
|
//on windows, you can get a EEXIST error if you rename a file to an existing file
|
||||||
|
//so, we'll try to delete the file we're renaming to first
|
||||||
|
fs.unlink(filename + '.' + (idx+1), function (err) {
|
||||||
|
//ignore err: if we could not delete, it's most likely that it doesn't exist
|
||||||
|
debug('Renaming ' + fileToRename + ' -> ' + filename + '.' + (idx+1));
|
||||||
|
fs.rename(path.join(path.dirname(filename), fileToRename), filename + '.' + (idx + 1), cb);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameTheFiles(cb) {
|
||||||
|
//roll the backups (rename file.n to file.n+1, where n <= numBackups)
|
||||||
|
debug("Renaming the old files");
|
||||||
|
fs.readdir(path.dirname(filename), function (err, files) {
|
||||||
|
async.forEachSeries(
|
||||||
|
files.filter(justTheseFiles).sort(byIndex).reverse(),
|
||||||
|
increaseFileIndex,
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openANewFile(cb) {
|
||||||
|
debug("Opening a new file");
|
||||||
|
fs.open(
|
||||||
|
filename,
|
||||||
|
that.options.flags,
|
||||||
|
that.options.mode,
|
||||||
|
function (err, fd) {
|
||||||
|
debug("opened new file");
|
||||||
|
var oldLogFileFD = that.fd;
|
||||||
|
that.fd = fd;
|
||||||
|
that.writable = true;
|
||||||
|
fs.close(oldLogFileFD, cb);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Starting roll");
|
||||||
|
debug("Queueing up data until we've finished rolling");
|
||||||
|
debug("Flushing underlying stream");
|
||||||
|
this.flush();
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
renameTheFiles,
|
||||||
|
openANewFile
|
||||||
|
], callback);
|
||||||
|
|
||||||
|
};
|
||||||
3
lib/streams/index.js
Normal file
3
lib/streams/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
exports.BufferedWriteStream = require('./BufferedWriteStream');
|
||||||
|
exports.RollingFileStream = require('./RollingFileStream');
|
||||||
|
exports.DateRollingFileStream = require('./DateRollingFileStream');
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "log4js",
|
"name": "log4js",
|
||||||
"version": "0.5.0",
|
"version": "0.5.2",
|
||||||
"description": "Port of Log4js to work with node.",
|
"description": "Port of Log4js to work with node.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"logging",
|
"logging",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
},
|
},
|
||||||
"engines": [ "node >=0.6" ],
|
"engines": [ "node >=0.6" ],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vows test/*.js"
|
"test": "vows"
|
||||||
},
|
},
|
||||||
"directories": {
|
"directories": {
|
||||||
"test": "test",
|
"test": "test",
|
||||||
|
|||||||
98
test/dateFileAppender-test.js
Normal file
98
test/dateFileAppender-test.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
var vows = require('vows'),
|
||||||
|
assert = require('assert'),
|
||||||
|
path = require('path'),
|
||||||
|
fs = require('fs'),
|
||||||
|
log4js = require('../lib/log4js');
|
||||||
|
|
||||||
|
function removeFile(filename) {
|
||||||
|
return function() {
|
||||||
|
fs.unlink(path.join(__dirname, filename), function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log("Could not delete ", filename, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
vows.describe('../lib/appenders/dateFile').addBatch({
|
||||||
|
'appender': {
|
||||||
|
'adding multiple dateFileAppenders': {
|
||||||
|
topic: function () {
|
||||||
|
var listenersCount = process.listeners('exit').length,
|
||||||
|
dateFileAppender = require('../lib/appenders/dateFile'),
|
||||||
|
count = 5,
|
||||||
|
logfile;
|
||||||
|
|
||||||
|
while (count--) {
|
||||||
|
logfile = path.join(__dirname, 'datefa-default-test' + count + '.log');
|
||||||
|
log4js.addAppender(dateFileAppender.appender(logfile));
|
||||||
|
}
|
||||||
|
|
||||||
|
return listenersCount;
|
||||||
|
},
|
||||||
|
teardown: function() {
|
||||||
|
removeFile('datefa-default-test0.log')();
|
||||||
|
removeFile('datefa-default-test1.log')();
|
||||||
|
removeFile('datefa-default-test2.log')();
|
||||||
|
removeFile('datefa-default-test3.log')();
|
||||||
|
removeFile('datefa-default-test4.log')();
|
||||||
|
},
|
||||||
|
|
||||||
|
'should only add one `exit` listener': function (initialCount) {
|
||||||
|
assert.equal(process.listeners('exit').length, initialCount + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'with default settings': {
|
||||||
|
topic: function() {
|
||||||
|
var that = this,
|
||||||
|
testFile = path.join(__dirname, 'date-appender-default.log'),
|
||||||
|
appender = require('../lib/appenders/dateFile').appender(testFile),
|
||||||
|
logger = log4js.getLogger('default-settings');
|
||||||
|
log4js.clearAppenders();
|
||||||
|
log4js.addAppender(appender, 'default-settings');
|
||||||
|
|
||||||
|
logger.info("This should be in the file.");
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fs.readFile(testFile, "utf8", that.callback);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
},
|
||||||
|
teardown: removeFile('date-appender-default.log'),
|
||||||
|
|
||||||
|
'should write to the file': function(contents) {
|
||||||
|
assert.include(contents, 'This should be in the file');
|
||||||
|
},
|
||||||
|
|
||||||
|
'should use the basic layout': function(contents) {
|
||||||
|
assert.match(contents, /\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}).addBatch({
|
||||||
|
'configure': {
|
||||||
|
'with dateFileAppender': {
|
||||||
|
topic: function() {
|
||||||
|
var log4js = require('../lib/log4js')
|
||||||
|
, logger;
|
||||||
|
//this config file defines one file appender (to ./date-file-test.log)
|
||||||
|
//and sets the log level for "tests" to WARN
|
||||||
|
log4js.configure('test/with-dateFile.json');
|
||||||
|
logger = log4js.getLogger('tests');
|
||||||
|
logger.info('this should not be written to the file');
|
||||||
|
logger.warn('this should be written to the file');
|
||||||
|
|
||||||
|
fs.readFile(path.join(__dirname, 'date-file-test.log'), 'utf8', this.callback);
|
||||||
|
},
|
||||||
|
teardown: removeFile('date-file-test.log'),
|
||||||
|
|
||||||
|
'should load appender configuration from a json file': function(err, contents) {
|
||||||
|
assert.include(contents, 'this should be written to the file\n');
|
||||||
|
assert.equal(contents.indexOf('this should not be written to the file'), -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}).exportTo(module);
|
||||||
@@ -15,6 +15,24 @@ function remove(filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vows.describe('log4js fileAppender').addBatch({
|
vows.describe('log4js fileAppender').addBatch({
|
||||||
|
'adding multiple fileAppenders': {
|
||||||
|
topic: function () {
|
||||||
|
var listenersCount = process.listeners('exit').length
|
||||||
|
, logger = log4js.getLogger('default-settings')
|
||||||
|
, count = 5, logfile;
|
||||||
|
|
||||||
|
while (count--) {
|
||||||
|
logfile = path.join(__dirname, '/fa-default-test' + count + '.log');
|
||||||
|
log4js.addAppender(require('../lib/appenders/file').appender(logfile), 'default-settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
return listenersCount;
|
||||||
|
},
|
||||||
|
|
||||||
|
'does not adds more than one `exit` listeners': function (initialCount) {
|
||||||
|
assert.ok(process.listeners('exit').length <= initialCount + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
'with default fileAppender settings': {
|
'with default fileAppender settings': {
|
||||||
topic: function() {
|
topic: function() {
|
||||||
@@ -76,8 +76,50 @@ vows.describe('log4js layouts').addBatch({
|
|||||||
, toString: function() { return "ERROR"; }
|
, toString: function() { return "ERROR"; }
|
||||||
}
|
}
|
||||||
}), "{ thing: 1 }");
|
}), "{ thing: 1 }");
|
||||||
|
},
|
||||||
|
'should print the stacks of a passed error objects': function(layout) {
|
||||||
|
assert.isArray(layout({
|
||||||
|
data: [ new Error() ]
|
||||||
|
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45)
|
||||||
|
, categoryName: "cheese"
|
||||||
|
, level: {
|
||||||
|
colour: "green"
|
||||||
|
, toString: function() { return "ERROR"; }
|
||||||
|
}
|
||||||
|
}).match(/Error\s+at Object\..*\s+\((.*)test[\\\/]layouts-test\.js\:\d+\:\d+\)\s+at runTest/)
|
||||||
|
, 'regexp did not return a match');
|
||||||
|
},
|
||||||
|
'with passed augmented errors':
|
||||||
|
{ topic:
|
||||||
|
function(layout){
|
||||||
|
var e = new Error("My Unique Error Message");
|
||||||
|
e.augmented = "My Unique attribute value"
|
||||||
|
e.augObj = { at1: "at2" }
|
||||||
|
return layout({
|
||||||
|
data: [ e ]
|
||||||
|
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45)
|
||||||
|
, categoryName: "cheese"
|
||||||
|
, level: {
|
||||||
|
colour: "green"
|
||||||
|
, toString: function() { return "ERROR"; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'should print error the contained error message': function(layoutOutput) {
|
||||||
|
var m = layoutOutput.match(/\{ \[Error: My Unique Error Message\]/);
|
||||||
|
assert.isArray(m);
|
||||||
|
},
|
||||||
|
'should print error augmented string attributes': function(layoutOutput) {
|
||||||
|
var m = layoutOutput.match(/augmented:\s'My Unique attribute value'/);
|
||||||
|
assert.isArray(m);
|
||||||
|
},
|
||||||
|
'should print error augmented object attributes': function(layoutOutput) {
|
||||||
|
var m = layoutOutput.match(/augObj:\s\{ at1: 'at2' \}/);
|
||||||
|
assert.isArray(m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'basicLayout': {
|
'basicLayout': {
|
||||||
@@ -106,10 +148,11 @@ vows.describe('log4js layouts').addBatch({
|
|||||||
output = layout(event);
|
output = layout(event);
|
||||||
lines = output.split(/\n/);
|
lines = output.split(/\n/);
|
||||||
|
|
||||||
assert.equal(lines.length, stack.length);
|
assert.equal(lines.length - 1, stack.length);
|
||||||
assert.equal(lines[0], "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test Error: Some made-up error");
|
assert.equal(lines[0], "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test [Error: Some made-up error]");
|
||||||
|
|
||||||
for (var i = 1; i < stack.length; i++) {
|
for (var i = 1; i < stack.length; i++) {
|
||||||
assert.equal(lines[i+1], stack[i+1]);
|
assert.equal(lines[i+2], stack[i+1]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'should output any extra data in the log event as util.inspect strings': function(args) {
|
'should output any extra data in the log event as util.inspect strings': function(args) {
|
||||||
@@ -115,8 +115,7 @@ vows.describe('log4js').addBatch({
|
|||||||
"type" : "file",
|
"type" : "file",
|
||||||
"filename" : "cheesy-wotsits.log",
|
"filename" : "cheesy-wotsits.log",
|
||||||
"maxLogSize" : 1024,
|
"maxLogSize" : 1024,
|
||||||
"backups" : 3,
|
"backups" : 3
|
||||||
"pollInterval" : 15
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -576,5 +575,4 @@ vows.describe('log4js').addBatch({
|
|||||||
assert.equal(logEvents[1].data[0], 'info3');
|
assert.equal(logEvents[1].data[0], 'info3');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}).export(module);
|
}).export(module);
|
||||||
242
test/multiprocess-test.js
Normal file
242
test/multiprocess-test.js
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
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: 'DEBUG', data: ['some debug']}) + '__LOG4JS__';
|
||||||
|
|
||||||
|
net.cbs['connect']();
|
||||||
|
net.cbs['data'](JSON.stringify({ level: '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: '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);
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
var vows = require('vows');
|
|
||||||
var assert = require('assert');
|
|
||||||
var sandbox = require('sandboxed-module');
|
|
||||||
var _ = require('underscore');
|
|
||||||
|
|
||||||
function fancyResultingMultiprocessAppender(opts) {
|
|
||||||
var result = { clientOns: {}, serverOns: {}, logged: [], ended: [] };
|
|
||||||
|
|
||||||
var fakeSocket = {
|
|
||||||
on: function (event, fn) {
|
|
||||||
result.clientOns[event] = fn;
|
|
||||||
if (event === 'connect') {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end: function (data, encoding) {
|
|
||||||
result.ended.push({ data: data, encoding: encoding });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fakeServerSocket = {
|
|
||||||
on: function (event, fn) {
|
|
||||||
result.serverOns[event] = fn;
|
|
||||||
if (event === 'connect') {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fakeServer = {
|
|
||||||
listen: function (port, host) {
|
|
||||||
result.listenPort = port;
|
|
||||||
result.listenHost = host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fakeNet = {
|
|
||||||
createServer: function (fn) {
|
|
||||||
fn(fakeServerSocket);
|
|
||||||
return fakeServer;
|
|
||||||
},
|
|
||||||
createConnection: function (port, host) {
|
|
||||||
result.connectPort = port;
|
|
||||||
result.connectHost = host;
|
|
||||||
return fakeSocket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fakeLog4Js = {
|
|
||||||
appenderMakers: {}
|
|
||||||
};
|
|
||||||
fakeLog4Js.loadAppender = function (appender) {
|
|
||||||
fakeLog4Js.appenderMakers[appender] = function (config) {
|
|
||||||
result.actualLoggerConfig = config;
|
|
||||||
return function log(logEvent) {
|
|
||||||
result.logged.push(logEvent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return { theResult: result,
|
|
||||||
theModule: sandbox.require('../lib/appenders/multiprocess', {
|
|
||||||
requires: {
|
|
||||||
'../log4js': fakeLog4Js,
|
|
||||||
'net': fakeNet
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function logMessages(result, logs, raw) {
|
|
||||||
logs.forEach(function log(item) {
|
|
||||||
var logItem = { startTime: "Wed, 02 Nov 2011 21:46:39 GMT", level: { levelStr: 'DEBUG' }, data: [ item ] };
|
|
||||||
result.serverOns.data(JSON.stringify(logItem));
|
|
||||||
result.serverOns.end();
|
|
||||||
result.serverOns.connect();
|
|
||||||
});
|
|
||||||
if (raw) {
|
|
||||||
raw.forEach(function log(rawItem) {
|
|
||||||
result.serverOns.data(rawItem);
|
|
||||||
result.serverOns.end();
|
|
||||||
result.serverOns.connect();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
vows.describe('log4js multiprocessAppender').addBatch({
|
|
||||||
'master': {
|
|
||||||
topic: function() {
|
|
||||||
var fancy = fancyResultingMultiprocessAppender();
|
|
||||||
var logger = fancy.theModule.configure({ mode: 'master', 'loggerPort': 5001, 'loggerHost': 'abba', appender: { type: 'file' } });
|
|
||||||
logMessages(fancy.theResult, [ 'ALRIGHTY THEN', 'OH WOW' ]);
|
|
||||||
return fancy.theResult;
|
|
||||||
},
|
|
||||||
|
|
||||||
'should write to the actual appender': function (result) {
|
|
||||||
assert.equal(result.listenPort, 5001);
|
|
||||||
assert.equal(result.listenHost, 'abba');
|
|
||||||
assert.equal(result.logged.length, 2);
|
|
||||||
assert.equal(result.logged[0].data[0], 'ALRIGHTY THEN');
|
|
||||||
assert.equal(result.logged[1].data[0], 'OH WOW');
|
|
||||||
},
|
|
||||||
|
|
||||||
'data written should be formatted correctly': function (result) {
|
|
||||||
assert.equal(result.logged[0].level.toString(), 'DEBUG');
|
|
||||||
assert.equal(result.logged[0].data, 'ALRIGHTY THEN');
|
|
||||||
assert.isTrue(typeof(result.logged[0].startTime) === 'object');
|
|
||||||
assert.equal(result.logged[1].level.toString(), 'DEBUG');
|
|
||||||
assert.equal(result.logged[1].data, 'OH WOW');
|
|
||||||
assert.isTrue(typeof(result.logged[1].startTime) === 'object');
|
|
||||||
},
|
|
||||||
|
|
||||||
'the actual logger should get the right config': function (result) {
|
|
||||||
assert.equal(result.actualLoggerConfig.type, 'file');
|
|
||||||
},
|
|
||||||
|
|
||||||
'client should not be called': function (result) {
|
|
||||||
assert.equal(_.keys(result.clientOns).length, 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'master with bad request': {
|
|
||||||
topic: function() {
|
|
||||||
var fancy = fancyResultingMultiprocessAppender();
|
|
||||||
var logger = fancy.theModule.configure({ mode: 'master', 'loggerPort': 5001, 'loggerHost': 'abba', appender: { type: 'file' } });
|
|
||||||
logMessages(fancy.theResult, [], [ 'ALRIGHTY THEN', 'OH WOW' ]);
|
|
||||||
return fancy.theResult;
|
|
||||||
},
|
|
||||||
|
|
||||||
'should write to the actual appender': function (result) {
|
|
||||||
assert.equal(result.listenPort, 5001);
|
|
||||||
assert.equal(result.listenHost, 'abba');
|
|
||||||
assert.equal(result.logged.length, 2);
|
|
||||||
assert.equal(result.logged[0].data[0], 'Unable to parse log: ALRIGHTY THEN');
|
|
||||||
assert.equal(result.logged[1].data[0], 'Unable to parse log: OH WOW');
|
|
||||||
},
|
|
||||||
|
|
||||||
'data written should be formatted correctly': function (result) {
|
|
||||||
assert.equal(result.logged[0].level.toString(), 'ERROR');
|
|
||||||
assert.equal(result.logged[0].data, 'Unable to parse log: ALRIGHTY THEN');
|
|
||||||
assert.isTrue(typeof(result.logged[0].startTime) === 'object');
|
|
||||||
assert.equal(result.logged[1].level.toString(), 'ERROR');
|
|
||||||
assert.equal(result.logged[1].data, 'Unable to parse log: OH WOW');
|
|
||||||
assert.isTrue(typeof(result.logged[1].startTime) === 'object');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'worker': {
|
|
||||||
'should emit logging events to the master': {
|
|
||||||
topic: function() {
|
|
||||||
var fancy = fancyResultingMultiprocessAppender();
|
|
||||||
var logger = fancy.theModule.configure({ loggerHost: 'baba', loggerPort: 1232, name: 'ohno', mode: 'worker', appender: { type: 'file' } });
|
|
||||||
logger({ level: { levelStr: 'INFO' }, data: "ALRIGHTY THEN", startTime: '2011-10-27T03:53:16.031Z' });
|
|
||||||
logger({ level: { levelStr: 'DEBUG' }, data: "OH WOW", startTime: '2011-10-27T04:53:16.031Z'});
|
|
||||||
return fancy.theResult;
|
|
||||||
},
|
|
||||||
|
|
||||||
'client configuration should be correct': function (result) {
|
|
||||||
assert.equal(result.connectHost, 'baba');
|
|
||||||
assert.equal(result.connectPort, 1232);
|
|
||||||
},
|
|
||||||
|
|
||||||
'should not write to the actual appender': function (result) {
|
|
||||||
assert.equal(result.logged.length, 0);
|
|
||||||
assert.equal(result.ended.length, 2);
|
|
||||||
assert.equal(result.ended[0].data, JSON.stringify({ level: { levelStr: 'INFO' }, data: "ALRIGHTY THEN", startTime: '2011-10-27T03:53:16.031Z' }));
|
|
||||||
assert.equal(result.ended[0].encoding, 'utf8');
|
|
||||||
assert.equal(result.ended[1].data, JSON.stringify({ level: { levelStr: 'DEBUG' }, data: "OH WOW", startTime: '2011-10-27T04:53:16.031Z'}));
|
|
||||||
assert.equal(result.ended[1].encoding, 'utf8');
|
|
||||||
assert.equal(_.keys(result.serverOns).length, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).exportTo(module);
|
|
||||||
168
test/smtpAppender-test.js
Normal file
168
test/smtpAppender-test.js
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
var vows = require('vows'),
|
||||||
|
assert = require('assert'),
|
||||||
|
log4js = require('../lib/log4js'),
|
||||||
|
sandbox = require('sandboxed-module');
|
||||||
|
|
||||||
|
function setupLogging(category, options) {
|
||||||
|
var msgs = [];
|
||||||
|
|
||||||
|
var fakeMailer = {
|
||||||
|
createTransport: function (name, options) {
|
||||||
|
return {
|
||||||
|
config: options,
|
||||||
|
sendMail: function (msg, callback) {
|
||||||
|
msgs.push(msg);
|
||||||
|
callback(null, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var smtpModule = sandbox.require('../lib/appenders/smtp', {
|
||||||
|
requires: {
|
||||||
|
'nodemailer': fakeMailer
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log4js.addAppender(smtpModule.configure(options), category);
|
||||||
|
|
||||||
|
return {
|
||||||
|
logger: log4js.getLogger(category),
|
||||||
|
mailer: fakeMailer,
|
||||||
|
results: msgs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMessages (result, sender, subject) {
|
||||||
|
for (var i = 0; i < result.results.length; ++i) {
|
||||||
|
assert.equal(result.results[i].from, sender);
|
||||||
|
assert.equal(result.results[i].to, 'recipient@domain.com');
|
||||||
|
assert.equal(result.results[i].subject, subject ? subject : 'Log event #' + (i+1));
|
||||||
|
assert.ok(new RegExp('.+Log event #' + (i+1) + '\n$').test(result.results[i].text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log4js.clearAppenders();
|
||||||
|
vows.describe('log4js smtpAppender').addBatch({
|
||||||
|
'minimal config': {
|
||||||
|
topic: function() {
|
||||||
|
var setup = setupLogging('minimal config', {
|
||||||
|
recipients: 'recipient@domain.com',
|
||||||
|
transport: "SMTP",
|
||||||
|
SMTP: {
|
||||||
|
port: 25,
|
||||||
|
auth: {
|
||||||
|
user: 'user@domain.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setup.logger.info('Log event #1');
|
||||||
|
return setup;
|
||||||
|
},
|
||||||
|
'there should be one message only': function (result) {
|
||||||
|
assert.equal(result.results.length, 1);
|
||||||
|
},
|
||||||
|
'message should contain proper data': function (result) {
|
||||||
|
checkMessages(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'fancy config': {
|
||||||
|
topic: function() {
|
||||||
|
var setup = setupLogging('fancy config', {
|
||||||
|
recipients: 'recipient@domain.com',
|
||||||
|
sender: 'sender@domain.com',
|
||||||
|
subject: 'This is subject',
|
||||||
|
transport: "SMTP",
|
||||||
|
SMTP: {
|
||||||
|
port: 25,
|
||||||
|
auth: {
|
||||||
|
user: 'user@domain.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setup.logger.info('Log event #1');
|
||||||
|
return setup;
|
||||||
|
},
|
||||||
|
'there should be one message only': function (result) {
|
||||||
|
assert.equal(result.results.length, 1);
|
||||||
|
},
|
||||||
|
'message should contain proper data': function (result) {
|
||||||
|
checkMessages(result, 'sender@domain.com', 'This is subject');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'separate email for each event': {
|
||||||
|
topic: function() {
|
||||||
|
var self = this;
|
||||||
|
var setup = setupLogging('separate email for each event', {
|
||||||
|
recipients: 'recipient@domain.com',
|
||||||
|
transport: "SMTP",
|
||||||
|
SMTP: {
|
||||||
|
port: 25,
|
||||||
|
auth: {
|
||||||
|
user: 'user@domain.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(function () {
|
||||||
|
setup.logger.info('Log event #1');
|
||||||
|
}, 0);
|
||||||
|
setTimeout(function () {
|
||||||
|
setup.logger.info('Log event #2');
|
||||||
|
}, 500);
|
||||||
|
setTimeout(function () {
|
||||||
|
setup.logger.info('Log event #3');
|
||||||
|
}, 1050);
|
||||||
|
setTimeout(function () {
|
||||||
|
self.callback(null, setup);
|
||||||
|
}, 2100);
|
||||||
|
},
|
||||||
|
'there should be three messages': function (result) {
|
||||||
|
assert.equal(result.results.length, 3);
|
||||||
|
},
|
||||||
|
'messages should contain proper data': function (result) {
|
||||||
|
checkMessages(result);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'multiple events in one email': {
|
||||||
|
topic: function() {
|
||||||
|
var self = this;
|
||||||
|
var setup = setupLogging('multiple events in one email', {
|
||||||
|
recipients: 'recipient@domain.com',
|
||||||
|
sendInterval: 1,
|
||||||
|
transport: "SMTP",
|
||||||
|
SMTP: {
|
||||||
|
port: 25,
|
||||||
|
auth: {
|
||||||
|
user: 'user@domain.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(function () {
|
||||||
|
setup.logger.info('Log event #1');
|
||||||
|
}, 0);
|
||||||
|
setTimeout(function () {
|
||||||
|
setup.logger.info('Log event #2');
|
||||||
|
}, 500);
|
||||||
|
setTimeout(function () {
|
||||||
|
setup.logger.info('Log event #3');
|
||||||
|
}, 1050);
|
||||||
|
setTimeout(function () {
|
||||||
|
self.callback(null, setup);
|
||||||
|
}, 2100);
|
||||||
|
},
|
||||||
|
'there should be two messages': function (result) {
|
||||||
|
assert.equal(result.results.length, 2);
|
||||||
|
},
|
||||||
|
'messages should contain proper data': function (result) {
|
||||||
|
assert.equal(result.results[0].to, 'recipient@domain.com');
|
||||||
|
assert.equal(result.results[0].subject, 'Log event #1');
|
||||||
|
assert.equal(result.results[0].text.match(new RegExp('.+Log event #[1-2]$', 'gm')).length, 2);
|
||||||
|
|
||||||
|
assert.equal(result.results[1].to, 'recipient@domain.com');
|
||||||
|
assert.equal(result.results[1].subject, 'Log event #3');
|
||||||
|
assert.ok(new RegExp('.+Log event #3\n$').test(result.results[1].text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).export(module);
|
||||||
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
var vows = require('vows'),
|
|
||||||
assert = require('assert'),
|
|
||||||
log4js = require('../lib/log4js'),
|
|
||||||
sandbox = require('sandboxed-module');
|
|
||||||
|
|
||||||
function setupLogging(category, options) {
|
|
||||||
var msgs = [];
|
|
||||||
|
|
||||||
var fakeMailer = {
|
|
||||||
send_mail: function (msg, callback) {
|
|
||||||
msgs.push(msg);
|
|
||||||
callback(null, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var smtpModule = sandbox.require('../lib/appenders/smtp', {
|
|
||||||
requires: {
|
|
||||||
'nodemailer': fakeMailer
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
log4js.addAppender(smtpModule.configure(options), category);
|
|
||||||
|
|
||||||
return {
|
|
||||||
logger: log4js.getLogger(category),
|
|
||||||
mailer: fakeMailer,
|
|
||||||
results: msgs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkMessages (result, sender, subject) {
|
|
||||||
for (var i = 0; i < result.results.length; ++i) {
|
|
||||||
assert.equal(result.results[i].sender, sender ? sender : result.mailer.SMTP.user);
|
|
||||||
assert.equal(result.results[i].to, 'recipient@domain.com');
|
|
||||||
assert.equal(result.results[i].subject, subject ? subject : 'Log event #' + (i+1));
|
|
||||||
assert.ok(new RegExp('.+Log event #' + (i+1) + '\n$').test(result.results[i].body));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log4js.clearAppenders();
|
|
||||||
vows.describe('log4js smtpAppender').addBatch({
|
|
||||||
'minimal config': {
|
|
||||||
topic: function() {
|
|
||||||
var setup = setupLogging('minimal config', {
|
|
||||||
recipients: 'recipient@domain.com',
|
|
||||||
smtp: {
|
|
||||||
port: 25,
|
|
||||||
user: 'user@domain.com'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setup.logger.info('Log event #1');
|
|
||||||
return setup;
|
|
||||||
},
|
|
||||||
'mailer should be configured properly': function (result) {
|
|
||||||
assert.ok(result.mailer.SMTP);
|
|
||||||
assert.equal(result.mailer.SMTP.port, 25);
|
|
||||||
assert.equal(result.mailer.SMTP.user, 'user@domain.com');
|
|
||||||
},
|
|
||||||
'there should be one message only': function (result) {
|
|
||||||
assert.equal(result.results.length, 1);
|
|
||||||
},
|
|
||||||
'message should contain proper data': function (result) {
|
|
||||||
checkMessages(result);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'fancy config': {
|
|
||||||
topic: function() {
|
|
||||||
var setup = setupLogging('fancy config', {
|
|
||||||
recipients: 'recipient@domain.com',
|
|
||||||
sender: 'sender@domain.com',
|
|
||||||
subject: 'This is subject',
|
|
||||||
smtp: {
|
|
||||||
port: 25,
|
|
||||||
user: 'user@domain.com'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setup.logger.info('Log event #1');
|
|
||||||
return setup;
|
|
||||||
},
|
|
||||||
'mailer should be configured properly': function (result) {
|
|
||||||
assert.ok(result.mailer.SMTP);
|
|
||||||
assert.equal(result.mailer.SMTP.port, 25);
|
|
||||||
assert.equal(result.mailer.SMTP.user, 'user@domain.com');
|
|
||||||
},
|
|
||||||
'there should be one message only': function (result) {
|
|
||||||
assert.equal(result.results.length, 1);
|
|
||||||
},
|
|
||||||
'message should contain proper data': function (result) {
|
|
||||||
checkMessages(result, 'sender@domain.com', 'This is subject');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'separate email for each event': {
|
|
||||||
topic: function() {
|
|
||||||
var self = this;
|
|
||||||
var setup = setupLogging('separate email for each event', {
|
|
||||||
recipients: 'recipient@domain.com',
|
|
||||||
smtp: {
|
|
||||||
port: 25,
|
|
||||||
user: 'user@domain.com'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setTimeout(function () {
|
|
||||||
setup.logger.info('Log event #1');
|
|
||||||
}, 0);
|
|
||||||
setTimeout(function () {
|
|
||||||
setup.logger.info('Log event #2');
|
|
||||||
}, 500);
|
|
||||||
setTimeout(function () {
|
|
||||||
setup.logger.info('Log event #3');
|
|
||||||
}, 1050);
|
|
||||||
setTimeout(function () {
|
|
||||||
self.callback(null, setup);
|
|
||||||
}, 2100);
|
|
||||||
},
|
|
||||||
'there should be three messages': function (result) {
|
|
||||||
assert.equal(result.results.length, 3);
|
|
||||||
},
|
|
||||||
'messages should contain proper data': function (result) {
|
|
||||||
checkMessages(result);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'multiple events in one email': {
|
|
||||||
topic: function() {
|
|
||||||
var self = this;
|
|
||||||
var setup = setupLogging('multiple events in one email', {
|
|
||||||
recipients: 'recipient@domain.com',
|
|
||||||
sendInterval: 1,
|
|
||||||
smtp: {
|
|
||||||
port: 25,
|
|
||||||
user: 'user@domain.com'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setTimeout(function () {
|
|
||||||
setup.logger.info('Log event #1');
|
|
||||||
}, 0);
|
|
||||||
setTimeout(function () {
|
|
||||||
setup.logger.info('Log event #2');
|
|
||||||
}, 500);
|
|
||||||
setTimeout(function () {
|
|
||||||
setup.logger.info('Log event #3');
|
|
||||||
}, 1050);
|
|
||||||
setTimeout(function () {
|
|
||||||
self.callback(null, setup);
|
|
||||||
}, 2100);
|
|
||||||
},
|
|
||||||
'there should be two messages': function (result) {
|
|
||||||
assert.equal(result.results.length, 2);
|
|
||||||
},
|
|
||||||
'messages should contain proper data': function (result) {
|
|
||||||
assert.equal(result.results[0].sender, result.mailer.SMTP.user);
|
|
||||||
assert.equal(result.results[0].to, 'recipient@domain.com');
|
|
||||||
assert.equal(result.results[0].subject, 'Log event #1');
|
|
||||||
assert.equal(result.results[0].body.match(new RegExp('.+Log event #[1-2]$', 'gm')).length, 2);
|
|
||||||
|
|
||||||
assert.equal(result.results[1].sender, result.mailer.SMTP.user);
|
|
||||||
assert.equal(result.results[1].to, 'recipient@domain.com');
|
|
||||||
assert.equal(result.results[1].subject, 'Log event #3');
|
|
||||||
assert.ok(new RegExp('.+Log event #3\n$').test(result.results[1].body));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}).export(module);
|
|
||||||
123
test/streams/DateRollingFileStream-test.js
Normal file
123
test/streams/DateRollingFileStream-test.js
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
var vows = require('vows'),
|
||||||
|
assert = require('assert'),
|
||||||
|
fs = require('fs'),
|
||||||
|
DateRollingFileStream = require('../../lib/streams').DateRollingFileStream,
|
||||||
|
testTime = new Date(2012, 8, 12, 10, 37, 11);
|
||||||
|
|
||||||
|
function cleanUp(filename) {
|
||||||
|
return function() {
|
||||||
|
fs.unlink(filename);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function now() {
|
||||||
|
return testTime.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
vows.describe('DateRollingFileStream').addBatch({
|
||||||
|
'arguments': {
|
||||||
|
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-1', 'yyyy-mm-dd.hh'),
|
||||||
|
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-1'),
|
||||||
|
|
||||||
|
'should take a filename and a pattern and return a FileWriteStream': function(stream) {
|
||||||
|
assert.equal(stream.filename, __dirname + '/test-date-rolling-file-stream-1');
|
||||||
|
assert.equal(stream.pattern, 'yyyy-mm-dd.hh');
|
||||||
|
assert.instanceOf(stream, fs.FileWriteStream);
|
||||||
|
},
|
||||||
|
'with default settings for the underlying stream': function(stream) {
|
||||||
|
assert.equal(stream.mode, 420);
|
||||||
|
assert.equal(stream.flags, 'a');
|
||||||
|
assert.equal(stream.encoding, 'utf8');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'default arguments': {
|
||||||
|
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-2'),
|
||||||
|
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-2'),
|
||||||
|
|
||||||
|
'pattern should be .yyyy-MM-dd': function(stream) {
|
||||||
|
assert.equal(stream.pattern, '.yyyy-MM-dd');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'with stream arguments': {
|
||||||
|
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-3', 'yyyy-MM-dd', { mode: 0666 }),
|
||||||
|
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-3'),
|
||||||
|
|
||||||
|
'should pass them to the underlying stream': function(stream) {
|
||||||
|
assert.equal(stream.mode, 0666);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'with stream arguments but no pattern': {
|
||||||
|
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-4', { mode: 0666 }),
|
||||||
|
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-4'),
|
||||||
|
|
||||||
|
'should pass them to the underlying stream': function(stream) {
|
||||||
|
assert.equal(stream.mode, 0666);
|
||||||
|
},
|
||||||
|
'should use default pattern': function(stream) {
|
||||||
|
assert.equal(stream.pattern, '.yyyy-MM-dd');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'with a pattern of .yyyy-MM-dd': {
|
||||||
|
topic: function() {
|
||||||
|
var that = this,
|
||||||
|
stream = new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-5', '.yyyy-MM-dd', null, now);
|
||||||
|
stream.on("open", function() {
|
||||||
|
stream.write("First message\n");
|
||||||
|
//wait for the file system to catch up with us
|
||||||
|
that.callback(null, stream);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-5'),
|
||||||
|
|
||||||
|
'should create a file with the base name': {
|
||||||
|
topic: function(stream) {
|
||||||
|
fs.readFile(__dirname + '/test-date-rolling-file-stream-5', this.callback);
|
||||||
|
},
|
||||||
|
'file should contain first message': function(result) {
|
||||||
|
assert.equal(result.toString(), "First message\n");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'when the day changes': {
|
||||||
|
topic: function(stream) {
|
||||||
|
testTime = new Date(2012, 8, 13, 0, 10, 12);
|
||||||
|
stream.write("Second message\n");
|
||||||
|
setTimeout(this.callback, 100);
|
||||||
|
},
|
||||||
|
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-5.2012-09-12'),
|
||||||
|
|
||||||
|
|
||||||
|
'the number of files': {
|
||||||
|
topic: function() {
|
||||||
|
fs.readdir(__dirname, this.callback);
|
||||||
|
},
|
||||||
|
'should be two': function(files) {
|
||||||
|
assert.equal(files.filter(function(file) { return file.indexOf('test-date-rolling-file-stream-5') > -1; }).length, 2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'the file without a date': {
|
||||||
|
topic: function() {
|
||||||
|
fs.readFile(__dirname + '/test-date-rolling-file-stream-5', this.callback);
|
||||||
|
},
|
||||||
|
'should contain the second message': function(contents) {
|
||||||
|
assert.equal(contents.toString(), "Second message\n");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'the file with the date': {
|
||||||
|
topic: function() {
|
||||||
|
fs.readFile(__dirname + '/test-date-rolling-file-stream-5.2012-09-12', this.callback);
|
||||||
|
},
|
||||||
|
'should contain the first message': function(contents) {
|
||||||
|
assert.equal(contents.toString(), "First message\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).exportTo(module);
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
var vows = require('vows')
|
var vows = require('vows')
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, events = require('events')
|
, events = require('events')
|
||||||
, BufferedWriteStream = require('../lib/streams').BufferedWriteStream;
|
, BufferedWriteStream = require('../../lib/streams').BufferedWriteStream;
|
||||||
|
|
||||||
function FakeStream() {
|
function FakeStream() {
|
||||||
this.writes = [];
|
this.writes = [];
|
||||||
@@ -127,4 +127,4 @@ vows.describe('BufferedWriteStream').addBatch({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}).exportTo(module);
|
}).exportTo(module);
|
||||||
@@ -2,7 +2,7 @@ var vows = require('vows')
|
|||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, events = require('events')
|
, events = require('events')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
, RollingFileStream = require('../lib/streams').RollingFileStream;
|
, RollingFileStream = require('../../lib/streams').RollingFileStream;
|
||||||
|
|
||||||
function remove(filename) {
|
function remove(filename) {
|
||||||
try {
|
try {
|
||||||
@@ -19,11 +19,11 @@ vows.describe('RollingFileStream').addBatch({
|
|||||||
return new RollingFileStream("test-rolling-file-stream", 1024, 5);
|
return new RollingFileStream("test-rolling-file-stream", 1024, 5);
|
||||||
},
|
},
|
||||||
'should take a filename, file size in bytes, number of backups as arguments and return a FileWriteStream': function(stream) {
|
'should take a filename, file size in bytes, number of backups as arguments and return a FileWriteStream': function(stream) {
|
||||||
assert.instanceOf(stream, fs.FileWriteStream);
|
assert.instanceOf(stream, fs.FileWriteStream);
|
||||||
assert.equal(stream.filename, "test-rolling-file-stream");
|
assert.equal(stream.filename, "test-rolling-file-stream");
|
||||||
assert.equal(stream.size, 1024);
|
assert.equal(stream.size, 1024);
|
||||||
assert.equal(stream.backups, 5);
|
assert.equal(stream.backups, 5);
|
||||||
},
|
},
|
||||||
'with default settings for the underlying stream': function(stream) {
|
'with default settings for the underlying stream': function(stream) {
|
||||||
assert.equal(stream.mode, 420);
|
assert.equal(stream.mode, 420);
|
||||||
assert.equal(stream.flags, 'a');
|
assert.equal(stream.flags, 'a');
|
||||||
17
test/with-dateFile.json
Normal file
17
test/with-dateFile.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"appenders": [
|
||||||
|
{
|
||||||
|
"category": "tests",
|
||||||
|
"type": "dateFile",
|
||||||
|
"filename": "test/date-file-test.log",
|
||||||
|
"pattern": "-from-MM-dd",
|
||||||
|
"layout": {
|
||||||
|
"type": "messagePassThrough"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"levels": {
|
||||||
|
"tests": "WARN"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,7 @@
|
|||||||
"type": "file",
|
"type": "file",
|
||||||
"filename": "tmp-test.log",
|
"filename": "tmp-test.log",
|
||||||
"maxLogSize": 1024,
|
"maxLogSize": 1024,
|
||||||
"backups": 3,
|
"backups": 3
|
||||||
"pollInterval": 15
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user