Compare commits

...
Author SHA1 Message Date
Gareth Jones 67b19aeaf3 changed supported node versions to 0.6 and 0.8 2013-04-02 10:09:02 +11:00
Gareth Jones 50eefcc701 Merge pull request #116 from imkira/master
Pass options from multiprocess appender to inner appender
2013-02-24 21:30:42 -08:00
Mário Freitas 8e53c6213e fix: pass options from multiprocess appender to inner appender 2013-02-21 00:06:59 +09:00
Gareth Jones a15a628311 Merge pull request #115 from NicolasPelletier/master
Speed up file logging for high rate of logging.
2013-02-14 16:32:03 -08:00
Nicolas Pelletier b75e3660f4 Speed up file logging for high rate of logging.
During an evaluation of multiple loggers, I saw a slow down when trying to
quickly log more than 100,000 messages to a file:
```javascript
    counter = 150000;
    while (counter) {
        logger.info('Message[' + counter + ']');
        counter -= 1;
    }
```

My detailed test can be found here:
 - https://gist.github.com/NicolasPelletier/4773843

The test demonstrate that writing 150,000 lines straight in a FileStream
takes about 22 seconds until the file content stabilizes. When calling
logger.debug() 150,000 times, the file stabilizes to its final content
after 229s ( almost 4 minutes ! ).

After investigation, it turns out that the problem is using an Array() to
accumulate the data. Pushing the data in the Array with Array.push() is
quick, but the code flushing the buffer uses Array.shift(), which forces
re-indexing of all 149,999 elements remaining in the Array. This is
exponentially slower as the buffer grows.

The solution is to use something else than an Array to accumulate the
messages. The fix was made using a package called Dequeue
( https://github.com/lleo/node-dequeue ). By replacing the Array with
a Dequeue object, it brought the logging of 150,000 messages back down to
31s. Seven times faster than the previous 229s.

There is a caveat that each log event is slightly longer due to the need
to create an object to put in the double-ended queue inside the Dequeue
object. According to a quick test, it takes about 4% more time per call
to logger.debug().
2013-02-13 09:35:02 -05:00
Gareth Jones 22da6226e5 Merge pull request #113 from bitcloud/patternLayout_tokens
add your own tokens to the patternLayout
2013-02-11 13:45:18 -08:00
Jan Schmidle a3bdac8e14 updated require in example to match other examles 2013-02-08 16:22:29 +01:00
Jan Schmidle af428c5669 added example on pattern tokens usage 2013-02-08 16:18:27 +01:00
Jan Schmidle 5c75ba9468 fixed small issue that could occur with wrong evaluated parameters 2013-02-08 16:17:24 +01:00
Jan Schmidle bec0d05847 added some documentation to the function header 2013-02-08 16:15:51 +01:00
Jan Schmidle e4bf405f20 add your own tokens to the patternLayout 2013-02-08 14:54:18 +01:00
Gareth Jones 95568f352b Merge pull request #110 from Dignifiedquire/fix-2
Move examples into their own directory.
2013-01-20 16:15:53 -08:00
Gareth Jones 6da6f3c90e Merge pull request #109 from Dignifiedquire/fix-1
Misc code highlighting fixes in readme.md
2013-01-20 14:16:04 -08:00
Friedel Ziegelmayer 7f57d14e70 Move examples into their own directory. 2013-01-19 22:14:14 +01:00
Friedel Ziegelmayer f478793da3 Misc code highlighting fixes in readme.md 2013-01-19 22:09:31 +01:00
Gareth Jones ec2f8fec3b Merge pull request #105 from ulikoehler/readme-syntax-highlighting
Added syntax highlighting to JS code in README.md
2013-01-06 13:33:52 -08:00
Uli Köhler 0167c84ea5 Added syntax highlighting to JS code in README.md 2013-01-06 01:09:55 +01:00
Gareth Jones 3e1a27e522 New version, with colours in pattern layout 2012-12-03 09:59:36 +11:00
Gareth Jones 8b42e46071 Merge pull request #101 from Dignifiedquire/feature-color-pattern
[feature] Add patternColoured Layout.
2012-12-02 14:51:27 -08:00
Friedel Ziegelmayer 4a7a90ed53 [feature] Add color option to pattern layout.
Based on #90 this implements the possibillity to add the color codes
according to the log level via %[ and %].
2012-12-02 23:41:59 +01:00
Gareth Jones a9307fd6da fix for issue #100, multiprocess appender and logLevelFilter don't play nicely 2012-11-09 16:02:16 +11:00
Gareth Jones 4739c65c68 Version 0.5.4 2012-10-16 11:54:21 +11:00
Gareth Jones 892181f88f Merge pull request #98 from danbell/master
Check environment variable LOG4JS_CONFIG for configuration file location.
2012-10-15 17:52:08 -07:00
Daniel Bell bdfa7f9a9b Delete LOG4JS_CONFIG environment variable after test has finished. 2012-10-16 10:55:30 +11:00
Daniel Bell ad63b801f7 Check environment variable LOG4JS_CONFIG for configuration file location. 2012-10-16 08:36:26 +11:00
Gareth Jones 2bfad6362a Version 0.5.3 2012-09-26 09:49:58 +10:00
Gareth Jones 2b889fe776 Working date rolling file appender. 2012-09-25 08:16:59 +10:00
Gareth Jones 9ac61e37f4 Refactored where the exit handler gets added 2012-09-25 07:43:37 +10:00
Gareth Jones 185f343e68 Working date rolling file stream 2012-09-18 08:46:39 +10:00
Gareth Jones be1272cd7c moved streams code around, added stub for DateRollingFileStream 2012-09-05 10:58:28 +10:00
Gareth Jones cbc1dd32f9 fixed up some dodgy tabbing 2012-09-05 08:00:31 +10:00
Gareth Jones a6fb26efb1 Removed mentions of pollInterval (issue #93) 2012-09-04 13:48:35 +10:00
Gareth Jones 012b0d5ed7 version 0.5.2 2012-08-14 10:47:25 +10:00
Gareth Jones de72005e7e Fixed layout stack trace test 2012-08-14 09:44:43 +10:00
Gareth Jones c6a0e58409 Merge pull request #89 from ixti/master
Fix possible memleak with `exit` event handlers
2012-08-13 16:32:08 -07:00
Aleksey V Zapparov f832a2ba79 Do not assign multiple exit handlers for FA 2012-08-09 15:21:30 +02:00
Aleksey V Zapparov 3f10b68c30 Add test for amount of exit listeners in FA 2012-08-09 15:15:28 +02:00
Gareth Jones 54c311842c Merge pull request #86 from osher/patch-3
Update lib/layouts.js
2012-08-01 16:21:01 -07:00
osher f948b5f5cd Add unit tests - layouts-test.js 2012-08-01 10:11:37 +03:00
osher 54e420eb58 Update lib/layouts.js
Errors sometimes carry additional attributes on them as part of the passed error data.
A utility that utilizes it, for example - is called 'errs', which is in use for instance 'nano' - the couch-db driver.

when only the stack is printed - all the additional information that is augmented on the error object does not sink to the log and is lost.

consider the following code:

```
//the oups throwing utility
function oups(){
  e = new Error();
  extend(
    { message    : "Oups error"
    , description: "huston, we got a problem"
    , status     : "MESS"
    , errorCode  : 991
    , arr :[1,2,3,4,{}]
    , data: 
      { c:{}
      , d:{e:{}}
      }
    }
  throw e;
}

var log = require('log4js')

try{
  oups()
} catch( e ) {
   log.error("error on oups", e );
}

```


output before the fix

```
error on oups Error: Oups error
    at repl:1:11
    at REPLServer.eval (repl.js:80:21)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream.<anonymous> (readline.js:82:12)
    at ReadStream.emit (events.js:88:20)
```


output after the fix would be

```
error on oups { [Error: My error message]
  name: 'Error',
  description: 'huston, we got a problem',
  status: 'MESS',
  errorCode: 991,
  arr: [ 1, 2, 3, 4, {} ],
  data: { c: {}, d: { e: {} } } }
Error: Oups error
    at repl:1:11
    at REPLServer.eval (repl.js:80:21)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream.<anonymous> (readline.js:82:12)
    at ReadStream.emit (events.js:88:20)
```
2012-07-31 14:32:03 +03:00
Gareth Jones 40ba24a55d Renamed tests so that vows will pick them up automatically 2012-07-31 14:52:36 +10:00
43 changed files with 1001 additions and 421 deletions
+74 -71
View File
@@ -21,10 +21,10 @@ NOTE: from log4js 0.5 onwards you'll need to explicitly enable replacement of no
```javascript ```javascript
{ {
appenders: [ appenders: [
{ type: "console" } { type: "console" }
], ],
replaceConsole: true replaceConsole: true
} }
``` ```
@@ -36,95 +36,98 @@ npm install log4js
## usage ## usage
Minimalist version: Minimalist version:
```javascript
var log4js = require('log4js'); var log4js = require('log4js');
var logger = log4js.getLogger(); var logger = log4js.getLogger();
logger.debug("Some debug messages"); logger.debug("Some debug messages");
```
By default, log4js outputs to stdout with the coloured layout (thanks to [masylum](http://github.com/masylum)), so for the above you would see: By default, log4js outputs to stdout with the coloured layout (thanks to [masylum](http://github.com/masylum)), so for the above you would see:
```bash
[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 for a full example, but here's a snippet (also in fromreadme.js): See example.js for a full example, but here's a snippet (also in fromreadme.js):
```javascript
var log4js = require('log4js');
//console log is loaded by default, so you won't normally need to do this
//log4js.loadAppender('console');
log4js.loadAppender('file');
//log4js.addAppender(log4js.appenders.console());
log4js.addAppender(log4js.appenders.file('logs/cheese.log'), 'cheese');
var log4js = require('log4js'); var logger = log4js.getLogger('cheese');
//console log is loaded by default, so you won't normally need to do this logger.setLevel('ERROR');
//log4js.loadAppender('console');
log4js.loadAppender('file');
//log4js.addAppender(log4js.appenders.console());
log4js.addAppender(log4js.appenders.file('logs/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.');
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.');
```
Output: Output:
```bash
[2010-01-17 11:43:37.987] [ERROR] cheese - Cheese is too ripe! [2010-01-17 11:43:37.987] [ERROR] cheese - Cheese is too ripe!
[2010-01-17 11:43:37.990] [FATAL] cheese - Cheese was breeding ground for listeria. [2010-01-17 11:43:37.990] [FATAL] cheese - Cheese was breeding ground for listeria.
```
The first 5 lines of the code above could also be written as: The first 5 lines of the code above could also be written as:
```javascript
var log4js = require('log4js'); var log4js = require('log4js');
log4js.configure({ log4js.configure({
appenders: [ appenders: [
{ type: 'console' }, { type: 'console' },
{ type: 'file', filename: 'logs/cheese.log', category: 'cheese' } { type: 'file', filename: 'logs/cheese.log', category: 'cheese' }
] ]
}); });
```
## configuration ## configuration
You can configure the appenders and log levels manually (as above), or provide a You can configure the appenders and log levels manually (as above), or provide a
configuration file (`log4js.configure('path/to/file.json')`), or a configuration object. configuration file (`log4js.configure('path/to/file.json')`), or a configuration object. The
configuration file location may also be specified via the environment variable
LOG4JS_CONFIG (`export LOG4JS_CONFIG=path/to/file.json`).
An example file can be found in `test/log4js.json`. An example config file with log rolling is in `test/with-log-rolling.json`. An example file can be found in `test/log4js.json`. An example config file with log rolling is in `test/with-log-rolling.json`.
By default, the configuration file is checked for changes every 60 seconds, and if changed, reloaded. This allows changes to logging levels to occur without restarting the application. By default, the configuration file is checked for changes every 60 seconds, and if changed, reloaded. This allows changes to logging levels to occur without restarting the application.
To turn off configuration file change checking, configure with: To turn off configuration file change checking, configure with:
var log4js = require('log4js'); ```javascript
log4js.configure('my_log4js_configuration.json', {}); var log4js = require('log4js');
log4js.configure('my_log4js_configuration.json', {});
```
To specify a different period: To specify a different period:
log4js.configure('file.json', { reloadSecs: 300 }); ```javascript
log4js.configure('file.json', { reloadSecs: 300 });
```
For FileAppender you can also pass the path to the log directory as an option where all your log files would be stored. For FileAppender you can also pass the path to the log directory as an option where all your log files would be stored.
log4js.configure('my_log4js_configuration.json', { cwd: '/absolute/path/to/log/dir' }); ```javascript
log4js.configure('my_log4js_configuration.json', { cwd: '/absolute/path/to/log/dir' });
```
If you have already defined an absolute path for one of the FileAppenders in the configuration file, you could add a "absolute": true to the particular FileAppender to override the cwd option passed. Here is an example configuration file: If you have already defined an absolute path for one of the FileAppenders in the configuration file, you could add a "absolute": true to the particular FileAppender to override the cwd option passed. Here is an example configuration file:
```json
#### my_log4js_configuration.json #### #### my_log4js_configuration.json ####
{
"appenders": [
{ {
"appenders": [ "type": "file",
{ "filename": "relative/path/to/log_file.log",
"type": "file", "maxLogSize": 20480,
"filename": "relative/path/to/log_file.log", "backups": 3,
"maxLogSize": 20480, "category": "relative-logger"
"backups": 3, },
"pollInterval": 15, {
"category": "relative-logger" "type": "file",
}, "absolute": true,
{ "filename": "/absolute/path/to/log_file.log",
"type": "file", "maxLogSize": 20480,
"absolute": true, "backups": 10,
"filename": "/absolute/path/to/log_file.log", "category": "absolute-logger"
"maxLogSize": 20480,
"backups": 10,
"pollInterval": 15,
"category": "absolute-logger"
}
]
} }
]
}
```
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 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
View File
+21
View File
@@ -0,0 +1,21 @@
var log4js = require('./lib/log4js');
var config = {
"appenders": [
{
"type": "console",
"layout": {
"type": "pattern",
"pattern": "%[%r (%x{pid}) %p %c -%] %m%n",
"tokens": {
"pid" : function() { return process.pid; }
}
}
}
]
};
log4js.configure(config, {});
var logger = log4js.getLogger("app");
logger.info("Test log message");
+49
View 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;
+15 -9
View File
@@ -1,9 +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') , os = require('os')
, eol = os.EOL || '\n'; , 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.
@@ -42,10 +50,8 @@ 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) + eol, "utf8"); logFile.write(layout(loggingEvent) + eol, "utf8");
+6 -11
View File
@@ -16,20 +16,15 @@ function logServer(config) {
function deserializeLoggingEvent(clientSocket, msg) { function deserializeLoggingEvent(clientSocket, msg) {
var loggingEvent; var loggingEvent;
try { try {
loggingEvent = JSON.parse(msg); loggingEvent = JSON.parse(msg);
loggingEvent.startTime = new Date(loggingEvent.startTime); loggingEvent.startTime = new Date(loggingEvent.startTime);
loggingEvent.level.toString = function levelToString() { loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
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.
loggingEvent = { loggingEvent = {
startTime: new Date(), startTime: new Date(),
categoryName: 'log4js', categoryName: 'log4js',
level: { toString: function () { level: log4js.levels.ERROR,
return 'ERROR';
}
},
data: [ 'Unable to parse log:', msg ] data: [ 'Unable to parse log:', msg ]
}; };
} }
@@ -120,11 +115,11 @@ function createAppender(config) {
} }
} }
function configure(config) { function configure(config, options) {
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, options);
config.actualAppender = actualAppender; config.actualAppender = actualAppender;
} }
return createAppender(config); return createAppender(config);
+65 -30
View File
@@ -10,8 +10,9 @@ var dateFormat = require('./date_format')
, "coloured": function() { return colouredLayout; } , "coloured": function() { return colouredLayout; }
, "pattern": function (config) { , "pattern": function (config) {
var pattern = config.pattern || undefined; var pattern = config.pattern || undefined;
return patternLayout(pattern); var tokens = config.tokens || undefined;
} return patternLayout(pattern, tokens);
}
} }
, colours = { , colours = {
ALL: "grey" ALL: "grey"
@@ -24,7 +25,6 @@ var dateFormat = require('./date_format')
, OFF: "grey" , 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)
@@ -49,40 +49,45 @@ 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);
} }
}); });
return output; return output;
} }
var styles = {
//styles
'bold' : [1, 22],
'italic' : [3, 23],
'underline' : [4, 24],
'inverse' : [7, 27],
//grayscale
'white' : [37, 39],
'grey' : [90, 39],
'black' : [90, 39],
//colors
'blue' : [34, 39],
'cyan' : [36, 39],
'green' : [32, 39],
'magenta' : [35, 39],
'red' : [31, 39],
'yellow' : [33, 39]
};
function colorizeStart(style) {
return style ? '\033[' + styles[style][0] + 'm' : '';
}
function colorizeEnd(style) {
return style ? '\033[' + styles[style][1] + 'm' : '';
}
/** /**
* Taken from masylum's fork (https://github.com/masylum/log4js-node) * Taken from masylum's fork (https://github.com/masylum/log4js-node)
*/ */
function colorize (str, style) { function colorize (str, style) {
var styles = { return colorizeStart(style) + str + colorizeEnd(style);
//styles
'bold' : [1, 22],
'italic' : [3, 23],
'underline' : [4, 24],
'inverse' : [7, 27],
//grayscale
'white' : [37, 39],
'grey' : [90, 39],
'black' : [90, 39],
//colors
'blue' : [34, 39],
'cyan' : [36, 39],
'green' : [32, 39],
'magenta' : [35, 39],
'red' : [31, 39],
'yellow' : [33, 39]
};
return style ? '\033[' + styles[style][0] + 'm' + str +
'\033[' + styles[style][1] + 'm' : str;
} }
function timestampLevelAndCategory(loggingEvent, colour) { function timestampLevelAndCategory(loggingEvent, colour) {
@@ -135,12 +140,26 @@ function messagePassThroughLayout (loggingEvent) {
* - %d date in various formats * - %d date in various formats
* - %% % * - %% %
* - %n newline * - %n newline
* Takes a pattern string and returns a layout function. * - %x{<tokenname>} add dynamic tokens to your log. Tokens are specified in the tokens parameter
* You can use %[ and %] to define a colored block.
*
* Tokens are specified as simple key:value objects.
* The key represents the token name whereas the value can be a string or function
* which is called to extract the value to put in the log message. If token is not
* found, it doesn't replace the field.
*
* A sample token would be: { "pid" : function() { return process.pid; } }
*
* Takes a pattern string, array of tokens and returns a layout function.
* @param {String} Log format pattern String
* @param {object} map object of different tokens
* @return {Function}
* @author Stephan Strittmatter * @author Stephan Strittmatter
* @author Jan Schmidle
*/ */
function patternLayout (pattern) { function patternLayout (pattern, tokens) {
var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/; var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdmnprx%])(\{([^\}]+)\})?|([^%]+)/;
pattern = pattern || TTCC_CONVERSION_PATTERN; pattern = pattern || TTCC_CONVERSION_PATTERN;
@@ -207,9 +226,26 @@ function patternLayout (pattern) {
case "r": case "r":
replacement = "" + loggingEvent.startTime.toLocaleTimeString(); replacement = "" + loggingEvent.startTime.toLocaleTimeString();
break; break;
case "[":
replacement = colorizeStart(colours[loggingEvent.level.toString()]);
break;
case "]":
replacement = colorizeEnd(colours[loggingEvent.level.toString()]);
break;
case "%": case "%":
replacement = "%"; replacement = "%";
break; break;
case "x":
if(typeof(tokens[specifier]) !== 'undefined') {
if(typeof(tokens[specifier]) === 'function') {
replacement = tokens[specifier]();
} else {
replacement = tokens[specifier];
}
} else {
replacement = matchedString;
}
break;
default: default:
replacement = matchedString; replacement = matchedString;
break; break;
@@ -249,7 +285,6 @@ function patternLayout (pattern) {
}; };
module.exports = { module.exports = {
basicLayout: basicLayout basicLayout: basicLayout
, messagePassThroughLayout: messagePassThroughLayout , messagePassThroughLayout: messagePassThroughLayout
+2 -1
View File
@@ -240,8 +240,9 @@ function initReloadConfiguration(filename, options) {
function configure(configurationFileOrObject, options) { function configure(configurationFileOrObject, options) {
var config = configurationFileOrObject; var config = configurationFileOrObject;
config = config || process.env.LOG4JS_CONFIG;
options = options || {}; options = options || {};
if (config === undefined || config === null || typeof(config) === 'string') { if (config === undefined || config === null || typeof(config) === 'string') {
if (options.reloadSecs) { if (options.reloadSecs) {
initReloadConfiguration(config, options); initReloadConfiguration(config, options);
-268
View File
@@ -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
View 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
};
+78
View File
@@ -0,0 +1,78 @@
var events = require('events'),
Dequeue = require('dequeue'),
util = require('util');
module.exports = BufferedWriteStream;
function BufferedWriteStream(stream) {
var that = this;
this.stream = stream;
this.buffer = new Dequeue();
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
View 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
View 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
View File
@@ -0,0 +1,3 @@
exports.BufferedWriteStream = require('./BufferedWriteStream');
exports.RollingFileStream = require('./RollingFileStream');
exports.DateRollingFileStream = require('./DateRollingFileStream');
+7 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "log4js", "name": "log4js",
"version": "0.5.1", "version": "0.5.8",
"description": "Port of Log4js to work with node.", "description": "Port of Log4js to work with node.",
"keywords": [ "keywords": [
"logging", "logging",
@@ -17,16 +17,19 @@
"bugs": { "bugs": {
"url": "http://github.com/nomiddlename/log4js-node/issues" "url": "http://github.com/nomiddlename/log4js-node/issues"
}, },
"engines": [ "node >=0.6" ], "engines": {
"node": "~0.6||~0.8"
},
"scripts": { "scripts": {
"test": "vows test/*.js" "test": "vows"
}, },
"directories": { "directories": {
"test": "test", "test": "test",
"lib": "lib" "lib": "lib"
}, },
"dependencies": { "dependencies": {
"async": "0.1.15" "async": "0.1.15",
"dequeue": "1.0.3"
}, },
"devDependencies": { "devDependencies": {
"vows": "0.6.2", "vows": "0.6.2",
@@ -84,6 +84,48 @@ vows.describe('log4js configure').addBatch({
'should add appender configure function to appenderMakers': function(log4js) { 'should add appender configure function to appenderMakers': function(log4js) {
assert.isFunction(log4js.appenderMakers['some/other/external']); assert.isFunction(log4js.appenderMakers['some/other/external']);
} }
},
'when configuration file loaded via LOG4JS_CONFIG environment variable': {
topic: function() {
process.env.LOG4JS_CONFIG = 'some/path/to/mylog4js.json';
var fileRead = 0,
modulePath = 'some/path/to/mylog4js.json',
pathsChecked = [],
mtime = new Date(),
fakeFS = {
config: { appenders: [ { type: 'console', layout: { type: 'messagePassThrough' } } ],
levels: { 'a-test' : 'INFO' } },
readdirSync: function(dir) {
return require('fs').readdirSync(dir);
},
readFileSync: function (file, encoding) {
fileRead += 1;
assert.isString(file);
assert.equal(file, modulePath);
assert.equal(encoding, 'utf8');
return JSON.stringify(fakeFS.config);
},
statSync: function (path) {
pathsChecked.push(path);
if (path === modulePath) {
return { mtime: mtime };
} else {
throw new Error("no such file");
}
}
},
log4js = sandbox.require('../lib/log4js',
{
requires: {
'fs': fakeFS,
}
});
delete process.env.LOG4JS_CONFIG;
return fileRead;
},
'should load the specified local configuration file' : function(fileRead) {
assert.equal(fileRead, 1);
}
} }
} }
}).exportTo(module); }).exportTo(module);
+98
View 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() {
+71 -11
View File
@@ -4,9 +4,10 @@ assert = require('assert');
//used for patternLayout tests. //used for patternLayout tests.
function test(args, pattern, value) { function test(args, pattern, value) {
var layout = args[0] var layout = args[0]
, event = args[1]; , event = args[1]
, tokens = args[2];
assert.equal(layout(pattern)(event), value); assert.equal(layout(pattern, tokens)(event), value);
} }
vows.describe('log4js layouts').addBatch({ vows.describe('log4js layouts').addBatch({
@@ -76,8 +77,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 +149,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) {
@@ -132,8 +176,12 @@ vows.describe('log4js layouts').addBatch({
level: { level: {
toString: function() { return "DEBUG"; } toString: function() { return "DEBUG"; }
} }
}, layout = require('../lib/layouts').patternLayout; }, layout = require('../lib/layouts').patternLayout
return [layout, event]; , tokens = {
testString: 'testStringToken',
testFunction: function() { return 'testFunctionToken'; }
};
return [layout, event, tokens];
}, },
'should default to "time logLevel loggerName - message"': function(args) { 'should default to "time logLevel loggerName - message"': function(args) {
@@ -200,9 +248,21 @@ vows.describe('log4js layouts').addBatch({
test(args, '%-6p', 'DEBUG '); test(args, '%-6p', 'DEBUG ');
test(args, '%-8p', 'DEBUG '); test(args, '%-8p', 'DEBUG ');
test(args, '%-10p', 'DEBUG '); test(args, '%-10p', 'DEBUG ');
} },
'%[%r%] should output colored time': function(args) {
test(args, '%[%r%]', '\033[36m14:18:30\033[39m');
},
'%x{testString} should output the string stored in tokens': function(args) {
test(args, '%x{testString}', 'testStringToken');
},
'%x{testFunction} should output the result of the function stored in tokens': function(args) {
test(args, '%x{testFunction}', 'testFunctionToken');
},
'%x{doesNotExist} should output the string stored in tokens': function(args) {
test(args, '%x{doesNotExist}', '%x{doesNotExist}');
},
'%x should output the string stored in tokens': function(args) {
test(args, '%x', '%x');
},
} }
}).export(module); }).export(module);
+1 -2
View File
@@ -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
} }
] ]
}; };
@@ -181,14 +181,14 @@ vows.describe('Multiprocess Appender').addBatch({
}, },
'when a client connects': { 'when a client connects': {
topic: function(net) { topic: function(net) {
var logString = JSON.stringify({ level: 'DEBUG', data: ['some debug']}) + '__LOG4JS__'; var logString = JSON.stringify({ level: { level: 10000, levelStr: 'DEBUG' }, data: ['some debug']}) + '__LOG4JS__';
net.cbs['connect'](); net.cbs['connect']();
net.cbs['data'](JSON.stringify({ level: 'ERROR', data: ['an error message'] }) + '__LOG4JS__'); net.cbs['data'](JSON.stringify({ level: { level: 40000, levelStr: 'ERROR' }, data: ['an error message'] }) + '__LOG4JS__');
net.cbs['data'](logString.substring(0, 10)); net.cbs['data'](logString.substring(0, 10));
net.cbs['data'](logString.substring(10)); net.cbs['data'](logString.substring(10));
net.cbs['data'](logString + logString + logString); net.cbs['data'](logString + logString + logString);
net.cbs['end'](JSON.stringify({ level: 'FATAL', data: ["that's all folks"] }) + '__LOG4JS__'); net.cbs['end'](JSON.stringify({ level: { level: 50000, levelStr: 'FATAL' }, data: ["that's all folks"] }) + '__LOG4JS__');
net.cbs['data']('bad message__LOG4JS__'); net.cbs['data']('bad message__LOG4JS__');
return net; return net;
}, },
+123
View 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
View 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"
}
}
+2 -3
View File
@@ -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
} }
] ]
} }