Compare commits

...

120 Commits

Author SHA1 Message Date
Gareth Jones
3018a49bde 0.6.8 2013-08-22 11:52:25 +10:00
Gareth Jones
a5bb94a048 Merge pull request #152 from fb55/patch-1
Browserify support
2013-08-20 15:52:32 -07:00
Felix Böhm
7a1a895e46 browserify support 2013-08-20 18:48:27 +02:00
Gareth Jones
48dc22eb63 Merge pull request #150 from wood1986/master
layouts supports hostname and ISO8601_WITH_TZ_OFFSET_FORMAT
2013-08-20 03:50:36 -07:00
wood1986
7888381991 Update layouts.js 2013-08-18 01:43:48 +08:00
wood1986
cd286fa25f Update layouts-test.js 2013-08-18 01:39:37 +08:00
wood1986
6df4753822 Update layouts.js 2013-08-18 01:36:07 +08:00
wood1986
613474eb44 Update layouts-test.js 2013-08-15 22:45:56 +08:00
wood1986
112246dd55 Update layouts-test.js 2013-08-15 22:39:59 +08:00
wood1986
069ed31759 Update layouts-test.js 2013-08-15 22:37:01 +08:00
wood1986
9e72189574 Update date_format-test.js 2013-08-15 22:30:57 +08:00
wood1986
5a167d853a Update date_format-test.js 2013-08-15 22:29:52 +08:00
wood1986
5755faa7bb Update layouts-test.js 2013-08-15 22:29:36 +08:00
wood1986
1ed026a8d9 Update fileAppender-test.js 2013-08-14 17:35:47 +08:00
wood1986
2d177d517b Update date_format.js 2013-08-13 23:04:52 +08:00
wood1986
21aebbde33 Update layouts.js 2013-08-13 23:04:11 +08:00
Gareth Jones
49892f35d3 Merge pull request #149 from mkielar/master
Clustered appender for log4js.
2013-08-08 18:04:18 -07:00
Marcin Kielar
61beac28d3 Clustered appender for log4js.
+ lib/appenders/clustered.js
+ test/clusteredAppender-test.js

Instead os using sockets (like multiprocess) or dead and unmaintained hook.io, Clustered appender
uses process.send(message) / worker.on('message', callback) mechanisms for transporting data
between worker processes and master logger.

Master logger takes an "appenders" array of actual appenders that are triggered when worker appenders send some data.
This guarantees sequential writes to appenders, so the log messages are not mixed in single lines of log.
2013-08-09 00:04:25 +02:00
Gareth Jones
8ad1cd67e2 formatting fixes, unnecessary code removed 2013-08-05 11:40:59 +10:00
Gareth Jones
c67ab855bb Merge branch 'master' of https://github.com/nomiddlename/log4js-node 2013-08-05 11:33:23 +10:00
Gareth Jones
4905761f60 Merge pull request #119 from UniversityofWarwick/category-filter
Category excluding filter.
2013-08-04 18:25:32 -07:00
Gareth Jones
1e17f88ded 0.6.7 2013-08-02 11:38:34 +10:00
Gareth Jones
d25e1abd48 Merge pull request #142 from crisply/master
Allows use of Console Appender when using with node-webkit
2013-07-14 18:32:26 -07:00
Lex
dde2e69948 Getting console appender to work with node-webkit 2013-07-10 05:07:28 -07:00
Gareth Jones
351a912a86 simplified the reload config code a little, moved the tests into their own file, improved coverage 2013-07-09 09:24:11 +10:00
Gareth Jones
c5fd75dac3 removed check on undefined configState.filename - should not happen, and is covered by the statSync anyway 2013-07-09 08:01:41 +10:00
Gareth Jones
4dd5989d27 Merge branch 'master' of https://github.com/nomiddlename/log4js-node
Conflicts:
	test/gelfAppender-test.js
2013-07-08 15:24:29 +10:00
Gareth Jones
46721465a1 Merge pull request #140 from karlvlam/master
Add custom field support to GELF appender
2013-07-07 16:17:23 -07:00
Gareth Jones
76ff7aa5fa improved coverage of date format 2013-07-08 08:51:42 +10:00
Gareth Jones
be5fa838be improved coverage of hookio appender 2013-07-08 08:46:11 +10:00
Gareth Jones
a86bed975c improved coverage of lib/log4js.js 2013-07-08 08:18:48 +10:00
Karl Lam
baaebef2ed GELF appender - test case covers custom fields, remove unused
console.log
2013-07-05 15:28:10 +08:00
Karl Lam
837d007de3 GELF appender can add customFields to config for every message 2013-07-05 11:23:59 +08:00
Karl Lam
be754f0c0e GELF appender can add custom fields 2013-07-05 10:54:31 +08:00
Gareth Jones
946b216a79 improved coverage of rolling file stream 2013-07-05 08:36:42 +10:00
Gareth Jones
508dbdadf8 improved coverage of gelf appender 2013-07-05 08:04:16 +10:00
Gareth Jones
2e7f6e5a66 improved coverage of logger 2013-07-01 08:24:29 +10:00
Gareth Jones
cbadb5fa19 improved coverage of multiprocess appender 2013-07-01 08:24:06 +10:00
Gareth Jones
c258470cda improved coverage of file appenders 2013-06-28 08:44:54 +10:00
Gareth Jones
2b070e5470 Fixed a problem when tests run in node 0.8 2013-06-28 07:55:25 +10:00
Gareth Jones
4cd546e8b3 improved coverage of baserollingfilestream 2013-06-27 08:46:18 +10:00
Gareth Jones
0e5da1d361 moved debug fn out to own module, added tests 2013-06-24 08:51:10 +10:00
Gareth Jones
fc7f686f65 improved coverage for console appender 2013-06-18 08:47:32 +10:00
Gareth Jones
4a8f0580de improved coverage for connect-logger 2013-06-18 08:47:18 +10:00
Gareth Jones
f50fab2b86 improved coverage for connect logger 2013-06-17 16:01:22 +10:00
Gareth Jones
f1c0767ca3 improved coverage 2013-06-17 16:01:03 +10:00
Gareth Jones
652888944b improved coverage for date_format 2013-06-17 16:00:42 +10:00
Gareth Jones
efc4e36317 improved coverage for layouts 2013-06-14 08:13:16 +10:00
Gareth Jones
d2f30b473f added test to improve levels coverage 2013-06-14 07:28:55 +10:00
Gareth Jones
fa179ecba2 added a delay to dateFile test, to let the filesystem catch up 2013-06-06 08:00:34 +10:00
Gareth Jones
dd25d30228 rolled back my clever map+join, because it broke the tests 2013-06-06 07:53:22 +10:00
Gareth Jones
11fe5bde5f increased test coverage for smtp appender 2013-06-05 18:30:11 +10:00
Gareth Jones
41ddf5eea7 merged util.format branch (fixes a lint error and simplifies the code) 2013-06-05 08:52:07 +10:00
Gareth Jones
81fa9c3568 removed unnecessary argument to createNoLogCondition 2013-06-05 08:38:39 +10:00
Gareth Jones
7ca517b5ed simplified createNoLogCondition 2013-06-05 08:37:27 +10:00
Gareth Jones
6368de1094 refactored pattern layout 2013-06-05 08:02:10 +10:00
Gareth Jones
94dbd22c71 reduced complex function to smaller ones 2013-06-04 08:37:36 +10:00
Gareth Jones
0a2a6c0769 don't create functions in a loop 2013-06-04 08:32:35 +10:00
Gareth Jones
5d6f00eda4 fixed all lint errors except ones which require refactoring of code 2013-06-04 08:17:36 +10:00
Gareth Jones
f998d7e81a more linting 2013-05-30 08:45:15 +10:00
Gareth Jones
46ae1a586d more linting 2013-05-30 08:26:26 +10:00
Gareth Jones
516320c79a more linting 2013-05-30 08:26:03 +10:00
Gareth Jones
40ec9e98e4 more linting 2013-05-30 08:00:04 +10:00
Gareth Jones
cc2e94cf11 more linting 2013-05-30 07:58:09 +10:00
Gareth Jones
2de838bc76 more linting 2013-05-30 07:56:28 +10:00
Gareth Jones
87dc7cf5aa more linting 2013-05-30 07:54:42 +10:00
Gareth Jones
913c748ee0 more linting 2013-05-29 08:42:09 +10:00
Gareth Jones
def0e8e371 more linting 2013-05-29 08:35:40 +10:00
Gareth Jones
20f80ff775 more linting 2013-05-29 08:29:30 +10:00
Gareth Jones
f24db59523 more linting 2013-05-29 08:28:35 +10:00
Gareth Jones
07869b915f more linting 2013-05-27 08:17:32 +10:00
Gareth Jones
2cd27e4293 more linting 2013-05-27 08:15:57 +10:00
Gareth Jones
3d11cbc0ad more linting 2013-05-27 08:14:51 +10:00
Gareth Jones
e5dba219d1 more linting 2013-05-27 08:11:24 +10:00
Gareth Jones
9853e13429 more linting 2013-05-27 08:01:00 +10:00
Gareth Jones
4fd138f87d more linting 2013-05-27 07:48:29 +10:00
Gareth Jones
1ad4977aec more linting 2013-05-27 07:44:59 +10:00
Gareth Jones
7cb7e6df72 more linting 2013-05-27 07:41:16 +10:00
Gareth Jones
2192a094b6 more linting 2013-05-26 17:21:39 +10:00
Gareth Jones
6a9441d261 more linting 2013-05-26 17:15:10 +10:00
Gareth Jones
50b676dec5 more linting 2013-05-26 16:51:46 +10:00
Gareth Jones
8b3c036245 more linting 2013-05-26 16:41:31 +10:00
Gareth Jones
b356dec318 Getting my lint on (via bob) 2013-05-25 14:00:06 +10:00
Gareth Jones
8383dfc4f4 0.6.6 2013-05-25 13:10:46 +10:00
Gareth Jones
4e8fb26099 Missed out the smtp test 2013-05-25 13:08:43 +10:00
Gareth Jones
8492519e3b Fixing issue #137 2013-05-25 13:04:48 +10:00
Gareth Jones
fdc9d253c9 0.6.5 2013-05-16 16:57:25 +10:00
Gareth Jones
18e21ca473 Merge branch 'master' of https://github.com/nomiddlename/log4js-node 2013-05-16 16:55:47 +10:00
Gareth Jones
ab8c7ed89d Merge pull request #136 from issacg/dontalwaysrename-bug
Dontalwaysrename bug
2013-05-15 23:52:57 -07:00
Gareth Jones
aa4f7c071b Merge pull request #135 from jmav/master
auto level detection from @jmav
2013-05-15 23:52:27 -07:00
Issac Goldstand
dc632f4705 Fixes bug introduced in github issue #132 where file rolling needs to be handled differently for alwaysIncludePattern streams 2013-05-11 23:01:28 +03:00
Jure Mav
ac6284add1 Added automatic level detection to connect-logger, depends on http status response.
Update of connect logger example code, compatible with express 3.x
2013-05-11 16:17:23 +02:00
Issac Goldstand
2da01cc611 Fixes bug introduced in github issue #132 where renaming a file to itself can cause an unhandled error 2013-05-09 13:09:59 +03:00
Gareth Jones
ad8229145e Merge pull request #133 from issacg/baseFileRollingStream-bug
Fixes bug in detecting empty options (see issue #132 on github)
2013-05-08 02:24:02 -07:00
Issac Goldstand
8c12c948d9 Fixes bug in detecting empty options (see issue #132 on github) 2013-05-08 12:05:32 +03:00
Gareth Jones
af6ae7af98 new version for alwaysIncludePattern 2013-05-05 14:01:40 +10:00
Gareth Jones
936ad4da8e fixed tests broken by alwaysIncludePattern 2013-05-05 13:44:01 +10:00
Gareth Jones
097ae3d7f1 Merge branch 'alwaysIncludePattern' of https://github.com/issacg/log4js-node into isaacg-alwaysIncludePattern 2013-05-04 16:10:02 +10:00
Issac Goldstand
04de4ed8d3 fix OS-specific endline mucking test results (:-O not everyone uses linux?!?!) 2013-05-03 11:14:28 +03:00
Issac Goldstand
29b02921b6 add option alwaysIncludePattern to dateTime appender to always use the filename with the pattern included when logging 2013-05-02 14:56:33 +03:00
Gareth Jones
48ed5d1222 Removed the warning about node 0.10 2013-04-11 22:34:49 +10:00
Gareth Jones
7844b0d2e4 0.6.3 2013-04-11 22:29:13 +10:00
Gareth Jones
8b49ba9f3d added node 0.8 to travis config and package.json 2013-04-11 21:49:08 +10:00
Gareth Jones
ed7462885f backporting new streams to node 0.8 for issue #129 2013-04-11 21:45:16 +10:00
Gareth Jones
36c5175a55 0.6.2 2013-04-02 12:02:47 +11:00
Gareth Jones
22160f90b3 fixed the multiprocess tests 2013-04-02 11:59:45 +11:00
Gareth Jones
73437ecb40 Merge branch 'master' of https://github.com/dsn/log4js-node into dsn-master 2013-04-02 11:34:25 +11:00
Gareth Jones
107e33c0d1 merged in change from @vojtajina for pull request #128 2013-04-02 10:18:25 +11:00
Gareth Jones
6352632fb2 fix version of node supported 2013-04-02 10:02:48 +11:00
Gareth Jones
0544342e9f Merge pull request #128 from Dignifiedquire/master-engine
Fix node engine in package.json
2013-04-01 15:42:41 -07:00
Friedel Ziegelmayer
1d1153d32f Fix node engine in package.json 2013-04-01 23:00:26 +02:00
Gary Steven
e58cf201ca Updated for Node 0.10.x
net.createServer no longer emits 'connect' event
2013-03-30 03:23:58 -07:00
Gareth Jones
83271e47fc Merge pull request #125 from jimschubert/master
Allow for somewhat standard debugging calls
2013-03-24 19:35:24 -07:00
Jim Schubert
f3271a3997 Add standard debug conditional function
: master
2013-03-23 18:50:13 -07:00
Gareth Jones
4b7cf589a2 Fixing the wiki links (issue #124) 2013-03-20 19:47:32 +11:00
Gareth Jones
c8f401c47d fixed travis node version format 2013-03-20 14:58:56 +11:00
Nick Howes
eb21e10208 Category excluding filter.
This filtering appender allows you to choose some category
names that won't be logged to the delegated appender. This
is useful if you have e.g. a category that you use to log
web requests to one file, but want to keep those entries
out of the main log file without having to explicitly list
all the other categories that you _do_ want to include.

Has one option, "exclude", which is a category name or
array of category names. The child appender is set in
"appender", modelled on the logLevelFilter.
2013-02-26 13:27:07 +00:00
Gareth Jones
f272e3fd0a Merge branch 'master' into util.format 2013-02-25 16:43:03 +11:00
Gareth Jones
c9a890b37b added some test output files to gitignore 2013-02-12 07:23:18 +11:00
Gareth Jones
0dbc4921a3 Changed layouts to use util.format instead of my own implementation 2013-01-11 15:35:00 +11:00
56 changed files with 5998 additions and 3654 deletions

12
.bob.json Normal file
View File

@@ -0,0 +1,12 @@
{
"build": "clean lint coverage test",
"lint": {
"type": "jshint"
},
"coverage": {
"type": "vows"
},
"test": {
"type": "vows"
}
}

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@
*.log??
build
node_modules
.bob/
test/streams/test-rolling-file-stream*

15
.jshintrc Normal file
View File

@@ -0,0 +1,15 @@
{
"node": true,
"laxcomma": true,
"indent": 2,
"globalstrict": true,
"maxparams": 5,
"maxdepth": 3,
"maxstatements": 20,
"maxcomplexity": 5,
"maxlen": 100,
"globals": {
"describe": true,
"it": true
}
}

View File

@@ -1,3 +1,5 @@
language: node_js
node_js:
- 0.10
- "0.10"
- "0.8"

View File

@@ -17,8 +17,6 @@ Out of the box it supports the following features:
* 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.)
NOTE: version 0.6.0 onwards will only work with node v0.10.x upwards, since it makes use of the new streams API. If you're using node 0.8 or lower, use log4js@0.5.7.
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
@@ -130,13 +128,13 @@ If you have already defined an absolute path for one of the FileAppenders in the
]
}
```
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](https://github.com/nomiddlename/log4js-node/wiki/Appenders), otherwise take a look at the tests and the examples.
## Documentation
See the [wiki](log4js-node/wiki). Improve the [wiki](log4js-node/wiki), please.
See the [wiki](https://github.com/nomiddlename/log4js-node/wiki). Improve the [wiki](https://github.com/nomiddlename/log4js-node/wiki), please.
## Contributing
Contributions welcome, but take a look at the [rules](log4js-node/wiki/Contributing) first.
Contributions welcome, but take a look at the [rules](https://github.com/nomiddlename/log4js-node/wiki/Contributing) first.
## License

View File

@@ -1,14 +1,46 @@
var log4js = require('./lib/log4js');
log4js.addAppender(log4js.fileAppender('cheese.log'), 'cheese');
//The connect/express logger was added to log4js by danbell. This allows connect/express servers to log using log4js.
//https://github.com/nomiddlename/log4js-node/wiki/Connect-Logger
var logger = log4js.getLogger('cheese');
logger.setLevel('INFO');
// load modules
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.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);
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');

43
examples/smtp-appender.js Normal file
View File

@@ -0,0 +1,43 @@
//Note that smtp appender needs nodemailer to work.
//If you haven't got nodemailer installed, you'll get cryptic
//"cannot find module" errors when using the smtp appender
var log4js = require('../lib/log4js')
, log
, logmailer
, i = 0;
log4js.configure({
"appenders": [
{
type: "console",
category: "test"
},
{
"type": "smtp",
"recipients": "logfilerecipient@logging.com",
"sendInterval": 5,
"transport": "SMTP",
"SMTP": {
"host": "smtp.gmail.com",
"secureConnection": true,
"port": 465,
"auth": {
"user": "someone@gmail",
"pass": "********************"
},
"debug": true
},
"category": "mailer"
}
]
});
log = log4js.getLogger("test");
logmailer = log4js.getLogger("mailer");
function doTheLogging(x) {
log.info("Logging something %d", x);
logmailer.info("Logging something %d", x);
}
for ( ; i < 500; i++) {
doTheLogging(i);
}

View File

@@ -0,0 +1,20 @@
"use strict";
var log4js = require('../log4js');
function categoryFilter (excludes, appender) {
if (typeof(excludes) === 'string') excludes = [excludes];
return function(logEvent) {
if (excludes.indexOf(logEvent.categoryName) === -1) {
appender(logEvent);
}
};
}
function configure(config) {
log4js.loadAppender(config.appender.type);
var appender = log4js.appenderMakers[config.appender.type](config.appender);
return categoryFilter(config.exclude, appender);
}
exports.appender = categoryFilter;
exports.configure = configure;

118
lib/appenders/clustered.js Executable file
View File

@@ -0,0 +1,118 @@
"use strict";
var cluster = require('cluster');
var log4js = require('../log4js');
/**
* Takes a loggingEvent object, returns string representation of it.
*/
function serializeLoggingEvent(loggingEvent) {
return JSON.stringify(loggingEvent);
}
/**
* Takes a string, returns an object with
* the correct log properties.
*
* This method has been "borrowed" from the `multiprocess` appender
* by `nomiddlename` (https://github.com/nomiddlename/log4js-node/blob/master/lib/appenders/multiprocess.js)
*
* Apparently, node.js serializes everything to strings when using `process.send()`,
* so we need smart deserialization that will recreate log date and level for further processing by log4js internals.
*/
function deserializeLoggingEvent(loggingEventString) {
var loggingEvent;
try {
loggingEvent = JSON.parse(loggingEventString);
loggingEvent.startTime = new Date(loggingEvent.startTime);
loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
} catch (e) {
// JSON.parse failed, just log the contents probably a naughty.
loggingEvent = {
startTime: new Date(),
categoryName: 'log4js',
level: log4js.levels.ERROR,
data: [ 'Unable to parse log:', loggingEventString ]
};
}
return loggingEvent;
}
/**
* Creates an appender.
*
* If the current process is a master (`cluster.isMaster`), then this will be a "master appender".
* Otherwise this will be a worker appender, that just sends loggingEvents to the master process.
*
* If you are using this method directly, make sure to provide it with `config.actualAppenders` array
* of actual appender instances.
*
* Or better use `configure(config, options)`
*/
function createAppender(config) {
if (cluster.isMaster) {
var masterAppender = function(loggingEvent) {
if (config.actualAppenders) {
var size = config.actualAppenders.length;
for(var i = 0; i < size; i++) {
config.actualAppenders[i](loggingEvent);
}
}
}
// Listen on new workers
cluster.on('fork', function(worker) {
worker.on('message', function(message) {
if (message.type && message.type === '::log-message') {
// console.log("master : " + cluster.isMaster + " received message: " + JSON.stringify(message.event));
var loggingEvent = deserializeLoggingEvent(message.event);
masterAppender(loggingEvent);
}
});
});
return masterAppender;
} else {
return function(loggingEvent) {
// If inside the worker process, then send the logger event to master.
if (cluster.isWorker) {
// console.log("worker " + cluster.worker.id + " is sending message");
process.send({ type: '::log-message', event: serializeLoggingEvent(loggingEvent)});
}
}
}
}
function configure(config, options) {
if (config.appenders && cluster.isMaster) {
var size = config.appenders.length;
config.actualAppenders = new Array(size);
for(var i = 0; i < size; i++) {
log4js.loadAppender(config.appenders[i].type);
config.actualAppenders[i] = log4js.appenderMakers[config.appenders[i].type](config.appenders[i], options);
}
}
return createAppender(config);
}
exports.appender = createAppender;
exports.configure = configure;

View File

@@ -1,19 +1,20 @@
var layouts = require('../layouts'),
consoleLog = console.log;
"use strict";
var layouts = require('../layouts')
, consoleLog = console.log.bind(console);
function consoleAppender (layout) {
layout = layout || layouts.colouredLayout;
return function(loggingEvent) {
consoleLog(layout(loggingEvent));
};
layout = layout || layouts.colouredLayout;
return function(loggingEvent) {
consoleLog(layout(loggingEvent));
};
}
function configure(config) {
var layout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return consoleAppender(layout);
var layout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return consoleAppender(layout);
}
exports.appender = consoleAppender;

View File

@@ -1,15 +1,16 @@
var streams = require('../streams'),
layouts = require('../layouts'),
path = require('path'),
os = require('os'),
eol = os.EOL || '\n',
openFiles = [];
"use strict";
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();
});
openFiles.forEach(function (file) {
file.end();
});
});
/**
@@ -19,30 +20,38 @@ process.on('exit', function() {
* 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;
function appender(filename, pattern, alwaysIncludePattern, layout) {
layout = layout || layouts.basicLayout;
var logFile = new streams.DateRollingFileStream(filename, pattern);
openFiles.push(logFile);
return function(logEvent) {
logFile.write(layout(logEvent) + eol, "utf8");
};
var logFile = new streams.DateRollingFileStream(
filename,
pattern,
{ alwaysIncludePattern: alwaysIncludePattern }
);
openFiles.push(logFile);
return function(logEvent) {
logFile.write(layout(logEvent) + eol, "utf8");
};
}
function configure(config, options) {
var layout;
var layout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
if (!config.alwaysIncludePattern) {
config.alwaysIncludePattern = false;
}
if (options && options.cwd && !config.absolute) {
config.filename = path.join(options.cwd, config.filename);
}
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);
return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout);
}
exports.appender = appender;

View File

@@ -1,25 +1,29 @@
"use strict";
var layouts = require('../layouts')
, path = require('path')
, fs = require('fs')
, streams = require('../streams')
, os = require('os')
, eol = os.EOL || '\n'
, openFiles = [];
, path = require('path')
, fs = require('fs')
, 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();
});
openFiles.forEach(function (file) {
file.end();
});
});
/**
* File Appender writing the logs to a text file. Supports rolling of logs by size.
*
* @param file file log messages will be written to
* @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
* @param logSize - the maximum size (in bytes) for a log file, if not provided then logs won't be rotated.
* @param numBackups - the number of log files to keep after logSize has been reached (default 5)
* @param layout a function that takes a logevent and returns a string
* (defaults to basicLayout).
* @param logSize - the maximum size (in bytes) for a log file,
* if not provided then logs won't be rotated.
* @param numBackups - the number of log files to keep after logSize
* has been reached (default 5)
*/
function fileAppender (file, layout, logSize, numBackups) {
var bytesWritten = 0;
@@ -38,7 +42,12 @@ function fileAppender (file, layout, logSize, numBackups) {
numFiles
);
} else {
stream = fs.createWriteStream(file, { encoding: "utf8", mode: 0644, flags: 'a' });
stream = fs.createWriteStream(
file,
{ encoding: "utf8",
mode: parseInt('0644', 8),
flags: 'a' }
);
}
stream.on("error", function (err) {
console.error("log4js.fileAppender - Writing to file %s, error happened ", file, err);

View File

@@ -1,8 +1,10 @@
"use strict";
var zlib = require('zlib');
var layouts = require('../layouts');
var levels = require('../levels');
var dgram = require('dgram');
var util = require('util');
var debug = require('../debug')('GELF Appender');
var LOG_EMERG=0; // system is unusable
var LOG_ALERT=1; // action must be taken immediately
@@ -33,60 +35,106 @@ levelMapping[levels.FATAL] = LOG_CRIT;
* @param facility - facility to log to (default:nodejs-server)
*/
function gelfAppender (layout, host, port, hostname, facility) {
var config, customFields;
if (typeof(host) === 'object') {
config = host;
host = config.host;
port = config.port;
hostname = config.hostname;
facility = config.facility;
customFields = config.customFields;
}
host = host || 'localhost';
port = port || 12201;
hostname = hostname || require('os').hostname();
facility = facility || 'nodejs-server';
layout = layout || layouts.messagePassThroughLayout;
host = host || 'localhost';
port = port || 12201;
hostname = hostname || require('os').hostname();
facility = facility || 'nodejs-server';
layout = layout || layouts.messagePassThroughLayout;
var defaultCustomFields = customFields || {};
var client = dgram.createSocket("udp4");
process.on('exit', function() {
if (client) client.close();
});
var client = dgram.createSocket("udp4");
/**
* Add custom fields (start with underscore )
* - if the first object passed to the logger contains 'GELF' field,
* copy the underscore fields to the message
* @param loggingEvent
* @param msg
*/
function addCustomFields(loggingEvent, msg){
process.on('exit', function() {
if (client) client.close();
/* append defaultCustomFields firsts */
Object.keys(defaultCustomFields).forEach(function(key) {
// skip _id field for graylog2, skip keys not starts with UNDERSCORE
if (key.match(/^_/) && key !== "_id") {
msg[key] = defaultCustomFields[key];
}
});
function preparePacket(loggingEvent) {
var msg = {};
msg.full_message = layout(loggingEvent);
msg.short_message = msg.full_message;
/* append custom fields per message */
var data = loggingEvent.data;
if (!Array.isArray(data) || data.length === 0) return;
var firstData = data[0];
if (!firstData.GELF) return; // identify with GELF field defined
Object.keys(firstData).forEach(function(key) {
// skip _id field for graylog2, skip keys not starts with UNDERSCORE
if (key.match(/^_/) || key !== "_id") {
msg[key] = firstData[key];
}
});
/* the custom field object should be removed, so it will not be looged by the later appenders */
loggingEvent.data.shift();
}
function preparePacket(loggingEvent) {
var msg = {};
addCustomFields(loggingEvent, msg);
msg.full_message = layout(loggingEvent);
msg.short_message = msg.full_message;
msg.version="1.0";
msg.timestamp = msg.timestamp || new Date().getTime() / 1000 >> 0;
msg.host = hostname;
msg.level = levelMapping[loggingEvent.level || levels.DEBUG];
msg.facility = facility;
return msg;
}
function sendPacket(packet) {
try {
client.send(packet, 0, packet.length, port, host);
} catch(e) {}
}
msg.version="1.0";
msg.timestamp = msg.timestamp || new Date().getTime() / 1000 >> 0;
msg.host = hostname;
msg.level = levelMapping[loggingEvent.level || levels.DEBUG];
msg.facility = facility;
return msg;
}
function sendPacket(packet) {
try {
client.send(packet, 0, packet.length, port, host);
} catch(e) {}
}
return function(loggingEvent) {
var message = preparePacket(loggingEvent);
zlib.gzip(new Buffer(JSON.stringify(message)), function(err, packet) {
if (err) {
console.error(err.stack);
} else {
if (packet.length > 8192) {
util.debug("Message packet length (" + packet.length + ") is larger than 8k. Not sending");
} else {
sendPacket(packet);
}
}
});
};
return function(loggingEvent) {
var message = preparePacket(loggingEvent);
zlib.gzip(new Buffer(JSON.stringify(message)), function(err, packet) {
if (err) {
console.error(err.stack);
} else {
if (packet.length > 8192) {
debug("Message packet length (" + packet.length + ") is larger than 8k. Not sending");
} else {
sendPacket(packet);
}
}
});
};
}
function configure(config) {
var layout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return gelfAppender(layout, config.host, config.port, config.hostname, config.facility);
var layout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return gelfAppender(layout, config);
}
exports.appender = gelfAppender;

View File

@@ -1,7 +1,8 @@
var log4js = require('../log4js');
var layouts = require('../layouts');
var Hook = require('hook.io').Hook;
var util = require('util');
"use strict";
var log4js = require('../log4js')
, layouts = require('../layouts')
, Hook = require('hook.io').Hook
, util = require('util');
var Logger = function createLogger(options) {
var self = this;
@@ -13,7 +14,7 @@ var Logger = function createLogger(options) {
actualAppender(loggingEvent);
});
});
}
};
util.inherits(Logger, Hook);
function deserializeLoggingEvent(loggingEvent) {
@@ -42,7 +43,7 @@ function getBufferedHook(hook, eventName) {
hook.on('hook::ready', function emptyBuffer() {
hookBuffer.forEach(function logBufferItem(loggingEvent) {
hook.emit(eventName, loggingEvent);
})
});
hookReady = true;
});
@@ -52,7 +53,7 @@ function getBufferedHook(hook, eventName) {
} else {
hookBuffer.push(loggingEvent);
}
}
};
}
function createAppender(hookioOptions) {

View File

@@ -1,19 +1,20 @@
var levels = require('../levels');
var log4js = require('../log4js');
"use strict";
var levels = require('../levels')
, log4js = require('../log4js');
function logLevelFilter (levelString, appender) {
var level = levels.toLevel(levelString);
return function(logEvent) {
if (logEvent.level.isGreaterThanOrEqualTo(level)) {
appender(logEvent);
}
var level = levels.toLevel(levelString);
return function(logEvent) {
if (logEvent.level.isGreaterThanOrEqualTo(level)) {
appender(logEvent);
}
};
}
function configure(config) {
log4js.loadAppender(config.appender.type);
var appender = log4js.appenderMakers[config.appender.type](config.appender);
return logLevelFilter(config.level, appender);
log4js.loadAppender(config.appender.type);
var appender = log4js.appenderMakers[config.appender.type](config.appender);
return logLevelFilter(config.level, appender);
}
exports.appender = logLevelFilter;

View File

@@ -1,6 +1,7 @@
var log4js = require('../log4js'),
net = require('net'),
END_MSG = '__LOG4JS__';
"use strict";
var log4js = require('../log4js')
, net = require('net')
, END_MSG = '__LOG4JS__';
/**
* Creates a server, listening on config.loggerPort, config.loggerHost.
@@ -8,121 +9,120 @@ var log4js = require('../log4js'),
* set up that appender).
*/
function logServer(config) {
/**
* Takes a utf-8 string, returns an object with
* the correct log properties.
*/
function deserializeLoggingEvent(clientSocket, msg) {
var loggingEvent;
try {
loggingEvent = JSON.parse(msg);
loggingEvent.startTime = new Date(loggingEvent.startTime);
loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
} catch (e) {
// JSON.parse failed, just log the contents probably a naughty.
loggingEvent = {
startTime: new Date(),
categoryName: 'log4js',
level: log4js.levels.ERROR,
data: [ 'Unable to parse log:', msg ]
};
}
loggingEvent.remoteAddress = clientSocket.remoteAddress;
loggingEvent.remotePort = clientSocket.remotePort;
return loggingEvent;
/**
* Takes a utf-8 string, returns an object with
* the correct log properties.
*/
function deserializeLoggingEvent(clientSocket, msg) {
var loggingEvent;
try {
loggingEvent = JSON.parse(msg);
loggingEvent.startTime = new Date(loggingEvent.startTime);
loggingEvent.level = log4js.levels.toLevel(loggingEvent.level.levelStr);
} catch (e) {
// JSON.parse failed, just log the contents probably a naughty.
loggingEvent = {
startTime: new Date(),
categoryName: 'log4js',
level: log4js.levels.ERROR,
data: [ 'Unable to parse log:', msg ]
};
}
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;
loggingEvent.remoteAddress = clientSocket.remoteAddress;
loggingEvent.remotePort = clientSocket.remotePort;
return loggingEvent;
}
var actualAppender = config.actualAppender,
server = net.createServer(function serverCreated(clientSocket) {
clientSocket.setEncoding('utf8');
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) {
var canWrite = false,
buffer = [],
socket;
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);
var canWrite = false,
buffer = [],
socket;
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 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 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) {
if (config.mode === 'master') {
return logServer(config);
} else {
return workerAppender(config);
}
if (config.mode === 'master') {
return logServer(config);
} else {
return workerAppender(config);
}
}
function configure(config, options) {
var actualAppender;
if (config.appender && config.mode === 'master') {
log4js.loadAppender(config.appender.type);
actualAppender = log4js.appenderMakers[config.appender.type](config.appender, options);
config.actualAppender = actualAppender;
}
return createAppender(config);
var actualAppender;
if (config.appender && config.mode === 'master') {
log4js.loadAppender(config.appender.type);
actualAppender = log4js.appenderMakers[config.appender.type](config.appender, options);
config.actualAppender = actualAppender;
}
return createAppender(config);
}
exports.appender = createAppender;

View File

@@ -1,14 +1,17 @@
var layouts = require("../layouts"),
mailer = require("nodemailer"),
os = require('os');
"use strict";
var layouts = require("../layouts")
, mailer = require("nodemailer")
, os = require('os');
/**
* 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 config appender configuration data
* config.sendInterval time between log emails (in seconds), if 0
* then every event sends an email
* @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
* all events are buffered and sent in one email during this time; if 0 than every event sends an email
*/
function smtpAppender(config, layout) {
layout = layout || layouts.basicLayout;
@@ -17,47 +20,51 @@ function smtpAppender(config, layout) {
var logEventBuffer = [];
var sendTimer;
var transport = mailer.createTransport(config.transport, config[config.transport]);
function sendBuffer() {
if (logEventBuffer.length == 0)
return;
if (logEventBuffer.length > 0) {
var firstEvent = logEventBuffer[0];
var body = "";
while (logEventBuffer.length > 0) {
body += layout(logEventBuffer.shift()) + "\n";
}
var msg = {
to: config.recipients,
subject: config.subject || subjectLayout(firstEvent),
text: body,
headers: {"Hostname": os.hostname()}
};
if (config.sender)
msg.from = config.sender;
transport.sendMail(msg, function(error, success) {
if (error) {
console.error("log4js.smtpAppender - Error happened ", error);
}
});
var transport = mailer.createTransport(config.transport, config[config.transport]);
var firstEvent = logEventBuffer[0];
var body = "";
while (logEventBuffer.length > 0) {
body += layout(logEventBuffer.shift()) + "\n";
}
var msg = {
to: config.recipients,
subject: config.subject || subjectLayout(firstEvent),
text: body,
headers: { "Hostname": os.hostname() }
};
if (config.sender) {
msg.from = config.sender;
}
transport.sendMail(msg, function(error, success) {
if (error) {
console.error("log4js.smtpAppender - Error happened", error);
}
transport.close();
});
}
}
function scheduleSend() {
if (!sendTimer)
if (!sendTimer) {
sendTimer = setTimeout(function() {
sendTimer = null;
sendBuffer();
}, sendInterval);
}
}
return function(loggingEvent) {
logEventBuffer.push(loggingEvent);
if (sendInterval > 0)
if (sendInterval > 0) {
scheduleSend();
else
} else {
sendBuffer();
}
};
}

View File

@@ -1,11 +1,16 @@
"use strict";
var levels = require("./levels");
var DEFAULT_FORMAT = ':remote-addr - -' +
' ":method :url HTTP/:http-version"' +
' :status :content-length ":referrer"' +
' ":user-agent"';
/**
* Log requests with the given `options` or a `format` string.
*
* Options:
*
* - `format` Format string, see below for tokens
* - `level` A log4js levels instance.
* - `level` A log4js levels instance. Supports also 'auto'
*
* Tokens:
*
@@ -27,63 +32,72 @@ var levels = require("./levels");
*/
function getLogger(logger4js, options) {
if ('object' == typeof options) {
options = options || {};
} else if (options) {
options = { format: options };
} else {
options = {};
}
var thislogger = logger4js
, level = levels.toLevel(options.level, levels.INFO)
, fmt = options.format || ':remote-addr - - ":method :url HTTP/:http-version" :status :content-length ":referrer" ":user-agent"'
, nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
return function (req, res, next) {
// mount safety
if (req._logging) return next();
// nologs
if (nolog && nolog.test(req.originalUrl)) return next();
if (thislogger.isLevelEnabled(level)) {
var start = +new Date
, statusCode
, writeHead = res.writeHead
, end = res.end
, url = req.originalUrl;
// flag as logging
req._logging = true;
// proxy for statusCode.
res.writeHead = function(code, headers){
res.writeHead = writeHead;
res.writeHead(code, headers);
res.__statusCode = statusCode = code;
res.__headers = headers || {};
};
// proxy end to output a line to the provided logger.
res.end = function(chunk, encoding) {
res.end = end;
res.end(chunk, encoding);
res.responseTime = +new Date - start;
if ('function' == typeof fmt) {
var line = fmt(req, res, function(str){ return format(str, req, res); });
if (line) thislogger.log(level, line);
} else {
thislogger.log(level, format(fmt, req, res));
}
};
if ('object' == typeof options) {
options = options || {};
} else if (options) {
options = { format: options };
} else {
options = {};
}
//ensure next gets always called
next();
};
var thislogger = logger4js
, level = levels.toLevel(options.level, levels.INFO)
, fmt = options.format || DEFAULT_FORMAT
, nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
return function (req, res, next) {
// mount safety
if (req._logging) return next();
// nologs
if (nolog && nolog.test(req.originalUrl)) return next();
if (thislogger.isLevelEnabled(level) || options.level === 'auto') {
var start = new Date()
, statusCode
, writeHead = res.writeHead
, end = res.end
, url = req.originalUrl;
// flag as logging
req._logging = true;
// proxy for statusCode.
res.writeHead = function(code, headers){
res.writeHead = writeHead;
res.writeHead(code, headers);
res.__statusCode = statusCode = code;
res.__headers = headers || {};
//status code response level handling
if(options.level === 'auto'){
level = levels.INFO;
if(code >= 300) level = levels.WARN;
if(code >= 400) level = levels.ERROR;
} else {
level = levels.toLevel(options.level, levels.INFO);
}
};
// proxy end to output a line to the provided logger.
res.end = function(chunk, encoding) {
res.end = end;
res.end(chunk, encoding);
res.responseTime = new Date() - start;
if (thislogger.isLevelEnabled(level)) {
if (typeof fmt === 'function') {
var line = fmt(req, res, function(str){ return format(str, req, res); });
if (line) thislogger.log(level, line);
} else {
thislogger.log(level, format(fmt, req, res));
}
}
};
}
//ensure next gets always called
next();
};
}
/**
@@ -97,23 +111,32 @@ function getLogger(logger4js, options) {
*/
function format(str, req, res) {
return str
.replace(':url', req.originalUrl)
.replace(':method', req.method)
.replace(':status', res.__statusCode || res.statusCode)
.replace(':response-time', res.responseTime)
.replace(':date', new Date().toUTCString())
.replace(':referrer', req.headers['referer'] || req.headers['referrer'] || '')
.replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor)
.replace(':remote-addr', req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)))
.replace(':user-agent', req.headers['user-agent'] || '')
.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(/:res\[([^\]]+)\]/g, function(_, field){
return res._headers
? (res._headers[field.toLowerCase()] || res.__headers[field])
: (res.__headers && res.__headers[field]);
});
return str
.replace(':url', req.originalUrl)
.replace(':method', req.method)
.replace(':status', res.__statusCode || res.statusCode)
.replace(':response-time', res.responseTime)
.replace(':date', new Date().toUTCString())
.replace(':referrer', req.headers.referer || req.headers.referrer || '')
.replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor)
.replace(
':remote-addr',
req.socket &&
(req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress))
)
.replace(':user-agent', req.headers['user-agent'] || '')
.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(/:res\[([^\]]+)\]/g, function(_, field){
return res._headers ?
(res._headers[field.toLowerCase()] || res.__headers[field])
: (res.__headers && res.__headers[field]);
});
}
/**
@@ -122,17 +145,17 @@ function format(str, req, res) {
* @param {String} nolog
* @return {RegExp}
* @api private
*/
/**
*
* syntax
* 1. String
* 1.1 "\\.gif"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga
* LOGGING http://example.com/hoge.agif
* 1.2 in "\\.gif|\\.jpg$"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga and http://example.com/hoge.jpg?fuga
* LOGGING http://example.com/hoge.agif, http://example.com/hoge.ajpg and http://example.com/hoge.jpg?hoge
* NOT LOGGING http://example.com/hoge.gif and
* http://example.com/hoge.gif?fuga and http://example.com/hoge.jpg?fuga
* LOGGING http://example.com/hoge.agif,
* http://example.com/hoge.ajpg and http://example.com/hoge.jpg?hoge
* 1.3 in "\\.(gif|jpe?g|png)$"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.jpeg
* LOGGING http://example.com/hoge.gif?uid=2 and http://example.com/hoge.jpg?pid=3
@@ -143,26 +166,29 @@ function format(str, req, res) {
* 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
* SAME AS "\\.jpg|\\.png|\\.gif"
*/
function createNoLogCondition(nolog, type) {
if(!nolog) return null;
type = type || '';
function createNoLogCondition(nolog) {
var regexp = null;
if(nolog instanceof RegExp){
if(type === 'string')
return nolog.source;
return nolog;
} else if(typeof nolog === 'string'){
if(type === 'string')
return nolog;
try{
return new RegExp(nolog);
} catch (ex) {
return null;
if (nolog) {
if (nolog instanceof RegExp) {
regexp = nolog;
}
if (typeof nolog === 'string') {
regexp = new RegExp(nolog);
}
if (Array.isArray(nolog)) {
var regexpsAsStrings = nolog.map(
function convertToStrings(o) {
return o.source ? o.source : o;
}
);
regexp = new RegExp(regexpsAsStrings.join('|'));
}
} else if(nolog instanceof Array){
var regexps = nolog.map(function(o){ return createNoLogCondition(o, 'string')});
return new RegExp(regexps.join('|'));
}
return regexp;
}
exports.connectLogger = getLogger;

View File

@@ -1,60 +1,66 @@
"use strict";
exports.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS";
exports.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO";
exports.DATETIME_FORMAT = "dd MM yyyy hh:mm:ss.SSS";
exports.ABSOLUTETIME_FORMAT = "hh:mm:ss.SSS";
function padWithZeros(vNumber, width) {
var numAsString = vNumber + "";
while (numAsString.length < width) {
numAsString = "0" + numAsString;
}
return numAsString;
}
function addZero(vNumber) {
return padWithZeros(vNumber, 2);
}
/**
* Formats the TimeOffest
* Thanks to http://www.svendtofte.com/code/date_format/
* @private
*/
function offset(date) {
// Difference to Greenwich time (GMT) in hours
var os = Math.abs(date.getTimezoneOffset());
var h = String(Math.floor(os/60));
var m = String(os%60);
if (h.length == 1) {
h = "0" + h;
}
if (m.length == 1) {
m = "0" + m;
}
return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
}
exports.asString = function(/*format,*/ date) {
var format = exports.ISO8601_FORMAT;
if (typeof(date) === "string") {
format = arguments[0];
date = arguments[1];
}
var format = exports.ISO8601_FORMAT;
if (typeof(date) === "string") {
format = arguments[0];
date = arguments[1];
}
var vDay = addZero(date.getDate());
var vMonth = addZero(date.getMonth()+1);
var vYearLong = addZero(date.getFullYear());
var vYearShort = addZero(date.getFullYear().toString().substring(3,4));
var vYear = (format.indexOf("yyyy") > -1 ? vYearLong : vYearShort);
var vHour = addZero(date.getHours());
var vMinute = addZero(date.getMinutes());
var vSecond = addZero(date.getSeconds());
var vMillisecond = padWithZeros(date.getMilliseconds(), 3);
var vTimeZone = offset(date);
var formatted = format
.replace(/dd/g, vDay)
.replace(/MM/g, vMonth)
.replace(/y{1,4}/g, vYear)
.replace(/hh/g, vHour)
.replace(/mm/g, vMinute)
.replace(/ss/g, vSecond)
.replace(/SSS/g, vMillisecond)
.replace(/O/g, vTimeZone);
return formatted;
var vDay = addZero(date.getDate());
var vMonth = addZero(date.getMonth()+1);
var vYearLong = addZero(date.getFullYear());
var vYearShort = addZero(date.getFullYear().toString().substring(2,4));
var vYear = (format.indexOf("yyyy") > -1 ? vYearLong : vYearShort);
var vHour = addZero(date.getHours());
var vMinute = addZero(date.getMinutes());
var vSecond = addZero(date.getSeconds());
var vMillisecond = padWithZeros(date.getMilliseconds(), 3);
var vTimeZone = offset(date);
var formatted = format
.replace(/dd/g, vDay)
.replace(/MM/g, vMonth)
.replace(/y{1,4}/g, vYear)
.replace(/hh/g, vHour)
.replace(/mm/g, vMinute)
.replace(/ss/g, vSecond)
.replace(/SSS/g, vMillisecond)
.replace(/O/g, vTimeZone);
return formatted;
function padWithZeros(vNumber, width) {
var numAsString = vNumber + "";
while (numAsString.length < width) {
numAsString = "0" + numAsString;
}
return numAsString;
}
function addZero(vNumber) {
return padWithZeros(vNumber, 2);
}
/**
* Formats the TimeOffest
* Thanks to http://www.svendtofte.com/code/date_format/
* @private
*/
function offset(date) {
// Difference to Greenwich time (GMT) in hours
var os = Math.abs(date.getTimezoneOffset());
var h = String(Math.floor(os/60));
var m = String(os%60);
h.length == 1? h = "0"+h:1;
m.length == 1? m = "0"+m:1;
return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
}
};

15
lib/debug.js Normal file
View File

@@ -0,0 +1,15 @@
"use strict";
module.exports = function(label) {
var debug;
if (process.env.NODE_DEBUG && /\blog4js\b/.test(process.env.NODE_DEBUG)) {
debug = function(message) {
console.error('LOG4JS: (%s) %s', label, message);
};
} else {
debug = function() { };
}
return debug;
};

View File

@@ -1,106 +1,87 @@
"use strict";
var dateFormat = require('./date_format')
, os = require('os')
, eol = os.EOL || '\n'
, util = require('util')
, replacementRegExp = /%[sdj]/g
, layoutMakers = {
"messagePassThrough": function() { return messagePassThroughLayout; }
, "basic": function() { return basicLayout; }
, "colored": function() { return colouredLayout; }
, "coloured": function() { return colouredLayout; }
, "pattern": function (config) {
var pattern = config.pattern || undefined;
var tokens = config.tokens || undefined;
return patternLayout(pattern, tokens);
, os = require('os')
, eol = os.EOL || '\n'
, util = require('util')
, replacementRegExp = /%[sdj]/g
, layoutMakers = {
"messagePassThrough": function() { return messagePassThroughLayout; },
"basic": function() { return basicLayout; },
"colored": function() { return colouredLayout; },
"coloured": function() { return colouredLayout; },
"pattern": function (config) {
return patternLayout(config && config.pattern, config && config.tokens);
}
}
, colours = {
ALL: "grey"
, TRACE: "blue"
, DEBUG: "cyan"
, INFO: "green"
, WARN: "yellow"
, ERROR: "red"
, FATAL: "magenta"
, OFF: "grey"
};
}
, colours = {
ALL: "grey",
TRACE: "blue",
DEBUG: "cyan",
INFO: "green",
WARN: "yellow",
ERROR: "red",
FATAL: "magenta",
OFF: "grey"
};
function wrapErrorsWithInspect(items) {
return items.map(function(item) {
if ((item instanceof Error) && item.stack) {
return { inspect: function() { return util.format(item) + '\n' + item.stack; } };
} else {
return item;
}
});
}
function formatLogData(logData) {
var output = ""
, data = Array.isArray(logData) ? logData.slice() : Array.prototype.slice.call(arguments)
, format = data.shift();
if (typeof format === "string") {
output = format.replace(replacementRegExp, function(match) {
switch (match) {
case "%s": return new String(data.shift());
case "%d": return new Number(data.shift());
case "%j": return JSON.stringify(data.shift());
default:
return match;
};
});
} else {
//put it back, it's not a format string
data.unshift(format);
}
data.forEach(function (item) {
if (output) {
output += ' ';
}
output += util.inspect(item);
if (item && item.stack) {
output += "\n" + item.stack;
}
});
return output;
var data = Array.isArray(logData) ? logData : Array.prototype.slice.call(arguments);
return util.format.apply(util, wrapErrorsWithInspect(data));
}
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]
'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' : '';
return style ? '\x1B[' + styles[style][0] + 'm' : '';
}
function colorizeEnd(style) {
return style ? '\033[' + styles[style][1] + 'm' : '';
return style ? '\x1B[' + styles[style][1] + 'm' : '';
}
/**
* Taken from masylum's fork (https://github.com/masylum/log4js-node)
*/
function colorize (str, style) {
return colorizeStart(style) + str + colorizeEnd(style);
return colorizeStart(style) + str + colorizeEnd(style);
}
function timestampLevelAndCategory(loggingEvent, colour) {
var output = colorize(
formatLogData(
'[%s] [%s] %s - '
, dateFormat.asString(loggingEvent.startTime)
, loggingEvent.level
, loggingEvent.categoryName
)
, colour
);
return output;
var output = colorize(
formatLogData(
'[%s] [%s] %s - '
, dateFormat.asString(loggingEvent.startTime)
, loggingEvent.level
, loggingEvent.categoryName
)
, colour
);
return output;
}
/**
@@ -113,7 +94,7 @@ function timestampLevelAndCategory(loggingEvent, colour) {
* @author Stephan Strittmatter
*/
function basicLayout (loggingEvent) {
return timestampLevelAndCategory(loggingEvent) + formatLogData(loggingEvent.data);
return timestampLevelAndCategory(loggingEvent) + formatLogData(loggingEvent.data);
}
/**
@@ -121,11 +102,14 @@ function basicLayout (loggingEvent) {
* same as basicLayout, but with colours.
*/
function colouredLayout (loggingEvent) {
return timestampLevelAndCategory(loggingEvent, colours[loggingEvent.level.toString()]) + formatLogData(loggingEvent.data);
return timestampLevelAndCategory(
loggingEvent,
colours[loggingEvent.level.toString()]
) + formatLogData(loggingEvent.data);
}
function messagePassThroughLayout (loggingEvent) {
return formatLogData(loggingEvent.data);
return formatLogData(loggingEvent.data);
}
/**
@@ -136,6 +120,7 @@ function messagePassThroughLayout (loggingEvent) {
* - %r time in toLocaleTimeString format
* - %p log level
* - %c log category
* - %h hostname
* - %m log data
* - %d date in various formats
* - %% %
@@ -158,140 +143,176 @@ function messagePassThroughLayout (loggingEvent) {
* @author Jan Schmidle
*/
function patternLayout (pattern, tokens) {
var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdmnprx%])(\{([^\}]+)\})?|([^%]+)/;
var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdhmnprx%])(\{([^\}]+)\})?|([^%]+)/;
pattern = pattern || TTCC_CONVERSION_PATTERN;
pattern = pattern || TTCC_CONVERSION_PATTERN;
function categoryName(loggingEvent, specifier) {
var loggerName = loggingEvent.categoryName;
if (specifier) {
var precision = parseInt(specifier, 10);
var loggerNameBits = loggerName.split(".");
if (precision < loggerNameBits.length) {
loggerName = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
}
}
return loggerName;
}
return function(loggingEvent) {
var formattedString = "";
var result;
var searchString = pattern;
function formatAsDate(loggingEvent, specifier) {
var format = dateFormat.ISO8601_FORMAT;
if (specifier) {
format = specifier;
// Pick up special cases
if (format == "ISO8601") {
format = dateFormat.ISO8601_FORMAT;
} else if (format == "ISO8601_WITH_TZ_OFFSET") {
format = dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT;
} else if (format == "ABSOLUTE") {
format = dateFormat.ABSOLUTETIME_FORMAT;
} else if (format == "DATE") {
format = dateFormat.DATETIME_FORMAT;
}
}
// Format the date
return dateFormat.asString(format, loggingEvent.startTime);
}
function hostname() {
return os.hostname().toString();
}
while ((result = regex.exec(searchString))) {
var matchedString = result[0];
var padding = result[1];
var truncation = result[2];
var conversionCharacter = result[3];
var specifier = result[5];
var text = result[6];
function formatMessage(loggingEvent) {
return formatLogData(loggingEvent.data);
}
function endOfLine() {
return eol;
}
// Check if the pattern matched was just normal text
if (text) {
formattedString += "" + text;
} else {
// Create a raw replacement string based on the conversion
// character and specifier
var replacement = "";
switch(conversionCharacter) {
case "c":
var loggerName = loggingEvent.categoryName;
if (specifier) {
var precision = parseInt(specifier, 10);
var loggerNameBits = loggingEvent.categoryName.split(".");
if (precision >= loggerNameBits.length) {
replacement = loggerName;
} else {
replacement = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
}
} else {
replacement = loggerName;
}
break;
case "d":
var format = dateFormat.ISO8601_FORMAT;
if (specifier) {
format = specifier;
// Pick up special cases
if (format == "ISO8601") {
format = dateFormat.ISO8601_FORMAT;
} else if (format == "ABSOLUTE") {
format = dateFormat.ABSOLUTETIME_FORMAT;
} else if (format == "DATE") {
format = dateFormat.DATETIME_FORMAT;
}
}
// Format the date
replacement = dateFormat.asString(format, loggingEvent.startTime);
break;
case "m":
replacement = formatLogData(loggingEvent.data);
break;
case "n":
replacement = eol;
break;
case "p":
replacement = loggingEvent.level.toString();
break;
case "r":
replacement = "" + loggingEvent.startTime.toLocaleTimeString();
break;
case "[":
replacement = colorizeStart(colours[loggingEvent.level.toString()]);
break;
case "]":
replacement = colorizeEnd(colours[loggingEvent.level.toString()]);
break;
case "%":
replacement = "%";
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:
replacement = matchedString;
break;
}
// Format the replacement according to any padding or
// truncation specified
function logLevel(loggingEvent) {
return loggingEvent.level.toString();
}
var len;
function startTime(loggingEvent) {
return "" + loggingEvent.startTime.toLocaleTimeString();
}
// First, truncation
if (truncation) {
len = parseInt(truncation.substr(1), 10);
replacement = replacement.substring(0, len);
}
// Next, padding
if (padding) {
if (padding.charAt(0) == "-") {
len = parseInt(padding.substr(1), 10);
// Right pad with spaces
while (replacement.length < len) {
replacement += " ";
}
} else {
len = parseInt(padding, 10);
// Left pad with spaces
while (replacement.length < len) {
replacement = " " + replacement;
}
}
}
formattedString += replacement;
}
searchString = searchString.substr(result.index + result[0].length);
}
return formattedString;
};
function startColour(loggingEvent) {
return colorizeStart(colours[loggingEvent.level.toString()]);
}
};
function endColour(loggingEvent) {
return colorizeEnd(colours[loggingEvent.level.toString()]);
}
function percent() {
return '%';
}
function userDefined(loggingEvent, specifier) {
if (typeof(tokens[specifier]) !== 'undefined') {
if (typeof(tokens[specifier]) === 'function') {
return tokens[specifier](loggingEvent);
} else {
return tokens[specifier];
}
}
return null;
}
var replacers = {
'c': categoryName,
'd': formatAsDate,
'h': hostname,
'm': formatMessage,
'n': endOfLine,
'p': logLevel,
'r': startTime,
'[': startColour,
']': endColour,
'%': percent,
'x': userDefined
};
function replaceToken(conversionCharacter, loggingEvent, specifier) {
return replacers[conversionCharacter](loggingEvent, specifier);
}
function truncate(truncation, toTruncate) {
var len;
if (truncation) {
len = parseInt(truncation.substr(1), 10);
return toTruncate.substring(0, len);
}
return toTruncate;
}
function pad(padding, toPad) {
var len;
if (padding) {
if (padding.charAt(0) == "-") {
len = parseInt(padding.substr(1), 10);
// Right pad with spaces
while (toPad.length < len) {
toPad += " ";
}
} else {
len = parseInt(padding, 10);
// Left pad with spaces
while (toPad.length < len) {
toPad = " " + toPad;
}
}
}
return toPad;
}
return function(loggingEvent) {
var formattedString = "";
var result;
var searchString = pattern;
while ((result = regex.exec(searchString))) {
var matchedString = result[0];
var padding = result[1];
var truncation = result[2];
var conversionCharacter = result[3];
var specifier = result[5];
var text = result[6];
// Check if the pattern matched was just normal text
if (text) {
formattedString += "" + text;
} else {
// Create a raw replacement string based on the conversion
// character and specifier
var replacement =
replaceToken(conversionCharacter, loggingEvent, specifier) ||
matchedString;
// Format the replacement according to any padding or
// truncation specified
replacement = truncate(truncation, replacement);
replacement = pad(padding, replacement);
formattedString += replacement;
}
searchString = searchString.substr(result.index + result[0].length);
}
return formattedString;
};
}
module.exports = {
basicLayout: basicLayout
, messagePassThroughLayout: messagePassThroughLayout
, patternLayout: patternLayout
, colouredLayout: colouredLayout
, coloredLayout: colouredLayout
, layout: function(name, config) {
return layoutMakers[name] && layoutMakers[name](config);
}
basicLayout: basicLayout,
messagePassThroughLayout: messagePassThroughLayout,
patternLayout: patternLayout,
colouredLayout: colouredLayout,
coloredLayout: colouredLayout,
layout: function(name, config) {
return layoutMakers[name] && layoutMakers[name](config);
}
};

View File

@@ -1,6 +1,8 @@
"use strict";
function Level(level, levelStr) {
this.level = level;
this.levelStr = levelStr;
this.level = level;
this.levelStr = levelStr;
}
/**
@@ -13,55 +15,54 @@ function Level(level, levelStr) {
function toLevel(sArg, defaultLevel) {
if (!sArg) {
return defaultLevel;
return defaultLevel;
}
if (typeof sArg == "string") {
var s = sArg.toUpperCase();
if (module.exports[s]) {
return module.exports[s];
} else {
return defaultLevel;
}
var s = sArg.toUpperCase();
if (module.exports[s]) {
return module.exports[s];
} else {
return defaultLevel;
}
}
return toLevel(sArg.toString());
};
}
Level.prototype.toString = function() {
return this.levelStr;
return this.levelStr;
};
Level.prototype.isLessThanOrEqualTo = function(otherLevel) {
if (typeof otherLevel === "string") {
otherLevel = toLevel(otherLevel);
}
return this.level <= otherLevel.level;
if (typeof otherLevel === "string") {
otherLevel = toLevel(otherLevel);
}
return this.level <= otherLevel.level;
};
Level.prototype.isGreaterThanOrEqualTo = function(otherLevel) {
if (typeof otherLevel === "string") {
otherLevel = toLevel(otherLevel);
}
return this.level >= otherLevel.level;
if (typeof otherLevel === "string") {
otherLevel = toLevel(otherLevel);
}
return this.level >= otherLevel.level;
};
Level.prototype.isEqualTo = function(otherLevel) {
if (typeof otherLevel == "string") {
otherLevel = toLevel(otherLevel);
}
return this.level === otherLevel.level;
}
if (typeof otherLevel == "string") {
otherLevel = toLevel(otherLevel);
}
return this.level === otherLevel.level;
};
module.exports = {
ALL: new Level(Number.MIN_VALUE, "ALL")
, TRACE: new Level(5000, "TRACE")
, DEBUG: new Level(10000, "DEBUG")
, INFO: new Level(20000, "INFO")
, WARN: new Level(30000, "WARN")
, ERROR: new Level(40000, "ERROR")
, FATAL: new Level(50000, "FATAL")
, OFF: new Level(Number.MAX_VALUE, "OFF")
, toLevel: toLevel
ALL: new Level(Number.MIN_VALUE, "ALL"),
TRACE: new Level(5000, "TRACE"),
DEBUG: new Level(10000, "DEBUG"),
INFO: new Level(20000, "INFO"),
WARN: new Level(30000, "WARN"),
ERROR: new Level(40000, "ERROR"),
FATAL: new Level(50000, "FATAL"),
OFF: new Level(Number.MAX_VALUE, "OFF"),
toLevel: toLevel
};

View File

@@ -1,3 +1,4 @@
"use strict";
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -12,8 +13,6 @@
* limitations under the License.
*/
/*jsl:option explicit*/
/**
* @fileoverview log4js is a library to log in JavaScript in similar manner
* than in log4j for Java. The API should be nearly the same.
@@ -57,10 +56,10 @@ var events = require('events')
, loggers = {}
, appenderMakers = {}
, defaultConfig = {
appenders: [
{ type: "console" }
],
replaceConsole: false
appenders: [
{ type: "console" }
],
replaceConsole: false
};
/**
@@ -71,102 +70,110 @@ var events = require('events')
*/
function getLogger (categoryName) {
// Use default logger if categoryName is not specified or invalid
if (!(typeof categoryName == "string")) {
categoryName = Logger.DEFAULT_CATEGORY;
}
// Use default logger if categoryName is not specified or invalid
if (typeof categoryName !== "string") {
categoryName = Logger.DEFAULT_CATEGORY;
}
var appenderList;
if (!loggers[categoryName]) {
// Create the logger for this name if it doesn't already exist
loggers[categoryName] = new Logger(categoryName);
if (appenders[categoryName]) {
appenderList = appenders[categoryName];
appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender);
});
}
if (appenders[ALL_CATEGORIES]) {
appenderList = appenders[ALL_CATEGORIES];
appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender);
});
}
var appenderList;
if (!loggers[categoryName]) {
// Create the logger for this name if it doesn't already exist
loggers[categoryName] = new Logger(categoryName);
if (appenders[categoryName]) {
appenderList = appenders[categoryName];
appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender);
});
}
return loggers[categoryName];
if (appenders[ALL_CATEGORIES]) {
appenderList = appenders[ALL_CATEGORIES];
appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender);
});
}
}
return loggers[categoryName];
}
/**
* args are appender, then zero or more categories
*/
function addAppender () {
var args = Array.prototype.slice.call(arguments);
var appender = args.shift();
if (args.length == 0 || args[0] === undefined) {
args = [ ALL_CATEGORIES ];
var args = Array.prototype.slice.call(arguments);
var appender = args.shift();
if (args.length === 0 || args[0] === undefined) {
args = [ ALL_CATEGORIES ];
}
//argument may already be an array
if (Array.isArray(args[0])) {
args = args[0];
}
args.forEach(function(category) {
addAppenderToCategory(appender, category);
if (category === ALL_CATEGORIES) {
addAppenderToAllLoggers(appender);
} else if (loggers[category]) {
loggers[category].addListener("log", appender);
}
//argument may already be an array
if (Array.isArray(args[0])) {
args = args[0];
});
}
function addAppenderToAllLoggers(appender) {
for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) {
loggers[logger].addListener("log", appender);
}
}
}
args.forEach(function(category) {
if (!appenders[category]) {
appenders[category] = [];
}
appenders[category].push(appender);
if (category === ALL_CATEGORIES) {
for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) {
loggers[logger].addListener("log", appender);
}
}
} else if (loggers[category]) {
loggers[category].addListener("log", appender);
}
});
function addAppenderToCategory(appender, category) {
if (!appenders[category]) {
appenders[category] = [];
}
appenders[category].push(appender);
}
function clearAppenders () {
appenders = {};
for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) {
loggers[logger].removeAllListeners("log");
}
appenders = {};
for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) {
loggers[logger].removeAllListeners("log");
}
}
}
function configureAppenders(appenderList, options) {
clearAppenders();
if (appenderList) {
appenderList.forEach(function(appenderConfig) {
loadAppender(appenderConfig.type);
var appender;
appenderConfig.makers = appenderMakers;
appender = appenderMakers[appenderConfig.type](appenderConfig, options);
if (appender) {
addAppender(appender, appenderConfig.category);
} else {
throw new Error("log4js configuration problem for "+util.inspect(appenderConfig));
}
});
}
clearAppenders();
if (appenderList) {
appenderList.forEach(function(appenderConfig) {
loadAppender(appenderConfig.type);
var appender;
appenderConfig.makers = appenderMakers;
try {
appender = appenderMakers[appenderConfig.type](appenderConfig, options);
addAppender(appender, appenderConfig.category);
} catch(e) {
throw new Error("log4js configuration problem for " + util.inspect(appenderConfig), e);
}
});
}
}
function configureLevels(levels) {
if (levels) {
for (var category in levels) {
if (levels.hasOwnProperty(category)) {
getLogger(category).setLevel(levels[category]);
}
}
if (levels) {
for (var category in levels) {
if (levels.hasOwnProperty(category)) {
getLogger(category).setLevel(levels[category]);
}
}
}
}
function setGlobalLogLevel(level) {
Logger.prototype.level = levels.toLevel(level, levels.TRACE);
Logger.prototype.level = levels.toLevel(level, levels.TRACE);
}
/**
@@ -175,143 +182,144 @@ function setGlobalLogLevel(level) {
* @static
*/
function getDefaultLogger () {
return getLogger(Logger.DEFAULT_CATEGORY);
return getLogger(Logger.DEFAULT_CATEGORY);
}
var configState = {};
function loadConfigurationFile(filename) {
if (filename && (!configState.lastFilename || filename !== configState.lastFilename ||
!configState.lastMTime || fs.statSync(filename).mtime !== configState.lastMTime)) {
configState.lastFilename = filename;
configState.lastMTime = fs.statSync(filename).mtime;
return JSON.parse(fs.readFileSync(filename, "utf8"));
}
return undefined;
if (filename) {
return JSON.parse(fs.readFileSync(filename, "utf8"));
}
return undefined;
}
function configureOnceOff(config, options) {
if (config) {
try {
configureAppenders(config.appenders, options);
configureLevels(config.levels);
if (config.replaceConsole) {
replaceConsole();
} else {
restoreConsole();
}
} catch (e) {
throw new Error("Problem reading log4js config " + util.inspect(config) + ". Error was \"" + e.message + "\" ("+e.stack+")");
}
if (config) {
try {
configureAppenders(config.appenders, options);
configureLevels(config.levels);
if (config.replaceConsole) {
replaceConsole();
} else {
restoreConsole();
}
} catch (e) {
throw new Error(
"Problem reading log4js config " + util.inspect(config) +
". Error was \"" + e.message + "\" (" + e.stack + ")"
);
}
}
}
function reloadConfiguration() {
var filename = configState.filename,
mtime;
if (!filename) {
// can't find anything to reload
return;
}
try {
mtime = fs.statSync(filename).mtime;
} catch (e) {
getLogger('log4js').warn('Failed to load configuration file ' + filename);
return;
}
if (configState.lastFilename && configState.lastFilename === filename) {
if (mtime.getTime() > configState.lastMTime.getTime()) {
configureOnceOff(loadConfigurationFile(filename));
}
} else {
configureOnceOff(loadConfigurationFile(filename));
}
var mtime = getMTime(configState.filename);
if (!mtime) return;
if (configState.lastMTime && (mtime.getTime() > configState.lastMTime.getTime())) {
configureOnceOff(loadConfigurationFile(configState.filename));
}
configState.lastMTime = mtime;
}
function getMTime(filename) {
var mtime;
try {
mtime = fs.statSync(configState.filename).mtime;
} catch (e) {
getLogger('log4js').warn('Failed to load configuration file ' + filename);
}
return mtime;
}
function initReloadConfiguration(filename, options) {
if (configState.timerId) {
clearInterval(configState.timerId);
delete configState.timerId;
}
configState.filename = filename;
configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000);
if (configState.timerId) {
clearInterval(configState.timerId);
delete configState.timerId;
}
configState.filename = filename;
configState.lastMTime = getMTime(filename);
configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000);
}
function configure(configurationFileOrObject, options) {
var config = configurationFileOrObject;
config = config || process.env.LOG4JS_CONFIG;
options = options || {};
var config = configurationFileOrObject;
config = config || process.env.LOG4JS_CONFIG;
options = options || {};
if (config === undefined || config === null || typeof(config) === 'string') {
if (options.reloadSecs) {
initReloadConfiguration(config, options);
}
config = loadConfigurationFile(config) || defaultConfig;
} else {
if (options.reloadSecs) {
getLogger('log4js').warn('Ignoring configuration reload parameter for "object" configuration.');
}
if (config === undefined || config === null || typeof(config) === 'string') {
if (options.reloadSecs) {
initReloadConfiguration(config, options);
}
configureOnceOff(config, options);
config = loadConfigurationFile(config) || defaultConfig;
} else {
if (options.reloadSecs) {
getLogger('log4js').warn(
'Ignoring configuration reload parameter for "object" configuration.'
);
}
}
configureOnceOff(config, options);
}
var originalConsoleFunctions = {
log: console.log,
debug: console.debug,
info: console.info,
warn: console.warn,
error: console.error
log: console.log,
debug: console.debug,
info: console.info,
warn: console.warn,
error: console.error
};
function replaceConsole(logger) {
function replaceWith(fn) {
return function() {
fn.apply(logger, arguments);
}
}
logger = logger || getLogger("console");
['log','debug','info','warn','error'].forEach(function (item) {
console[item] = replaceWith(item === 'log' ? logger.info : logger[item]);
});
function replaceWith(fn) {
return function() {
fn.apply(logger, arguments);
};
}
logger = logger || getLogger("console");
['log','debug','info','warn','error'].forEach(function (item) {
console[item] = replaceWith(item === 'log' ? logger.info : logger[item]);
});
}
function restoreConsole() {
['log', 'debug', 'info', 'warn', 'error'].forEach(function (item) {
console[item] = originalConsoleFunctions[item];
});
['log', 'debug', 'info', 'warn', 'error'].forEach(function (item) {
console[item] = originalConsoleFunctions[item];
});
}
function loadAppender(appender) {
var appenderModule;
try {
appenderModule = require('./appenders/' + appender);
} catch (e) {
appenderModule = require(appender);
}
module.exports.appenders[appender] = appenderModule.appender.bind(appenderModule);
appenderMakers[appender] = appenderModule.configure.bind(appenderModule);
var appenderModule;
try {
appenderModule = require('./appenders/' + appender);
} catch (e) {
appenderModule = require(appender);
}
module.exports.appenders[appender] = appenderModule.appender.bind(appenderModule);
appenderMakers[appender] = appenderModule.configure.bind(appenderModule);
}
module.exports = {
getLogger: getLogger,
getDefaultLogger: getDefaultLogger,
addAppender: addAppender,
loadAppender: loadAppender,
clearAppenders: clearAppenders,
configure: configure,
replaceConsole: replaceConsole,
restoreConsole: restoreConsole,
levels: levels,
setGlobalLogLevel: setGlobalLogLevel,
layouts: layouts,
appenders: {},
appenderMakers: appenderMakers,
connectLogger: require('./connect-logger').connectLogger
getLogger: getLogger,
getDefaultLogger: getDefaultLogger,
addAppender: addAppender,
loadAppender: loadAppender,
clearAppenders: clearAppenders,
configure: configure,
replaceConsole: replaceConsole,
restoreConsole: restoreConsole,
levels: levels,
setGlobalLogLevel: setGlobalLogLevel,
layouts: layouts,
appenders: {},
appenderMakers: appenderMakers,
connectLogger: require('./connect-logger').connectLogger
};
//set ourselves up

View File

@@ -1,7 +1,8 @@
var levels = require('./levels'),
util = require('util'),
events = require('events'),
DEFAULT_CATEGORY = '[default]';
"use strict";
var levels = require('./levels')
, util = require('util')
, events = require('events')
, DEFAULT_CATEGORY = '[default]';
/**
* Models a logging event.
@@ -13,11 +14,11 @@ var levels = require('./levels'),
* @author Seth Chisamore
*/
function LoggingEvent (categoryName, level, data, logger) {
this.startTime = new Date();
this.categoryName = categoryName;
this.data = data;
this.level = level;
this.logger = logger;
this.startTime = new Date();
this.categoryName = categoryName;
this.data = data;
this.level = level;
this.logger = logger;
}
/**
@@ -28,49 +29,50 @@ function LoggingEvent (categoryName, level, data, logger) {
* @author Stephan Strittmatter
*/
function Logger (name, level) {
this.category = name || DEFAULT_CATEGORY;
if (! this.level) {
this.__proto__.level = levels.TRACE;
}
this.category = name || DEFAULT_CATEGORY;
if (level) {
this.setLevel(level);
}
}
util.inherits(Logger, events.EventEmitter);
Logger.DEFAULT_CATEGORY = DEFAULT_CATEGORY;
Logger.prototype.level = levels.TRACE;
Logger.prototype.setLevel = function(level) {
this.level = levels.toLevel(level, this.level || levels.TRACE);
this.level = levels.toLevel(level, this.level || levels.TRACE);
};
Logger.prototype.removeLevel = function() {
delete this.level;
delete this.level;
};
Logger.prototype.log = function() {
var args = Array.prototype.slice.call(arguments)
var args = Array.prototype.slice.call(arguments)
, logLevel = args.shift()
, loggingEvent = new LoggingEvent(this.category, logLevel, args, this);
this.emit("log", loggingEvent);
this.emit("log", loggingEvent);
};
Logger.prototype.isLevelEnabled = function(otherLevel) {
return this.level.isLessThanOrEqualTo(otherLevel);
return this.level.isLessThanOrEqualTo(otherLevel);
};
['Trace','Debug','Info','Warn','Error','Fatal'].forEach(
function(levelString) {
var level = levels.toLevel(levelString);
Logger.prototype['is'+levelString+'Enabled'] = function() {
return this.isLevelEnabled(level);
};
Logger.prototype[levelString.toLowerCase()] = function () {
if (this.isLevelEnabled(level)) {
var args = Array.prototype.slice.call(arguments);
args.unshift(level);
Logger.prototype.log.apply(this, args);
}
};
}
function(levelString) {
var level = levels.toLevel(levelString);
Logger.prototype['is'+levelString+'Enabled'] = function() {
return this.isLevelEnabled(level);
};
Logger.prototype[levelString.toLowerCase()] = function () {
if (this.isLevelEnabled(level)) {
var args = Array.prototype.slice.call(arguments);
args.unshift(level);
Logger.prototype.log.apply(this, args);
}
};
}
);

View File

@@ -1,9 +1,14 @@
var fs = require('fs'),
stream = require('stream'),
util = require('util');
"use strict";
var fs = require('fs')
, stream
, debug = require('../debug')('BaseRollingFileStream')
, util = require('util')
, semver = require('semver');
function debug(message) {
// console.log(message);
if (semver.satisfies(process.version, '>=0.10.0')) {
stream = require('stream');
} else {
stream = require('readable-stream');
}
module.exports = BaseRollingFileStream;
@@ -11,9 +16,9 @@ module.exports = BaseRollingFileStream;
function BaseRollingFileStream(filename, options) {
debug("In BaseRollingFileStream");
this.filename = filename;
this.options = options || { encoding: 'utf8', mode: 0644, flags: 'a' };
this.options = options || { encoding: 'utf8', mode: parseInt('0644', 8), flags: 'a' };
this.currentSize = 0;
function currentFileSize(file) {
var fileSize = 0;
try {
@@ -66,14 +71,14 @@ BaseRollingFileStream.prototype.openTheStream = function(cb) {
BaseRollingFileStream.prototype.closeTheStream = function(cb) {
debug("closing the underlying stream");
this.theStream.end(null, null, cb);
this.theStream.end(cb);
};
BaseRollingFileStream.prototype.shouldRoll = function() {
return false; // default behaviour is never to roll
return false; // default behaviour is never to roll
};
BaseRollingFileStream.prototype.roll = function(filename, callback) {
callback(); // default behaviour is not to do anything
callback(); // default behaviour is not to do anything
};

View File

@@ -1,68 +1,88 @@
var BaseRollingFileStream = require('./BaseRollingFileStream'),
format = require('../date_format'),
async = require('async'),
fs = require('fs'),
util = require('util');
"use strict";
var BaseRollingFileStream = require('./BaseRollingFileStream')
, debug = require('../debug')('DateRollingFileStream')
, 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;
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()));
this.baseFilename = filename;
this.alwaysIncludePattern = false;
if (options) {
if (options.alwaysIncludePattern) {
this.alwaysIncludePattern = true;
filename = this.baseFilename + this.lastTimeWeWroteSomething;
}
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);
delete options.alwaysIncludePattern;
if (Object.keys(options).length === 0) {
options = null;
}
}
debug("this.now is " + this.now + ", now is " + now);
DateRollingFileStream.super_.call(this, filename, options);
}
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;
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");
var that = this;
debug("Starting roll");
if (this.alwaysIncludePattern) {
this.filename = this.baseFilename + this.lastTimeWeWroteSomething;
async.series([
this.closeTheStream.bind(this),
this.openTheStream.bind(this)
], callback);
} else {
var newFilename = this.baseFilename + this.previousTime;
async.series([
this.closeTheStream.bind(this),
deleteAnyExistingFile,
renameTheCurrentFile,
this.openTheStream.bind(this)
], 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 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 renameTheCurrentFile(cb) {
debug("Renaming the " + filename + " -> " + newFilename);
fs.rename(filename, newFilename, cb);
}
};

View File

@@ -1,86 +1,83 @@
var BaseRollingFileStream = require('./BaseRollingFileStream'),
util = require('util'),
path = require('path'),
fs = require('fs'),
async = require('async');
function debug() {
// util.debug(message);
// console.log.apply(console, arguments);
}
"use strict";
var BaseRollingFileStream = require('./BaseRollingFileStream')
, debug = require('../debug')('RollingFileStream')
, util = require('util')
, path = require('path')
, fs = require('fs')
, async = require('async');
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");
}
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);
}
throwErrorIfArgumentsAreNotValid();
RollingFileStream.super_.call(this, filename, options);
}
util.inherits(RollingFileStream, BaseRollingFileStream);
RollingFileStream.prototype.shouldRoll = function() {
debug("should roll with current size %d, and max size %d", this.currentSize, this.size);
return this.currentSize >= this.size;
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);
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 index(filename_) {
return parseInt(filename_.substring((path.basename(filename) + '.').length), 10) || 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 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 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
);
});
}
debug("Rolling, rolling, rolling");
async.series([

View File

@@ -1,38 +1,45 @@
{
"name": "log4js",
"version": "0.6.0",
"description": "Port of Log4js to work with node.",
"keywords": [
"logging",
"log",
"log4j",
"node"
],
"main": "./lib/log4js",
"author": "Gareth Jones <gareth.jones@sensis.com.au>",
"repository": {
"type": "git",
"url": "https://github.com/nomiddlename/log4js-node.git"
},
"bugs": {
"url": "http://github.com/nomiddlename/log4js-node/issues"
},
"engines": [ "node >=0.10" ],
"scripts": {
"test": "vows"
},
"directories": {
"test": "test",
"lib": "lib"
},
"dependencies": {
"async": "0.1.15",
"dequeue": "1.0.3"
},
"devDependencies": {
"vows": "0.7.0",
"sandboxed-module": "0.1.3",
"hook.io": "0.8.10",
"underscore": "1.2.1"
}
"name": "log4js",
"version": "0.6.8",
"description": "Port of Log4js to work with node.",
"keywords": [
"logging",
"log",
"log4j",
"node"
],
"main": "./lib/log4js",
"author": "Gareth Jones <gareth.jones@sensis.com.au>",
"repository": {
"type": "git",
"url": "https://github.com/nomiddlename/log4js-node.git"
},
"bugs": {
"url": "http://github.com/nomiddlename/log4js-node/issues"
},
"engines": {
"node": ">=0.8"
},
"scripts": {
"test": "vows"
},
"directories": {
"test": "test",
"lib": "lib"
},
"dependencies": {
"async": "0.1.15",
"dequeue": "1.0.3",
"semver": "~1.1.4",
"readable-stream": "~1.0.2"
},
"devDependencies": {
"vows": "0.7.0",
"sandboxed-module": "0.1.3",
"hook.io": "0.8.10",
"underscore": "1.2.1"
},
"browser": {
"os": false
}
}

View File

@@ -0,0 +1,83 @@
'use strict';
var vows = require('vows')
, fs = require('fs')
, assert = require('assert');
function remove(filename) {
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
}
vows.describe('log4js categoryFilter').addBatch({
'appender': {
topic: function() {
var log4js = require('../lib/log4js'), logEvents = [], webLogger, appLogger;
log4js.clearAppenders();
var appender = require('../lib/appenders/categoryFilter')
.appender(
['app'],
function(evt) { logEvents.push(evt); }
);
log4js.addAppender(appender, ["app","web"]);
webLogger = log4js.getLogger("web");
appLogger = log4js.getLogger("app");
webLogger.debug('This should get logged');
appLogger.debug('This should not');
webLogger.debug('Hello again');
log4js.getLogger('db').debug('This shouldn\'t be included by the appender anyway');
return logEvents;
},
'should only pass matching category' : function(logEvents) {
assert.equal(logEvents.length, 2);
assert.equal(logEvents[0].data[0], 'This should get logged');
assert.equal(logEvents[1].data[0], 'Hello again');
}
},
'configure': {
topic: function() {
var log4js = require('../lib/log4js')
, logger, weblogger;
remove(__dirname + '/categoryFilter-web.log');
remove(__dirname + '/categoryFilter-noweb.log');
log4js.configure('test/with-categoryFilter.json');
logger = log4js.getLogger("app");
weblogger = log4js.getLogger("web");
logger.info('Loading app');
logger.debug('Initialising indexes');
weblogger.info('00:00:00 GET / 200');
weblogger.warn('00:00:00 GET / 500');
//wait for the file system to catch up
setTimeout(this.callback, 100);
},
'tmp-tests.log': {
topic: function() {
fs.readFile(__dirname + '/categoryFilter-noweb.log', 'utf8', this.callback);
},
'should contain all log messages': function(contents) {
var messages = contents.trim().split('\n');
assert.deepEqual(messages, ['Loading app','Initialising indexes']);
}
},
'tmp-tests-web.log': {
topic: function() {
fs.readFile(__dirname + '/categoryFilter-web.log','utf8',this.callback);
},
'should contain only error and warning log messages': function(contents) {
var messages = contents.trim().split('\n');
assert.deepEqual(messages, ['00:00:00 GET / 200','00:00:00 GET / 500']);
}
}
}
}).export(module);

116
test/clusteredAppender-test.js Executable file
View File

@@ -0,0 +1,116 @@
"use strict";
var assert = require('assert');
var vows = require('vows');
var layouts = require('../lib/layouts');
var sandbox = require('sandboxed-module');
var LoggingEvent = require('../lib/logger').LoggingEvent;
var cluster = require('cluster');
vows.describe('log4js cluster appender').addBatch({
'when in master mode': {
topic: function() {
var registeredClusterEvents = [];
var loggingEvents = [];
// Fake cluster module, so no cluster listeners be really added
var fakeCluster = {
on: function(event, callback) {
registeredClusterEvents.push(event);
},
isMaster: true,
isWorker: false,
};
var fakeActualAppender = function(loggingEvent) {
loggingEvents.push(loggingEvent);
}
// Load appender and fake modules in it
var appenderModule = sandbox.require('../lib/appenders/clustered', {
requires: {
'cluster': fakeCluster,
}
});
var masterAppender = appenderModule.appender({
actualAppenders: [ fakeActualAppender ]
});
// Actual test - log message using masterAppender
masterAppender(new LoggingEvent('wovs', 'Info', ['masterAppender test']));
var returnValue = {
registeredClusterEvents: registeredClusterEvents,
loggingEvents: loggingEvents,
};
return returnValue;
},
"should register 'fork' event listener on 'cluster'": function(topic) {
assert.equal(topic.registeredClusterEvents[0], 'fork');
},
"should log using actual appender": function(topic) {
assert.equal(topic.loggingEvents[0].data[0], 'masterAppender test');
},
},
'when in worker mode': {
topic: function() {
var registeredProcessEvents = [];
// Fake cluster module, to fake we're inside a worker process
var fakeCluster = {
isMaster: false,
isWorker: true,
};
var fakeProcess = {
send: function(data) {
registeredProcessEvents.push(data);
},
};
// Load appender and fake modules in it
var appenderModule = sandbox.require('../lib/appenders/clustered', {
requires: {
'cluster': fakeCluster,
},
globals: {
'process': fakeProcess,
}
});
var workerAppender = appenderModule.appender();
// Actual test - log message using masterAppender
workerAppender(new LoggingEvent('wovs', 'Info', ['workerAppender test']));
var returnValue = {
registeredProcessEvents: registeredProcessEvents,
};
return returnValue;
},
"worker appender should call process.send" : function(topic) {
assert.equal(topic.registeredProcessEvents[0].type, '::log-message');
assert.equal(JSON.parse(topic.registeredProcessEvents[0].event).data[0], "workerAppender test");
}
}
}).exportTo(module);

View File

@@ -1,131 +1,134 @@
var assert = require('assert'),
vows = require('vows'),
sandbox = require('sandboxed-module');
"use strict";
var assert = require('assert')
, vows = require('vows')
, sandbox = require('sandboxed-module');
function makeTestAppender() {
return {
configure: function(config, options) {
this.configureCalled = true;
this.config = config;
this.options = options;
return this.appender();
},
appender: function() {
var self = this;
return function(logEvt) { self.logEvt = logEvt; }
}
};
return {
configure: function(config, options) {
this.configureCalled = true;
this.config = config;
this.options = options;
return this.appender();
},
appender: function() {
var self = this;
return function(logEvt) { self.logEvt = logEvt; };
}
};
}
vows.describe('log4js configure').addBatch({
'appenders': {
'when specified by type': {
topic: function() {
var testAppender = makeTestAppender(),
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'./appenders/cheese': testAppender
}
}
);
log4js.configure(
{
appenders: [
{ type: "cheese", flavour: "gouda" }
]
},
{ pants: "yes" }
);
return testAppender;
},
'should load appender': function(testAppender) {
assert.ok(testAppender.configureCalled);
},
'should pass config to appender': function(testAppender) {
assert.equal(testAppender.config.flavour, 'gouda');
},
'should pass log4js options to appender': function(testAppender) {
assert.equal(testAppender.options.pants, 'yes');
'appenders': {
'when specified by type': {
topic: function() {
var testAppender = makeTestAppender(),
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'./appenders/cheese': testAppender
}
},
'when core appender loaded via loadAppender': {
topic: function() {
var testAppender = makeTestAppender(),
log4js = sandbox.require(
'../lib/log4js',
{ requires: { './appenders/cheese': testAppender } }
);
log4js.loadAppender('cheese');
return log4js;
},
'should load appender from ../lib/appenders': function(log4js) {
assert.ok(log4js.appenders.cheese);
},
'should add appender configure function to appenderMakers' : function(log4js) {
assert.isFunction(log4js.appenderMakers.cheese);
}
},
'when appender in node_modules loaded via loadAppender': {
topic: function() {
var testAppender = makeTestAppender(),
log4js = sandbox.require(
'../lib/log4js',
{ requires: { 'some/other/external': testAppender } }
);
log4js.loadAppender('some/other/external');
return log4js;
},
'should load appender via require': function(log4js) {
assert.ok(log4js.appenders['some/other/external']);
},
'should add appender configure function to appenderMakers': function(log4js) {
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);
}
}
);
log4js.configure(
{
appenders: [
{ type: "cheese", flavour: "gouda" }
]
},
{ pants: "yes" }
);
return testAppender;
},
'should load appender': function(testAppender) {
assert.ok(testAppender.configureCalled);
},
'should pass config to appender': function(testAppender) {
assert.equal(testAppender.config.flavour, 'gouda');
},
'should pass log4js options to appender': function(testAppender) {
assert.equal(testAppender.options.pants, 'yes');
}
},
'when core appender loaded via loadAppender': {
topic: function() {
var testAppender = makeTestAppender(),
log4js = sandbox.require(
'../lib/log4js',
{ requires: { './appenders/cheese': testAppender } }
);
log4js.loadAppender('cheese');
return log4js;
},
'should load appender from ../lib/appenders': function(log4js) {
assert.ok(log4js.appenders.cheese);
},
'should add appender configure function to appenderMakers' : function(log4js) {
assert.isFunction(log4js.appenderMakers.cheese);
}
},
'when appender in node_modules loaded via loadAppender': {
topic: function() {
var testAppender = makeTestAppender(),
log4js = sandbox.require(
'../lib/log4js',
{ requires: { 'some/other/external': testAppender } }
);
log4js.loadAppender('some/other/external');
return log4js;
},
'should load appender via require': function(log4js) {
assert.ok(log4js.appenders['some/other/external']);
},
'should add appender configure function to appenderMakers': function(log4js) {
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);

View File

@@ -1,7 +1,10 @@
"use strict";
// This test shows unexpected behaviour for log4js.configure() in log4js-node@0.4.3 and earlier:
// 1) log4js.configure(), log4js.configure(null), log4js.configure({}), log4js.configure(<some object with no levels prop>)
// 1) log4js.configure(), log4js.configure(null),
// log4js.configure({}), log4js.configure(<some object with no levels prop>)
// all set all loggers levels to trace, even if they were previously set to something else.
// 2) log4js.configure({levels:{}}), log4js.configure({levels: {foo: bar}}) leaves previously set logger levels intact.
// 2) log4js.configure({levels:{}}), log4js.configure({levels: {foo:
// bar}}) leaves previously set logger levels intact.
//
// Basic set up
@@ -28,7 +31,7 @@ var configs = {
'has empty levels': {levels: {}},
'has random levels': {levels: {foo: 'bar'}},
'has some valid levels': {levels: {A: 'INFO'}}
}
};
// Set up the basic vows batches for this test
var batches = [];
@@ -60,13 +63,85 @@ function getTopLevelContext(nop, configToTest, name) {
}
return log4js;
}
}
};
};
}
showProgress('Populating batch object...');
// Populating the batches programmatically,
// as there are (configs.length x strLevels.length x strLevels.length) = 324 possible test combinations
function checkForMismatch(topic) {
var er = topic.log4js.levels.toLevel(topic.baseLevel)
.isLessThanOrEqualTo(topic.log4js.levels.toLevel(topic.comparisonLevel));
assert.equal(
er,
topic.expectedResult,
'Mismatch: for setLevel(' + topic.baseLevel +
') was expecting a comparison with ' + topic.comparisonLevel +
' to be ' + topic.expectedResult
);
}
function checkExpectedResult(topic) {
var result = topic.log4js
.getLogger(getLoggerName(topic.baseLevel))
.isLevelEnabled(topic.log4js.levels.toLevel(topic.comparisonLevel));
assert.equal(
result,
topic.expectedResult,
'Failed: ' + getLoggerName(topic.baseLevel) +
'.isLevelEnabled( ' + topic.comparisonLevel + ' ) returned ' + result
);
}
function setupBaseLevelAndCompareToOtherLevels(baseLevel) {
var baseLevelSubContext = 'and checking the logger whose level was set to '+baseLevel ;
var subContext = { topic: baseLevel };
batch[context][baseLevelSubContext] = subContext;
// each logging level has strLevels sub-contexts,
// to exhaustively test all the combinations of
// setLevel(baseLevel) and isLevelEnabled(comparisonLevel) per config
strLevels.forEach(compareToOtherLevels(subContext));
}
function compareToOtherLevels(subContext) {
var baseLevel = subContext.topic;
return function (comparisonLevel) {
var comparisonLevelSubContext = 'with isLevelEnabled('+comparisonLevel+')';
// calculate this independently of log4js, but we'll add a vow
// later on to check that we're not mismatched with log4js
var expectedResult = strLevels.indexOf(baseLevel) <= strLevels.indexOf(comparisonLevel);
// the topic simply gathers all the parameters for the vow
// into an object, to simplify the vow's work.
subContext[comparisonLevelSubContext] = {
topic: function(baseLevel, log4js) {
return {
comparisonLevel: comparisonLevel,
baseLevel: baseLevel,
log4js: log4js,
expectedResult: expectedResult
};
}
};
var vow = 'should return '+expectedResult;
subContext[comparisonLevelSubContext][vow] = checkExpectedResult;
// the extra vow to check the comparison between baseLevel and
// comparisonLevel we performed earlier matches log4js'
// comparison too
var subSubContext = subContext[comparisonLevelSubContext];
subSubContext['finally checking for comparison mismatch with log4js'] = checkForMismatch;
};
}
// Populating the batches programmatically, as there are
// (configs.length x strLevels.length x strLevels.length) = 324
// possible test combinations
for (var cfg in configs) {
var configToTest = configs[cfg];
var nop = configToTest === 'nop';
@@ -84,43 +159,15 @@ for (var cfg in configs) {
batch[context]= getTopLevelContext(nop, configToTest, context);
batches.push(batch);
// each top-level context has strLevels sub-contexts, one per logger which has set to a specific level in the top-level context's topic
strLevels.forEach(function (baseLevel) {
var baseLevelSubContext = 'and checking the logger whose level was set to '+baseLevel ;
batch[context][baseLevelSubContext] = {topic: baseLevel};
// each logging level has strLevels sub-contexts,
// to exhaustively test all the combinations of setLevel(baseLevel) and isLevelEnabled(comparisonLevel) per config
strLevels.forEach(function (comparisonLevel) {
var comparisonLevelSubContext = 'with isLevelEnabled('+comparisonLevel+')';
// calculate this independently of log4js, but we'll add a vow later on to check that we're not mismatched with log4js
var expectedResult = strLevels.indexOf(baseLevel) <= strLevels.indexOf(comparisonLevel);
// the topic simply gathers all the parameters for the vow into an object, to simplify the vow's work.
batch[context][baseLevelSubContext][comparisonLevelSubContext] = {topic: function(baseLevel, log4js){
return {comparisonLevel: comparisonLevel, baseLevel: baseLevel, log4js: log4js, expectedResult: expectedResult};
}};
var vow = 'should return '+expectedResult;
batch[context][baseLevelSubContext][comparisonLevelSubContext][vow] = function(topic){
var result = topic.log4js.getLogger(getLoggerName(topic.baseLevel)).isLevelEnabled(topic.log4js.levels.toLevel(topic.comparisonLevel));
assert.equal(result, topic.expectedResult, 'Failed: '+getLoggerName(topic.baseLevel)+'.isLevelEnabled( '+topic.comparisonLevel+' ) returned '+result);
};
// the extra vow to check the comparison between baseLevel and comparisonLevel we performed earlier matches log4js' comparison too
batch[context][baseLevelSubContext][comparisonLevelSubContext]['finally checking for comparison mismatch with log4js'] = function(topic){
var er = topic.log4js.levels.toLevel(topic.baseLevel).isLessThanOrEqualTo(topic.log4js.levels.toLevel(topic.comparisonLevel));
assert.equal(er, topic.expectedResult, 'Mismatch: for setLevel('+topic.baseLevel+') was expecting a comparison with '+topic.comparisonLevel+' to be '+topic.expectedResult);
};
});
});
};
// each top-level context has strLevels sub-contexts, one per logger
// which has set to a specific level in the top-level context's topic
strLevels.forEach(setupBaseLevelAndCompareToOtherLevels);
}
showProgress('Running tests');
var v = vows.describe('log4js.configure(), with or without a "levels" property');
batches.forEach(function(batch) {v=v.addBatch(batch)});
batches.forEach(function(batch) {v=v.addBatch(batch);});
v.export(module);

View File

@@ -1,128 +1,226 @@
/* jshint maxparams:7 */
"use strict";
var vows = require('vows')
, assert = require('assert')
, levels = require('../lib/levels');
function MockLogger() {
var that = this;
this.messages = [];
var that = this;
this.messages = [];
this.log = function(level, message, exception) {
that.messages.push({ level: level, message: message });
};
this.log = function(level, message, exception) {
that.messages.push({ level: level, message: message });
};
this.isLevelEnabled = function(level) {
return level.isGreaterThanOrEqualTo(that.level);
};
this.level = levels.TRACE;
this.isLevelEnabled = function(level) {
return level.isGreaterThanOrEqualTo(that.level);
};
this.level = levels.TRACE;
}
function MockRequest(remoteAddr, method, originalUrl) {
function MockRequest(remoteAddr, method, originalUrl, headers) {
this.socket = { remoteAddress: remoteAddr };
this.originalUrl = originalUrl;
this.method = method;
this.httpVersionMajor = '5';
this.httpVersionMinor = '0';
this.headers = {}
this.socket = { remoteAddress: remoteAddr };
this.originalUrl = originalUrl;
this.method = method;
this.httpVersionMajor = '5';
this.httpVersionMinor = '0';
this.headers = headers || {};
var self = this;
Object.keys(this.headers).forEach(function(key) {
self.headers[key.toLowerCase()] = self.headers[key];
});
}
function MockResponse() {
this.end = function(chunk, encoding) {
};
this.writeHead = function(code, headers) {
};
}
function MockResponse(statusCode) {
this.statusCode = statusCode;
this.end = function(chunk, encoding) {
}
function request(cl, method, url, code, reqHeaders, resHeaders) {
var req = new MockRequest('my.remote.addr', method, url, reqHeaders);
var res = new MockResponse();
cl(req, res, function() {});
res.writeHead(code, resHeaders);
res.end('chunk','encoding');
}
vows.describe('log4js connect logger').addBatch({
'getConnectLoggerModule': {
topic: function() {
var clm = require('../lib/connect-logger');
return clm;
},
'getConnectLoggerModule': {
topic: function() {
var clm = require('../lib/connect-logger');
return clm;
},
'should return a "connect logger" factory' : function(clm) {
assert.isObject(clm);
},
'should return a "connect logger" factory' : function(clm) {
assert.isObject(clm);
},
'take a log4js logger and return a "connect logger"' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml);
return cl;
},
'should return a "connect logger"': function(cl) {
assert.isFunction(cl);
}
},
'log events' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml);
request(cl, 'GET', 'http://url', 200);
return ml.messages;
},
'take a log4js logger and return a "connect logger"' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml);
return cl;
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
}
},
'should return a "connect logger"': function(cl) {
assert.isFunction(cl);
}
},
'log events with level below logging level' : {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.FATAL;
var cl = clm.connectLogger(ml);
request(cl, 'GET', 'http://url', 200);
return ml.messages;
},
'check message': function(messages) {
assert.isArray(messages);
assert.isEmpty(messages);
}
},
'log events' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml);
var req = new MockRequest('my.remote.addr', 'GET', 'http://url');
var res = new MockResponse(200);
cl(req, res, function() { });
res.end('chunk', 'encoding');
return ml.messages;
},
'log events with non-default level and custom format' : {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url' } );
request(cl, 'GET', 'http://url', 200);
return ml.messages;
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.equal(messages[0].message, 'GET http://url');
}
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
}
},
'logger with options as string': {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, ':method :url');
request(cl, 'POST', 'http://meh', 200);
return ml.messages;
},
'should use the passed in format': function(messages) {
assert.equal(messages[0].message, 'POST http://meh');
}
},
'log events with level below logging level' : {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.FATAL;
var cl = clm.connectLogger(ml);
var req = new MockRequest('my.remote.addr', 'GET', 'http://url');
var res = new MockResponse(200);
cl(req, res, function() { });
res.end('chunk', 'encoding');
return ml.messages;
},
'auto log levels': {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, { level: 'auto', format: ':method :url' });
request(cl, 'GET', 'http://meh', 200);
request(cl, 'GET', 'http://meh', 201);
request(cl, 'GET', 'http://meh', 302);
request(cl, 'GET', 'http://meh', 404);
request(cl, 'GET', 'http://meh', 500);
return ml.messages;
},
'check message': function(messages) {
assert.isArray(messages);
assert.isEmpty(messages);
}
},
'should use INFO for 2xx': function(messages) {
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.ok(levels.INFO.isEqualTo(messages[1].level));
},
'log events with non-default level and custom format' : {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url' } );
var req = new MockRequest('my.remote.addr', 'GET', 'http://url');
var res = new MockResponse(200);
cl(req, res, function() { });
res.end('chunk', 'encoding');
return ml.messages;
},
'should use WARN for 3xx': function(messages) {
assert.ok(levels.WARN.isEqualTo(messages[2].level));
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.equal(messages[0].message, 'GET http://url');
}
}
'should use ERROR for 4xx': function(messages) {
assert.ok(levels.ERROR.isEqualTo(messages[3].level));
},
}
'should use ERROR for 5xx': function(messages) {
assert.ok(levels.ERROR.isEqualTo(messages[4].level));
}
},
'format using a function': {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, function(req, res, formatFn) { return "I was called"; });
request(cl, 'GET', 'http://blah', 200);
return ml.messages;
},
'should call the format function': function(messages) {
assert.equal(messages[0].message, 'I was called');
}
},
'format that includes request headers': {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, ':req[Content-Type]');
request(
cl,
'GET', 'http://blah', 200,
{ 'Content-Type': 'application/json' }
);
return ml.messages;
},
'should output the request header': function(messages) {
assert.equal(messages[0].message, 'application/json');
}
},
'format that includes response headers': {
topic: function(clm) {
var ml = new MockLogger();
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, ':res[Content-Type]');
request(
cl,
'GET', 'http://blah', 200,
null,
{ 'Content-Type': 'application/cheese' }
);
return ml.messages;
},
'should output the response header': function(messages) {
assert.equal(messages[0].message, 'application/cheese');
}
}
}
}).export(module);

View File

@@ -0,0 +1,33 @@
"use strict";
var assert = require('assert')
, vows = require('vows')
, layouts = require('../lib/layouts')
, sandbox = require('sandboxed-module');
vows.describe('../lib/appenders/console').addBatch({
'appender': {
topic: function() {
var messages = []
, fakeConsole = {
log: function(msg) { messages.push(msg); }
}
, appenderModule = sandbox.require(
'../lib/appenders/console',
{
globals: {
'console': fakeConsole
}
}
)
, appender = appenderModule.appender(layouts.messagePassThroughLayout);
appender({ data: ["blah"] });
return messages;
},
'should output to console': function(messages) {
assert.equal(messages[0], 'blah');
}
}
}).exportTo(module);

View File

@@ -1,8 +1,10 @@
var vows = require('vows'),
assert = require('assert'),
path = require('path'),
fs = require('fs'),
log4js = require('../lib/log4js');
"use strict";
var vows = require('vows')
, assert = require('assert')
, path = require('path')
, fs = require('fs')
, sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js');
function removeFile(filename) {
return function() {
@@ -15,84 +17,202 @@ function removeFile(filename) {
}
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;
'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);
},
while (count--) {
logfile = path.join(__dirname, 'datefa-default-test' + count + '.log');
log4js.addAppender(dateFileAppender.appender(logfile));
},
'exit listener': {
topic: function() {
var exitListener
, openedFiles = []
, dateFileAppender = sandbox.require(
'../lib/appenders/dateFile',
{
globals: {
process: {
on: function(evt, listener) {
exitListener = listener;
}
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')();
}
},
requires: {
'../streams': {
DateRollingFileStream: function(filename) {
openedFiles.push(filename);
'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 - /);
}
this.end = function() {
openedFiles.shift();
};
}
}
}
}
);
for (var i=0; i < 5; i += 1) {
dateFileAppender.appender('test' + i);
}
assert.isNotEmpty(openedFiles);
exitListener();
return openedFiles;
},
'should close all open files': function(openedFiles) {
assert.isEmpty(openedFiles);
}
},
'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);
}
'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' + require('os').EOL);
assert.equal(contents.indexOf('this should not be written to the file'), -1);
}
},
'with options.alwaysIncludePattern': {
topic: function() {
var self = this
, log4js = require('../lib/log4js')
, format = require('../lib/date_format')
, logger
, options = {
"appenders": [
{
"category": "tests",
"type": "dateFile",
"filename": "test/date-file-test",
"pattern": "-from-MM-dd.log",
"alwaysIncludePattern": true,
"layout": {
"type": "messagePassThrough"
}
}
]
}
, 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.configure(options);
logger = log4js.getLogger('tests');
logger.warn('this should be written to the file with the appended date');
this.teardown = removeFile('date-file-test' + thisTime);
//wait for filesystem to catch up
setTimeout(function() {
fs.readFile(path.join(__dirname, 'date-file-test' + thisTime), 'utf8', self.callback);
}, 100);
},
'should create file with the correct pattern': function(contents) {
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');
}
},
'with cwd option': {
topic: function() {
var fileOpened,
appender = sandbox.require(
'../lib/appenders/dateFile',
{ requires:
{ '../streams':
{ DateRollingFileStream:
function(file) {
fileOpened = file;
return {
on: function() {},
end: function() {}
};
}
}
}
}
);
appender.configure(
{
filename: "whatever.log",
maxLogSize: 10
},
{ cwd: '/absolute/path/to' }
);
return fileOpened;
},
'should prepend options.cwd to config.filename': function(fileOpened) {
assert.equal(fileOpened, "/absolute/path/to/whatever.log");
}
}
}
}).exportTo(module);

View File

@@ -1,23 +1,51 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, dateFormat = require('../lib/date_format');
vows.describe('date_format').addBatch({
'Date extensions': {
topic: function() {
return new Date(2010, 0, 11, 14, 31, 30, 5);
},
'should format a date as string using a pattern': function(date) {
assert.equal(
dateFormat.asString(dateFormat.DATETIME_FORMAT, date),
"11 01 2010 14:31:30.005"
);
},
'should default to the ISO8601 format': function(date) {
assert.equal(
dateFormat.asString(date),
'2010-01-11 14:31:30.005'
);
}
'Date extensions': {
topic: function() {
return new Date(2010, 0, 11, 14, 31, 30, 5);
},
'should format a date as string using a pattern': function(date) {
assert.equal(
dateFormat.asString(dateFormat.DATETIME_FORMAT, date),
"11 01 2010 14:31:30.005"
);
},
'should default to the ISO8601 format': function(date) {
assert.equal(
dateFormat.asString(date),
'2010-01-11 14:31:30.005'
);
},
'should provide a ISO8601 with timezone offset format': function(date) {
date.getTimezoneOffset = function() { return -660; };
assert.equal(
dateFormat.asString(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, date),
"2010-01-11T14:31:30+1100"
);
date.getTimezoneOffset = function() { return 120; };
assert.equal(
dateFormat.asString(dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT, date),
"2010-01-11T14:31:30-0200"
);
},
'should provide a just-the-time format': function(date) {
assert.equal(
dateFormat.asString(dateFormat.ABSOLUTETIME_FORMAT, date),
'14:31:30.005'
);
},
'should provide a custom format': function(date) {
date.getTimezoneOffset = function() { return 120; };
assert.equal(
dateFormat.asString("O.SSS.ss.mm.hh.dd.MM.yy", date),
'-0200.005.30.31.14.11.01.10'
);
}
}
}).export(module);

72
test/debug-test.js Normal file
View File

@@ -0,0 +1,72 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, sandbox = require('sandboxed-module')
, fakeConsole = {
error: function(format, label, message) {
this.logged = [ format, label, message ];
}
}
, globals = function(debugValue) {
return {
process: {
env: {
'NODE_DEBUG': debugValue
}
},
console: fakeConsole
};
};
vows.describe('../lib/debug').addBatch({
'when NODE_DEBUG is set to log4js': {
topic: function() {
var debug = sandbox.require(
'../lib/debug',
{ 'globals': globals('log4js') }
);
fakeConsole.logged = [];
debug('cheese')('biscuits');
return fakeConsole.logged;
},
'it should log to console.error': function(logged) {
assert.equal(logged[0], 'LOG4JS: (%s) %s');
assert.equal(logged[1], 'cheese');
assert.equal(logged[2], 'biscuits');
}
},
'when NODE_DEBUG is set to not log4js': {
topic: function() {
var debug = sandbox.require(
'../lib/debug',
{ globals: globals('other_module') }
);
fakeConsole.logged = [];
debug('cheese')('biscuits');
return fakeConsole.logged;
},
'it should not log to console.error': function(logged) {
assert.equal(logged.length, 0);
}
},
'when NODE_DEBUG is not set': {
topic: function() {
var debug = sandbox.require(
'../lib/debug',
{ globals: globals(null) }
);
fakeConsole.logged = [];
debug('cheese')('biscuits');
return fakeConsole.logged;
},
'it should not log to console.error': function(logged) {
assert.equal(logged.length, 0);
}
}
}).exportTo(module);

View File

@@ -1,179 +1,280 @@
"use strict";
var vows = require('vows')
, fs = require('fs')
, path = require('path')
, sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js')
, assert = require('assert');
log4js.clearAppenders();
function remove(filename) {
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
}
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);
'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;
},
'with default fileAppender settings': {
topic: function() {
var that = this
, testFile = path.join(__dirname, '/fa-default-test.log')
, logger = log4js.getLogger('default-settings');
remove(testFile);
//log4js.configure({ appenders:[ { type: "file", filename: testFile, category: 'default-settings' } ] });
log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/file').appender(testFile), 'default-settings');
logger.info("This should be in the file.");
setTimeout(function() {
fs.readFile(testFile, "utf8", that.callback);
}, 100);
},
'should write log messages to the file': function(err, fileContents) {
assert.include(fileContents, "This should be in the file.\n");
},
'log messages should be in the basic layout format': function(err, fileContents) {
assert.match(fileContents, /\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /);
}
},
'with a max file size and no backups': {
topic: function() {
var testFile = path.join(__dirname, '/fa-maxFileSize-test.log')
, logger = log4js.getLogger('max-file-size')
, that = this;
remove(testFile);
remove(testFile + '.1');
//log file of 100 bytes maximum, no backups
log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 100, 0), 'max-file-size');
logger.info("This is the first log message.");
logger.info("This is an intermediate log message.");
logger.info("This is the second log message.");
//wait for the file system to catch up
setTimeout(function() {
fs.readFile(testFile, "utf8", that.callback);
}, 100);
},
'log file should only contain the second message': function(err, fileContents) {
assert.include(fileContents, "This is the second log message.\n");
assert.equal(fileContents.indexOf("This is the first log message."), -1);
},
'the number of files': {
topic: function() {
fs.readdir(__dirname, this.callback);
},
'starting with the test file name should be two': function(err, files) {
//there will always be one backup if you've specified a max log size
var logFiles = files.filter(function(file) { return file.indexOf('fa-maxFileSize-test.log') > -1; });
assert.equal(logFiles.length, 2);
}
}
},
'with a max file size and 2 backups': {
topic: function() {
var testFile = path.join(__dirname, '/fa-maxFileSize-with-backups-test.log')
, logger = log4js.getLogger('max-file-size-backups');
remove(testFile);
remove(testFile+'.1');
remove(testFile+'.2');
//log file of 50 bytes maximum, 2 backups
log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 50, 2), 'max-file-size-backups');
logger.info("This is the first log message.");
logger.info("This is the second log message.");
logger.info("This is the third log message.");
logger.info("This is the fourth log message.");
var that = this;
//give the system a chance to open the stream
setTimeout(function() {
fs.readdir(__dirname, function(err, files) {
if (files) {
that.callback(null, files.sort());
} else {
that.callback(err, files);
}
});
}, 200);
},
'the log files': {
topic: function(files) {
var logFiles = files.filter(function(file) { return file.indexOf('fa-maxFileSize-with-backups-test.log') > -1; });
return logFiles;
},
'should be 3': function (files) {
assert.equal(files.length, 3);
},
'should be named in sequence': function (files) {
assert.deepEqual(files, ['fa-maxFileSize-with-backups-test.log', 'fa-maxFileSize-with-backups-test.log.1', 'fa-maxFileSize-with-backups-test.log.2']);
},
'and the contents of the first file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[0]), "utf8", this.callback);
},
'should be the last log message': function(contents) {
assert.include(contents, 'This is the fourth log message.');
}
},
'and the contents of the second file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[1]), "utf8", this.callback);
},
'should be the third log message': function(contents) {
assert.include(contents, 'This is the third log message.');
}
},
'and the contents of the third file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[2]), "utf8", this.callback);
},
'should be the second log message': function(contents) {
assert.include(contents, 'This is the second log message.');
}
}
}
'does not add more than one `exit` listeners': function (initialCount) {
assert.ok(process.listeners('exit').length <= initialCount + 1);
}
},
'exit listener': {
topic: function() {
var exitListener
, openedFiles = []
, fileAppender = sandbox.require(
'../lib/appenders/file',
{
globals: {
process: {
on: function(evt, listener) {
exitListener = listener;
}
}
},
requires: {
'../streams': {
RollingFileStream: function(filename) {
openedFiles.push(filename);
this.end = function() {
openedFiles.shift();
};
this.on = function() {};
}
}
}
}
);
for (var i=0; i < 5; i += 1) {
fileAppender.appender('test' + i, null, 100);
}
assert.isNotEmpty(openedFiles);
exitListener();
return openedFiles;
},
'should close all open files': function(openedFiles) {
assert.isEmpty(openedFiles);
}
},
'with default fileAppender settings': {
topic: function() {
var that = this
, testFile = path.join(__dirname, '/fa-default-test.log')
, logger = log4js.getLogger('default-settings');
remove(testFile);
log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/file').appender(testFile), 'default-settings');
logger.info("This should be in the file.");
setTimeout(function() {
fs.readFile(testFile, "utf8", that.callback);
}, 100);
},
'should write log messages to the file': function(err, fileContents) {
assert.include(fileContents, "This should be in the file.\n");
},
'log messages should be in the basic layout format': function(err, fileContents) {
assert.match(
fileContents,
/\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /
);
}
},
'with a max file size and no backups': {
topic: function() {
var testFile = path.join(__dirname, '/fa-maxFileSize-test.log')
, logger = log4js.getLogger('max-file-size')
, that = this;
remove(testFile);
remove(testFile + '.1');
//log file of 100 bytes maximum, no backups
log4js.clearAppenders();
log4js.addAppender(
require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 100, 0),
'max-file-size'
);
logger.info("This is the first log message.");
logger.info("This is an intermediate log message.");
logger.info("This is the second log message.");
//wait for the file system to catch up
setTimeout(function() {
fs.readFile(testFile, "utf8", that.callback);
}, 100);
},
'log file should only contain the second message': function(err, fileContents) {
assert.include(fileContents, "This is the second log message.\n");
assert.equal(fileContents.indexOf("This is the first log message."), -1);
},
'the number of files': {
topic: function() {
fs.readdir(__dirname, this.callback);
},
'starting with the test file name should be two': function(err, files) {
//there will always be one backup if you've specified a max log size
var logFiles = files.filter(
function(file) { return file.indexOf('fa-maxFileSize-test.log') > -1; }
);
assert.equal(logFiles.length, 2);
}
}
},
'with a max file size and 2 backups': {
topic: function() {
var testFile = path.join(__dirname, '/fa-maxFileSize-with-backups-test.log')
, logger = log4js.getLogger('max-file-size-backups');
remove(testFile);
remove(testFile+'.1');
remove(testFile+'.2');
//log file of 50 bytes maximum, 2 backups
log4js.clearAppenders();
log4js.addAppender(
require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 50, 2),
'max-file-size-backups'
);
logger.info("This is the first log message.");
logger.info("This is the second log message.");
logger.info("This is the third log message.");
logger.info("This is the fourth log message.");
var that = this;
//give the system a chance to open the stream
setTimeout(function() {
fs.readdir(__dirname, function(err, files) {
if (files) {
that.callback(null, files.sort());
} else {
that.callback(err, files);
}
});
}, 200);
},
'the log files': {
topic: function(files) {
var logFiles = files.filter(
function(file) { return file.indexOf('fa-maxFileSize-with-backups-test.log') > -1; }
);
return logFiles;
},
'should be 3': function (files) {
assert.equal(files.length, 3);
},
'should be named in sequence': function (files) {
assert.deepEqual(files, [
'fa-maxFileSize-with-backups-test.log',
'fa-maxFileSize-with-backups-test.log.1',
'fa-maxFileSize-with-backups-test.log.2'
]);
},
'and the contents of the first file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[0]), "utf8", this.callback);
},
'should be the last log message': function(contents) {
assert.include(contents, 'This is the fourth log message.');
}
},
'and the contents of the second file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[1]), "utf8", this.callback);
},
'should be the third log message': function(contents) {
assert.include(contents, 'This is the third log message.');
}
},
'and the contents of the third file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[2]), "utf8", this.callback);
},
'should be the second log message': function(contents) {
assert.include(contents, 'This is the second log message.');
}
}
}
}
}).addBatch({
'configure' : {
'with fileAppender': {
topic: function() {
var log4js = require('../lib/log4js')
, logger;
//this config file defines one file appender (to ./tmp-tests.log)
//and sets the log level for "tests" to WARN
log4js.configure('test/log4js.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('tmp-tests.log', 'utf8', this.callback);
},
'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);
}
}
'configure' : {
'with fileAppender': {
topic: function() {
var log4js = require('../lib/log4js')
, logger;
//this config file defines one file appender (to ./tmp-tests.log)
//and sets the log level for "tests" to WARN
log4js.configure('./test/log4js.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('tmp-tests.log', 'utf8', this.callback);
},
'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);
}
}
}
}).addBatch({
'when underlying stream errors': {
topic: function() {
var consoleArgs
, errorHandler
, fileAppender = sandbox.require(
'../lib/appenders/file',
{
globals: {
console: {
error: function() {
consoleArgs = Array.prototype.slice.call(arguments);
}
}
},
requires: {
'../streams': {
RollingFileStream: function(filename) {
this.end = function() {};
this.on = function(evt, cb) {
if (evt === 'error') {
errorHandler = cb;
}
};
}
}
}
}
);
fileAppender.appender('test1.log', null, 100);
errorHandler({ error: 'aargh' });
return consoleArgs;
},
'should log the error to console.error': function(consoleArgs) {
assert.isNotEmpty(consoleArgs);
assert.equal(consoleArgs[0], 'log4js.fileAppender - Writing to file %s, error happened ');
assert.equal(consoleArgs[1], 'test1.log');
assert.equal(consoleArgs[2].error, 'aargh');
}
}
}).export(module);

View File

@@ -1,138 +1,259 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js')
, realLayouts = require('../lib/layouts')
, setupLogging = function(options, category, compressedLength) {
var fakeDgram = {
sent: false,
socket: {
packetLength: 0,
close: function() {
},
send: function(pkt, offset, pktLength, port, host) {
fakeDgram.sent = true;
this.packet = pkt;
this.offset = offset;
this.packetLength = pktLength;
this.port = port;
this.host = host;
}
},
createSocket: function(type) {
this.type = type;
return this.socket;
}
}
, fakeZlib = {
gzip: function(objectToCompress, callback) {
fakeZlib.uncompressed = objectToCompress;
if (compressedLength) {
callback(null, { length: compressedLength });
} else {
callback(null, "I've been compressed");
}
var fakeDgram = {
sent: false,
socket: {
packetLength: 0,
closed: false,
close: function() {
this.closed = true;
},
send: function(pkt, offset, pktLength, port, host) {
fakeDgram.sent = true;
this.packet = pkt;
this.offset = offset;
this.packetLength = pktLength;
this.port = port;
this.host = host;
}
},
createSocket: function(type) {
this.type = type;
return this.socket;
}
}
, fakeZlib = {
gzip: function(objectToCompress, callback) {
fakeZlib.uncompressed = objectToCompress;
if (this.shouldError) {
callback({ stack: "oh noes" });
return;
}
if (compressedLength) {
callback(null, { length: compressedLength });
} else {
callback(null, "I've been compressed");
}
}
}
, exitHandler
, fakeConsole = {
error: function(message) {
this.message = message;
}
}
, fakeLayouts = {
layout: function(type, options) {
this.type = type;
this.options = options;
return realLayouts.messagePassThroughLayout;
},
messagePassThroughLayout: realLayouts.messagePassThroughLayout
}
, appender = sandbox.require('../lib/appenders/gelf', {
requires: {
dgram: fakeDgram,
zlib: fakeZlib
}
requires: {
dgram: fakeDgram,
zlib: fakeZlib,
'../layouts': fakeLayouts
},
globals: {
process: {
on: function(evt, handler) {
if (evt === 'exit') {
exitHandler = handler;
}
}
},
console: fakeConsole
}
});
log4js.clearAppenders();
log4js.addAppender(appender.configure(options || {}), category || "gelf-test");
return {
dgram: fakeDgram,
compress: fakeZlib,
logger: log4js.getLogger(category || "gelf-test")
};
log4js.clearAppenders();
log4js.addAppender(appender.configure(options || {}), category || "gelf-test");
return {
dgram: fakeDgram,
compress: fakeZlib,
exitHandler: exitHandler,
console: fakeConsole,
layouts: fakeLayouts,
logger: log4js.getLogger(category || "gelf-test")
};
};
//log4js.configure({ doNotReplaceConsole: true });
vows.describe('log4js gelfAppender').addBatch({
'with default gelfAppender settings': {
topic: function() {
var setup = setupLogging();
setup.logger.info("This is a test");
return setup;
},
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should be sent via udp to the localhost gelf server': function(dgram) {
assert.equal(dgram.type, "udp4");
assert.equal(dgram.socket.host, "localhost");
assert.equal(dgram.socket.port, 12201);
assert.equal(dgram.socket.offset, 0);
assert.ok(dgram.socket.packetLength > 0, "Received blank message");
},
'should be compressed': function(dgram) {
assert.equal(dgram.socket.packet, "I've been compressed");
}
},
'the uncompressed log message': {
topic: function(setup) {
var message = JSON.parse(setup.compress.uncompressed);
return message;
},
'should be in the gelf format': function(message) {
assert.equal(message.version, '1.0');
assert.equal(message.host, require('os').hostname());
assert.equal(message.level, 6); //INFO
assert.equal(message.facility, 'nodejs-server');
assert.equal(message.full_message, message.short_message);
assert.equal(message.full_message, 'This is a test');
}
}
'with default gelfAppender settings': {
topic: function() {
var setup = setupLogging();
setup.logger.info("This is a test");
return setup;
},
'with a message longer than 8k': {
topic: function() {
var setup = setupLogging(undefined, undefined, 10240);
setup.logger.info("Blah.");
return setup;
},
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should not be sent': function(dgram) {
assert.equal(dgram.sent, false);
}
}
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should be sent via udp to the localhost gelf server': function(dgram) {
assert.equal(dgram.type, "udp4");
assert.equal(dgram.socket.host, "localhost");
assert.equal(dgram.socket.port, 12201);
assert.equal(dgram.socket.offset, 0);
assert.ok(dgram.socket.packetLength > 0, "Received blank message");
},
'should be compressed': function(dgram) {
assert.equal(dgram.socket.packet, "I've been compressed");
}
},
'with non-default options': {
topic: function() {
var setup = setupLogging({
host: 'somewhere',
port: 12345,
hostname: 'cheese',
facility: 'nonsense'
});
setup.logger.debug("Just testing.");
return setup;
},
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should pick up the options': function(dgram) {
assert.equal(dgram.socket.host, 'somewhere');
assert.equal(dgram.socket.port, 12345);
}
},
'the uncompressed packet': {
topic: function(setup) {
var message = JSON.parse(setup.compress.uncompressed);
return message;
},
'should pick up the options': function(message) {
assert.equal(message.host, 'cheese');
assert.equal(message.facility, 'nonsense');
}
}
'the uncompressed log message': {
topic: function(setup) {
var message = JSON.parse(setup.compress.uncompressed);
return message;
},
'should be in the gelf format': function(message) {
assert.equal(message.version, '1.0');
assert.equal(message.host, require('os').hostname());
assert.equal(message.level, 6); //INFO
assert.equal(message.facility, 'nodejs-server');
assert.equal(message.full_message, message.short_message);
assert.equal(message.full_message, 'This is a test');
}
}
}).export(module);
},
'with a message longer than 8k': {
topic: function() {
var setup = setupLogging(undefined, undefined, 10240);
setup.logger.info("Blah.");
return setup;
},
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should not be sent': function(dgram) {
assert.equal(dgram.sent, false);
}
}
},
'with non-default options': {
topic: function() {
var setup = setupLogging({
host: 'somewhere',
port: 12345,
hostname: 'cheese',
facility: 'nonsense'
});
setup.logger.debug("Just testing.");
return setup;
},
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should pick up the options': function(dgram) {
assert.equal(dgram.socket.host, 'somewhere');
assert.equal(dgram.socket.port, 12345);
}
},
'the uncompressed packet': {
topic: function(setup) {
var message = JSON.parse(setup.compress.uncompressed);
return message;
},
'should pick up the options': function(message) {
assert.equal(message.host, 'cheese');
assert.equal(message.facility, 'nonsense');
}
}
},
'on process.exit': {
topic: function() {
var setup = setupLogging();
setup.exitHandler();
return setup;
},
'should close open sockets': function(setup) {
assert.isTrue(setup.dgram.socket.closed);
}
},
'on zlib error': {
topic: function() {
var setup = setupLogging();
setup.compress.shouldError = true;
setup.logger.info('whatever');
return setup;
},
'should output to console.error': function(setup) {
assert.equal(setup.console.message, 'oh noes');
}
},
'with layout in configuration': {
topic: function() {
var setup = setupLogging({
layout: {
type: 'madeuplayout',
earlgrey: 'yes, please'
}
});
return setup;
},
'should pass options to layout': function(setup) {
assert.equal(setup.layouts.type, 'madeuplayout');
assert.equal(setup.layouts.options.earlgrey, 'yes, please');
}
},
'with custom fields options': {
topic: function() {
var setup = setupLogging({
host: 'somewhere',
port: 12345,
hostname: 'cheese',
facility: 'nonsense',
customFields: {
_every1: 'Hello every one',
_every2: 'Hello every two'
}
});
var myFields = {
GELF: true,
_every2: 'Overwritten!',
_myField: 'This is my field!'
};
setup.logger.debug(myFields, "Just testing.");
return setup;
},
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should pick up the options': function(dgram) {
assert.equal(dgram.socket.host, 'somewhere');
assert.equal(dgram.socket.port, 12345);
}
},
'the uncompressed packet': {
topic: function(setup) {
var message = JSON.parse(setup.compress.uncompressed);
return message;
},
'should pick up the options': function(message) {
assert.equal(message.host, 'cheese');
assert.equal(message.facility, 'nonsense');
assert.equal(message._every1, 'Hello every one'); // the default value
assert.equal(message._every2, 'Overwritten!'); // the overwritten value
assert.equal(message._myField, 'This is my field!'); // the value for this message only
assert.equal(message.short_message, 'Just testing.'); // skip the field object
assert.equal(message.full_message, 'Just testing.'); // should be as same as short_message
}
}
}
}).export(module);

View File

@@ -1,85 +1,86 @@
var vows = require('vows'),
assert = require('assert');
"use strict";
var vows = require('vows')
, assert = require('assert');
vows.describe('log4js global loglevel').addBatch({
'global loglevel' : {
topic: function() {
var log4js = require('../lib/log4js');
return log4js;
},
'global loglevel' : {
topic: function() {
var log4js = require('../lib/log4js');
return log4js;
},
'set global loglevel on creation': function(log4js) {
var log1 = log4js.getLogger('log1');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
log4js.setGlobalLogLevel(level);
assert.equal(log1.level.toString(), level);
'set global loglevel on creation': function(log4js) {
var log1 = log4js.getLogger('log1');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
var log2 = log4js.getLogger('log2');
assert.equal(log2.level.toString(), level);
},
'global change loglevel': function(log4js) {
var log1 = log4js.getLogger('log1');
var log2 = log4js.getLogger('log2');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
log4js.setGlobalLogLevel(level);
assert.equal(log1.level.toString(), level);
assert.equal(log2.level.toString(), level);
},
log4js.setGlobalLogLevel(level);
assert.equal(log1.level.toString(), level);
var log2 = log4js.getLogger('log2');
assert.equal(log2.level.toString(), level);
},
'global change loglevel': function(log4js) {
var log1 = log4js.getLogger('log1');
var log2 = log4js.getLogger('log2');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
log4js.setGlobalLogLevel(level);
assert.equal(log1.level.toString(), level);
assert.equal(log2.level.toString(), level);
},
'override loglevel': function(log4js) {
var log1 = log4js.getLogger('log1');
var log2 = log4js.getLogger('log2');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
var oldLevel = log1.level.toString();
assert.equal(log2.level.toString(), oldLevel);
log2.setLevel(level);
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), level);
assert.notEqual(oldLevel, level);
log2.removeLevel();
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), oldLevel);
},
'preload loglevel': function(log4js) {
var log1 = log4js.getLogger('log1');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
var oldLevel = log1.level.toString();
log4js.getLogger('log2').setLevel(level);
assert.equal(log1.level.toString(), oldLevel);
// get again same logger but as different variable
var log2 = log4js.getLogger('log2');
assert.equal(log2.level.toString(), level);
assert.notEqual(oldLevel, level);
log2.removeLevel();
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), oldLevel);
}
'override loglevel': function(log4js) {
var log1 = log4js.getLogger('log1');
var log2 = log4js.getLogger('log2');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
var oldLevel = log1.level.toString();
assert.equal(log2.level.toString(), oldLevel);
log2.setLevel(level);
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), level);
assert.notEqual(oldLevel, level);
log2.removeLevel();
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), oldLevel);
},
'preload loglevel': function(log4js) {
var log1 = log4js.getLogger('log1');
var level = 'OFF';
if (log1.level.toString() == level) {
level = 'TRACE';
}
assert.notEqual(log1.level.toString(), level);
var oldLevel = log1.level.toString();
log4js.getLogger('log2').setLevel(level);
assert.equal(log1.level.toString(), oldLevel);
// get again same logger but as different variable
var log2 = log4js.getLogger('log2');
assert.equal(log2.level.toString(), level);
assert.notEqual(oldLevel, level);
log2.removeLevel();
assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), oldLevel);
}
}
}).export(module);

View File

@@ -1,9 +1,11 @@
var vows = require('vows');
var assert = require('assert');
var sandbox = require('sandboxed-module');
"use strict";
var vows = require('vows')
, assert = require('assert')
, sandbox = require('sandboxed-module');
function fancyResultingHookioAppender(opts) {
var result = { ons: {}, emissions: {}, logged: [], configs: [] };
function fancyResultingHookioAppender(hookNotReady) {
var emitHook = !hookNotReady
, result = { ons: {}, emissions: {}, logged: [], configs: [] };
var fakeLog4Js = {
appenderMakers: {}
@@ -13,7 +15,7 @@ function fancyResultingHookioAppender(opts) {
result.actualLoggerConfig = config;
return function log(logEvent) {
result.logged.push(logEvent);
}
};
};
};
@@ -23,7 +25,7 @@ function fancyResultingHookioAppender(opts) {
};
fakeHookIo.Hook.prototype.on = function (eventName, functionToExec) {
result.ons[eventName] = { functionToExec: functionToExec };
if (eventName === 'hook::ready') {
if (emitHook && eventName === 'hook::ready') {
functionToExec();
}
};
@@ -32,7 +34,8 @@ function fancyResultingHookioAppender(opts) {
result.emissions[eventName].push({data: data});
var on = '*::' + eventName;
if (eventName !== 'hook::ready' && result.ons[on]) {
result.ons[on].callingCount = result.ons[on].callingCount ? result.ons[on].callingCount += 1 : 1;
result.ons[on].callingCount =
result.ons[on].callingCount ? result.ons[on].callingCount += 1 : 1;
result.ons[on].functionToExec(data);
}
};
@@ -52,9 +55,28 @@ vows.describe('log4js hookioAppender').addBatch({
'master': {
topic: function() {
var fancy = fancyResultingHookioAppender();
var logger = fancy.theModule.configure({ name: 'ohno', mode: 'master', 'hook-port': 5001, 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'});
var logger = fancy.theModule.configure(
{
name: 'ohno',
mode: 'master',
'hook-port': 5001,
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;
},
@@ -84,9 +106,21 @@ vows.describe('log4js hookioAppender').addBatch({
'should emit logging events to the master': {
topic: function() {
var fancy = fancyResultingHookioAppender();
var logger = fancy.theModule.configure({ 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'});
var logger = fancy.theModule.configure({
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;
},
@@ -97,5 +131,46 @@ vows.describe('log4js hookioAppender').addBatch({
assert.isUndefined(result.ons['*::ohno::log']);
}
}
},
'when hook not ready': {
topic: function() {
var fancy = fancyResultingHookioAppender(true)
, logger = fancy.theModule.configure({
name: 'ohno',
mode: 'worker'
});
logger({
level: { levelStr: 'INFO' },
data: "something",
startTime: '2011-10-27T03:45:12.031Z'
});
return fancy;
},
'should buffer the log events': function(fancy) {
assert.isUndefined(fancy.theResult.emissions['ohno::log']);
},
},
'when hook ready': {
topic: function() {
var fancy = fancyResultingHookioAppender(true)
, logger = fancy.theModule.configure({
name: 'ohno',
mode: 'worker'
});
logger({
level: { levelStr: 'INFO' },
data: "something",
startTime: '2011-10-27T03:45:12.031Z'
});
fancy.theResult.ons['hook::ready'].functionToExec();
return fancy;
},
'should emit the buffered events': function(fancy) {
assert.equal(fancy.theResult.emissions['ohno::log'].length, 1);
}
}
}).exportTo(module);

View File

@@ -1,268 +1,296 @@
var vows = require('vows'),
assert = require('assert');
"use strict";
var vows = require('vows')
, assert = require('assert');
//used for patternLayout tests.
function test(args, pattern, value) {
var layout = args[0]
, event = args[1]
, tokens = args[2];
assert.equal(layout(pattern, tokens)(event), value);
var layout = args[0]
, event = args[1]
, tokens = args[2];
assert.equal(layout(pattern, tokens)(event), value);
}
vows.describe('log4js layouts').addBatch({
'colouredLayout': {
topic: function() {
return require('../lib/layouts').colouredLayout;
},
'should apply level colour codes to output': function(layout) {
var output = layout({
data: ["nonsense"],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level: {
toString: function() { return "ERROR"; }
}
});
assert.equal(output, '\033[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \033[39mnonsense');
},
'should support the console.log format for the message': function(layout) {
var output = layout({
data: ["thing %d", 2],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level: {
toString: function() { return "ERROR"; }
}
});
assert.equal(output, '\033[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \033[39mthing 2');
}
'colouredLayout': {
topic: function() {
return require('../lib/layouts').colouredLayout;
},
'messagePassThroughLayout': {
topic: function() {
return require('../lib/layouts').messagePassThroughLayout;
},
'should take a logevent and output only the message' : function(layout) {
assert.equal(layout({
data: ["nonsense"],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level: {
colour: "green",
toString: function() { return "ERROR"; }
}
}), "nonsense");
},
'should support the console.log format for the message' : function(layout) {
assert.equal(layout({
data: ["thing %d", 1, "cheese"]
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45)
, categoryName: "cheese"
, level : {
colour: "green"
, toString: function() { return "ERROR"; }
}
}), "thing 1 'cheese'");
},
'should output the first item even if it is not a string': function(layout) {
assert.equal(layout({
data: [ { thing: 1} ]
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45)
, categoryName: "cheese"
, level: {
colour: "green"
, toString: function() { return "ERROR"; }
}
}), "{ 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);
}
'should apply level colour codes to output': function(layout) {
var output = layout({
data: ["nonsense"],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level: {
toString: function() { return "ERROR"; }
}
});
assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mnonsense');
},
'basicLayout': {
topic: function() {
var layout = require('../lib/layouts').basicLayout,
event = {
data: ['this is a test'],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "tests",
level: {
toString: function() { return "DEBUG"; }
}
};
return [layout, event];
},
'should take a logevent and output a formatted string': function(args) {
var layout = args[0], event = args[1];
assert.equal(layout(event), "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test");
},
'should output a stacktrace, message if the event has an error attached': function(args) {
var layout = args[0], event = args[1], output, lines,
error = new Error("Some made-up error"),
stack = error.stack.split(/\n/);
event.data = ['this is a test', error];
output = layout(event);
lines = output.split(/\n/);
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]");
for (var i = 1; i < stack.length; i++) {
assert.equal(lines[i+2], stack[i+1]);
}
},
'should output any extra data in the log event as util.inspect strings': function(args) {
var layout = args[0], event = args[1], output, lines;
event.data = ['this is a test', {
name: 'Cheese',
message: 'Gorgonzola smells.'
}];
output = layout(event);
assert.equal(output, "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test { name: 'Cheese', message: 'Gorgonzola smells.' }");
'should support the console.log format for the message': function(layout) {
var output = layout({
data: ["thing %d", 2],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level: {
toString: function() { return "ERROR"; }
}
},
'patternLayout': {
topic: function() {
var event = {
data: ['this is a test'],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "multiple.levels.of.tests",
level: {
toString: function() { return "DEBUG"; }
}
}, layout = require('../lib/layouts').patternLayout
, tokens = {
testString: 'testStringToken',
testFunction: function() { return 'testFunctionToken'; }
};
return [layout, event, tokens];
},
'should default to "time logLevel loggerName - message"': function(args) {
test(args, null, "14:18:30 DEBUG multiple.levels.of.tests - this is a test\n");
},
'%r should output time only': function(args) {
test(args, '%r', '14:18:30');
},
'%p should output the log level': function(args) {
test(args, '%p', 'DEBUG');
},
'%c should output the log category': function(args) {
test(args, '%c', 'multiple.levels.of.tests');
},
'%m should output the log data': function(args) {
test(args, '%m', 'this is a test');
},
'%n should output a new line': function(args) {
test(args, '%n', '\n');
},
'%c should handle category names like java-style package names': function(args) {
test(args, '%c{1}', 'tests');
test(args, '%c{2}', 'of.tests');
test(args, '%c{3}', 'levels.of.tests');
test(args, '%c{4}', 'multiple.levels.of.tests');
test(args, '%c{5}', 'multiple.levels.of.tests');
test(args, '%c{99}', 'multiple.levels.of.tests');
},
'%d should output the date in ISO8601 format': function(args) {
test(args, '%d', '2010-12-05 14:18:30.045');
},
'%d should allow for format specification': function(args) {
test(args, '%d{ISO8601}', '2010-12-05 14:18:30.045');
test(args, '%d{ABSOLUTE}', '14:18:30.045');
test(args, '%d{DATE}', '05 12 2010 14:18:30.045');
test(args, '%d{yyyy MM dd}', '2010 12 05');
test(args, '%d{yyyy MM dd hh mm ss SSS}', '2010 12 05 14 18 30 045');
},
'%% should output %': function(args) {
test(args, '%%', '%');
},
'should output anything not preceded by % as literal': function(args) {
test(args, 'blah blah blah', 'blah blah blah');
},
'should handle complicated patterns': function(args) {
test(args,
'%m%n %c{2} at %d{ABSOLUTE} cheese %p%n',
'this is a test\n of.tests at 14:18:30.045 cheese DEBUG\n'
);
},
'should truncate fields if specified': function(args) {
test(args, '%.4m', 'this');
test(args, '%.7m', 'this is');
test(args, '%.9m', 'this is a');
test(args, '%.14m', 'this is a test');
test(args, '%.2919102m', 'this is a test');
},
'should pad fields if specified': function(args) {
test(args, '%10p', ' DEBUG');
test(args, '%8p', ' DEBUG');
test(args, '%6p', ' DEBUG');
test(args, '%4p', 'DEBUG');
test(args, '%-4p', 'DEBUG');
test(args, '%-6p', 'DEBUG ');
test(args, '%-8p', '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');
},
});
assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mthing 2');
}
},
'messagePassThroughLayout': {
topic: function() {
return require('../lib/layouts').messagePassThroughLayout;
},
'should take a logevent and output only the message' : function(layout) {
assert.equal(layout({
data: ["nonsense"],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level: {
colour: "green",
toString: function() { return "ERROR"; }
}
}), "nonsense");
},
'should support the console.log format for the message' : function(layout) {
assert.equal(layout({
data: ["thing %d", 1, "cheese"],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level : {
colour: "green",
toString: function() { return "ERROR"; }
}
}), "thing 1 cheese");
},
'should output the first item even if it is not a string': function(layout) {
assert.equal(layout({
data: [ { thing: 1} ],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "cheese",
level: {
colour: "green",
toString: function() { return "ERROR"; }
}
}), "{ 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': {
topic: function() {
var layout = require('../lib/layouts').basicLayout,
event = {
data: ['this is a test'],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "tests",
level: {
toString: function() { return "DEBUG"; }
}
};
return [layout, event];
},
'should take a logevent and output a formatted string': function(args) {
var layout = args[0], event = args[1];
assert.equal(layout(event), "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test");
},
'should output a stacktrace, message if the event has an error attached': function(args) {
var layout = args[0], event = args[1], output, lines,
error = new Error("Some made-up error"),
stack = error.stack.split(/\n/);
event.data = ['this is a test', error];
output = layout(event);
lines = output.split(/\n/);
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]"
);
for (var i = 1; i < stack.length; i++) {
assert.equal(lines[i+2], stack[i+1]);
}
},
'should output any extra data in the log event as util.inspect strings': function(args) {
var layout = args[0], event = args[1], output, lines;
event.data = ['this is a test', {
name: 'Cheese',
message: 'Gorgonzola smells.'
}];
output = layout(event);
assert.equal(
output,
"[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test " +
"{ name: 'Cheese', message: 'Gorgonzola smells.' }"
);
}
},
'patternLayout': {
topic: function() {
var event = {
data: ['this is a test'],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "multiple.levels.of.tests",
level: {
toString: function() { return "DEBUG"; }
}
}, layout = require('../lib/layouts').patternLayout
, tokens = {
testString: 'testStringToken',
testFunction: function() { return 'testFunctionToken'; },
fnThatUsesLogEvent: function(logEvent) { return logEvent.level.toString(); }
};
return [layout, event, tokens];
},
'should default to "time logLevel loggerName - message"': function(args) {
test(args, null, "14:18:30 DEBUG multiple.levels.of.tests - this is a test\n");
},
'%r should output time only': function(args) {
test(args, '%r', '14:18:30');
},
'%p should output the log level': function(args) {
test(args, '%p', 'DEBUG');
},
'%c should output the log category': function(args) {
test(args, '%c', 'multiple.levels.of.tests');
},
'%m should output the log data': function(args) {
test(args, '%m', 'this is a test');
},
'%n should output a new line': function(args) {
test(args, '%n', '\n');
},
'%h should output hostname' : function(args) {
test(args, '%h', require('os').hostname().toString());
},
'%c should handle category names like java-style package names': function(args) {
test(args, '%c{1}', 'tests');
test(args, '%c{2}', 'of.tests');
test(args, '%c{3}', 'levels.of.tests');
test(args, '%c{4}', 'multiple.levels.of.tests');
test(args, '%c{5}', 'multiple.levels.of.tests');
test(args, '%c{99}', 'multiple.levels.of.tests');
},
'%d should output the date in ISO8601 format': function(args) {
test(args, '%d', '2010-12-05 14:18:30.045');
},
'%d should allow for format specification': function(args) {
test(args, '%d{ISO8601_WITH_TZ_OFFSET}', '2010-12-05T14:18:30-0000');
test(args, '%d{ISO8601}', '2010-12-05 14:18:30.045');
test(args, '%d{ABSOLUTE}', '14:18:30.045');
test(args, '%d{DATE}', '05 12 2010 14:18:30.045');
test(args, '%d{yy MM dd hh mm ss}', '10 12 05 14 18 30');
test(args, '%d{yyyy MM dd}', '2010 12 05');
test(args, '%d{yyyy MM dd hh mm ss SSS}', '2010 12 05 14 18 30 045');
},
'%% should output %': function(args) {
test(args, '%%', '%');
},
'should output anything not preceded by % as literal': function(args) {
test(args, 'blah blah blah', 'blah blah blah');
},
'should output the original string if no replacer matches the token': function(args) {
test(args, '%a{3}', 'a{3}');
},
'should handle complicated patterns': function(args) {
test(args,
'%m%n %c{2} at %d{ABSOLUTE} cheese %p%n',
'this is a test\n of.tests at 14:18:30.045 cheese DEBUG\n'
);
},
'should truncate fields if specified': function(args) {
test(args, '%.4m', 'this');
test(args, '%.7m', 'this is');
test(args, '%.9m', 'this is a');
test(args, '%.14m', 'this is a test');
test(args, '%.2919102m', 'this is a test');
},
'should pad fields if specified': function(args) {
test(args, '%10p', ' DEBUG');
test(args, '%8p', ' DEBUG');
test(args, '%6p', ' DEBUG');
test(args, '%4p', 'DEBUG');
test(args, '%-4p', 'DEBUG');
test(args, '%-6p', 'DEBUG ');
test(args, '%-8p', 'DEBUG ');
test(args, '%-10p', 'DEBUG ');
},
'%[%r%] should output colored time': function(args) {
test(args, '%[%r%]', '\x1B[36m14:18:30\x1B[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{fnThatUsesLogEvent} should be able to use the logEvent': function(args) {
test(args, '%x{fnThatUsesLogEvent}', 'DEBUG');
},
'%x should output the string stored in tokens': function(args) {
test(args, '%x', '%x');
},
},
'layout makers': {
topic: require('../lib/layouts'),
'should have a maker for each layout': function(layouts) {
assert.ok(layouts.layout("messagePassThrough"));
assert.ok(layouts.layout("basic"));
assert.ok(layouts.layout("colored"));
assert.ok(layouts.layout("coloured"));
assert.ok(layouts.layout("pattern"));
}
}
}).export(module);

View File

@@ -1,210 +1,404 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, levels = require('../lib/levels');
function assertThat(level) {
function assertForEach(assertion, test, otherLevels) {
otherLevels.forEach(function(other) {
assertion.call(assert, test.call(level, other));
});
}
function assertForEach(assertion, test, otherLevels) {
otherLevels.forEach(function(other) {
assertion.call(assert, test.call(level, other));
});
}
return {
isLessThanOrEqualTo: function(levels) {
assertForEach(assert.isTrue, level.isLessThanOrEqualTo, levels);
},
isNotLessThanOrEqualTo: function(levels) {
assertForEach(assert.isFalse, level.isLessThanOrEqualTo, levels);
},
isGreaterThanOrEqualTo: function(levels) {
assertForEach(assert.isTrue, level.isGreaterThanOrEqualTo, levels);
},
isNotGreaterThanOrEqualTo: function(levels) {
assertForEach(assert.isFalse, level.isGreaterThanOrEqualTo, levels);
},
isEqualTo: function(levels) {
assertForEach(assert.isTrue, level.isEqualTo, levels);
},
isNotEqualTo: function(levels) {
assertForEach(assert.isFalse, level.isEqualTo, levels);
}
};
return {
isLessThanOrEqualTo: function(levels) {
assertForEach(assert.isTrue, level.isLessThanOrEqualTo, levels);
},
isNotLessThanOrEqualTo: function(levels) {
assertForEach(assert.isFalse, level.isLessThanOrEqualTo, levels);
},
isGreaterThanOrEqualTo: function(levels) {
assertForEach(assert.isTrue, level.isGreaterThanOrEqualTo, levels);
},
isNotGreaterThanOrEqualTo: function(levels) {
assertForEach(assert.isFalse, level.isGreaterThanOrEqualTo, levels);
},
isEqualTo: function(levels) {
assertForEach(assert.isTrue, level.isEqualTo, levels);
},
isNotEqualTo: function(levels) {
assertForEach(assert.isFalse, level.isEqualTo, levels);
}
};
}
vows.describe('levels').addBatch({
'values': {
topic: levels,
'should define some levels': function(levels) {
assert.isNotNull(levels.ALL);
assert.isNotNull(levels.TRACE);
assert.isNotNull(levels.DEBUG);
assert.isNotNull(levels.INFO);
assert.isNotNull(levels.WARN);
assert.isNotNull(levels.ERROR);
assert.isNotNull(levels.FATAL);
assert.isNotNull(levels.OFF);
},
'ALL': {
topic: levels.ALL,
'should be less than the other levels': function(all) {
assertThat(all).isLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
},
'should be greater than no levels': function(all) {
assertThat(all).isNotGreaterThanOrEqualTo([levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
},
'should only be equal to ALL': function(all) {
assertThat(all).isEqualTo([levels.toLevel("ALL")]);
assertThat(all).isNotEqualTo([levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
}
},
'TRACE': {
topic: levels.TRACE,
'should be less than DEBUG': function(trace) {
assertThat(trace).isLessThanOrEqualTo([levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
assertThat(trace).isNotLessThanOrEqualTo([levels.ALL]);
},
'should be greater than ALL': function(trace) {
assertThat(trace).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
assertThat(trace).isNotGreaterThanOrEqualTo([levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
},
'should only be equal to TRACE': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("TRACE")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
}
},
'DEBUG': {
topic: levels.DEBUG,
'should be less than INFO': function(debug) {
assertThat(debug).isLessThanOrEqualTo([levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
assertThat(debug).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE]);
},
'should be greater than TRACE': function(debug) {
assertThat(debug).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
assertThat(debug).isNotGreaterThanOrEqualTo([levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
},
'should only be equal to DEBUG': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("DEBUG")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
}
},
'INFO': {
topic: levels.INFO,
'should be less than WARN': function(info) {
assertThat(info).isLessThanOrEqualTo([levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
assertThat(info).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
},
'should be greater than DEBUG': function(info) {
assertThat(info).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
assertThat(info).isNotGreaterThanOrEqualTo([levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
},
'should only be equal to INFO': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("INFO")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]);
}
},
'WARN': {
topic: levels.WARN,
'should be less than ERROR': function(warn) {
assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
assertThat(warn).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO]);
},
'should be greater than INFO': function(warn) {
assertThat(warn).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO]);
assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
},
'should only be equal to WARN': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("WARN")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.ERROR, levels.FATAL, levels.OFF]);
}
},
'ERROR': {
topic: levels.ERROR,
'should be less than FATAL': function(error) {
assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.OFF]);
assertThat(error).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN]);
},
'should be greater than WARN': function(error) {
assertThat(error).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN]);
assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.OFF]);
},
'should only be equal to ERROR': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("ERROR")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.FATAL, levels.OFF]);
}
},
'FATAL': {
topic: levels.FATAL,
'should be less than OFF': function(fatal) {
assertThat(fatal).isLessThanOrEqualTo([levels.OFF]);
assertThat(fatal).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR]);
},
'should be greater than ERROR': function(fatal) {
assertThat(fatal).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR]);
assertThat(fatal).isNotGreaterThanOrEqualTo([levels.OFF]);
},
'should only be equal to FATAL': function(fatal) {
assertThat(fatal).isEqualTo([levels.toLevel("FATAL")]);
assertThat(fatal).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.OFF]);
}
},
'OFF': {
topic: levels.OFF,
'should not be less than anything': function(off) {
assertThat(off).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL]);
},
'should be greater than everything': function(off) {
assertThat(off).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL]);
},
'should only be equal to OFF': function(off) {
assertThat(off).isEqualTo([levels.toLevel("OFF")]);
assertThat(off).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL]);
}
}
'values': {
topic: levels,
'should define some levels': function(levels) {
assert.isNotNull(levels.ALL);
assert.isNotNull(levels.TRACE);
assert.isNotNull(levels.DEBUG);
assert.isNotNull(levels.INFO);
assert.isNotNull(levels.WARN);
assert.isNotNull(levels.ERROR);
assert.isNotNull(levels.FATAL);
assert.isNotNull(levels.OFF);
},
'isGreaterThanOrEqualTo': {
topic: levels.INFO,
'should handle string arguments': function(info) {
assertThat(info).isGreaterThanOrEqualTo(["all", "trace", "debug"]);
assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
}
'ALL': {
topic: levels.ALL,
'should be less than the other levels': function(all) {
assertThat(all).isLessThanOrEqualTo(
[
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
},
'should be greater than no levels': function(all) {
assertThat(all).isNotGreaterThanOrEqualTo(
[
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
},
'should only be equal to ALL': function(all) {
assertThat(all).isEqualTo([levels.toLevel("ALL")]);
assertThat(all).isNotEqualTo(
[
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
}
},
'isLessThanOrEqualTo': {
topic: levels.INFO,
'should handle string arguments': function(info) {
assertThat(info).isNotLessThanOrEqualTo(["all", "trace", "debug"]);
assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
}
'TRACE': {
topic: levels.TRACE,
'should be less than DEBUG': function(trace) {
assertThat(trace).isLessThanOrEqualTo(
[
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
assertThat(trace).isNotLessThanOrEqualTo([levels.ALL]);
},
'should be greater than ALL': function(trace) {
assertThat(trace).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
assertThat(trace).isNotGreaterThanOrEqualTo(
[
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
},
'should only be equal to TRACE': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("TRACE")]);
assertThat(trace).isNotEqualTo(
[
levels.ALL,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
}
},
'toLevel': {
'with lowercase argument': {
topic: levels.toLevel("debug"),
'should take the string and return the corresponding level': function(level) {
assert.equal(level, levels.DEBUG);
}
},
'with uppercase argument': {
topic: levels.toLevel("DEBUG"),
'should take the string and return the corresponding level': function(level) {
assert.equal(level, levels.DEBUG);
}
},
'with varying case': {
topic: levels.toLevel("DeBuG"),
'should take the string and return the corresponding level': function(level) {
assert.equal(level, levels.DEBUG);
}
},
'with unrecognised argument': {
topic: levels.toLevel("cheese"),
'should return undefined': function(level) {
assert.isUndefined(level);
}
},
'with unrecognised argument and default value': {
topic: levels.toLevel("cheese", levels.DEBUG),
'should return default value': function(level) {
assert.equal(level, levels.DEBUG);
}
}
'DEBUG': {
topic: levels.DEBUG,
'should be less than INFO': function(debug) {
assertThat(debug).isLessThanOrEqualTo(
[
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
assertThat(debug).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE]);
},
'should be greater than TRACE': function(debug) {
assertThat(debug).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
assertThat(debug).isNotGreaterThanOrEqualTo(
[
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
},
'should only be equal to DEBUG': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("DEBUG")]);
assertThat(trace).isNotEqualTo(
[
levels.ALL,
levels.TRACE,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]
);
}
},
'INFO': {
topic: levels.INFO,
'should be less than WARN': function(info) {
assertThat(info).isLessThanOrEqualTo([
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]);
assertThat(info).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
},
'should be greater than DEBUG': function(info) {
assertThat(info).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
assertThat(info).isNotGreaterThanOrEqualTo([
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]);
},
'should only be equal to INFO': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("INFO")]);
assertThat(trace).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]);
}
},
'WARN': {
topic: levels.WARN,
'should be less than ERROR': function(warn) {
assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
assertThat(warn).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO
]);
},
'should be greater than INFO': function(warn) {
assertThat(warn).isGreaterThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO
]);
assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
},
'should only be equal to WARN': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("WARN")]);
assertThat(trace).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.ERROR,
levels.FATAL,
levels.OFF
]);
}
},
'ERROR': {
topic: levels.ERROR,
'should be less than FATAL': function(error) {
assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.OFF]);
assertThat(error).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN
]);
},
'should be greater than WARN': function(error) {
assertThat(error).isGreaterThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN
]);
assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.OFF]);
},
'should only be equal to ERROR': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("ERROR")]);
assertThat(trace).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.FATAL,
levels.OFF
]);
}
},
'FATAL': {
topic: levels.FATAL,
'should be less than OFF': function(fatal) {
assertThat(fatal).isLessThanOrEqualTo([levels.OFF]);
assertThat(fatal).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR
]);
},
'should be greater than ERROR': function(fatal) {
assertThat(fatal).isGreaterThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR
]);
assertThat(fatal).isNotGreaterThanOrEqualTo([levels.OFF]);
},
'should only be equal to FATAL': function(fatal) {
assertThat(fatal).isEqualTo([levels.toLevel("FATAL")]);
assertThat(fatal).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.OFF
]);
}
},
'OFF': {
topic: levels.OFF,
'should not be less than anything': function(off) {
assertThat(off).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL
]);
},
'should be greater than everything': function(off) {
assertThat(off).isGreaterThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL
]);
},
'should only be equal to OFF': function(off) {
assertThat(off).isEqualTo([levels.toLevel("OFF")]);
assertThat(off).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL
]);
}
}
},
'isGreaterThanOrEqualTo': {
topic: levels.INFO,
'should handle string arguments': function(info) {
assertThat(info).isGreaterThanOrEqualTo(["all", "trace", "debug"]);
assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
}
},
'isLessThanOrEqualTo': {
topic: levels.INFO,
'should handle string arguments': function(info) {
assertThat(info).isNotLessThanOrEqualTo(["all", "trace", "debug"]);
assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
}
},
'isEqualTo': {
topic: levels.INFO,
'should handle string arguments': function(info) {
assertThat(info).isEqualTo(["info", "INFO", "iNfO"]);
}
},
'toLevel': {
'with lowercase argument': {
topic: levels.toLevel("debug"),
'should take the string and return the corresponding level': function(level) {
assert.equal(level, levels.DEBUG);
}
},
'with uppercase argument': {
topic: levels.toLevel("DEBUG"),
'should take the string and return the corresponding level': function(level) {
assert.equal(level, levels.DEBUG);
}
},
'with varying case': {
topic: levels.toLevel("DeBuG"),
'should take the string and return the corresponding level': function(level) {
assert.equal(level, levels.DEBUG);
}
},
'with unrecognised argument': {
topic: levels.toLevel("cheese"),
'should return undefined': function(level) {
assert.isUndefined(level);
}
},
'with unrecognised argument and default value': {
topic: levels.toLevel("cheese", levels.DEBUG),
'should return default value': function(level) {
assert.equal(level, levels.DEBUG);
}
}
}
}).export(module);

View File

@@ -1,69 +1,75 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, sandbox = require('sandboxed-module');
vows.describe('log4js-abspath').addBatch({
'options': {
topic: function() {
var appenderOptions,
log4js = sandbox.require(
'../lib/log4js',
{ requires:
{ './appenders/fake':
{
name: "fake",
appender: function() {},
configure: function(configuration, options) {
appenderOptions = options;
return function() {};
}
}
}
}
),
config = {
"appenders": [
{
"type" : "fake",
"filename" : "cheesy-wotsits.log"
}
]
'options': {
topic: function() {
var appenderOptions,
log4js = sandbox.require(
'../lib/log4js',
{ requires:
{ './appenders/fake':
{ name: "fake",
appender: function() {},
configure: function(configuration, options) {
appenderOptions = options;
return function() {};
}
}
}
}
),
config = {
"appenders": [
{
"type" : "fake",
"filename" : "cheesy-wotsits.log"
}
]
};
log4js.configure(config, {
cwd: '/absolute/path/to'
});
return appenderOptions;
},
'should be passed to appenders during configuration': function(options) {
assert.equal(options.cwd, '/absolute/path/to');
}
},
'file appender': {
topic: function() {
var fileOpened,
fileAppender = sandbox.require(
'../lib/appenders/file',
{ requires:
{ '../streams':
{ RollingFileStream:
function(file) {
fileOpened = file;
return {
on: function() {},
end: function() {}
};
log4js.configure(config, {
cwd: '/absolute/path/to'
});
return appenderOptions;
},
'should be passed to appenders during configuration': function(options) {
assert.equal(options.cwd, '/absolute/path/to');
}
}
}
}
);
fileAppender.configure(
{
filename: "whatever.log",
maxLogSize: 10
},
{ cwd: '/absolute/path/to' }
);
return fileOpened;
},
'file appender': {
topic: function() {
var fileOpened,
fileAppender = sandbox.require(
'../lib/appenders/file',
{ requires:
{ '../streams':
{
RollingFileStream: function(file) {
fileOpened = file;
return {
on: function() {},
end: function() {}
};
}
}
}
}
);
fileAppender.configure({ filename: "whatever.log", maxLogSize: 10 }, { cwd: '/absolute/path/to' });
return fileOpened;
},
'should prepend options.cwd to config.filename': function(fileOpened) {
assert.equal(fileOpened, "/absolute/path/to/whatever.log");
}
},
}).export(module);
'should prepend options.cwd to config.filename': function(fileOpened) {
assert.equal(fileOpened, "/absolute/path/to/whatever.log");
}
},
}).export(module);

View File

@@ -1,69 +1,78 @@
"use strict";
var vows = require('vows')
, fs = require('fs')
, assert = require('assert');
function remove(filename) {
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
}
vows.describe('log4js logLevelFilter').addBatch({
'appender': {
topic: function() {
var log4js = require('../lib/log4js'), logEvents = [], logger;
log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/logLevelFilter').appender('ERROR', function(evt) { logEvents.push(evt); }), "logLevelTest");
logger = log4js.getLogger("logLevelTest");
logger.debug('this should not trigger an event');
logger.warn('neither should this');
logger.error('this should, though');
logger.fatal('so should this');
return logEvents;
},
'should only pass log events greater than or equal to its own level' : function(logEvents) {
assert.equal(logEvents.length, 2);
assert.equal(logEvents[0].data[0], 'this should, though');
assert.equal(logEvents[1].data[0], 'so should this');
}
'appender': {
topic: function() {
var log4js = require('../lib/log4js'), logEvents = [], logger;
log4js.clearAppenders();
log4js.addAppender(
require('../lib/appenders/logLevelFilter')
.appender(
'ERROR',
function(evt) { logEvents.push(evt); }
),
"logLevelTest"
);
logger = log4js.getLogger("logLevelTest");
logger.debug('this should not trigger an event');
logger.warn('neither should this');
logger.error('this should, though');
logger.fatal('so should this');
return logEvents;
},
'configure': {
topic: function() {
var log4js = require('../lib/log4js')
, logger;
remove(__dirname + '/logLevelFilter.log');
remove(__dirname + '/logLevelFilter-warnings.log');
log4js.configure('test/with-logLevelFilter.json');
logger = log4js.getLogger("tests");
logger.info('main');
logger.error('both');
logger.warn('both');
logger.debug('main');
//wait for the file system to catch up
setTimeout(this.callback, 100);
},
'tmp-tests.log': {
topic: function() {
fs.readFile(__dirname + '/logLevelFilter.log', 'utf8', this.callback);
},
'should contain all log messages': function(contents) {
var messages = contents.trim().split('\n');
assert.deepEqual(messages, ['main','both','both','main']);
}
},
'tmp-tests-warnings.log': {
topic: function() {
fs.readFile(__dirname + '/logLevelFilter-warnings.log','utf8',this.callback);
},
'should contain only error and warning log messages': function(contents) {
var messages = contents.trim().split('\n');
assert.deepEqual(messages, ['both','both']);
}
}
'should only pass log events greater than or equal to its own level' : function(logEvents) {
assert.equal(logEvents.length, 2);
assert.equal(logEvents[0].data[0], 'this should, though');
assert.equal(logEvents[1].data[0], 'so should this');
}
},
'configure': {
topic: function() {
var log4js = require('../lib/log4js')
, logger;
remove(__dirname + '/logLevelFilter.log');
remove(__dirname + '/logLevelFilter-warnings.log');
log4js.configure('test/with-logLevelFilter.json');
logger = log4js.getLogger("tests");
logger.info('main');
logger.error('both');
logger.warn('both');
logger.debug('main');
//wait for the file system to catch up
setTimeout(this.callback, 100);
},
'tmp-tests.log': {
topic: function() {
fs.readFile(__dirname + '/logLevelFilter.log', 'utf8', this.callback);
},
'should contain all log messages': function(contents) {
var messages = contents.trim().split('\n');
assert.deepEqual(messages, ['main','both','both','main']);
}
},
'tmp-tests-warnings.log': {
topic: function() {
fs.readFile(__dirname + '/logLevelFilter-warnings.log','utf8',this.callback);
},
'should contain only error and warning log messages': function(contents) {
var messages = contents.trim().split('\n');
assert.deepEqual(messages, ['both','both']);
}
}
}
}).export(module);

57
test/logger-test.js Normal file
View File

@@ -0,0 +1,57 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, levels = require('../lib/levels')
, Logger = require('../lib/logger').Logger;
vows.describe('../lib/logger').addBatch({
'constructor with no parameters': {
topic: new Logger(),
'should use default category': function(logger) {
assert.equal(logger.category, Logger.DEFAULT_CATEGORY);
},
'should use TRACE log level': function(logger) {
assert.equal(logger.level, levels.TRACE);
}
},
'constructor with category': {
topic: new Logger('cheese'),
'should use category': function(logger) {
assert.equal(logger.category, 'cheese');
},
'should use TRACE log level': function(logger) {
assert.equal(logger.level, levels.TRACE);
}
},
'constructor with category and level': {
topic: new Logger('cheese', 'debug'),
'should use category': function(logger) {
assert.equal(logger.category, 'cheese');
},
'should use level': function(logger) {
assert.equal(logger.level, levels.DEBUG);
}
},
'isLevelEnabled': {
topic: new Logger('cheese', 'info'),
'should provide a level enabled function for all levels': function(logger) {
assert.isFunction(logger.isTraceEnabled);
assert.isFunction(logger.isDebugEnabled);
assert.isFunction(logger.isInfoEnabled);
assert.isFunction(logger.isWarnEnabled);
assert.isFunction(logger.isErrorEnabled);
assert.isFunction(logger.isFatalEnabled);
},
'should return the right values': function(logger) {
assert.isFalse(logger.isTraceEnabled());
assert.isFalse(logger.isDebugEnabled());
assert.isTrue(logger.isInfoEnabled());
assert.isTrue(logger.isWarnEnabled());
assert.isTrue(logger.isErrorEnabled());
assert.isTrue(logger.isFatalEnabled());
}
}
}).exportTo(module);

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,3 +1,4 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, levels = require('../lib/levels');
@@ -8,7 +9,7 @@ function MockLogger() {
this.messages = [];
this.log = function(level, message, exception) {
that.messages.push({ level: level, message: message });
that.messages.push({ level: level, message: message });
};
this.isLevelEnabled = function(level) {
@@ -20,7 +21,7 @@ function MockLogger() {
}
function MockRequest(remoteAddr, method, originalUrl) {
this.socket = { remoteAddress: remoteAddr };
this.originalUrl = originalUrl;
this.method = method;
@@ -34,226 +35,226 @@ function MockResponse(statusCode) {
this.statusCode = statusCode;
this.end = function(chunk, encoding) {
};
}
vows.describe('log4js connect logger').addBatch({
'getConnectLoggerModule': {
topic: function() {
var clm = require('../lib/connect-logger');
return clm;
},
'should return a "connect logger" factory' : function(clm) {
assert.isObject(clm);
},
'nolog String' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, { nolog: "\\.gif" });
return {cl: cl, ml: ml};
},
'check unmatch url request': {
topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
messages.pop();
}
},
'check match url request': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
}
topic: function() {
var clm = require('../lib/connect-logger');
return clm;
},
'nolog Strings' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, {nolog: "\\.gif|\\.jpe?g"});
return {cl: cl, ml: ml};
},
'should return a "connect logger" factory' : function(clm) {
assert.isObject(clm);
},
'check unmatch url request (png)': {
topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
messages.pop();
}
},
'nolog String' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, { nolog: "\\.gif" });
return {cl: cl, ml: ml};
},
'check unmatch url request': {
topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
},
'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
messages.pop();
}
},
'check match url request': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
}
},
'nolog Strings' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, {nolog: "\\.gif|\\.jpe?g"});
return {cl: cl, ml: ml};
},
'check unmatch url request (png)': {
topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
},
'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
messages.pop();
}
},
'check match url request (gif)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
'check match url request (jpeg)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
}
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
}
},
'nolog Array<String>' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, {nolog: ["\\.gif", "\\.jpe?g"]});
'nolog Array<String>' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, {nolog: ["\\.gif", "\\.jpe?g"]});
return {cl: cl, ml: ml};
},
'check unmatch url request (png)': {
topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
},
'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
messages.pop();
}
},
'check match url request (gif)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
'check match url request (jpeg)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
},
'nolog RegExp' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, {nolog: /\.gif|\.jpe?g/});
return {cl: cl, ml: ml};
},
'check unmatch url request (png)': {
topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
},
'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
messages.pop();
}
},
'check match url request (gif)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
'check match url request (jpeg)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
},
'nolog RegExp' : {
topic: function(clm) {
var ml = new MockLogger();
var cl = clm.connectLogger(ml, {nolog: /\.gif|\.jpe?g/});
return {cl: cl, ml: ml};
}
},
'check unmatch url request (png)': {
topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages){
assert.isArray(messages);
assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.include(messages[0].message, 'GET');
assert.include(messages[0].message, 'http://url');
assert.include(messages[0].message, 'my.remote.addr');
assert.include(messages[0].message, '200');
messages.pop();
}
},
'check match url request (gif)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
'check match url request (jpeg)': {
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200);
d.cl(req, res, function() { });
res.end('chunk', 'encoding');
return d.ml.messages;
}
, 'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
},
},
'check message': function(messages) {
assert.isArray(messages);
assert.equal(messages.length, 0);
}
}
}
}

View File

@@ -0,0 +1,340 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, sandbox = require('sandboxed-module');
function setupConsoleTest() {
var fakeConsole = {}
, logEvents = []
, log4js;
['trace','debug','log','info','warn','error'].forEach(function(fn) {
fakeConsole[fn] = function() {
throw new Error("this should not be called.");
};
});
log4js = sandbox.require(
'../lib/log4js',
{
globals: {
console: fakeConsole
}
}
);
log4js.clearAppenders();
log4js.addAppender(function(evt) {
logEvents.push(evt);
});
return { log4js: log4js, logEvents: logEvents, fakeConsole: fakeConsole };
}
vows.describe('reload configuration').addBatch({
'with config file changing' : {
topic: function() {
var pathsChecked = [],
logEvents = [],
logger,
modulePath = 'path/to/log4js.json',
fakeFS = {
lastMtime: Date.now(),
config: {
appenders: [
{ type: 'console', layout: { type: 'messagePassThrough' } }
],
levels: { 'a-test' : 'INFO' }
},
readFileSync: function (file, encoding) {
assert.equal(file, modulePath);
assert.equal(encoding, 'utf8');
return JSON.stringify(fakeFS.config);
},
statSync: function (path) {
pathsChecked.push(path);
if (path === modulePath) {
fakeFS.lastMtime += 1;
return { mtime: new Date(fakeFS.lastMtime) };
} else {
throw new Error("no such file");
}
}
},
fakeConsole = {
'name': 'console',
'appender': function () {
return function(evt) { logEvents.push(evt); };
},
'configure': function (config) {
return fakeConsole.appender();
}
},
setIntervalCallback,
fakeSetInterval = function(cb, timeout) {
setIntervalCallback = cb;
},
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'fs': fakeFS,
'./appenders/console': fakeConsole
},
globals: {
'console': fakeConsole,
'setInterval' : fakeSetInterval,
}
}
);
log4js.configure('path/to/log4js.json', { reloadSecs: 30 });
logger = log4js.getLogger('a-test');
logger.info("info1");
logger.debug("debug2 - should be ignored");
fakeFS.config.levels['a-test'] = "DEBUG";
setIntervalCallback();
logger.info("info3");
logger.debug("debug4");
return logEvents;
},
'should configure log4js from first log4js.json found': function(logEvents) {
assert.equal(logEvents[0].data[0], 'info1');
assert.equal(logEvents[1].data[0], 'info3');
assert.equal(logEvents[2].data[0], 'debug4');
assert.equal(logEvents.length, 3);
}
},
'with config file staying the same' : {
topic: function() {
var pathsChecked = [],
fileRead = 0,
logEvents = [],
logger,
modulePath = require('path').normalize(__dirname + '/../lib/log4js.json'),
mtime = new Date(),
fakeFS = {
config: {
appenders: [
{ type: 'console', layout: { type: 'messagePassThrough' } }
],
levels: { 'a-test' : 'INFO' }
},
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");
}
}
},
fakeConsole = {
'name': 'console',
'appender': function () {
return function(evt) { logEvents.push(evt); };
},
'configure': function (config) {
return fakeConsole.appender();
}
},
setIntervalCallback,
fakeSetInterval = function(cb, timeout) {
setIntervalCallback = cb;
},
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'fs': fakeFS,
'./appenders/console': fakeConsole
},
globals: {
'console': fakeConsole,
'setInterval' : fakeSetInterval,
}
}
);
log4js.configure(modulePath, { reloadSecs: 3 });
logger = log4js.getLogger('a-test');
logger.info("info1");
logger.debug("debug2 - should be ignored");
setIntervalCallback();
logger.info("info3");
logger.debug("debug4");
return [ pathsChecked, logEvents, modulePath, fileRead ];
},
'should only read the configuration file once': function(args) {
var fileRead = args[3];
assert.equal(fileRead, 1);
},
'should configure log4js from first log4js.json found': function(args) {
var logEvents = args[1];
assert.equal(logEvents.length, 2);
assert.equal(logEvents[0].data[0], 'info1');
assert.equal(logEvents[1].data[0], 'info3');
}
},
'when config file is removed': {
topic: function() {
var pathsChecked = [],
fileRead = 0,
logEvents = [],
logger,
modulePath = require('path').normalize(__dirname + '/../lib/log4js.json'),
mtime = new Date(),
fakeFS = {
config: {
appenders: [
{ type: 'console', layout: { type: 'messagePassThrough' } }
],
levels: { 'a-test' : 'INFO' }
},
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) {
this.statSync = function() {
throw new Error("no such file");
};
return { mtime: new Date() };
}
},
fakeConsole = {
'name': 'console',
'appender': function () {
return function(evt) { logEvents.push(evt); };
},
'configure': function (config) {
return fakeConsole.appender();
}
},
setIntervalCallback,
fakeSetInterval = function(cb, timeout) {
setIntervalCallback = cb;
},
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'fs': fakeFS,
'./appenders/console': fakeConsole
},
globals: {
'console': fakeConsole,
'setInterval' : fakeSetInterval,
}
}
);
log4js.configure(modulePath, { reloadSecs: 3 });
logger = log4js.getLogger('a-test');
logger.info("info1");
logger.debug("debug2 - should be ignored");
setIntervalCallback();
logger.info("info3");
logger.debug("debug4");
return [ pathsChecked, logEvents, modulePath, fileRead ];
},
'should only read the configuration file once': function(args) {
var fileRead = args[3];
assert.equal(fileRead, 1);
},
'should not clear configuration when config file not found': function(args) {
var logEvents = args[1];
assert.equal(logEvents.length, 3);
assert.equal(logEvents[0].data[0], 'info1');
assert.equal(logEvents[1].level.toString(), 'WARN');
assert.include(logEvents[1].data[0], 'Failed to load configuration file');
assert.equal(logEvents[2].data[0], 'info3');
}
},
'when passed an object': {
topic: function() {
var test = setupConsoleTest();
test.log4js.configure({}, { reloadSecs: 30 });
return test.logEvents;
},
'should log a warning': function(events) {
assert.equal(events[0].level.toString(), 'WARN');
assert.equal(
events[0].data[0],
'Ignoring configuration reload parameter for "object" configuration.'
);
}
},
'when called twice with reload options': {
topic: function() {
var modulePath = require('path').normalize(__dirname + '/../lib/log4js.json'),
fakeFS = {
readFileSync: function (file, encoding) {
return JSON.stringify({});
},
statSync: function (path) {
return { mtime: new Date() };
}
},
fakeConsole = {
'name': 'console',
'appender': function () {
return function(evt) { };
},
'configure': function (config) {
return fakeConsole.appender();
}
},
setIntervalCallback,
intervalCleared = false,
clearedId,
fakeSetInterval = function(cb, timeout) {
setIntervalCallback = cb;
return 1234;
},
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'fs': fakeFS,
'./appenders/console': fakeConsole
},
globals: {
'console': fakeConsole,
'setInterval' : fakeSetInterval,
'clearInterval': function(interval) {
intervalCleared = true;
clearedId = interval;
}
}
}
);
log4js.configure(modulePath, { reloadSecs: 3 });
log4js.configure(modulePath, { reloadSecs: 15 });
return { cleared: intervalCleared, id: clearedId };
},
'should clear the previous interval': function(result) {
assert.isTrue(result.cleared);
assert.equal(result.id, 1234);
}
}
}).exportTo(module);

View File

@@ -1,5 +1,9 @@
// This test shows an asymmetry between setLevel and isLevelEnabled (in log4js-node@0.4.3 and earlier):
// 1) setLevel("foo") works, but setLevel(log4js.levels.foo) silently does not (sets the level to TRACE).
"use strict";
/* jshint loopfunc: true */
// This test shows an asymmetry between setLevel and isLevelEnabled
// (in log4js-node@0.4.3 and earlier):
// 1) setLevel("foo") works, but setLevel(log4js.levels.foo) silently
// does not (sets the level to TRACE).
// 2) isLevelEnabled("foo") works as does isLevelEnabled(log4js.levels.foo).
//
@@ -19,7 +23,8 @@ var strLevels= ['Trace','Debug','Info','Warn','Error','Fatal'];
var log4jsLevels =[];
// populate an array with the log4js.levels that match the strLevels.
// Would be nice if we could iterate over log4js.levels instead, but log4js.levels.toLevel prevents that for now.
// Would be nice if we could iterate over log4js.levels instead,
// but log4js.levels.toLevel prevents that for now.
strLevels.forEach(function(l) {
log4jsLevels.push(log4js.levels.toLevel(l));
});
@@ -29,18 +34,19 @@ strLevels.forEach(function(l) {
var levelTypes = {
'string': strLevels,
'log4js.levels.level': log4jsLevels,
}
};
// Set up the basic vows batch for this test
var batch = {
setLevel: {
}
}
};
showProgress('Populating batch object...');
// Populating the batch object programmatically,
// as I don't have the patience to manually populate it with the (strLevels.length x levelTypes.length) ^ 2 = 144 possible test combinations
// as I don't have the patience to manually populate it with
// the (strLevels.length x levelTypes.length) ^ 2 = 144 possible test combinations
for (var type in levelTypes) {
var context = 'is called with a '+type;
var levelsToTest = levelTypes[type];
@@ -58,15 +64,30 @@ for (var type in levelTypes) {
var t = type;
var ct = comparisonType;
var expectedResult = log4jsLevel.isLessThanOrEqualTo(comparisonLevel);
var vow = 'isLevelEnabled('+comparisonLevel+') called with a '+comparisonType+' should return '+expectedResult;
var vow = 'isLevelEnabled(' + comparisonLevel +
') called with a ' + comparisonType +
' should return ' + expectedResult;
showProgress('Setting up the vows vow for '+vow);
batch.setLevel[context][subContext][vow] = function(levelToSet) {
logger.setLevel(levelToSet);
showProgress('*** Checking setLevel( '+level+' ) of type '+t+', and isLevelEnabled( '+comparisonLevel+' ) of type '+ct+'. Expecting: '+expectedResult);
assert.equal(logger.isLevelEnabled(comparisonLevel), expectedResult, 'Failed: calling setLevel( '+level+' ) with type '+type+', isLevelEnabled( '+comparisonLevel+' ) of type '+comparisonType+' did not return '+expectedResult);
showProgress(
'*** Checking setLevel( ' + level +
' ) of type ' + t +
', and isLevelEnabled( ' + comparisonLevel +
' ) of type ' + ct + '. Expecting: ' + expectedResult
);
assert.equal(
logger.isLevelEnabled(comparisonLevel),
expectedResult,
'Failed: calling setLevel( ' + level +
' ) with type ' + type +
', isLevelEnabled( ' + comparisonLevel +
' ) of type ' + comparisonType +
' did not return ' + expectedResult
);
};
})
});
}
});

View File

@@ -1,168 +1,233 @@
var vows = require('vows'),
assert = require('assert'),
log4js = require('../lib/log4js'),
sandbox = require('sandboxed-module');
"use strict";
var vows = require('vows')
, assert = require('assert')
, log4js = require('../lib/log4js')
, sandbox = require('sandboxed-module')
;
function setupLogging(category, options) {
var msgs = [];
var fakeMailer = {
var msgs = [];
var fakeMailer = {
createTransport: function (name, options) {
return {
config: options,
sendMail: function (msg, callback) {
msgs.push(msg);
callback(null, true);
}
msgs.push(msg);
callback(null, true);
},
close: function() {}
};
}
};
};
var smtpModule = sandbox.require('../lib/appenders/smtp', {
var fakeLayouts = {
layout: function(type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
};
var fakeConsole = {
errors: [],
error: function(msg, value) {
this.errors.push({ msg: msg, value: value });
}
};
var smtpModule = sandbox.require('../lib/appenders/smtp', {
requires: {
'nodemailer': fakeMailer
}
});
'nodemailer': fakeMailer,
'../layouts': fakeLayouts
},
globals: {
console: fakeConsole
}
});
log4js.addAppender(smtpModule.configure(options), category);
return {
log4js.addAppender(smtpModule.configure(options), category);
return {
logger: log4js.getLogger(category),
mailer: fakeMailer,
layouts: fakeLayouts,
console: fakeConsole,
results: msgs
};
};
}
function checkMessages (result, sender, subject) {
for (var i = 0; i < result.results.length; ++i) {
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': {
'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;
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);
assert.equal(result.results.length, 1);
},
'message should contain proper data': function (result) {
checkMessages(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;
},
'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');
}
'there should be one message only': function (result) {
assert.equal(result.results.length, 1);
},
'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));
}
'message should contain proper data': function (result) {
checkMessages(result, 'sender@domain.com', 'This is subject');
}
},
'config with layout': {
topic: function() {
var setup = setupLogging('config with layout', {
layout: {
type: "tester"
}
});
return setup;
},
'should configure layout': function(result) {
assert.equal(result.layouts.type, 'tester');
}
},
'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));
}
},
'error when sending email': {
topic: function() {
var setup = setupLogging('error when sending email', {
recipients: 'recipient@domain.com',
sendInterval: 0,
transport: 'SMTP',
SMTP: { port: 25, auth: { user: 'user@domain.com' } }
});
setup.mailer.createTransport = function() {
return {
sendMail: function(msg, cb) {
cb({ message: "oh noes" });
},
close: function() { }
};
};
setup.logger.info("This will break");
return setup.console;
},
'should be logged to console': function(cons) {
assert.equal(cons.errors.length, 1);
assert.equal(cons.errors[0].msg, "log4js.smtpAppender - Error happened");
assert.equal(cons.errors[0].value.message, 'oh noes');
}
}
}).export(module);

View File

@@ -0,0 +1,93 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, fs = require('fs')
, sandbox = require('sandboxed-module');
vows.describe('../../lib/streams/BaseRollingFileStream').addBatch({
'when node version < 0.10.0': {
topic: function() {
var streamLib = sandbox.load(
'../../lib/streams/BaseRollingFileStream',
{
globals: {
process: {
version: '0.8.11'
}
},
requires: {
'readable-stream': {
Writable: function() {}
}
}
}
);
return streamLib.required;
},
'it should use readable-stream to maintain compatibility': function(required) {
assert.ok(required['readable-stream']);
assert.ok(!required.stream);
}
},
'when node version > 0.10.0': {
topic: function() {
var streamLib = sandbox.load(
'../../lib/streams/BaseRollingFileStream',
{
globals: {
process: {
version: '0.10.1'
}
},
requires: {
'stream': {
Writable: function() {}
}
}
}
);
return streamLib.required;
},
'it should use the core stream module': function(required) {
assert.ok(required.stream);
assert.ok(!required['readable-stream']);
}
},
'when no filename is passed': {
topic: require('../../lib/streams/BaseRollingFileStream'),
'it should throw an error': function(BaseRollingFileStream) {
try {
new BaseRollingFileStream();
assert.fail('should not get here');
} catch (e) {
assert.ok(e);
}
}
},
'default behaviour': {
topic: function() {
var BaseRollingFileStream = require('../../lib/streams/BaseRollingFileStream')
, stream = new BaseRollingFileStream('basetest.log');
return stream;
},
teardown: function() {
try {
fs.unlink('basetest.log');
} catch (e) {
console.error("could not remove basetest.log", e);
}
},
'it should not want to roll': function(stream) {
assert.isFalse(stream.shouldRoll());
},
'it should not roll': function(stream) {
var cbCalled = false;
//just calls the callback straight away, no async calls
stream.roll('basetest.log', function() { cbCalled = true; });
assert.isTrue(cbCalled);
}
}
}).exportTo(module);

View File

@@ -1,122 +1,227 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, streams = require('stream')
, fs = require('fs')
, DateRollingFileStream = require('../../lib/streams').DateRollingFileStream
, semver = require('semver')
, streams
, DateRollingFileStream
, testTime = new Date(2012, 8, 12, 10, 37, 11);
if (semver.satisfies(process.version, '>=0.10.0')) {
streams = require('stream');
} else {
streams = require('readable-stream');
}
DateRollingFileStream = require('../../lib/streams').DateRollingFileStream;
function cleanUp(filename) {
return function() {
fs.unlink(filename);
};
return function() {
fs.unlink(filename);
};
}
function now() {
return testTime.getTime();
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 WritableStream': function(stream) {
assert.equal(stream.filename, __dirname + '/test-date-rolling-file-stream-1');
assert.equal(stream.pattern, 'yyyy-mm-dd.hh');
assert.instanceOf(stream, streams.Writable);
},
'with default settings for the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, 420);
assert.equal(stream.theStream.flags, 'a');
//encoding is not available on the underlying stream
//assert.equal(stream.encoding, 'utf8');
}
'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 WritableStream': function(stream) {
assert.equal(stream.filename, __dirname + '/test-date-rolling-file-stream-1');
assert.equal(stream.pattern, 'yyyy-mm-dd.hh');
assert.instanceOf(stream, streams.Writable);
},
'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.theStream.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.theStream.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.write("First message\n", 'utf8', function() {
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", 'utf8', this.callback);
},
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");
}
}
}
'with default settings for the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, 420);
assert.equal(stream.theStream.flags, 'a');
//encoding is not available on the underlying stream
//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: parseInt('0666', 8) }
),
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-3'),
'should pass them to the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, parseInt('0666', 8));
}
},
'with stream arguments but no pattern': {
topic: new DateRollingFileStream(
__dirname + '/test-date-rolling-file-stream-4',
{ mode: parseInt('0666', 8) }
),
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-4'),
'should pass them to the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, parseInt('0666', 8));
},
'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.write("First message\n", 'utf8', function() {
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", 'utf8', this.callback);
},
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");
}
}
}
},
'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);

View File

@@ -1,126 +1,210 @@
"use strict";
var vows = require('vows')
, async = require('async')
, assert = require('assert')
, events = require('events')
, fs = require('fs')
, streams = require('stream')
, RollingFileStream = require('../../lib/streams').RollingFileStream;
, semver = require('semver')
, streams
, RollingFileStream;
if (semver.satisfies(process.version, '>=0.10.0')) {
streams = require('stream');
} else {
streams = require('readable-stream');
}
RollingFileStream = require('../../lib/streams').RollingFileStream;
function remove(filename) {
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
}
function create(filename) {
fs.writeFileSync(filename, "test file");
}
vows.describe('RollingFileStream').addBatch({
'arguments': {
topic: function() {
remove(__dirname + "/test-rolling-file-stream");
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 Writable': function(stream) {
assert.instanceOf(stream, streams.Writable);
assert.equal(stream.filename, "test-rolling-file-stream");
assert.equal(stream.size, 1024);
assert.equal(stream.backups, 5);
},
'with default settings for the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, 420);
assert.equal(stream.theStream.flags, 'a');
//encoding isn't a property on the underlying stream
//assert.equal(stream.theStream.encoding, 'utf8');
}
'arguments': {
topic: function() {
remove(__dirname + "/test-rolling-file-stream");
return new RollingFileStream("test-rolling-file-stream", 1024, 5);
},
'with stream arguments': {
topic: function() {
remove(__dirname + '/test-rolling-file-stream');
return new RollingFileStream('test-rolling-file-stream', 1024, 5, { mode: 0666 });
},
'should pass them to the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, 0666);
}
'should take a filename, file size (bytes), no. backups, return Writable': function(stream) {
assert.instanceOf(stream, streams.Writable);
assert.equal(stream.filename, "test-rolling-file-stream");
assert.equal(stream.size, 1024);
assert.equal(stream.backups, 5);
},
'without size': {
topic: function() {
try {
new RollingFileStream(__dirname + "/test-rolling-file-stream");
} catch (e) {
return e;
}
},
'should throw an error': function(err) {
assert.instanceOf(err, Error);
}
'with default settings for the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, 420);
assert.equal(stream.theStream.flags, 'a');
//encoding isn't a property on the underlying stream
//assert.equal(stream.theStream.encoding, 'utf8');
}
},
'with stream arguments': {
topic: function() {
remove(__dirname + '/test-rolling-file-stream');
return new RollingFileStream(
'test-rolling-file-stream',
1024,
5,
{ mode: parseInt('0666', 8) }
);
},
'without number of backups': {
topic: function() {
remove('test-rolling-file-stream');
return new RollingFileStream(__dirname + "/test-rolling-file-stream", 1024);
},
'should default to 1 backup': function(stream) {
assert.equal(stream.backups, 1);
}
},
'writing less than the file size': {
topic: function() {
remove(__dirname + "/test-rolling-file-stream-write-less");
var that = this, stream = new RollingFileStream(__dirname + "/test-rolling-file-stream-write-less", 100);
stream.write("cheese", "utf8", function() {
stream.end();
fs.readFile(__dirname + "/test-rolling-file-stream-write-less", "utf8", that.callback);
});
},
'should write to the file': function(contents) {
assert.equal(contents, "cheese");
},
'the number of files': {
topic: function() {
fs.readdir(__dirname, this.callback);
},
'should be one': function(files) {
assert.equal(files.filter(function(file) { return file.indexOf('test-rolling-file-stream-write-less') > -1; }).length, 1);
}
'should pass them to the underlying stream': function(stream) {
assert.equal(stream.theStream.mode, parseInt('0666', 8));
}
},
'without size': {
topic: function() {
try {
new RollingFileStream(__dirname + "/test-rolling-file-stream");
} catch (e) {
return e;
}
},
'writing more than the file size': {
'should throw an error': function(err) {
assert.instanceOf(err, Error);
}
},
'without number of backups': {
topic: function() {
remove('test-rolling-file-stream');
return new RollingFileStream(__dirname + "/test-rolling-file-stream", 1024);
},
'should default to 1 backup': function(stream) {
assert.equal(stream.backups, 1);
}
},
'writing less than the file size': {
topic: function() {
remove(__dirname + "/test-rolling-file-stream-write-less");
var that = this
, stream = new RollingFileStream(
__dirname + "/test-rolling-file-stream-write-less",
100
);
stream.write("cheese", "utf8", function() {
stream.end();
fs.readFile(__dirname + "/test-rolling-file-stream-write-less", "utf8", that.callback);
});
},
'should write to the file': function(contents) {
assert.equal(contents, "cheese");
},
'the number of files': {
topic: function() {
remove(__dirname + "/test-rolling-file-stream-write-more");
remove(__dirname + "/test-rolling-file-stream-write-more.1");
var that = this, stream = new RollingFileStream(__dirname + "/test-rolling-file-stream-write-more", 45);
async.forEach([0, 1, 2, 3, 4, 5, 6], function(i, cb) {
stream.write(i +".cheese\n", "utf8", cb);
}, function() {
stream.end();
that.callback();
});
fs.readdir(__dirname, this.callback);
},
'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-rolling-file-stream-write-more') > -1; }
).length, 2);
}
},
'the first file': {
topic: function() {
fs.readFile(__dirname + "/test-rolling-file-stream-write-more", "utf8", this.callback);
},
'should contain the last two log messages': function(contents) {
assert.equal(contents, '5.cheese\n6.cheese\n');
}
},
'the second file': {
topic: function() {
fs.readFile(__dirname + '/test-rolling-file-stream-write-more.1', "utf8", this.callback);
},
'should contain the first five log messages': function(contents) {
assert.equal(contents, '0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n');
}
'should be one': function(files) {
assert.equal(
files.filter(
function(file) {
return file.indexOf('test-rolling-file-stream-write-less') > -1;
}
).length,
1
);
}
}
},
'writing more than the file size': {
topic: function() {
remove(__dirname + "/test-rolling-file-stream-write-more");
remove(__dirname + "/test-rolling-file-stream-write-more.1");
var that = this
, stream = new RollingFileStream(
__dirname + "/test-rolling-file-stream-write-more",
45
);
async.forEach(
[0, 1, 2, 3, 4, 5, 6],
function(i, cb) {
stream.write(i +".cheese\n", "utf8", cb);
},
function() {
stream.end();
that.callback();
}
);
},
'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-rolling-file-stream-write-more') > -1;
}
).length, 2);
}
},
'the first file': {
topic: function() {
fs.readFile(__dirname + "/test-rolling-file-stream-write-more", "utf8", this.callback);
},
'should contain the last two log messages': function(contents) {
assert.equal(contents, '5.cheese\n6.cheese\n');
}
},
'the second file': {
topic: function() {
fs.readFile(__dirname + '/test-rolling-file-stream-write-more.1', "utf8", this.callback);
},
'should contain the first five log messages': function(contents) {
assert.equal(contents, '0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n');
}
}
},
'when many files already exist': {
topic: function() {
remove(__dirname + '/test-rolling-stream-with-existing-files.11');
remove(__dirname + '/test-rolling-stream-with-existing-files.20');
remove(__dirname + '/test-rolling-stream-with-existing-files.-1');
remove(__dirname + '/test-rolling-stream-with-existing-files.1.1');
remove(__dirname + '/test-rolling-stream-with-existing-files.1');
create(__dirname + '/test-rolling-stream-with-existing-files.11');
create(__dirname + '/test-rolling-stream-with-existing-files.20');
create(__dirname + '/test-rolling-stream-with-existing-files.-1');
create(__dirname + '/test-rolling-stream-with-existing-files.1.1');
create(__dirname + '/test-rolling-stream-with-existing-files.1');
var that = this
, stream = new RollingFileStream(
__dirname + "/test-rolling-stream-with-existing-files",
45,
5
);
async.forEach(
[0, 1, 2, 3, 4, 5, 6],
function(i, cb) {
stream.write(i +".cheese\n", "utf8", cb);
},
function() {
stream.end();
that.callback();
}
);
},
'the files': {
topic: function() {
fs.readdir(__dirname, this.callback);
},
'should be rolled': function(files) {
assert.include(files, 'test-rolling-stream-with-existing-files');
assert.include(files, 'test-rolling-stream-with-existing-files.1');
assert.include(files, 'test-rolling-stream-with-existing-files.2');
assert.include(files, 'test-rolling-stream-with-existing-files.11');
assert.include(files, 'test-rolling-stream-with-existing-files.20');
}
}
}
}).exportTo(module);

View File

@@ -0,0 +1,23 @@
{
"appenders": [
{
"type": "categoryFilter",
"exclude": "web",
"appender": {
"type": "file",
"filename": "test/categoryFilter-noweb.log",
"layout": {
"type": "messagePassThrough"
}
}
},
{
"category": "web",
"type": "file",
"filename": "test/categoryFilter-web.log",
"layout": {
"type": "messagePassThrough"
}
}
]
}