This commit is contained in:
Gareth Jones 2013-05-16 16:55:47 +10:00
commit 18e21ca473
5 changed files with 215 additions and 102 deletions

View File

@ -1,14 +1,46 @@
var log4js = require('./lib/log4js'); //The connect/express logger was added to log4js by danbell. This allows connect/express servers to log using log4js.
log4js.addAppender(log4js.fileAppender('cheese.log'), 'cheese'); //https://github.com/nomiddlename/log4js-node/wiki/Connect-Logger
var logger = log4js.getLogger('cheese'); // load modules
logger.setLevel('INFO'); var log4js = require('log4js');
var express = require("express");
var app = express();
var app = require('express').createServer(); //config
log4js.configure({
appenders: [
{ type: 'console' },
{ type: 'file', filename: 'logs/log4jsconnect.log', category: 'log4jslog' }
]
});
//define logger
var logger = log4js.getLogger('log4jslog');
// set at which time msg is logged print like: only on error & above
// logger.setLevel('ERROR');
//express app
app.configure(function() { app.configure(function() {
app.use(log4js.connectLogger(logger, { level: log4js.levels.INFO })); app.use(express.favicon(''));
// app.use(log4js.connectLogger(logger, { level: log4js.levels.INFO }));
// app.use(log4js.connectLogger(logger, { level: 'auto', format: ':method :url :status' }));
//### AUTO LEVEL DETECTION
//http responses 3xx, level = WARN
//http responses 4xx & 5xx, level = ERROR
//else.level = INFO
app.use(log4js.connectLogger(logger, { level: 'auto' }));
}); });
app.get('*', function(req,res) {
res.send('hello world\n <a href="/cheese">cheese</a>\n'); //route
app.get('/', function(req,res) {
res.send('hello world');
}); });
//start app
app.listen(5000); app.listen(5000);
console.log('server runing at localhost:5000');
console.log('Simulation of normal response: goto localhost:5000');
console.log('Simulation of error response: goto localhost:5000/xxx');

View File

@ -5,7 +5,7 @@ var levels = require("./levels");
* Options: * Options:
* *
* - `format` Format string, see below for tokens * - `format` Format string, see below for tokens
* - `level` A log4js levels instance. * - `level` A log4js levels instance. Supports also 'auto'
* *
* Tokens: * Tokens:
* *
@ -26,64 +26,74 @@ var levels = require("./levels");
* @api public * @api public
*/ */
function getLogger(logger4js, options) { function getLogger(logger4js, options) {
if ('object' == typeof options) { if ('object' == typeof options) {
options = options || {}; options = options || {};
} else if (options) { } else if (options) {
options = { format: options }; options = { format: options };
} else { } else {
options = {}; options = {};
} }
var thislogger = logger4js var thislogger = logger4js
, level = levels.toLevel(options.level, levels.INFO) , level = levels.toLevel(options.level, levels.INFO)
, fmt = options.format || ':remote-addr - - ":method :url HTTP/:http-version" :status :content-length ":referrer" ":user-agent"' , fmt = options.format || ':remote-addr - - ":method :url HTTP/:http-version" :status :content-length ":referrer" ":user-agent"'
, nolog = options.nolog ? createNoLogCondition(options.nolog) : null; , nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
return function (req, res, next) { return function (req, res, next) {
// mount safety // mount safety
if (req._logging) return next(); if (req._logging) return next();
// nologs // nologs
if (nolog && nolog.test(req.originalUrl)) return next(); if (nolog && nolog.test(req.originalUrl)) return next();
if (thislogger.isLevelEnabled(level) || options.level === 'auto') {
if (thislogger.isLevelEnabled(level)) { var start = +new Date
, statusCode
, writeHead = res.writeHead
, end = res.end
, url = req.originalUrl;
var start = +new Date // flag as logging
, statusCode req._logging = true;
, writeHead = res.writeHead
, end = res.end
, url = req.originalUrl;
// flag as logging // proxy for statusCode.
req._logging = true; res.writeHead = function(code, headers){
res.writeHead = writeHead;
res.writeHead(code, headers);
res.__statusCode = statusCode = code;
res.__headers = headers || {};
// proxy for statusCode. //status code response level handling
res.writeHead = function(code, headers){ if(options.level === 'auto'){
res.writeHead = writeHead; level = levels.INFO;
res.writeHead(code, headers); if(code >= 300) level = levels.WARN;
res.__statusCode = statusCode = code; if(code >= 400) level = levels.ERROR;
res.__headers = headers || {}; } else {
}; level = levels.toLevel(options.level, levels.INFO)
}
};
// proxy end to output a line to the provided logger. // proxy end to output a line to the provided logger.
res.end = function(chunk, encoding) { res.end = function(chunk, encoding) {
res.end = end; res.end = end;
res.end(chunk, encoding); res.end(chunk, encoding);
res.responseTime = +new Date - start; res.responseTime = +new Date - start;
if ('function' == typeof fmt) { if (thislogger.isLevelEnabled(level)) {
var line = fmt(req, res, function(str){ return format(str, req, res); }); if (typeof fmt === 'function') {
if (line) thislogger.log(level, line); var line = fmt(req, res, function(str){ return format(str, req, res); });
} else { if (line) thislogger.log(level, line);
thislogger.log(level, format(fmt, req, res)); } else {
thislogger.log(level, format(fmt, req, res));
}
}
};
} }
};
}
//ensure next gets always called //ensure next gets always called
next(); next();
}; };
} }
/** /**
@ -96,25 +106,25 @@ function getLogger(logger4js, options) {
* @api private * @api private
*/ */
function format(str, req, res) { function format(str, req, res) {
return str return str
.replace(':url', req.originalUrl) .replace(':url', req.originalUrl)
.replace(':method', req.method) .replace(':method', req.method)
.replace(':status', res.__statusCode || res.statusCode) .replace(':status', res.__statusCode || res.statusCode)
.replace(':response-time', res.responseTime) .replace(':response-time', res.responseTime)
.replace(':date', new Date().toUTCString()) .replace(':date', new Date().toUTCString())
.replace(':referrer', req.headers['referer'] || req.headers['referrer'] || '') .replace(':referrer', req.headers['referer'] || req.headers['referrer'] || '')
.replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor) .replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor)
.replace(':remote-addr', req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress))) .replace(':remote-addr', req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)))
.replace(':user-agent', req.headers['user-agent'] || '') .replace(':user-agent', req.headers['user-agent'] || '')
.replace(':content-length', (res._headers && res._headers['content-length']) || (res.__headers && res.__headers['Content-Length']) || '-') .replace(':content-length', (res._headers && res._headers['content-length']) || (res.__headers && res.__headers['Content-Length']) || '-')
.replace(/:req\[([^\]]+)\]/g, function(_, field){ return req.headers[field.toLowerCase()]; }) .replace(/:req\[([^\]]+)\]/g, function(_, field){ return req.headers[field.toLowerCase()]; })
.replace(/:res\[([^\]]+)\]/g, function(_, field){ .replace(/:res\[([^\]]+)\]/g, function(_, field){
return res._headers return res._headers
? (res._headers[field.toLowerCase()] || res.__headers[field]) ? (res._headers[field.toLowerCase()] || res.__headers[field])
: (res.__headers && res.__headers[field]); : (res.__headers && res.__headers[field]);
}); });
} }
/** /**
* Return RegExp Object about nolog * Return RegExp Object about nolog
@ -143,26 +153,26 @@ function format(str, req, res) {
* 3.1 ["\\.jpg$", "\\.png", "\\.gif"] * 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
* SAME AS "\\.jpg|\\.png|\\.gif" * SAME AS "\\.jpg|\\.png|\\.gif"
*/ */
function createNoLogCondition(nolog, type) { function createNoLogCondition(nolog, type) {
if(!nolog) return null; if(!nolog) return null;
type = type || ''; type = type || '';
if(nolog instanceof RegExp){ if(nolog instanceof RegExp){
if(type === 'string') if(type === 'string')
return nolog.source; return nolog.source;
return nolog; return nolog;
} else if(typeof nolog === 'string'){ } else if(typeof nolog === 'string'){
if(type === 'string') if(type === 'string')
return nolog; return nolog;
try{ try{
return new RegExp(nolog); return new RegExp(nolog);
} catch (ex) { } catch (ex) {
return null; return null;
} }
} else if(nolog instanceof Array){ } else if(nolog instanceof Array){
var regexps = nolog.map(function(o){ return createNoLogCondition(o, 'string')}); var regexps = nolog.map(function(o){ return createNoLogCondition(o, 'string')});
return new RegExp(regexps.join('|')); return new RegExp(regexps.join('|'));
} }
} }
exports.connectLogger = getLogger; exports.connectLogger = getLogger;

View File

@ -24,13 +24,15 @@ function DateRollingFileStream(filename, pattern, options, now) {
this.now = now || Date.now; this.now = now || Date.now;
this.lastTimeWeWroteSomething = format.asString(this.pattern, new Date(this.now())); this.lastTimeWeWroteSomething = format.asString(this.pattern, new Date(this.now()));
this.baseFilename = filename; this.baseFilename = filename;
this.alwaysIncludePattern = false;
if (options) { if (options) {
if (options.alwaysIncludePattern) { if (options.alwaysIncludePattern) {
filename = filename + this.lastTimeWeWroteSomething; this.alwaysIncludePattern = true;
filename = this.baseFilename + this.lastTimeWeWroteSomething;
} }
delete options.alwaysIncludePattern; delete options.alwaysIncludePattern;
if (options === {}) { if (Object.keys(options).length === 0) {
options = null; options = null;
} }
} }
@ -53,18 +55,26 @@ DateRollingFileStream.prototype.shouldRoll = function() {
}; };
DateRollingFileStream.prototype.roll = function(filename, callback) { DateRollingFileStream.prototype.roll = function(filename, callback) {
var that = this, var that = this;
newFilename = this.baseFilename + this.previousTime;
debug("Starting roll"); debug("Starting roll");
async.series([ if (this.alwaysIncludePattern) {
this.closeTheStream.bind(this), this.filename = this.baseFilename + this.lastTimeWeWroteSomething;
deleteAnyExistingFile, async.series([
renameTheCurrentFile, this.closeTheStream.bind(this),
this.openTheStream.bind(this) this.openTheStream.bind(this)
], callback); ], callback);
} else {
var newFilename = this.baseFilename + this.previousTime;
async.series([
this.closeTheStream.bind(this),
deleteAnyExistingFile,
renameTheCurrentFile,
this.openTheStream.bind(this)
], callback);
}
function deleteAnyExistingFile(cb) { function deleteAnyExistingFile(cb) {
//on windows, you can get a EEXIST error if you rename a file to an existing file //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 //so, we'll try to delete the file we're renaming to first

View File

@ -113,6 +113,7 @@ vows.describe('../lib/appenders/dateFile').addBatch({
] ]
} }
, thisTime = format.asString(options.appenders[0].pattern, new Date()); , thisTime = format.asString(options.appenders[0].pattern, new Date());
fs.writeFileSync(path.join(__dirname, 'date-file-test' + thisTime), "this is existing data" + require('os').EOL, 'utf8');
log4js.clearAppenders(); log4js.clearAppenders();
log4js.configure(options); log4js.configure(options);
logger = log4js.getLogger('tests'); logger = log4js.getLogger('tests');
@ -122,6 +123,9 @@ vows.describe('../lib/appenders/dateFile').addBatch({
}, },
'should create file with the correct pattern': function(contents) { 'should create file with the correct pattern': function(contents) {
assert.include(contents, 'this should be written to the file with the appended date'); assert.include(contents, 'this should be written to the file with the appended date');
},
'should not overwrite the file on open (bug found in issue #132)': function(contents) {
assert.include(contents, 'this is existing data');
} }
} }

View File

@ -125,6 +125,63 @@ vows.describe('DateRollingFileStream').addBatch({
} }
} }
} }
},
'with alwaysIncludePattern': {
topic: function() {
var that = this,
testTime = new Date(2012, 8, 12, 0, 10, 12),
stream = new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-pattern', '.yyyy-MM-dd', {alwaysIncludePattern: true}, now);
stream.write("First message\n", 'utf8', function() {
that.callback(null, stream);
});
},
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-pattern.2012-09-12'),
'should create a file with the pattern set': {
topic: function(stream) {
fs.readFile(__dirname + '/test-date-rolling-file-stream-pattern.2012-09-12', 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", 'utf8', this.callback);
},
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-pattern.2012-09-13'),
'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-pattern') > -1; }).length, 2);
}
},
'the file with the later date': {
topic: function() {
fs.readFile(__dirname + '/test-date-rolling-file-stream-pattern.2012-09-13', 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-pattern.2012-09-12', this.callback);
},
'should contain the first message': function(contents) {
assert.equal(contents.toString(), "First message\n");
}
}
}
} }
}).exportTo(module); }).exportTo(module);