Compare commits
160 Commits
node-0.8-r
...
logger-dec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a136404021 | ||
|
|
d1c6ad6f39 | ||
|
|
aad2bb1c2e | ||
|
|
5c13469bf6 | ||
|
|
491c2709e7 | ||
|
|
245b2e3b1a | ||
|
|
6dabcf5ee5 | ||
|
|
3142497e98 | ||
|
|
09d4300875 | ||
|
|
80f8f5a795 | ||
|
|
72c4fb48db | ||
|
|
5e144e4004 | ||
|
|
46ad57b4e0 | ||
|
|
fe1f1228ca | ||
|
|
d43d49d83d | ||
|
|
3312724d7d | ||
|
|
045b0dda2b | ||
|
|
b6dc0b9557 | ||
|
|
7c0cfbdcfd | ||
|
|
25e9835521 | ||
|
|
be1a9ca411 | ||
|
|
b2569c6d9d | ||
|
|
04d0113224 | ||
|
|
ac4fd2a7fc | ||
|
|
50074842ad | ||
|
|
5a2771cfed | ||
|
|
3b4a30587a | ||
|
|
50a8164b4b | ||
|
|
eabcaf8aef | ||
|
|
d8cf8cb2dc | ||
|
|
9afbbb580e | ||
|
|
6c09a6fb71 | ||
|
|
5f68db41b4 | ||
|
|
731e217505 | ||
|
|
3018a49bde | ||
|
|
4e6e51e9fa | ||
|
|
7f38837f9b | ||
|
|
b70e2e6220 | ||
|
|
834e084e5a | ||
|
|
270ba0fcc6 | ||
|
|
bdb56e4256 | ||
|
|
b12a39ac79 | ||
|
|
c3d3a8c363 | ||
|
|
7bad76d8ec | ||
|
|
a0a5a3aa99 | ||
|
|
a5bb94a048 | ||
|
|
631ca75e57 | ||
|
|
ab1c81a61c | ||
|
|
a0d0373480 | ||
|
|
eb875b6d98 | ||
|
|
a8679aced1 | ||
|
|
7a1a895e46 | ||
|
|
48dc22eb63 | ||
|
|
7888381991 | ||
|
|
cd286fa25f | ||
|
|
6df4753822 | ||
|
|
613474eb44 | ||
|
|
112246dd55 | ||
|
|
069ed31759 | ||
|
|
9e72189574 | ||
|
|
5a167d853a | ||
|
|
5755faa7bb | ||
|
|
1ed026a8d9 | ||
|
|
2d177d517b | ||
|
|
21aebbde33 | ||
|
|
49892f35d3 | ||
|
|
61beac28d3 | ||
|
|
c60d629608 | ||
|
|
8ad1cd67e2 | ||
|
|
c67ab855bb | ||
|
|
4905761f60 | ||
|
|
9897dcbc93 | ||
|
|
9c510f7705 | ||
|
|
5bd7ce3ab9 | ||
|
|
1e17f88ded | ||
|
|
3b55aefe6f | ||
|
|
d25e1abd48 | ||
|
|
dde2e69948 | ||
|
|
351a912a86 | ||
|
|
c5fd75dac3 | ||
|
|
4dd5989d27 | ||
|
|
46721465a1 | ||
|
|
76ff7aa5fa | ||
|
|
be5fa838be | ||
|
|
a86bed975c | ||
|
|
baaebef2ed | ||
|
|
837d007de3 | ||
|
|
be754f0c0e | ||
|
|
946b216a79 | ||
|
|
508dbdadf8 | ||
|
|
2e7f6e5a66 | ||
|
|
cbadb5fa19 | ||
|
|
c258470cda | ||
|
|
2b070e5470 | ||
|
|
4cd546e8b3 | ||
|
|
0e5da1d361 | ||
|
|
fc7f686f65 | ||
|
|
4a8f0580de | ||
|
|
f50fab2b86 | ||
|
|
f1c0767ca3 | ||
|
|
652888944b | ||
|
|
efc4e36317 | ||
|
|
d2f30b473f | ||
|
|
fa179ecba2 | ||
|
|
dd25d30228 | ||
|
|
11fe5bde5f | ||
|
|
41ddf5eea7 | ||
|
|
81fa9c3568 | ||
|
|
7ca517b5ed | ||
|
|
6368de1094 | ||
|
|
94dbd22c71 | ||
|
|
0a2a6c0769 | ||
|
|
5d6f00eda4 | ||
|
|
f998d7e81a | ||
|
|
46ae1a586d | ||
|
|
516320c79a | ||
|
|
40ec9e98e4 | ||
|
|
cc2e94cf11 | ||
|
|
2de838bc76 | ||
|
|
87dc7cf5aa | ||
|
|
913c748ee0 | ||
|
|
def0e8e371 | ||
|
|
20f80ff775 | ||
|
|
f24db59523 | ||
|
|
07869b915f | ||
|
|
2cd27e4293 | ||
|
|
3d11cbc0ad | ||
|
|
e5dba219d1 | ||
|
|
9853e13429 | ||
|
|
4fd138f87d | ||
|
|
1ad4977aec | ||
|
|
7cb7e6df72 | ||
|
|
2192a094b6 | ||
|
|
6a9441d261 | ||
|
|
50b676dec5 | ||
|
|
8b3c036245 | ||
|
|
b356dec318 | ||
|
|
8383dfc4f4 | ||
|
|
4e8fb26099 | ||
|
|
8492519e3b | ||
|
|
fdc9d253c9 | ||
|
|
18e21ca473 | ||
|
|
ab8c7ed89d | ||
|
|
aa4f7c071b | ||
|
|
dc632f4705 | ||
|
|
ac6284add1 | ||
|
|
2da01cc611 | ||
|
|
ad8229145e | ||
|
|
8c12c948d9 | ||
|
|
af6ae7af98 | ||
|
|
936ad4da8e | ||
|
|
097ae3d7f1 | ||
|
|
04de4ed8d3 | ||
|
|
29b02921b6 | ||
|
|
48ed5d1222 | ||
|
|
7844b0d2e4 | ||
|
|
eb21e10208 | ||
|
|
f272e3fd0a | ||
|
|
c9a890b37b | ||
|
|
0dbc4921a3 |
12
.bob.json
Normal file
12
.bob.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"build": "clean lint test coverage",
|
||||
"lint": {
|
||||
"type": "jshint"
|
||||
},
|
||||
"coverage": {
|
||||
"type": "mocha-istanbul"
|
||||
},
|
||||
"test": {
|
||||
"type": "mocha"
|
||||
}
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
*.log??
|
||||
build
|
||||
node_modules
|
||||
|
||||
.bob/
|
||||
test/streams/test-rolling-file-stream*
|
||||
|
||||
18
.jshintrc
Normal file
18
.jshintrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": true,
|
||||
"laxcomma": true,
|
||||
"indent": 2,
|
||||
"globalstrict": true,
|
||||
"maxparams": 5,
|
||||
"maxdepth": 3,
|
||||
"maxstatements": 20,
|
||||
"maxcomplexity": 5,
|
||||
"maxlen": 100,
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"it": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"after": true
|
||||
}
|
||||
}
|
||||
38
0.7-changes
Normal file
38
0.7-changes
Normal file
@@ -0,0 +1,38 @@
|
||||
changes
|
||||
=======
|
||||
LogEvent.categoryName -> LogEvent.category
|
||||
Logger is immutable (no setLevel any more)
|
||||
Log levels defined in configure call, nowhere else
|
||||
References to Loggers not retained
|
||||
Clustered appender, multiprocess appender removed - core handles clusters now
|
||||
Default category needs to be defined, with appender
|
||||
connect logger, gelf, smtp, hookio appenders removed from core.
|
||||
reload configuration removed from core - use 'watchr' or something instead
|
||||
appenders now only need to provide configure function
|
||||
log4js.configure now only takes single argument (no options)
|
||||
tests use mocha not vows
|
||||
replaced my debug lib with tjholowaychuk's debug (more of a standard)
|
||||
options.cwd removed - filenames should always be specified in full, not relative
|
||||
loglevelfilter changed to accept a list of log levels it allows
|
||||
appenders that wrap other appenders must reference them by name
|
||||
extracted streams to streamroller
|
||||
extracted date_format.js to date-format
|
||||
console.log replacement has been removed.
|
||||
|
||||
to-do
|
||||
=====
|
||||
documentation pages (gh-pages)
|
||||
* configuration
|
||||
* file appenders
|
||||
* layouts
|
||||
* optional components
|
||||
* writing your own appender (use couchdb as example)
|
||||
readme
|
||||
* getting started
|
||||
* typical config - file with max size, file with date rolling
|
||||
* optional components
|
||||
fix and publish the optional components
|
||||
* connect
|
||||
* smtp
|
||||
* gelf
|
||||
* hookio ?
|
||||
195
README.md
195
README.md
@@ -1,136 +1,121 @@
|
||||
# log4js-node [](http://travis-ci.org/nomiddlename/log4js-node)
|
||||
|
||||
|
||||
This is a conversion of the [log4js](http://log4js.berlios.de/index.html)
|
||||
framework to work with [node](http://nodejs.org). I've mainly stripped out the browser-specific code and tidied up some of the javascript.
|
||||
This was a conversion of the [log4js](http://log4js.berlios.de/index.html)
|
||||
framework to work with [node](http://nodejs.org). It's changed a lot since then, but there are still plenty of the original parts involved.
|
||||
|
||||
Out of the box it supports the following features:
|
||||
|
||||
* coloured console logging
|
||||
* replacement of node's console.log functions (optional)
|
||||
* file appender, with log rolling based on file size
|
||||
* SMTP appender
|
||||
* GELF appender
|
||||
* hook.io appender
|
||||
* multiprocess appender (useful when you've got worker processes)
|
||||
* a logger for connect/express servers
|
||||
* file appender, with log rolling based on file size or date
|
||||
* multi-process logging (works fine with node's clusters)
|
||||
* 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
|
||||
{
|
||||
appenders: [
|
||||
{ type: "console" }
|
||||
],
|
||||
replaceConsole: true
|
||||
}
|
||||
```
|
||||
NOTE: There have been a lot of changes in version 0.7.x, if you're upgrading from an older version, you should read [0.7-changes](http://github.com/nomiddlename/log4js-node/0.7-changes)
|
||||
|
||||
## installation
|
||||
|
||||
npm install log4js
|
||||
npm install log4js
|
||||
|
||||
|
||||
## usage
|
||||
|
||||
Minimalist version:
|
||||
```javascript
|
||||
var log4js = require('log4js');
|
||||
var logger = log4js.getLogger();
|
||||
logger.debug("Some debug messages");
|
||||
```
|
||||
|
||||
var log4js = require('log4js');
|
||||
var logger = log4js.getLogger();
|
||||
logger.debug("Some debug messages");
|
||||
|
||||
By default, log4js outputs to stdout with the coloured layout (thanks to [masylum](http://github.com/masylum)), so for the above you would see:
|
||||
```bash
|
||||
[2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages
|
||||
```
|
||||
See example.js for a full example, but here's a snippet (also in fromreadme.js):
|
||||
```javascript
|
||||
var log4js = require('log4js');
|
||||
//console log is loaded by default, so you won't normally need to do this
|
||||
//log4js.loadAppender('console');
|
||||
log4js.loadAppender('file');
|
||||
//log4js.addAppender(log4js.appenders.console());
|
||||
log4js.addAppender(log4js.appenders.file('logs/cheese.log'), 'cheese');
|
||||
|
||||
var logger = log4js.getLogger('cheese');
|
||||
logger.setLevel('ERROR');
|
||||
[2010-01-17 11:43:37.987] [DEBUG] default - Some debug messages
|
||||
|
||||
logger.trace('Entering cheese testing');
|
||||
logger.debug('Got cheese.');
|
||||
logger.info('Cheese is Gouda.');
|
||||
logger.warn('Cheese is quite smelly.');
|
||||
logger.error('Cheese is too ripe!');
|
||||
logger.fatal('Cheese was breeding ground for listeria.');
|
||||
```
|
||||
Output:
|
||||
```bash
|
||||
[2010-01-17 11:43:37.987] [ERROR] cheese - Cheese is too ripe!
|
||||
[2010-01-17 11:43:37.990] [FATAL] cheese - Cheese was breeding ground for listeria.
|
||||
```
|
||||
The first 5 lines of the code above could also be written as:
|
||||
```javascript
|
||||
var log4js = require('log4js');
|
||||
log4js.configure({
|
||||
appenders: [
|
||||
{ type: 'console' },
|
||||
{ type: 'file', filename: 'logs/cheese.log', category: 'cheese' }
|
||||
]
|
||||
});
|
||||
```
|
||||
See the examples directory for lots of sample setup and usage code.
|
||||
|
||||
## configuration
|
||||
## API
|
||||
Log4js exposes two public functions: `configure` and `getLogger`. If
|
||||
you're writing your own appender, your code will get access to some
|
||||
internal APIs, see
|
||||
[writing-appenders](http://github.com/nomiddlename/log4js-node/writing-appenders.md).
|
||||
|
||||
You can configure the appenders and log levels manually (as above), or provide a
|
||||
configuration file (`log4js.configure('path/to/file.json')`), or a configuration object. The
|
||||
configuration file location may also be specified via the environment variable
|
||||
LOG4JS_CONFIG (`export LOG4JS_CONFIG=path/to/file.json`).
|
||||
An example file can be found in `test/log4js.json`. An example config file with log rolling is in `test/with-log-rolling.json`.
|
||||
By default, the configuration file is checked for changes every 60 seconds, and if changed, reloaded. This allows changes to logging levels to occur without restarting the application.
|
||||
### log4js.configure(config)
|
||||
Configure takes a single argument. If that argument is a string, it is
|
||||
considered the path to a JSON file containing the configuration
|
||||
object. If the argument is an object, it must have the following
|
||||
fields:
|
||||
|
||||
To turn off configuration file change checking, configure with:
|
||||
* `appenders` (Object) - this should be a map of named appenders to
|
||||
their configuration. At least one appender must be defined.
|
||||
* `categories` (Object) - this should be a map of logger categories to
|
||||
their levels and configuration. The "default" logger category must
|
||||
be defined, as this is used to route all log events that do not have
|
||||
an explicit category defined in the config. Category objects have
|
||||
two fields:
|
||||
* `level` - (String) the log level for that category: "trace",
|
||||
"debug", "info", "warn", "error", "fatal", "off"
|
||||
* `appenders` - (Array) the list of appender names to which log
|
||||
events for this category should be sent
|
||||
|
||||
```javascript
|
||||
var log4js = require('log4js');
|
||||
log4js.configure('my_log4js_configuration.json', {});
|
||||
```
|
||||
To specify a different period:
|
||||
The default configuration for log4js, the one used if `configure` is
|
||||
not called, looks like this:
|
||||
|
||||
```javascript
|
||||
log4js.configure('file.json', { reloadSecs: 300 });
|
||||
```
|
||||
For FileAppender you can also pass the path to the log directory as an option where all your log files would be stored.
|
||||
|
||||
```javascript
|
||||
log4js.configure('my_log4js_configuration.json', { cwd: '/absolute/path/to/log/dir' });
|
||||
```
|
||||
If you have already defined an absolute path for one of the FileAppenders in the configuration file, you could add a "absolute": true to the particular FileAppender to override the cwd option passed. Here is an example configuration file:
|
||||
```json
|
||||
#### my_log4js_configuration.json ####
|
||||
{
|
||||
"appenders": [
|
||||
{
|
||||
"type": "file",
|
||||
"filename": "relative/path/to/log_file.log",
|
||||
"maxLogSize": 20480,
|
||||
"backups": 3,
|
||||
"category": "relative-logger"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"absolute": true,
|
||||
"filename": "/absolute/path/to/log_file.log",
|
||||
"maxLogSize": 20480,
|
||||
"backups": 10,
|
||||
"category": "absolute-logger"
|
||||
"appenders": {
|
||||
"console": { "type": "console" }
|
||||
},
|
||||
"categories": {
|
||||
"default": { level: "TRACE", appenders: [ "console" ] }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
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.
|
||||
|
||||
Use of the default configuration can be overridden by setting the
|
||||
`LOG4JS_CONFIG` environment variable to the location of a JSON
|
||||
configuration file. log4js will use this file in preference to the
|
||||
defaults, if `configure` is not called. An example file can be found
|
||||
in `test/log4js.json`. An example config file with log rolling is in
|
||||
`test/with-log-rolling.json`.
|
||||
|
||||
### log4js.getLogger([category])
|
||||
|
||||
* `category` (String), optional. Category to use for log events
|
||||
generated by the Logger.
|
||||
|
||||
Returns a Logger instance. Unlike in previous versions, log4js
|
||||
does not hold a reference to Loggers so feel free to use as many as
|
||||
you like.
|
||||
|
||||
### Logger
|
||||
|
||||
Loggers provide the following functions:
|
||||
|
||||
* `trace`
|
||||
* `debug`
|
||||
* `info`
|
||||
* `warn`
|
||||
* `error`
|
||||
* `fatal`
|
||||
|
||||
All can take a variable list of arguments which are used to construct
|
||||
a log event. They work the same way as console.log, so you can pass a
|
||||
format string with placeholders. e.g.
|
||||
|
||||
logger.debug("number of widgets is %d", widgets);
|
||||
|
||||
|
||||
## Appenders
|
||||
|
||||
Log4js comes with file appenders included, which can be configured to
|
||||
roll over based on a time or a file size. Other appenders are
|
||||
available as separate modules:
|
||||
|
||||
* [log4js-gelf](http://github.com/nomiddlename/log4js-gelf)
|
||||
* [log4js-smtp](http://github.com/nomiddlename/log4js-smtp)
|
||||
* [log4js-hookio](http://github.com/nomiddlename/log4js-hookio)
|
||||
|
||||
There's also
|
||||
[log4js-connect](http://github.com/nomiddlename/log4s-connect), for
|
||||
logging http access in connect-based servers, like express.
|
||||
|
||||
## Documentation
|
||||
See the [wiki](https://github.com/nomiddlename/log4js-node/wiki). Improve the [wiki](https://github.com/nomiddlename/log4js-node/wiki), please.
|
||||
|
||||
@@ -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
43
examples/smtp-appender.js
Normal 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);
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
var layouts = require('../layouts'),
|
||||
consoleLog = console.log;
|
||||
"use strict";
|
||||
var consoleLog = console.log.bind(console);
|
||||
|
||||
function consoleAppender (layout) {
|
||||
module.exports = function(layouts, levels) {
|
||||
|
||||
function consoleAppender (layout) {
|
||||
layout = layout || layouts.colouredLayout;
|
||||
return function(loggingEvent) {
|
||||
consoleLog(layout(loggingEvent));
|
||||
consoleLog(layout(loggingEvent));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function configure(config) {
|
||||
return function configure(config) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
return consoleAppender(layout);
|
||||
}
|
||||
};
|
||||
|
||||
exports.appender = consoleAppender;
|
||||
exports.configure = configure;
|
||||
};
|
||||
|
||||
@@ -1,49 +1,53 @@
|
||||
var streams = require('../streams'),
|
||||
layouts = require('../layouts'),
|
||||
path = require('path'),
|
||||
os = require('os'),
|
||||
eol = os.EOL || '\n',
|
||||
openFiles = [];
|
||||
"use strict";
|
||||
var streams = require('streamroller')
|
||||
, 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();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* File appender that rolls files according to a date pattern.
|
||||
* @filename base filename.
|
||||
* @pattern the format that will be added to the end of filename when rolling,
|
||||
* also used to check when to roll files - defaults to '.yyyy-MM-dd'
|
||||
* @layout layout function for log messages - defaults to basicLayout
|
||||
*/
|
||||
function appender(filename, pattern, layout) {
|
||||
module.exports = function(layouts, levels) {
|
||||
/**
|
||||
* File appender that rolls files according to a date pattern.
|
||||
* @filename base filename.
|
||||
* @pattern the format that will be added to the end of filename when rolling,
|
||||
* also used to check when to roll files - defaults to '.yyyy-MM-dd'
|
||||
* @layout layout function for log messages - defaults to basicLayout
|
||||
*/
|
||||
function appender(filename, pattern, alwaysIncludePattern, layout) {
|
||||
layout = layout || layouts.basicLayout;
|
||||
|
||||
var logFile = new streams.DateRollingFileStream(filename, pattern);
|
||||
|
||||
var logFile = new streams.DateRollingFileStream(
|
||||
filename,
|
||||
pattern,
|
||||
{ alwaysIncludePattern: alwaysIncludePattern }
|
||||
);
|
||||
openFiles.push(logFile);
|
||||
|
||||
|
||||
return function(logEvent) {
|
||||
logFile.write(layout(logEvent) + eol, "utf8");
|
||||
logFile.write(layout(logEvent) + eol, "utf8");
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function configure(config, options) {
|
||||
return function configure(config) {
|
||||
var layout;
|
||||
|
||||
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
|
||||
if (options && options.cwd && !config.absolute) {
|
||||
config.filename = path.join(options.cwd, config.filename);
|
||||
|
||||
if (!config.alwaysIncludePattern) {
|
||||
config.alwaysIncludePattern = false;
|
||||
}
|
||||
|
||||
return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout);
|
||||
};
|
||||
|
||||
return appender(config.filename, config.pattern, layout);
|
||||
}
|
||||
|
||||
exports.appender = appender;
|
||||
exports.configure = configure;
|
||||
};
|
||||
|
||||
@@ -1,73 +1,78 @@
|
||||
var layouts = require('../layouts')
|
||||
, path = require('path')
|
||||
, fs = require('fs')
|
||||
, streams = require('../streams')
|
||||
, os = require('os')
|
||||
, eol = os.EOL || '\n'
|
||||
, openFiles = [];
|
||||
"use strict";
|
||||
var path = require('path')
|
||||
, fs = require('fs')
|
||||
, streams = require('streamroller')
|
||||
, 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)
|
||||
*/
|
||||
function fileAppender (file, layout, logSize, numBackups) {
|
||||
var bytesWritten = 0;
|
||||
file = path.normalize(file);
|
||||
layout = layout || layouts.basicLayout;
|
||||
numBackups = numBackups === undefined ? 5 : numBackups;
|
||||
//there has to be at least one backup if logSize has been specified
|
||||
numBackups = numBackups === 0 ? 1 : numBackups;
|
||||
module.exports = function(layouts, levels) {
|
||||
|
||||
function openTheStream(file, fileSize, numFiles) {
|
||||
var stream;
|
||||
if (fileSize) {
|
||||
stream = new streams.RollingFileStream(
|
||||
file,
|
||||
fileSize,
|
||||
numFiles
|
||||
);
|
||||
} else {
|
||||
stream = fs.createWriteStream(file, { encoding: "utf8", mode: 0644, flags: 'a' });
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
function fileAppender (file, layout, logSize, numBackups) {
|
||||
var bytesWritten = 0;
|
||||
file = path.normalize(file);
|
||||
layout = layout || layouts.basicLayout;
|
||||
numBackups = numBackups === undefined ? 5 : numBackups;
|
||||
//there has to be at least one backup if logSize has been specified
|
||||
numBackups = numBackups === 0 ? 1 : numBackups;
|
||||
|
||||
function openTheStream(file, fileSize, numFiles) {
|
||||
var stream;
|
||||
if (fileSize) {
|
||||
stream = new streams.RollingFileStream(
|
||||
file,
|
||||
fileSize,
|
||||
numFiles
|
||||
);
|
||||
} else {
|
||||
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);
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
stream.on("error", function (err) {
|
||||
console.error("log4js.fileAppender - Writing to file %s, error happened ", file, err);
|
||||
});
|
||||
return stream;
|
||||
|
||||
var logFile = openTheStream(file, logSize, numBackups);
|
||||
|
||||
// push file to the stack of open handlers
|
||||
openFiles.push(logFile);
|
||||
|
||||
return function(loggingEvent) {
|
||||
logFile.write(layout(loggingEvent) + eol, "utf8");
|
||||
};
|
||||
}
|
||||
|
||||
var logFile = openTheStream(file, logSize, numBackups);
|
||||
|
||||
// push file to the stack of open handlers
|
||||
openFiles.push(logFile);
|
||||
|
||||
return function(loggingEvent) {
|
||||
logFile.write(layout(loggingEvent) + eol, "utf8");
|
||||
return function configure(config) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
|
||||
return fileAppender(config.filename, layout, config.maxLogSize, config.backups);
|
||||
};
|
||||
}
|
||||
|
||||
function configure(config, options) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
|
||||
if (options && options.cwd && !config.absolute) {
|
||||
config.filename = path.join(options.cwd, config.filename);
|
||||
}
|
||||
|
||||
return fileAppender(config.filename, layout, config.maxLogSize, config.backups);
|
||||
}
|
||||
|
||||
exports.appender = fileAppender;
|
||||
exports.configure = configure;
|
||||
};
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
var zlib = require('zlib');
|
||||
var layouts = require('../layouts');
|
||||
var levels = require('../levels');
|
||||
var dgram = require('dgram');
|
||||
var util = require('util');
|
||||
|
||||
var LOG_EMERG=0; // system is unusable
|
||||
var LOG_ALERT=1; // action must be taken immediately
|
||||
var LOG_CRIT=2; // critical conditions
|
||||
var LOG_ERR=3; // error conditions
|
||||
var LOG_ERROR=3; // because people WILL typo
|
||||
var LOG_WARNING=4; // warning conditions
|
||||
var LOG_NOTICE=5; // normal, but significant, condition
|
||||
var LOG_INFO=6; // informational message
|
||||
var LOG_DEBUG=7; // debug-level message
|
||||
|
||||
var levelMapping = {};
|
||||
levelMapping[levels.ALL] = LOG_DEBUG;
|
||||
levelMapping[levels.TRACE] = LOG_DEBUG;
|
||||
levelMapping[levels.DEBUG] = LOG_DEBUG;
|
||||
levelMapping[levels.INFO] = LOG_INFO;
|
||||
levelMapping[levels.WARN] = LOG_WARNING;
|
||||
levelMapping[levels.ERROR] = LOG_ERR;
|
||||
levelMapping[levels.FATAL] = LOG_CRIT;
|
||||
|
||||
/**
|
||||
* GELF appender that supports sending UDP packets to a GELF compatible server such as Graylog
|
||||
*
|
||||
* @param layout a function that takes a logevent and returns a string (defaults to none).
|
||||
* @param host - host to which to send logs (default:localhost)
|
||||
* @param port - port at which to send logs to (default:12201)
|
||||
* @param hostname - hostname of the current host (default:os hostname)
|
||||
* @param facility - facility to log to (default:nodejs-server)
|
||||
*/
|
||||
function gelfAppender (layout, host, port, hostname, facility) {
|
||||
|
||||
host = host || 'localhost';
|
||||
port = port || 12201;
|
||||
hostname = hostname || require('os').hostname();
|
||||
facility = facility || 'nodejs-server';
|
||||
layout = layout || layouts.messagePassThroughLayout;
|
||||
|
||||
var client = dgram.createSocket("udp4");
|
||||
|
||||
process.on('exit', function() {
|
||||
if (client) client.close();
|
||||
});
|
||||
|
||||
function preparePacket(loggingEvent) {
|
||||
var 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) {}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
exports.appender = gelfAppender;
|
||||
exports.configure = configure;
|
||||
@@ -1,75 +0,0 @@
|
||||
var log4js = require('../log4js');
|
||||
var layouts = require('../layouts');
|
||||
var Hook = require('hook.io').Hook;
|
||||
var util = require('util');
|
||||
|
||||
var Logger = function createLogger(options) {
|
||||
var self = this;
|
||||
var actualAppender = options.actualAppender;
|
||||
Hook.call(self, options);
|
||||
self.on('hook::ready', function hookReady() {
|
||||
self.on('*::' + options.name + '::log', function log(loggingEvent) {
|
||||
deserializeLoggingEvent(loggingEvent);
|
||||
actualAppender(loggingEvent);
|
||||
});
|
||||
});
|
||||
}
|
||||
util.inherits(Logger, Hook);
|
||||
|
||||
function deserializeLoggingEvent(loggingEvent) {
|
||||
loggingEvent.startTime = new Date(loggingEvent.startTime);
|
||||
loggingEvent.level.toString = function levelToString() {
|
||||
return loggingEvent.level.levelStr;
|
||||
};
|
||||
}
|
||||
|
||||
function initHook(hookioOptions) {
|
||||
var loggerHook;
|
||||
if (hookioOptions.mode === 'master') {
|
||||
// Start the master hook, handling the actual logging
|
||||
loggerHook = new Logger(hookioOptions);
|
||||
} else {
|
||||
// Start a worker, just emitting events for a master
|
||||
loggerHook = new Hook(hookioOptions);
|
||||
}
|
||||
loggerHook.start();
|
||||
return loggerHook;
|
||||
}
|
||||
|
||||
function getBufferedHook(hook, eventName) {
|
||||
var hookBuffer = [];
|
||||
var hookReady = false;
|
||||
hook.on('hook::ready', function emptyBuffer() {
|
||||
hookBuffer.forEach(function logBufferItem(loggingEvent) {
|
||||
hook.emit(eventName, loggingEvent);
|
||||
})
|
||||
hookReady = true;
|
||||
});
|
||||
|
||||
return function log(loggingEvent) {
|
||||
if (hookReady) {
|
||||
hook.emit(eventName, loggingEvent);
|
||||
} else {
|
||||
hookBuffer.push(loggingEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createAppender(hookioOptions) {
|
||||
var loggerHook = initHook(hookioOptions);
|
||||
var loggerEvent = hookioOptions.name + '::log';
|
||||
return getBufferedHook(loggerHook, loggerEvent);
|
||||
}
|
||||
|
||||
function configure(config) {
|
||||
var actualAppender;
|
||||
if (config.appender && config.mode === 'master') {
|
||||
log4js.loadAppender(config.appender.type);
|
||||
actualAppender = log4js.appenderMakers[config.appender.type](config.appender);
|
||||
config.actualAppender = actualAppender;
|
||||
}
|
||||
return createAppender(config);
|
||||
}
|
||||
|
||||
exports.appender = createAppender;
|
||||
exports.configure = configure;
|
||||
@@ -1,20 +1,40 @@
|
||||
var levels = require('../levels');
|
||||
var log4js = require('../log4js');
|
||||
"use strict";
|
||||
var debug = require('debug')('log4js:logLevelFilter');
|
||||
|
||||
function logLevelFilter (levelString, appender) {
|
||||
var level = levels.toLevel(levelString);
|
||||
module.exports = function(layouts, levels) {
|
||||
|
||||
function logLevelFilter(allowedLevels, appender) {
|
||||
return function(logEvent) {
|
||||
if (logEvent.level.isGreaterThanOrEqualTo(level)) {
|
||||
appender(logEvent);
|
||||
}
|
||||
debug("Checking ", logEvent.level, " against ", allowedLevels);
|
||||
if (allowedLevels.some(function(item) { return item.level === logEvent.level.level; })) {
|
||||
debug("Sending ", logEvent, " to appender ", appender);
|
||||
appender(logEvent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return function configure(config, appenderByName) {
|
||||
if (!Array.isArray(config.allow)) {
|
||||
throw new Error("No allowed log levels specified.");
|
||||
}
|
||||
}
|
||||
|
||||
function configure(config) {
|
||||
log4js.loadAppender(config.appender.type);
|
||||
var appender = log4js.appenderMakers[config.appender.type](config.appender);
|
||||
return logLevelFilter(config.level, appender);
|
||||
}
|
||||
var allowedLevels = config.allow.map(function(allowed) {
|
||||
var level = levels.toLevel(allowed);
|
||||
if (!level) {
|
||||
throw new Error("Unrecognised log level '" + allowed + "'.");
|
||||
}
|
||||
return level;
|
||||
});
|
||||
|
||||
exports.appender = logLevelFilter;
|
||||
exports.configure = configure;
|
||||
if (allowedLevels.length === 0) {
|
||||
throw new Error("No allowed log levels specified.");
|
||||
}
|
||||
|
||||
if (!config.appender) {
|
||||
throw new Error("Missing an appender.");
|
||||
}
|
||||
|
||||
return logLevelFilter(allowedLevels, appenderByName(config.appender));
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
var log4js = require('../log4js'),
|
||||
net = require('net'),
|
||||
END_MSG = '__LOG4JS__';
|
||||
|
||||
/**
|
||||
* Creates a server, listening on config.loggerPort, config.loggerHost.
|
||||
* Output goes to config.actualAppender (config.appender is used to
|
||||
* 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function emptyBuffer() {
|
||||
var evt;
|
||||
while ((evt = buffer.shift())) {
|
||||
write(evt);
|
||||
}
|
||||
}
|
||||
|
||||
function write(loggingEvent) {
|
||||
socket.write(JSON.stringify(loggingEvent), 'utf8');
|
||||
socket.write(END_MSG, 'utf8');
|
||||
}
|
||||
|
||||
return function log(loggingEvent) {
|
||||
if (canWrite) {
|
||||
write(loggingEvent);
|
||||
} else {
|
||||
buffer.push(loggingEvent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createAppender(config) {
|
||||
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);
|
||||
}
|
||||
|
||||
exports.appender = createAppender;
|
||||
exports.configure = configure;
|
||||
@@ -1,75 +0,0 @@
|
||||
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.
|
||||
*
|
||||
* @param config appender configuration data
|
||||
* @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;
|
||||
var subjectLayout = layouts.messagePassThroughLayout;
|
||||
var sendInterval = config.sendInterval*1000 || 0;
|
||||
|
||||
var logEventBuffer = [];
|
||||
var sendTimer;
|
||||
var transport = mailer.createTransport(config.transport, config[config.transport]);
|
||||
|
||||
function sendBuffer() {
|
||||
if (logEventBuffer.length == 0)
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleSend() {
|
||||
if (!sendTimer)
|
||||
sendTimer = setTimeout(function() {
|
||||
sendTimer = null;
|
||||
sendBuffer();
|
||||
}, sendInterval);
|
||||
}
|
||||
|
||||
return function(loggingEvent) {
|
||||
logEventBuffer.push(loggingEvent);
|
||||
if (sendInterval > 0)
|
||||
scheduleSend();
|
||||
else
|
||||
sendBuffer();
|
||||
};
|
||||
}
|
||||
|
||||
function configure(config) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
return smtpAppender(config, layout);
|
||||
}
|
||||
|
||||
exports.name = "smtp";
|
||||
exports.appender = smtpAppender;
|
||||
exports.configure = configure;
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
var levels = require("./levels");
|
||||
/**
|
||||
* Log requests with the given `options` or a `format` string.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - `format` Format string, see below for tokens
|
||||
* - `level` A log4js levels instance.
|
||||
*
|
||||
* Tokens:
|
||||
*
|
||||
* - `:req[header]` ex: `:req[Accept]`
|
||||
* - `:res[header]` ex: `:res[Content-Length]`
|
||||
* - `:http-version`
|
||||
* - `:response-time`
|
||||
* - `:remote-addr`
|
||||
* - `:date`
|
||||
* - `:method`
|
||||
* - `:url`
|
||||
* - `:referrer`
|
||||
* - `:user-agent`
|
||||
* - `:status`
|
||||
*
|
||||
* @param {String|Function|Object} format or options
|
||||
* @return {Function}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//ensure next gets always called
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return formatted log line.
|
||||
*
|
||||
* @param {String} str
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
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 RegExp Object about nolog
|
||||
*
|
||||
* @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
|
||||
* 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
|
||||
* 2. RegExp
|
||||
* 2.1 in /\.(gif|jpe?g|png)$/
|
||||
* SAME AS 1.3
|
||||
* 3. Array
|
||||
* 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
|
||||
* SAME AS "\\.jpg|\\.png|\\.gif"
|
||||
*/
|
||||
function createNoLogCondition(nolog, type) {
|
||||
if(!nolog) return null;
|
||||
type = type || '';
|
||||
|
||||
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;
|
||||
}
|
||||
} else if(nolog instanceof Array){
|
||||
var regexps = nolog.map(function(o){ return createNoLogCondition(o, 'string')});
|
||||
return new RegExp(regexps.join('|'));
|
||||
}
|
||||
}
|
||||
|
||||
exports.connectLogger = getLogger;
|
||||
@@ -1,60 +0,0 @@
|
||||
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";
|
||||
|
||||
exports.asString = function(/*format,*/ date) {
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
446
lib/layouts.js
446
lib/layouts.js
@@ -1,119 +1,100 @@
|
||||
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);
|
||||
"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) {
|
||||
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.category
|
||||
)
|
||||
, colour
|
||||
);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* BasicLayout is a simple layout for storing the logs. The logs are stored
|
||||
* in following format:
|
||||
* <pre>
|
||||
* [startTime] [logLevel] categoryName - message\n
|
||||
* [startTime] [logLevel] category - message\n
|
||||
* </pre>
|
||||
*
|
||||
* @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,173 @@ 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 category(loggingEvent, specifier) {
|
||||
var loggerName = loggingEvent.category;
|
||||
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;
|
||||
var formats = {
|
||||
"ISO8601": dateFormat.ISO8601_FORMAT,
|
||||
"ISO8601_WITH_TZ_OFFSET": dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT,
|
||||
"ABSOLUTE": dateFormat.ABSOLUTETIME_FORMAT,
|
||||
"DATE": dateFormat.DATETIME_FORMAT
|
||||
};
|
||||
|
||||
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 formatAsDate(loggingEvent, specifier) {
|
||||
var format = dateFormat.ISO8601_FORMAT;
|
||||
if (specifier) {
|
||||
format = formats[specifier] || specifier;
|
||||
}
|
||||
// Format the date
|
||||
return dateFormat.asString(format, loggingEvent.startTime);
|
||||
}
|
||||
|
||||
function hostname() {
|
||||
return os.hostname().toString();
|
||||
}
|
||||
|
||||
// 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 formatMessage(loggingEvent) {
|
||||
return formatLogData(loggingEvent.data);
|
||||
}
|
||||
|
||||
function endOfLine() {
|
||||
return eol;
|
||||
}
|
||||
|
||||
var len;
|
||||
function logLevel(loggingEvent) {
|
||||
return loggingEvent.level.toString();
|
||||
}
|
||||
|
||||
// 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 startTime(loggingEvent) {
|
||||
return "" + loggingEvent.startTime.toLocaleTimeString();
|
||||
}
|
||||
|
||||
};
|
||||
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': category,
|
||||
'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);
|
||||
}
|
||||
};
|
||||
|
||||
107
lib/levels.js
107
lib/levels.js
@@ -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,70 @@ 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;
|
||||
};
|
||||
|
||||
Level.prototype.isLessThanOrEqualTo = function(otherLevel) {
|
||||
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;
|
||||
};
|
||||
|
||||
Level.prototype.isEqualTo = function(otherLevel) {
|
||||
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
|
||||
Level.prototype.toString = function() {
|
||||
return this.levelStr;
|
||||
};
|
||||
|
||||
function convertAndCompare(comparison) {
|
||||
return function(otherLevel) {
|
||||
if (typeof otherLevel === "string") {
|
||||
otherLevel = toLevel(otherLevel);
|
||||
}
|
||||
return comparison.call(this, otherLevel);
|
||||
};
|
||||
}
|
||||
|
||||
Level.prototype.isLessThanOrEqualTo = convertAndCompare(
|
||||
function(otherLevel) {
|
||||
return this.level <= otherLevel.level;
|
||||
}
|
||||
);
|
||||
|
||||
Level.prototype.isGreaterThanOrEqualTo = convertAndCompare(
|
||||
function(otherLevel) {
|
||||
return this.level >= otherLevel.level;
|
||||
}
|
||||
);
|
||||
|
||||
Level.prototype.isEqualTo = convertAndCompare(
|
||||
function(otherLevel) {
|
||||
return this.level === otherLevel.level;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
exports.ALL = new Level(Number.MIN_VALUE, "ALL");
|
||||
exports.TRACE = new Level(5000, "TRACE");
|
||||
exports.DEBUG = new Level(10000, "DEBUG");
|
||||
exports.INFO = new Level(20000, "INFO");
|
||||
exports.WARN = new Level(30000, "WARN");
|
||||
exports.ERROR = new Level(40000, "ERROR");
|
||||
exports.FATAL = new Level(50000, "FATAL");
|
||||
exports.OFF = new Level(Number.MAX_VALUE, "OFF");
|
||||
|
||||
exports.levels = [
|
||||
exports.OFF,
|
||||
exports.TRACE,
|
||||
exports.DEBUG,
|
||||
exports.INFO,
|
||||
exports.WARN,
|
||||
exports.ERROR,
|
||||
exports.FATAL
|
||||
];
|
||||
|
||||
exports.toLevel = toLevel;
|
||||
|
||||
431
lib/log4js.js
431
lib/log4js.js
@@ -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.
|
||||
@@ -21,18 +20,21 @@
|
||||
* <h3>Example:</h3>
|
||||
* <pre>
|
||||
* var logging = require('log4js');
|
||||
* //add an appender that logs all messages to stdout.
|
||||
* logging.addAppender(logging.consoleAppender());
|
||||
* //add an appender that logs "some-category" to a file
|
||||
* logging.addAppender(logging.fileAppender("file.log"), "some-category");
|
||||
* logging.configure({
|
||||
* appenders: {
|
||||
* "errorFile": { type: "file", filename: "error.log" }
|
||||
* },
|
||||
* categories: {
|
||||
* "default": { level: "ERROR", appenders: [ "errorFile" ] }
|
||||
* }
|
||||
* });
|
||||
* //get a logger
|
||||
* var log = logging.getLogger("some-category");
|
||||
* log.setLevel(logging.levels.TRACE); //set the Level
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* //call the log
|
||||
* log.trace("trace me" );
|
||||
* log.error("oh noes");
|
||||
* </pre>
|
||||
*
|
||||
* NOTE: the authors below are the original browser-based log4js authors
|
||||
@@ -44,276 +46,231 @@
|
||||
* @static
|
||||
* Website: http://log4js.berlios.de
|
||||
*/
|
||||
var events = require('events')
|
||||
var debug = require('debug')('log4js:core')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, cluster = require('cluster')
|
||||
, util = require('util')
|
||||
, layouts = require('./layouts')
|
||||
, levels = require('./levels')
|
||||
, LoggingEvent = require('./logger').LoggingEvent
|
||||
, Logger = require('./logger').Logger
|
||||
, ALL_CATEGORIES = '[all]'
|
||||
, Logger = require('./logger')
|
||||
, appenders = {}
|
||||
, loggers = {}
|
||||
, categories = {}
|
||||
, appenderMakers = {}
|
||||
, defaultConfig = {
|
||||
appenders: [
|
||||
{ type: "console" }
|
||||
],
|
||||
replaceConsole: false
|
||||
appenders: {
|
||||
console: { type: "console" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: levels.DEBUG, appenders: [ "console" ] }
|
||||
}
|
||||
};
|
||||
|
||||
function serialise(event) {
|
||||
return JSON.stringify(event);
|
||||
}
|
||||
|
||||
function deserialise(serialised) {
|
||||
var event;
|
||||
try {
|
||||
event = JSON.parse(serialised);
|
||||
event.startTime = new Date(event.startTime);
|
||||
event.level = levels.toLevel(event.level.levelStr);
|
||||
} catch(e) {
|
||||
event = {
|
||||
startTime: new Date(),
|
||||
category: 'log4js',
|
||||
level: levels.ERROR,
|
||||
data: [ 'Unable to parse log:', serialised ]
|
||||
};
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
//in a multi-process node environment, worker loggers will use
|
||||
//process.send
|
||||
cluster.on('fork', function(worker) {
|
||||
debug('listening to worker: ', worker);
|
||||
worker.on('message', function(message) {
|
||||
if (message.type && message.type === '::log4js-message') {
|
||||
debug("received message: ", message.event);
|
||||
dispatch(deserialise(message.event));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a logger instance. Instance is cached on categoryName level.
|
||||
* @param {String} categoryName name of category to log to.
|
||||
* Get a logger instance.
|
||||
* @param {String} category to log to.
|
||||
* @return {Logger} instance of logger for the category
|
||||
* @static
|
||||
*/
|
||||
function getLogger (categoryName) {
|
||||
function getLogger (category) {
|
||||
debug("getLogger(", category, ")");
|
||||
|
||||
// Use default logger if categoryName is not specified or invalid
|
||||
if (!(typeof categoryName == "string")) {
|
||||
categoryName = Logger.DEFAULT_CATEGORY;
|
||||
}
|
||||
return new Logger(
|
||||
cluster.isMaster ? dispatch : workerDispatch,
|
||||
category || 'default'
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return loggers[categoryName];
|
||||
function workerDispatch(event) {
|
||||
process.send({ type: "::log4js-message", event: serialise(event) });
|
||||
}
|
||||
|
||||
/**
|
||||
* args are appender, then zero or more categories
|
||||
* Log event routing to appenders
|
||||
* This would be a good place to implement category hierarchies/wildcards, etc
|
||||
*/
|
||||
function addAppender () {
|
||||
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];
|
||||
}
|
||||
function dispatch(event) {
|
||||
debug("event is ", event);
|
||||
var category = categories[event.category] || categories.default;
|
||||
debug(
|
||||
"category.level[",
|
||||
category.level,
|
||||
"] <= ",
|
||||
event.level,
|
||||
" ? ",
|
||||
category.level.isLessThanOrEqualTo(event.level)
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
if (category.level.isLessThanOrEqualTo(event.level)) {
|
||||
category.appenders.forEach(function(appender) {
|
||||
appenders[appender](event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function load(file) {
|
||||
debug("loading ", file);
|
||||
var contents = fs.readFileSync(file, "utf-8");
|
||||
debug("file contents ", contents);
|
||||
return JSON.parse(contents);
|
||||
}
|
||||
|
||||
function configure(configurationFileOrObject) {
|
||||
debug("configure(", configurationFileOrObject, ")");
|
||||
debug("process.env.LOG4JS_CONFIG = ", process.env.LOG4JS_CONFIG);
|
||||
|
||||
var filename, config = process.env.LOG4JS_CONFIG || configurationFileOrObject;
|
||||
|
||||
debug("config ", config);
|
||||
|
||||
if (!config || !(typeof config === 'string' || typeof config === 'object')) {
|
||||
throw new Error("You must specify configuration as an object or a filename.");
|
||||
}
|
||||
|
||||
if (typeof config === 'string') {
|
||||
debug("config is string");
|
||||
filename = config;
|
||||
config = load(filename);
|
||||
}
|
||||
|
||||
if (!config.appenders || !Object.keys(config.appenders).length) {
|
||||
throw new Error("You must specify at least one appender.");
|
||||
}
|
||||
|
||||
configureAppenders(config.appenders);
|
||||
|
||||
validateCategories(config.categories);
|
||||
categories = config.categories;
|
||||
|
||||
}
|
||||
|
||||
function validateCategories(cats) {
|
||||
if (!cats || !cats.default) {
|
||||
throw new Error("You must specify an appender for the default category");
|
||||
}
|
||||
|
||||
Object.keys(cats).forEach(function(categoryName) {
|
||||
var category = cats[categoryName], inputLevel = category.level;
|
||||
if (!category.level) {
|
||||
throw new Error("You must specify a level for category '" + categoryName + "'.");
|
||||
}
|
||||
category.level = levels.toLevel(inputLevel);
|
||||
if (!category.level) {
|
||||
throw new Error(
|
||||
"Level '" + inputLevel +
|
||||
"' is not valid for category '" + categoryName +
|
||||
"'. Acceptable values are: " + levels.levels.join(', ') + "."
|
||||
);
|
||||
}
|
||||
|
||||
if (!category.appenders || !category.appenders.length) {
|
||||
throw new Error("You must specify an appender for category '" + categoryName + "'.");
|
||||
}
|
||||
|
||||
category.appenders.forEach(function(appender) {
|
||||
if (!appenders[appender]) {
|
||||
throw new Error(
|
||||
"Appender '" + appender +
|
||||
"' for category '" + categoryName +
|
||||
"' does not exist. Known appenders are: " + Object.keys(appenders).join(', ') + "."
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clearAppenders () {
|
||||
appenders = {};
|
||||
for (var logger in loggers) {
|
||||
if (loggers.hasOwnProperty(logger)) {
|
||||
loggers[logger].removeAllListeners("log");
|
||||
}
|
||||
}
|
||||
debug("clearing appenders and appender makers");
|
||||
appenders = {};
|
||||
appenderMakers = {};
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
function appenderByName(name) {
|
||||
if (appenders.hasOwnProperty(name)) {
|
||||
return appenders[name];
|
||||
} else {
|
||||
throw new Error("Appender '" + name + "' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
function configureLevels(levels) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default logger instance.
|
||||
* @return {Logger} instance of default logger
|
||||
* @static
|
||||
*/
|
||||
function getDefaultLogger () {
|
||||
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;
|
||||
}
|
||||
|
||||
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+")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reloadConfiguration() {
|
||||
var filename = configState.filename,
|
||||
mtime;
|
||||
if (!filename) {
|
||||
// can't find anything to reload
|
||||
return;
|
||||
}
|
||||
function configureAppenders(appenderMap) {
|
||||
clearAppenders();
|
||||
Object.keys(appenderMap).forEach(function(appenderName) {
|
||||
var appender, appenderConfig = appenderMap[appenderName];
|
||||
loadAppender(appenderConfig.type);
|
||||
try {
|
||||
mtime = fs.statSync(filename).mtime;
|
||||
} catch (e) {
|
||||
getLogger('log4js').warn('Failed to load configuration file ' + filename);
|
||||
return;
|
||||
appenders[appenderName] = appenderMakers[appenderConfig.type](
|
||||
appenderConfig,
|
||||
appenderByName
|
||||
);
|
||||
} catch(e) {
|
||||
throw new Error(
|
||||
"log4js configuration problem for appender '" + appenderName +
|
||||
"'. Error was " + e.stack
|
||||
);
|
||||
}
|
||||
if (configState.lastFilename && configState.lastFilename === filename) {
|
||||
if (mtime.getTime() > configState.lastMTime.getTime()) {
|
||||
configureOnceOff(loadConfigurationFile(filename));
|
||||
}
|
||||
} else {
|
||||
configureOnceOff(loadConfigurationFile(filename));
|
||||
}
|
||||
}
|
||||
|
||||
function initReloadConfiguration(filename, options) {
|
||||
if (configState.timerId) {
|
||||
clearInterval(configState.timerId);
|
||||
delete configState.timerId;
|
||||
}
|
||||
configState.filename = filename;
|
||||
configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000);
|
||||
}
|
||||
|
||||
function configure(configurationFileOrObject, 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.');
|
||||
}
|
||||
}
|
||||
configureOnceOff(config, options);
|
||||
}
|
||||
|
||||
var originalConsoleFunctions = {
|
||||
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 restoreConsole() {
|
||||
['log', 'debug', 'info', 'warn', 'error'].forEach(function (item) {
|
||||
console[item] = originalConsoleFunctions[item];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadAppender(appender) {
|
||||
var appenderModule;
|
||||
var appenderModule;
|
||||
|
||||
if (!appenderMakers[appender]) {
|
||||
debug("Loading appender ", appender);
|
||||
try {
|
||||
appenderModule = require('./appenders/' + appender);
|
||||
appenderModule = require('./appenders/' + appender);
|
||||
} catch (e) {
|
||||
try {
|
||||
debug("Appender ", appender, " is not a core log4js appender.");
|
||||
appenderModule = require(appender);
|
||||
} catch (err) {
|
||||
debug("Error loading appender %s: ", appender, err);
|
||||
throw new Error("Could not load appender of type '" + appender + "'.");
|
||||
}
|
||||
}
|
||||
module.exports.appenders[appender] = appenderModule.appender.bind(appenderModule);
|
||||
appenderMakers[appender] = appenderModule.configure.bind(appenderModule);
|
||||
appenderMakers[appender] = appenderModule(layouts, levels);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
configure: configure
|
||||
};
|
||||
|
||||
//set ourselves up
|
||||
configure();
|
||||
debug("Starting configuration");
|
||||
configure(defaultConfig);
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"appenders": [
|
||||
{
|
||||
"type": "console"
|
||||
}
|
||||
]
|
||||
}
|
||||
107
lib/logger.js
107
lib/logger.js
@@ -1,78 +1,49 @@
|
||||
var levels = require('./levels'),
|
||||
util = require('util'),
|
||||
events = require('events'),
|
||||
DEFAULT_CATEGORY = '[default]';
|
||||
"use strict";
|
||||
var debug = require('debug')('log4js:logger')
|
||||
, levels = require('./levels');
|
||||
|
||||
module.exports = function Logger(dispatch, category) {
|
||||
if (typeof dispatch !== 'function') {
|
||||
throw new Error("Logger must have a dispatch delegate.");
|
||||
}
|
||||
|
||||
if (!category) {
|
||||
throw new Error("Logger must have a category.");
|
||||
}
|
||||
|
||||
function log() {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
, logLevel = args.shift()
|
||||
, loggingEvent = new LoggingEvent(category, logLevel, args);
|
||||
debug("Logging event ", loggingEvent, " to dispatch = ", dispatch);
|
||||
dispatch(loggingEvent);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
['trace','debug','info','warn','error','fatal'].forEach(
|
||||
function(level) {
|
||||
self[level] = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift(level);
|
||||
log.apply(this, args);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Models a logging event.
|
||||
* @constructor
|
||||
* @param {String} categoryName name of category
|
||||
* @param {String} category name of category
|
||||
* @param {Log4js.Level} level level of message
|
||||
* @param {Array} data objects to log
|
||||
* @param {Log4js.Logger} logger the associated logger
|
||||
* @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;
|
||||
function LoggingEvent (category, level, data) {
|
||||
this.startTime = new Date();
|
||||
this.category = category;
|
||||
this.data = data;
|
||||
this.level = levels.toLevel(level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger to log messages.
|
||||
* use {@see Log4js#getLogger(String)} to get an instance.
|
||||
* @constructor
|
||||
* @param name name of category to log to
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
function Logger (name, level) {
|
||||
this.category = name || DEFAULT_CATEGORY;
|
||||
|
||||
if (! this.level) {
|
||||
this.__proto__.level = levels.TRACE;
|
||||
}
|
||||
}
|
||||
util.inherits(Logger, events.EventEmitter);
|
||||
Logger.DEFAULT_CATEGORY = DEFAULT_CATEGORY;
|
||||
|
||||
Logger.prototype.setLevel = function(level) {
|
||||
this.level = levels.toLevel(level, this.level || levels.TRACE);
|
||||
};
|
||||
|
||||
Logger.prototype.removeLevel = function() {
|
||||
delete this.level;
|
||||
};
|
||||
|
||||
Logger.prototype.log = function() {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
, logLevel = args.shift()
|
||||
, loggingEvent = new LoggingEvent(this.category, logLevel, args, this);
|
||||
this.emit("log", loggingEvent);
|
||||
};
|
||||
|
||||
Logger.prototype.isLevelEnabled = function(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);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
exports.LoggingEvent = LoggingEvent;
|
||||
exports.Logger = Logger;
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
var fs = require('fs'),
|
||||
stream,
|
||||
util = require('util'),
|
||||
semver = require('semver');
|
||||
|
||||
if (semver.satisfies(process.version, '>=0.10.0')) {
|
||||
stream = require('stream');
|
||||
} else {
|
||||
stream = require('readable-stream');
|
||||
}
|
||||
|
||||
var debug;
|
||||
if (process.env.NODE_DEBUG && /\blog4js\b/.test(process.env.NODE_DEBUG)) {
|
||||
debug = function(message) { console.error('LOG4JS: (BaseRollingFileStream) %s', message); };
|
||||
} else {
|
||||
debug = function() { };
|
||||
}
|
||||
|
||||
module.exports = BaseRollingFileStream;
|
||||
|
||||
function BaseRollingFileStream(filename, options) {
|
||||
debug("In BaseRollingFileStream");
|
||||
this.filename = filename;
|
||||
this.options = options || { encoding: 'utf8', mode: 0644, flags: 'a' };
|
||||
this.currentSize = 0;
|
||||
|
||||
function currentFileSize(file) {
|
||||
var fileSize = 0;
|
||||
try {
|
||||
fileSize = fs.statSync(file).size;
|
||||
} catch (e) {
|
||||
// file does not exist
|
||||
}
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
function throwErrorIfArgumentsAreNotValid() {
|
||||
if (!filename) {
|
||||
throw new Error("You must specify a filename");
|
||||
}
|
||||
}
|
||||
|
||||
throwErrorIfArgumentsAreNotValid();
|
||||
debug("Calling BaseRollingFileStream.super");
|
||||
BaseRollingFileStream.super_.call(this);
|
||||
this.openTheStream();
|
||||
this.currentSize = currentFileSize(this.filename);
|
||||
}
|
||||
util.inherits(BaseRollingFileStream, stream.Writable);
|
||||
|
||||
BaseRollingFileStream.prototype._write = function(chunk, encoding, callback) {
|
||||
var that = this;
|
||||
function writeTheChunk() {
|
||||
debug("writing the chunk to the underlying stream");
|
||||
that.currentSize += chunk.length;
|
||||
that.theStream.write(chunk, encoding, callback);
|
||||
}
|
||||
|
||||
debug("in _write");
|
||||
|
||||
if (this.shouldRoll()) {
|
||||
this.currentSize = 0;
|
||||
this.roll(this.filename, writeTheChunk);
|
||||
} else {
|
||||
writeTheChunk();
|
||||
}
|
||||
};
|
||||
|
||||
BaseRollingFileStream.prototype.openTheStream = function(cb) {
|
||||
debug("opening the underlying stream");
|
||||
this.theStream = fs.createWriteStream(this.filename, this.options);
|
||||
if (cb) {
|
||||
this.theStream.on("open", cb);
|
||||
}
|
||||
};
|
||||
|
||||
BaseRollingFileStream.prototype.closeTheStream = function(cb) {
|
||||
debug("closing the underlying stream");
|
||||
this.theStream.end(cb);
|
||||
};
|
||||
|
||||
BaseRollingFileStream.prototype.shouldRoll = function() {
|
||||
return false; // default behaviour is never to roll
|
||||
};
|
||||
|
||||
BaseRollingFileStream.prototype.roll = function(filename, callback) {
|
||||
callback(); // default behaviour is not to do anything
|
||||
};
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
var BaseRollingFileStream = require('./BaseRollingFileStream'),
|
||||
format = require('../date_format'),
|
||||
async = require('async'),
|
||||
fs = require('fs'),
|
||||
util = require('util');
|
||||
|
||||
module.exports = DateRollingFileStream;
|
||||
|
||||
var debug;
|
||||
if (process.env.NODE_DEBUG && /\blog4js\b/.test(process.env.NODE_DEBUG)) {
|
||||
debug = function(message) { console.error('LOG4JS: (DateRollingFileStream) %s', message); };
|
||||
} else {
|
||||
debug = function() { };
|
||||
}
|
||||
|
||||
function DateRollingFileStream(filename, pattern, options, now) {
|
||||
debug("Now is " + now);
|
||||
if (pattern && typeof(pattern) === 'object') {
|
||||
now = options;
|
||||
options = pattern;
|
||||
pattern = null;
|
||||
}
|
||||
this.pattern = pattern || '.yyyy-MM-dd';
|
||||
this.now = now || Date.now;
|
||||
this.lastTimeWeWroteSomething = format.asString(this.pattern, new Date(this.now()));
|
||||
debug("this.now is " + this.now + ", now is " + now);
|
||||
|
||||
DateRollingFileStream.super_.call(this, filename, options);
|
||||
}
|
||||
util.inherits(DateRollingFileStream, BaseRollingFileStream);
|
||||
|
||||
DateRollingFileStream.prototype.shouldRoll = function() {
|
||||
var lastTime = this.lastTimeWeWroteSomething,
|
||||
thisTime = format.asString(this.pattern, new Date(this.now()));
|
||||
|
||||
debug("DateRollingFileStream.shouldRoll with now = " + this.now() + ", thisTime = " + thisTime + ", lastTime = " + lastTime);
|
||||
|
||||
this.lastTimeWeWroteSomething = thisTime;
|
||||
this.previousTime = lastTime;
|
||||
|
||||
return thisTime !== lastTime;
|
||||
};
|
||||
|
||||
DateRollingFileStream.prototype.roll = function(filename, callback) {
|
||||
var that = this,
|
||||
newFilename = filename + this.previousTime;
|
||||
|
||||
debug("Starting roll");
|
||||
|
||||
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 renameTheCurrentFile(cb) {
|
||||
debug("Renaming the " + filename + " -> " + newFilename);
|
||||
fs.rename(filename, newFilename, cb);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,94 +0,0 @@
|
||||
var BaseRollingFileStream = require('./BaseRollingFileStream'),
|
||||
util = require('util'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
async = require('async');
|
||||
|
||||
var debug;
|
||||
if (process.env.NODE_DEBUG && /\blog4js\b/.test(process.env.NODE_DEBUG)) {
|
||||
debug = function(message) { console.error('LOG4JS: (RollingFileStream) %s', message); };
|
||||
} else {
|
||||
debug = function() { };
|
||||
}
|
||||
|
||||
module.exports = RollingFileStream;
|
||||
|
||||
function RollingFileStream (filename, size, backups, options) {
|
||||
this.size = size;
|
||||
this.backups = backups || 1;
|
||||
|
||||
function throwErrorIfArgumentsAreNotValid() {
|
||||
if (!filename || !size || size <= 0) {
|
||||
throw new Error("You must specify a filename and file size");
|
||||
}
|
||||
}
|
||||
|
||||
throwErrorIfArgumentsAreNotValid();
|
||||
|
||||
RollingFileStream.super_.call(this, filename, options);
|
||||
}
|
||||
util.inherits(RollingFileStream, BaseRollingFileStream);
|
||||
|
||||
RollingFileStream.prototype.shouldRoll = function() {
|
||||
debug("should roll with current size %d, and max size %d", 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);
|
||||
}
|
||||
|
||||
function index(filename_) {
|
||||
return parseInt(filename_.substring((path.basename(filename) + '.').length), 10) || 0;
|
||||
}
|
||||
|
||||
function byIndex(a, b) {
|
||||
if (index(a) > index(b)) {
|
||||
return 1;
|
||||
} else if (index(a) < index(b) ) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function increaseFileIndex (fileToRename, cb) {
|
||||
var idx = index(fileToRename);
|
||||
debug('Index of ' + fileToRename + ' is ' + idx);
|
||||
if (idx < that.backups) {
|
||||
//on windows, you can get a EEXIST error if you rename a file to an existing file
|
||||
//so, we'll try to delete the file we're renaming to first
|
||||
fs.unlink(filename + '.' + (idx+1), function (err) {
|
||||
//ignore err: if we could not delete, it's most likely that it doesn't exist
|
||||
debug('Renaming ' + fileToRename + ' -> ' + filename + '.' + (idx+1));
|
||||
fs.rename(path.join(path.dirname(filename), fileToRename), filename + '.' + (idx + 1), cb);
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
function renameTheFiles(cb) {
|
||||
//roll the backups (rename file.n to file.n+1, where n <= numBackups)
|
||||
debug("Renaming the old files");
|
||||
fs.readdir(path.dirname(filename), function (err, files) {
|
||||
async.forEachSeries(
|
||||
files.filter(justTheseFiles).sort(byIndex).reverse(),
|
||||
increaseFileIndex,
|
||||
cb
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
debug("Rolling, rolling, rolling");
|
||||
async.series([
|
||||
this.closeTheStream.bind(this),
|
||||
renameTheFiles,
|
||||
this.openTheStream.bind(this)
|
||||
], callback);
|
||||
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
exports.RollingFileStream = require('./RollingFileStream');
|
||||
exports.DateRollingFileStream = require('./DateRollingFileStream');
|
||||
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "log4js",
|
||||
"version": "0.6.2",
|
||||
"version": "0.7.0",
|
||||
"description": "Port of Log4js to work with node.",
|
||||
"keywords": [
|
||||
"logging",
|
||||
@@ -21,22 +21,24 @@
|
||||
"node": ">=0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vows"
|
||||
"test": "mocha --recursive"
|
||||
},
|
||||
"directories": {
|
||||
"test": "test",
|
||||
"lib": "lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "0.1.15",
|
||||
"dequeue": "1.0.3",
|
||||
"semver": "~1.1.4",
|
||||
"readable-stream": "~1.0.2"
|
||||
"debug": "~0.7.2",
|
||||
"streamroller": "0.0.1",
|
||||
"date-format": "0.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vows": "0.7.0",
|
||||
"async": "0.1.15",
|
||||
"sandboxed-module": "0.1.3",
|
||||
"hook.io": "0.8.10",
|
||||
"underscore": "1.2.1"
|
||||
"mocha": "~1.12.0",
|
||||
"should": "~1.2.2"
|
||||
},
|
||||
"browser": {
|
||||
"os": false
|
||||
}
|
||||
}
|
||||
|
||||
99
test/categoryFilter-test.js
Normal file
99
test/categoryFilter-test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async')
|
||||
, should = require('should')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, assert = require('assert');
|
||||
|
||||
function remove() {
|
||||
var files = Array.prototype.slice.call(arguments);
|
||||
return function(done) {
|
||||
async.forEach(
|
||||
files.map(function(file) { return path.join(__dirname, file); }),
|
||||
fs.unlink.bind(fs),
|
||||
function() { done(); }
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
describe('log4js', function() {
|
||||
|
||||
before(
|
||||
remove(
|
||||
'test-category-filter-web.log',
|
||||
'test-category-filter-all.log'
|
||||
)
|
||||
);
|
||||
|
||||
after(
|
||||
remove(
|
||||
'test-category-filter-web.log',
|
||||
'test-category-filter-all.log'
|
||||
)
|
||||
);
|
||||
|
||||
describe('category filtering', function() {
|
||||
before(function() {
|
||||
var log4js = require('../lib/log4js')
|
||||
, webLogger = log4js.getLogger("web")
|
||||
, appLogger = log4js.getLogger("app");
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
rest: {
|
||||
type: "file",
|
||||
layout: { type: "messagePassThrough" },
|
||||
filename: path.join(__dirname, "test-category-filter-all.log")
|
||||
},
|
||||
web: {
|
||||
type: "file",
|
||||
layout: { type: "messagePassThrough"},
|
||||
filename: path.join(__dirname, "test-category-filter-web.log")
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "rest" ] },
|
||||
web: { level: "debug", appenders: [ "web" ] }
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('should only pass matching category', function(done) {
|
||||
setTimeout(function() {
|
||||
fs.readFile(
|
||||
path.join(__dirname, 'test-category-filter-web.log'),
|
||||
'utf8',
|
||||
function(err, contents) {
|
||||
var lines = contents.trim().split('\n');
|
||||
lines.should.eql(["This should get logged", "Hello again"]);
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it('should send everything else to default appender', function(done) {
|
||||
setTimeout(function() {
|
||||
fs.readFile(
|
||||
path.join(__dirname, 'test-category-filter-all.log'),
|
||||
'utf8',
|
||||
function(err, contents) {
|
||||
var lines = contents.trim().split('\n');
|
||||
lines.should.eql([
|
||||
"This should not",
|
||||
"This shouldn't be included by the appender anyway"
|
||||
]);
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
147
test/clusteredAppender-test.js
Executable file
147
test/clusteredAppender-test.js
Executable file
@@ -0,0 +1,147 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, sandbox = require('sandboxed-module');
|
||||
|
||||
|
||||
describe('log4js in a cluster', function() {
|
||||
describe('when in master mode', function() {
|
||||
|
||||
var log4js
|
||||
, clusterOnFork = false
|
||||
, workerCb
|
||||
, events = []
|
||||
, worker = {
|
||||
on: function(evt, cb) {
|
||||
evt.should.eql('message');
|
||||
this.cb = cb;
|
||||
}
|
||||
};
|
||||
|
||||
before(function() {
|
||||
log4js = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cluster': {
|
||||
isMaster: true,
|
||||
on: function(evt, cb) {
|
||||
evt.should.eql('fork');
|
||||
clusterOnFork = true;
|
||||
cb(worker);
|
||||
}
|
||||
},
|
||||
'./appenders/console': function() {
|
||||
return function() {
|
||||
return function(event) {
|
||||
events.push(event);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should listen for fork events', function() {
|
||||
clusterOnFork.should.eql(true);
|
||||
});
|
||||
|
||||
it('should listen for messages from workers', function() {
|
||||
//workerCb was created in a different context to the test
|
||||
//(thanks to sandbox.require), so doesn't pick up the should prototype
|
||||
(typeof worker.cb).should.eql('function');
|
||||
});
|
||||
|
||||
it('should log valid ::log4js-message events', function() {
|
||||
worker.cb({
|
||||
type: '::log4js-message',
|
||||
event: JSON.stringify({
|
||||
startTime: '2010-10-10 18:54:06',
|
||||
category: 'cheese',
|
||||
level: { levelStr: 'DEBUG' },
|
||||
data: [ "blah" ]
|
||||
})
|
||||
});
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("blah");
|
||||
events[0].category.should.eql('cheese');
|
||||
//startTime was created in a different context to the test
|
||||
//(thanks to sandbox.require), so instanceof doesn't think
|
||||
//it's a Date.
|
||||
events[0].startTime.constructor.name.should.eql('Date');
|
||||
events[0].level.toString().should.eql('DEBUG');
|
||||
});
|
||||
|
||||
it('should handle invalid ::log4js-message events', function() {
|
||||
worker.cb({
|
||||
type: '::log4js-message',
|
||||
event: "biscuits"
|
||||
});
|
||||
worker.cb({
|
||||
type: '::log4js-message',
|
||||
event: JSON.stringify({
|
||||
startTime: 'whatever'
|
||||
})
|
||||
});
|
||||
|
||||
events.should.have.length(3);
|
||||
events[1].data[0].should.eql('Unable to parse log:');
|
||||
events[1].data[1].should.eql('biscuits');
|
||||
events[1].category.should.eql('log4js');
|
||||
events[1].level.toString().should.eql('ERROR');
|
||||
|
||||
events[2].data[0].should.eql('Unable to parse log:');
|
||||
events[2].data[1].should.eql(JSON.stringify({ startTime: 'whatever'}));
|
||||
|
||||
});
|
||||
|
||||
it('should ignore other events', function() {
|
||||
worker.cb({
|
||||
type: "::blah-blah",
|
||||
event: "blah"
|
||||
});
|
||||
|
||||
events.should.have.length(3);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when in worker mode', function() {
|
||||
var log4js, events = [];
|
||||
|
||||
before(function() {
|
||||
log4js = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cluster': {
|
||||
isMaster: false,
|
||||
on: function() {}
|
||||
}
|
||||
},
|
||||
globals: {
|
||||
'process': {
|
||||
'send': function(event) {
|
||||
events.push(event);
|
||||
},
|
||||
'env': {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js.getLogger('test').debug("just testing");
|
||||
});
|
||||
|
||||
it('should emit ::log4js-message events', function() {
|
||||
events.should.have.length(1);
|
||||
events[0].type.should.eql('::log4js-message');
|
||||
events[0].event.should.be.a('string');
|
||||
|
||||
var evt = JSON.parse(events[0].event);
|
||||
evt.category.should.eql('test');
|
||||
evt.level.levelStr.should.eql('DEBUG');
|
||||
evt.data[0].should.eql('just testing');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,131 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
},
|
||||
'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);
|
||||
@@ -1,126 +0,0 @@
|
||||
// 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>)
|
||||
// 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.
|
||||
//
|
||||
|
||||
// Basic set up
|
||||
var vows = require('vows');
|
||||
var assert = require('assert');
|
||||
var toLevel = require('../lib/levels').toLevel;
|
||||
|
||||
// uncomment one or other of the following to see progress (or not) while running the tests
|
||||
// var showProgress = console.log;
|
||||
var showProgress = function() {};
|
||||
|
||||
|
||||
// Define the array of levels as string to iterate over.
|
||||
var strLevels= ['Trace','Debug','Info','Warn','Error','Fatal'];
|
||||
|
||||
// setup the configurations we want to test
|
||||
var configs = {
|
||||
'nop': 'nop', // special case where the iterating vows generator will not call log4js.configure
|
||||
'is undefined': undefined,
|
||||
'is null': null,
|
||||
'is empty': {},
|
||||
'has no levels': {foo: 'bar'},
|
||||
'has null levels': {levels: null},
|
||||
'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 = [];
|
||||
|
||||
|
||||
function getLoggerName(level) {
|
||||
return level+'-logger';
|
||||
}
|
||||
|
||||
// the common vows top-level context, whether log4js.configure is called or not
|
||||
// just making sure that the code is common,
|
||||
// so that there are no spurious errors in the tests themselves.
|
||||
function getTopLevelContext(nop, configToTest, name) {
|
||||
return {
|
||||
topic: function() {
|
||||
var log4js = require('../lib/log4js');
|
||||
// create loggers for each level,
|
||||
// keeping the level in the logger's name for traceability
|
||||
strLevels.forEach(function(l) {
|
||||
log4js.getLogger(getLoggerName(l)).setLevel(l);
|
||||
});
|
||||
|
||||
if (!nop) {
|
||||
showProgress('** Configuring log4js with', configToTest);
|
||||
log4js.configure(configToTest);
|
||||
}
|
||||
else {
|
||||
showProgress('** Not configuring log4js');
|
||||
}
|
||||
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
|
||||
for (var cfg in configs) {
|
||||
var configToTest = configs[cfg];
|
||||
var nop = configToTest === 'nop';
|
||||
var context;
|
||||
if (nop) {
|
||||
context = 'Setting up loggers with initial levels, then NOT setting a configuration,';
|
||||
}
|
||||
else {
|
||||
context = 'Setting up loggers with initial levels, then setting a configuration which '+cfg+',';
|
||||
}
|
||||
|
||||
showProgress('Setting up the vows batch and context for '+context);
|
||||
// each config to be tested has its own vows batch with a single top-level context
|
||||
var batch={};
|
||||
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);
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
showProgress('Running tests');
|
||||
var v = vows.describe('log4js.configure(), with or without a "levels" property');
|
||||
|
||||
batches.forEach(function(batch) {v=v.addBatch(batch)});
|
||||
|
||||
v.export(module);
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
var vows = require('vows')
|
||||
, assert = require('assert')
|
||||
, levels = require('../lib/levels');
|
||||
|
||||
function MockLogger() {
|
||||
|
||||
var that = this;
|
||||
this.messages = [];
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
function MockRequest(remoteAddr, method, originalUrl) {
|
||||
|
||||
this.socket = { remoteAddress: remoteAddr };
|
||||
this.originalUrl = originalUrl;
|
||||
this.method = method;
|
||||
this.httpVersionMajor = '5';
|
||||
this.httpVersionMinor = '0';
|
||||
this.headers = {}
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
'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);
|
||||
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;
|
||||
},
|
||||
|
||||
'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');
|
||||
}
|
||||
},
|
||||
|
||||
'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;
|
||||
},
|
||||
|
||||
'check message': function(messages) {
|
||||
assert.isArray(messages);
|
||||
assert.isEmpty(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' } );
|
||||
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;
|
||||
},
|
||||
|
||||
'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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}).export(module);
|
||||
31
test/consoleAppender-test.js
Normal file
31
test/consoleAppender-test.js
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, sandbox = require('sandboxed-module');
|
||||
|
||||
describe('../lib/appenders/console', function() {
|
||||
var messages = [];
|
||||
|
||||
before(function() {
|
||||
var fakeConsole = {
|
||||
log: function(msg) { messages.push(msg); }
|
||||
}
|
||||
, appenderModule = sandbox.require(
|
||||
'../lib/appenders/console',
|
||||
{
|
||||
globals: {
|
||||
'console': fakeConsole
|
||||
}
|
||||
}
|
||||
)
|
||||
, appender = appenderModule(require('../lib/layouts'))(
|
||||
{ layout: { type: "messagePassThrough" } }
|
||||
);
|
||||
|
||||
appender({ data: ["blah"] });
|
||||
});
|
||||
|
||||
it('should output to console', function() {
|
||||
messages.should.eql(["blah"]);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,98 +1,221 @@
|
||||
var vows = require('vows'),
|
||||
assert = require('assert'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
log4js = require('../lib/log4js');
|
||||
"use strict";
|
||||
/*jshint expr:true */
|
||||
var should = require('should')
|
||||
, async = require('async')
|
||||
, path = require('path')
|
||||
, fs = require('fs')
|
||||
, sandbox = require('sandboxed-module');
|
||||
|
||||
function removeFile(filename) {
|
||||
return function() {
|
||||
fs.unlink(path.join(__dirname, filename), function(err) {
|
||||
if (err) {
|
||||
console.log("Could not delete ", filename, err);
|
||||
}
|
||||
});
|
||||
};
|
||||
function remove(filename, cb) {
|
||||
fs.unlink(path.join(__dirname, filename), function(err) {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
describe('../lib/appenders/dateFile', function() {
|
||||
describe('adding multiple dateFileAppenders', function() {
|
||||
var files = [], initialListeners;
|
||||
|
||||
while (count--) {
|
||||
logfile = path.join(__dirname, 'datefa-default-test' + count + '.log');
|
||||
log4js.addAppender(dateFileAppender.appender(logfile));
|
||||
}
|
||||
before(function() {
|
||||
var dateFileAppender = require('../lib/appenders/dateFile')({ basicLayout: function() {} }),
|
||||
count = 5,
|
||||
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')();
|
||||
},
|
||||
initialListeners = process.listeners('exit').length;
|
||||
|
||||
while (count--) {
|
||||
logfile = path.join(__dirname, 'datefa-default-test' + count + '.log');
|
||||
dateFileAppender({
|
||||
filename: logfile
|
||||
});
|
||||
files.push(logfile);
|
||||
}
|
||||
});
|
||||
|
||||
'should only add one `exit` listener': function (initialCount) {
|
||||
assert.equal(process.listeners('exit').length, initialCount + 1);
|
||||
after(function(done) {
|
||||
async.forEach(files, remove, done);
|
||||
});
|
||||
|
||||
it('should only add one `exit` listener', function () {
|
||||
process.listeners('exit').length.should.be.below(initialListeners + 2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('exit listener', function() {
|
||||
var openedFiles = [];
|
||||
|
||||
before(function() {
|
||||
var exitListener
|
||||
, dateFileAppender = sandbox.require(
|
||||
'../lib/appenders/dateFile',
|
||||
{
|
||||
globals: {
|
||||
process: {
|
||||
on: function(evt, listener) {
|
||||
exitListener = listener;
|
||||
}
|
||||
}
|
||||
},
|
||||
requires: {
|
||||
'streamroller': {
|
||||
DateRollingFileStream: function(filename) {
|
||||
openedFiles.push(filename);
|
||||
|
||||
this.end = function() {
|
||||
openedFiles.shift();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)({ basicLayout: function() {} });
|
||||
|
||||
for (var i=0; i < 5; i += 1) {
|
||||
dateFileAppender({
|
||||
filename: 'test' + i
|
||||
});
|
||||
}
|
||||
|
||||
openedFiles.should.not.be.empty;
|
||||
exitListener();
|
||||
});
|
||||
|
||||
it('should close all open files', function() {
|
||||
openedFiles.should.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe('with default settings', function() {
|
||||
var contents;
|
||||
|
||||
before(function(done) {
|
||||
var testFile = path.join(__dirname, 'date-appender-default.log'),
|
||||
log4js = require('../lib/log4js'),
|
||||
logger = log4js.getLogger('default-settings');
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"date": { type: "dateFile", filename: testFile }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "debug", appenders: [ "date" ] }
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("This should be in the file.");
|
||||
|
||||
setTimeout(function() {
|
||||
fs.readFile(testFile, "utf8", function(err, data) {
|
||||
contents = data;
|
||||
done(err);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
'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');
|
||||
after(function(done) {
|
||||
remove('date-appender-default.log', done);
|
||||
});
|
||||
|
||||
it('should write to the file', function() {
|
||||
contents.should.include('This should be in the file');
|
||||
});
|
||||
|
||||
it('should use the basic layout', function() {
|
||||
contents.should.match(
|
||||
/\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
logger.info("This should be in the file.");
|
||||
describe('configure', function() {
|
||||
describe('with dateFileAppender', function() {
|
||||
var contents;
|
||||
|
||||
setTimeout(function() {
|
||||
fs.readFile(testFile, "utf8", that.callback);
|
||||
}, 100);
|
||||
before(function(done) {
|
||||
var log4js = require('../lib/log4js')
|
||||
, logger = log4js.getLogger('tests');
|
||||
|
||||
},
|
||||
teardown: removeFile('date-appender-default.log'),
|
||||
//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.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', function(err, data) {
|
||||
contents = data;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
'should write to the file': function(contents) {
|
||||
assert.include(contents, 'This should be in the file');
|
||||
},
|
||||
after(function(done) {
|
||||
remove('date-file-test.log', done);
|
||||
});
|
||||
|
||||
it('should load appender configuration from a json file', function() {
|
||||
contents.should.include('this should be written to the file' + require('os').EOL);
|
||||
contents.should.not.include('this should not be written to 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 - /);
|
||||
describe('with options.alwaysIncludePattern', function() {
|
||||
var contents, thisTime;
|
||||
|
||||
before(function(done) {
|
||||
var log4js = require('../lib/log4js')
|
||||
, format = require('date-format')
|
||||
, logger
|
||||
, options = {
|
||||
"appenders": {
|
||||
"datefile": {
|
||||
"type": "dateFile",
|
||||
"filename": "test/date-file-test",
|
||||
"pattern": "-from-MM-dd.log",
|
||||
"alwaysIncludePattern": true,
|
||||
"layout": {
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
categories: { default: { level: "debug", appenders: [ "datefile" ] } }
|
||||
};
|
||||
thisTime = format.asString(options.appenders.datefile.pattern, new Date());
|
||||
|
||||
}
|
||||
}).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.writeFile(
|
||||
path.join(__dirname, 'date-file-test' + thisTime),
|
||||
"this is existing data" + require('os').EOL,
|
||||
'utf8',
|
||||
function(err) {
|
||||
log4js.configure(options);
|
||||
logger = log4js.getLogger('tests');
|
||||
logger.warn('this should be written to the file with the appended date');
|
||||
//wait for filesystem to catch up
|
||||
setTimeout(function() {
|
||||
fs.readFile(
|
||||
path.join(__dirname, 'date-file-test' + thisTime),
|
||||
'utf8',
|
||||
function(err, data) {
|
||||
contents = data;
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
}, 200);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
fs.readFile(path.join(__dirname, 'date-file-test.log'), 'utf8', this.callback);
|
||||
},
|
||||
teardown: removeFile('date-file-test.log'),
|
||||
after(function(done) {
|
||||
remove('date-file-test' + thisTime, done);
|
||||
});
|
||||
|
||||
'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);
|
||||
}
|
||||
}
|
||||
it('should create file with the correct pattern', function() {
|
||||
contents.should.include('this should be written to the file with the appended date');
|
||||
});
|
||||
|
||||
}
|
||||
}).exportTo(module);
|
||||
it('should not overwrite the file on open (bug found in issue #132)', function() {
|
||||
contents.should.include('this is existing data');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
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'
|
||||
);
|
||||
}
|
||||
}
|
||||
}).export(module);
|
||||
@@ -1,179 +1,335 @@
|
||||
var vows = require('vows')
|
||||
, fs = require('fs')
|
||||
"use strict";
|
||||
/*jshint expr:true */
|
||||
var fs = require('fs')
|
||||
, async = require('async')
|
||||
, path = require('path')
|
||||
, sandbox = require('sandboxed-module')
|
||||
, log4js = require('../lib/log4js')
|
||||
, assert = require('assert');
|
||||
, should = require('should');
|
||||
|
||||
log4js.clearAppenders();
|
||||
|
||||
function remove(filename) {
|
||||
try {
|
||||
fs.unlinkSync(filename);
|
||||
} catch (e) {
|
||||
//doesn't really matter if it failed
|
||||
}
|
||||
function remove(filename, cb) {
|
||||
fs.unlink(filename, function(err) { cb(); });
|
||||
}
|
||||
|
||||
vows.describe('log4js fileAppender').addBatch({
|
||||
'adding multiple fileAppenders': {
|
||||
topic: function () {
|
||||
var listenersCount = process.listeners('exit').length
|
||||
, logger = log4js.getLogger('default-settings')
|
||||
, count = 5, logfile;
|
||||
describe('log4js fileAppender', function() {
|
||||
|
||||
while (count--) {
|
||||
logfile = path.join(__dirname, '/fa-default-test' + count + '.log');
|
||||
log4js.addAppender(require('../lib/appenders/file').appender(logfile), 'default-settings');
|
||||
}
|
||||
describe('adding multiple fileAppenders', function() {
|
||||
var files = [], initialCount, listenersCount;
|
||||
|
||||
return listenersCount;
|
||||
},
|
||||
before(function() {
|
||||
var logfile
|
||||
, count = 5
|
||||
, config = {
|
||||
appenders: {},
|
||||
categories: { default: { level: "debug", appenders: ["file0"] } }
|
||||
};
|
||||
|
||||
'does not adds more than one `exit` listeners': function (initialCount) {
|
||||
assert.ok(process.listeners('exit').length <= initialCount + 1);
|
||||
initialCount = process.listeners('exit').length;
|
||||
|
||||
while (count--) {
|
||||
logfile = path.join(__dirname, '/fa-default-test' + count + '.log');
|
||||
config.appenders["file" + count] = { type: "file", filename: logfile };
|
||||
files.push(logfile);
|
||||
}
|
||||
},
|
||||
|
||||
'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');
|
||||
log4js.configure(config);
|
||||
|
||||
logger.info("This should be in the file.");
|
||||
listenersCount = process.listeners('exit').length;
|
||||
});
|
||||
|
||||
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);
|
||||
after(function(done) {
|
||||
async.forEach(files, remove, done);
|
||||
});
|
||||
|
||||
it('does not add more than one `exit` listeners', function () {
|
||||
listenersCount.should.be.below(initialCount + 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exit listener', function() {
|
||||
var openedFiles = [];
|
||||
|
||||
before(function() {
|
||||
var exitListener
|
||||
, fileAppender = sandbox.require(
|
||||
'../lib/appenders/file',
|
||||
{
|
||||
globals: {
|
||||
process: {
|
||||
on: function(evt, listener) {
|
||||
exitListener = listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'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');
|
||||
},
|
||||
requires: {
|
||||
'streamroller': {
|
||||
RollingFileStream: function(filename) {
|
||||
openedFiles.push(filename);
|
||||
|
||||
this.end = function() {
|
||||
openedFiles.shift();
|
||||
};
|
||||
|
||||
//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.');
|
||||
}
|
||||
this.on = function() {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).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');
|
||||
)(require('../lib/layouts'));
|
||||
for (var i=0; i < 5; i += 1) {
|
||||
fileAppender({ filename: 'test' + i, maxLogSize: 100 });
|
||||
}
|
||||
openedFiles.should.not.be.empty;
|
||||
exitListener();
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
it('should close all open files', function() {
|
||||
openedFiles.should.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe('with default fileAppender settings', function() {
|
||||
var fileContents
|
||||
, testFile = path.join(__dirname, '/fa-default-test.log');
|
||||
|
||||
before(function(done) {
|
||||
var logger = log4js.getLogger('default-settings');
|
||||
|
||||
remove(testFile, function() {
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: testFile }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("This should be in the file.");
|
||||
|
||||
setTimeout(function() {
|
||||
fs.readFile(testFile, "utf8", function(err, contents) {
|
||||
if (!err) {
|
||||
fileContents = contents;
|
||||
}
|
||||
done(err);
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
remove(testFile, done);
|
||||
});
|
||||
|
||||
it('should write log messages to the file', function() {
|
||||
fileContents.should.include("This should be in the file.\n");
|
||||
});
|
||||
|
||||
it('log messages should be in the basic layout format', function() {
|
||||
fileContents.should.match(
|
||||
/\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a max file size and no backups', function() {
|
||||
var testFile = path.join(__dirname, '/fa-maxFileSize-test.log');
|
||||
|
||||
before(function(done) {
|
||||
var logger = log4js.getLogger('max-file-size');
|
||||
|
||||
async.forEach([
|
||||
testFile,
|
||||
testFile + '.1'
|
||||
], remove, function() {
|
||||
|
||||
//log file of 100 bytes maximum, no backups
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: testFile, maxLogSize: 100, backups: 0 }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
logger.info("This is the first log message.");
|
||||
logger.info("This is an intermediate log message.");
|
||||
logger.info("This is the second log message.");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
async.forEach([ testFile, testFile + '.1' ], remove, done);
|
||||
});
|
||||
|
||||
describe('log file', function() {
|
||||
it('should only contain the second message', function(done) {
|
||||
//wait for the file system to catch up
|
||||
setTimeout(function() {
|
||||
fs.readFile(testFile, "utf8", function(err, fileContents) {
|
||||
fileContents.should.include("This is the second log message.\n");
|
||||
fileContents.should.not.include("This is the first log message.");
|
||||
done(err);
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the number of files starting with the test file name', function() {
|
||||
it('should be two', function(done) {
|
||||
fs.readdir(__dirname, 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; }
|
||||
);
|
||||
logFiles.should.have.length(2);
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a max file size and 2 backups', function() {
|
||||
var testFile = path.join(__dirname, '/fa-maxFileSize-with-backups-test.log');
|
||||
|
||||
before(function(done) {
|
||||
var logger = log4js.getLogger('max-file-size-backups');
|
||||
|
||||
async.forEach([
|
||||
testFile,
|
||||
testFile+'.1',
|
||||
testFile+'.2'
|
||||
], remove, function() {
|
||||
|
||||
//log file of 50 bytes maximum, 2 backups
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: testFile, maxLogSize: 50, backups: 2 }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
|
||||
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.");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the log files', function() {
|
||||
var logFiles;
|
||||
|
||||
before(function(done) {
|
||||
setTimeout(function() {
|
||||
fs.readdir(__dirname, function(err, files) {
|
||||
if (files) {
|
||||
logFiles = files.sort().filter(
|
||||
function(file) {
|
||||
return file.indexOf('fa-maxFileSize-with-backups-test.log') > -1;
|
||||
}
|
||||
);
|
||||
done(null);
|
||||
} else {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
async.forEach(logFiles, remove, done);
|
||||
});
|
||||
|
||||
it('should be 3', function () {
|
||||
logFiles.should.have.length(3);
|
||||
});
|
||||
|
||||
it('should be named in sequence', function() {
|
||||
logFiles.should.eql([
|
||||
'fa-maxFileSize-with-backups-test.log',
|
||||
'fa-maxFileSize-with-backups-test.log.1',
|
||||
'fa-maxFileSize-with-backups-test.log.2'
|
||||
]);
|
||||
});
|
||||
|
||||
describe('and the contents of the first file', function() {
|
||||
it('should be the last log message', function(done) {
|
||||
fs.readFile(path.join(__dirname, logFiles[0]), "utf8", function(err, contents) {
|
||||
contents.should.include('This is the fourth log message.');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the contents of the second file', function() {
|
||||
it('should be the third log message', function(done) {
|
||||
fs.readFile(path.join(__dirname, logFiles[1]), "utf8", function(err, contents) {
|
||||
contents.should.include('This is the third log message.');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the contents of the third file', function() {
|
||||
it('should be the second log message', function(done) {
|
||||
fs.readFile(path.join(__dirname, logFiles[2]), "utf8", function(err, contents) {
|
||||
contents.should.include('This is the second log message.');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when underlying stream errors', function() {
|
||||
var consoleArgs;
|
||||
|
||||
before(function() {
|
||||
var errorHandler
|
||||
, fileAppender = sandbox.require(
|
||||
'../lib/appenders/file',
|
||||
{
|
||||
globals: {
|
||||
console: {
|
||||
error: function() {
|
||||
consoleArgs = Array.prototype.slice.call(arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
requires: {
|
||||
'streamroller': {
|
||||
RollingFileStream: function(filename) {
|
||||
|
||||
this.end = function() {};
|
||||
this.on = function(evt, cb) {
|
||||
if (evt === 'error') {
|
||||
errorHandler = cb;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}).export(module);
|
||||
)(require('../lib/layouts'));
|
||||
fileAppender({
|
||||
filename: 'test1.log',
|
||||
maxLogSize: 100
|
||||
});
|
||||
errorHandler({ error: 'aargh' });
|
||||
});
|
||||
|
||||
it('should log the error to console.error', function() {
|
||||
consoleArgs.should.not.be.empty;
|
||||
consoleArgs[0].should.eql('log4js.fileAppender - Writing to file %s, error happened ');
|
||||
consoleArgs[1].should.eql('test1.log');
|
||||
consoleArgs[2].error.should.eql('aargh');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
var vows = require('vows')
|
||||
, assert = require('assert')
|
||||
, sandbox = require('sandboxed-module')
|
||||
, log4js = require('../lib/log4js')
|
||||
, 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
, appender = sandbox.require('../lib/appenders/gelf', {
|
||||
requires: {
|
||||
dgram: fakeDgram,
|
||||
zlib: fakeZlib
|
||||
}
|
||||
});
|
||||
|
||||
log4js.clearAppenders();
|
||||
log4js.addAppender(appender.configure(options || {}), category || "gelf-test");
|
||||
return {
|
||||
dgram: fakeDgram,
|
||||
compress: fakeZlib,
|
||||
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 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}).export(module);
|
||||
@@ -1,85 +0,0 @@
|
||||
var vows = require('vows'),
|
||||
assert = require('assert');
|
||||
|
||||
vows.describe('log4js global loglevel').addBatch({
|
||||
'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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}).export(module);
|
||||
@@ -1,101 +0,0 @@
|
||||
var vows = require('vows');
|
||||
var assert = require('assert');
|
||||
var sandbox = require('sandboxed-module');
|
||||
|
||||
function fancyResultingHookioAppender(opts) {
|
||||
var result = { ons: {}, emissions: {}, logged: [], configs: [] };
|
||||
|
||||
var fakeLog4Js = {
|
||||
appenderMakers: {}
|
||||
};
|
||||
fakeLog4Js.loadAppender = function (appender) {
|
||||
fakeLog4Js.appenderMakers[appender] = function (config) {
|
||||
result.actualLoggerConfig = config;
|
||||
return function log(logEvent) {
|
||||
result.logged.push(logEvent);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var fakeHookIo = { Hook: function(config) { result.configs.push(config); } };
|
||||
fakeHookIo.Hook.prototype.start = function () {
|
||||
result.startCalled = true;
|
||||
};
|
||||
fakeHookIo.Hook.prototype.on = function (eventName, functionToExec) {
|
||||
result.ons[eventName] = { functionToExec: functionToExec };
|
||||
if (eventName === 'hook::ready') {
|
||||
functionToExec();
|
||||
}
|
||||
};
|
||||
fakeHookIo.Hook.prototype.emit = function (eventName, data) {
|
||||
result.emissions[eventName] = result.emissions[eventName] || [];
|
||||
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].functionToExec(data);
|
||||
}
|
||||
};
|
||||
|
||||
return { theResult: result,
|
||||
theModule: sandbox.require('../lib/appenders/hookio', {
|
||||
requires: {
|
||||
'../log4js': fakeLog4Js,
|
||||
'hook.io': fakeHookIo
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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'});
|
||||
return fancy.theResult;
|
||||
},
|
||||
|
||||
'should write to the actual appender': function (result) {
|
||||
assert.isTrue(result.startCalled);
|
||||
assert.equal(result.configs.length, 1);
|
||||
assert.equal(result.configs[0]['hook-port'], 5001);
|
||||
assert.equal(result.logged.length, 2);
|
||||
assert.equal(result.emissions['ohno::log'].length, 2);
|
||||
assert.equal(result.ons['*::ohno::log'].callingCount, 2);
|
||||
},
|
||||
|
||||
'data written should be formatted correctly': function (result) {
|
||||
assert.equal(result.logged[0].level.toString(), 'INFO');
|
||||
assert.equal(result.logged[0].data, 'ALRIGHTY THEN');
|
||||
assert.isTrue(typeof(result.logged[0].startTime) === 'object');
|
||||
assert.equal(result.logged[1].level.toString(), 'DEBUG');
|
||||
assert.equal(result.logged[1].data, 'OH WOW');
|
||||
assert.isTrue(typeof(result.logged[1].startTime) === 'object');
|
||||
},
|
||||
|
||||
'the actual logger should get the right config': function (result) {
|
||||
assert.equal(result.actualLoggerConfig.type, 'file');
|
||||
}
|
||||
},
|
||||
'worker': {
|
||||
'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'});
|
||||
return fancy.theResult;
|
||||
},
|
||||
|
||||
'should not write to the actual appender': function (result) {
|
||||
assert.isTrue(result.startCalled);
|
||||
assert.equal(result.logged.length, 0);
|
||||
assert.equal(result.emissions['ohno::log'].length, 2);
|
||||
assert.isUndefined(result.ons['*::ohno::log']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exportTo(module);
|
||||
@@ -1,268 +1,350 @@
|
||||
var vows = require('vows'),
|
||||
assert = require('assert');
|
||||
"use strict";
|
||||
var 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);
|
||||
function test(layout, event, tokens, pattern, value) {
|
||||
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');
|
||||
describe('log4js layouts', function() {
|
||||
describe('colouredLayout', function() {
|
||||
var layout = require('../lib/layouts').colouredLayout;
|
||||
|
||||
it('should apply level colour codes to output', function() {
|
||||
var output = layout({
|
||||
data: ["nonsense"],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
},
|
||||
});
|
||||
assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mnonsense');
|
||||
});
|
||||
|
||||
'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);
|
||||
it('should support the console.log format for the message', function() {
|
||||
var output = layout({
|
||||
data: ["thing %d", 2],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
});
|
||||
assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mthing 2');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('messagePassThroughLayout', function() {
|
||||
var layout = require('../lib/layouts').messagePassThroughLayout;
|
||||
|
||||
it('should take a logevent and output only the message', function() {
|
||||
assert.equal(layout({
|
||||
data: ["nonsense"],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}), "nonsense");
|
||||
});
|
||||
|
||||
it('should support the console.log format for the message', function() {
|
||||
assert.equal(layout({
|
||||
data: ["thing %d", 1, "cheese"],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level : {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}), "thing 1 cheese");
|
||||
});
|
||||
|
||||
it('should output the first item even if it is not a string', function() {
|
||||
assert.equal(layout({
|
||||
data: [ { thing: 1} ],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}), "{ thing: 1 }");
|
||||
});
|
||||
|
||||
it('should print the stacks of a passed error objects', function() {
|
||||
assert.ok(Array.isArray(
|
||||
layout({
|
||||
data: [ new Error() ],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}
|
||||
}).match(
|
||||
/Error\s+at Context\..*\s+\((.*)test[\\\/]layouts-test\.js\:\d+\:\d+\)\s/
|
||||
)
|
||||
), 'regexp did not return a match');
|
||||
});
|
||||
|
||||
describe('with passed augmented errors', function() {
|
||||
var layoutOutput;
|
||||
|
||||
},
|
||||
before(function() {
|
||||
var e = new Error("My Unique Error Message");
|
||||
e.augmented = "My Unique attribute value";
|
||||
e.augObj = { at1: "at2" };
|
||||
|
||||
layoutOutput = layout({
|
||||
data: [ e ],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
'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/);
|
||||
it('should print error the contained error message', function() {
|
||||
var m = layoutOutput.match(/\{ \[Error: My Unique Error Message\]/);
|
||||
assert.ok(Array.isArray(m));
|
||||
});
|
||||
|
||||
event.data = ['this is a test', error];
|
||||
output = layout(event);
|
||||
lines = output.split(/\n/);
|
||||
it('should print error augmented string attributes', function() {
|
||||
var m = layoutOutput.match(/augmented:\s'My Unique attribute value'/);
|
||||
assert.ok(Array.isArray(m));
|
||||
});
|
||||
|
||||
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]");
|
||||
it('should print error augmented object attributes', function() {
|
||||
var m = layoutOutput.match(/augObj:\s\{ at1: 'at2' \}/);
|
||||
assert.ok(Array.isArray(m));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('basicLayout', function() {
|
||||
var layout = require('../lib/layouts').basicLayout
|
||||
, event = {
|
||||
data: ['this is a test'],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "tests",
|
||||
level: {
|
||||
toString: function() { return "DEBUG"; }
|
||||
}
|
||||
};
|
||||
|
||||
it('should take a logevent and output a formatted string', function() {
|
||||
assert.equal(layout(event), "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test");
|
||||
});
|
||||
|
||||
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.' }");
|
||||
}
|
||||
},
|
||||
it('should output a stacktrace, message if the event has an error attached', function() {
|
||||
var 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]);
|
||||
}
|
||||
});
|
||||
|
||||
'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];
|
||||
},
|
||||
it('should output any extra data in the log event as util.inspect strings', function() {
|
||||
var output, lines;
|
||||
|
||||
'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');
|
||||
},
|
||||
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.' }"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('patternLayout', function() {
|
||||
var event = {
|
||||
data: ['this is a test'],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "multiple.levels.of.tests",
|
||||
level: {
|
||||
toString: function() { return "DEBUG"; }
|
||||
}
|
||||
}
|
||||
}).export(module);
|
||||
, layout = require('../lib/layouts').patternLayout
|
||||
, tokens = {
|
||||
testString: 'testStringToken',
|
||||
testFunction: function() { return 'testFunctionToken'; },
|
||||
fnThatUsesLogEvent: function(logEvent) { return logEvent.level.toString(); }
|
||||
};
|
||||
|
||||
event.startTime.getTimezoneOffset = function() { return 0; };
|
||||
|
||||
it('should default to "time logLevel loggerName - message"', function() {
|
||||
test(
|
||||
layout,
|
||||
event,
|
||||
tokens,
|
||||
null,
|
||||
"14:18:30 DEBUG multiple.levels.of.tests - this is a test\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('%r should output time only', function() {
|
||||
test(layout, event, tokens, '%r', '14:18:30');
|
||||
});
|
||||
|
||||
it('%p should output the log level', function() {
|
||||
test(layout, event, tokens, '%p', 'DEBUG');
|
||||
});
|
||||
|
||||
it('%c should output the log category', function() {
|
||||
test(layout, event, tokens, '%c', 'multiple.levels.of.tests');
|
||||
});
|
||||
|
||||
it('%m should output the log data', function() {
|
||||
test(layout, event, tokens, '%m', 'this is a test');
|
||||
});
|
||||
|
||||
it('%n should output a new line', function() {
|
||||
test(layout, event, tokens, '%n', '\n');
|
||||
});
|
||||
|
||||
it('%h should output hostname', function() {
|
||||
test(layout, event, tokens, '%h', require('os').hostname().toString());
|
||||
});
|
||||
|
||||
it('%c should handle category names like java-style package names', function() {
|
||||
test(layout, event, tokens, '%c{1}', 'tests');
|
||||
test(layout, event, tokens, '%c{2}', 'of.tests');
|
||||
test(layout, event, tokens, '%c{3}', 'levels.of.tests');
|
||||
test(layout, event, tokens, '%c{4}', 'multiple.levels.of.tests');
|
||||
test(layout, event, tokens, '%c{5}', 'multiple.levels.of.tests');
|
||||
test(layout, event, tokens, '%c{99}', 'multiple.levels.of.tests');
|
||||
});
|
||||
|
||||
it('%d should output the date in ISO8601 format', function() {
|
||||
test(layout, event, tokens, '%d', '2010-12-05 14:18:30.045');
|
||||
});
|
||||
|
||||
it('%d should allow for format specification', function() {
|
||||
test(layout, event, tokens, '%d{ISO8601_WITH_TZ_OFFSET}', '2010-12-05T14:18:30-0000');
|
||||
test(layout, event, tokens, '%d{ISO8601}', '2010-12-05 14:18:30.045');
|
||||
test(layout, event, tokens, '%d{ABSOLUTE}', '14:18:30.045');
|
||||
test(layout, event, tokens, '%d{DATE}', '05 12 2010 14:18:30.045');
|
||||
test(layout, event, tokens, '%d{yy MM dd hh mm ss}', '10 12 05 14 18 30');
|
||||
test(layout, event, tokens, '%d{yyyy MM dd}', '2010 12 05');
|
||||
test(layout, event, tokens, '%d{yyyy MM dd hh mm ss SSS}', '2010 12 05 14 18 30 045');
|
||||
});
|
||||
|
||||
it('%% should output %', function() {
|
||||
test(layout, event, tokens, '%%', '%');
|
||||
});
|
||||
|
||||
it('should output anything not preceded by % as literal', function() {
|
||||
test(layout, event, tokens, 'blah blah blah', 'blah blah blah');
|
||||
});
|
||||
|
||||
it('should output the original string if no replacer matches the token', function() {
|
||||
test(layout, event, tokens, '%a{3}', 'a{3}');
|
||||
});
|
||||
|
||||
it('should handle complicated patterns', function() {
|
||||
test(layout, event, tokens,
|
||||
'%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'
|
||||
);
|
||||
});
|
||||
|
||||
it('should truncate fields if specified', function() {
|
||||
test(layout, event, tokens, '%.4m', 'this');
|
||||
test(layout, event, tokens, '%.7m', 'this is');
|
||||
test(layout, event, tokens, '%.9m', 'this is a');
|
||||
test(layout, event, tokens, '%.14m', 'this is a test');
|
||||
test(layout, event, tokens, '%.2919102m', 'this is a test');
|
||||
});
|
||||
|
||||
it('should pad fields if specified', function() {
|
||||
test(layout, event, tokens, '%10p', ' DEBUG');
|
||||
test(layout, event, tokens, '%8p', ' DEBUG');
|
||||
test(layout, event, tokens, '%6p', ' DEBUG');
|
||||
test(layout, event, tokens, '%4p', 'DEBUG');
|
||||
test(layout, event, tokens, '%-4p', 'DEBUG');
|
||||
test(layout, event, tokens, '%-6p', 'DEBUG ');
|
||||
test(layout, event, tokens, '%-8p', 'DEBUG ');
|
||||
test(layout, event, tokens, '%-10p', 'DEBUG ');
|
||||
});
|
||||
|
||||
it('%[%r%] should output colored time', function() {
|
||||
test(layout, event, tokens, '%[%r%]', '\x1B[36m14:18:30\x1B[39m');
|
||||
});
|
||||
|
||||
describe('%x{}', function() {
|
||||
it('%x{testString} should output the string stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x{testString}', 'testStringToken');
|
||||
});
|
||||
|
||||
it('%x{testFunction} should output the result of the function stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x{testFunction}', 'testFunctionToken');
|
||||
});
|
||||
|
||||
it('%x{doesNotExist} should output the string stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x{doesNotExist}', '%x{doesNotExist}');
|
||||
});
|
||||
|
||||
it('%x{fnThatUsesLogEvent} should be able to use the logEvent', function() {
|
||||
test(layout, event, tokens, '%x{fnThatUsesLogEvent}', 'DEBUG');
|
||||
});
|
||||
|
||||
it('%x should output the string stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x', '%x');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('layout makers', function() {
|
||||
var layouts = require('../lib/layouts');
|
||||
|
||||
it('should have a maker for each layout', function() {
|
||||
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"));
|
||||
});
|
||||
|
||||
it('should return falsy if a layout does not exist', function() {
|
||||
assert.ok(!layouts.layout("cheese"));
|
||||
});
|
||||
|
||||
it('should pass config to layouts that need it', function() {
|
||||
var layout = layouts.layout(
|
||||
"pattern",
|
||||
{
|
||||
pattern: "%m"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(layout({ data: [ "blah" ] }), "blah");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,210 +1,427 @@
|
||||
var vows = require('vows')
|
||||
, assert = require('assert')
|
||||
"use strict";
|
||||
var assert = require('assert')
|
||||
, should = require('should')
|
||||
, 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(val, test, otherLevels) {
|
||||
otherLevels.forEach(function(other) {
|
||||
test.call(level, other).should.eql(val);
|
||||
});
|
||||
}
|
||||
|
||||
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(true, level.isLessThanOrEqualTo, levels);
|
||||
},
|
||||
isNotLessThanOrEqualTo: function(levels) {
|
||||
assertForEach(false, level.isLessThanOrEqualTo, levels);
|
||||
},
|
||||
isGreaterThanOrEqualTo: function(levels) {
|
||||
assertForEach(true, level.isGreaterThanOrEqualTo, levels);
|
||||
},
|
||||
isNotGreaterThanOrEqualTo: function(levels) {
|
||||
assertForEach(false, level.isGreaterThanOrEqualTo, levels);
|
||||
},
|
||||
isEqualTo: function(levels) {
|
||||
assertForEach(true, level.isEqualTo, levels);
|
||||
},
|
||||
isNotEqualTo: function(levels) {
|
||||
assertForEach(false, 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]);
|
||||
}
|
||||
}
|
||||
},
|
||||
'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']);
|
||||
}
|
||||
},
|
||||
'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);
|
||||
describe('../lib/levels', function() {
|
||||
it('should define some levels', function() {
|
||||
should.exist(levels.ALL);
|
||||
should.exist(levels.TRACE);
|
||||
should.exist(levels.DEBUG);
|
||||
should.exist(levels.INFO);
|
||||
should.exist(levels.WARN);
|
||||
should.exist(levels.ERROR);
|
||||
should.exist(levels.FATAL);
|
||||
should.exist(levels.OFF);
|
||||
});
|
||||
|
||||
describe('ALL', function() {
|
||||
var all = levels.ALL;
|
||||
|
||||
it('should be less than the other levels', function() {
|
||||
assertThat(all).isLessThanOrEqualTo(
|
||||
[
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should be greater than no levels', function() {
|
||||
assertThat(all).isNotGreaterThanOrEqualTo(
|
||||
[
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should only be equal to ALL', function() {
|
||||
assertThat(all).isEqualTo([levels.toLevel("ALL")]);
|
||||
assertThat(all).isNotEqualTo(
|
||||
[
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TRACE', function() {
|
||||
var trace = levels.TRACE;
|
||||
|
||||
it('should be less than DEBUG', function() {
|
||||
assertThat(trace).isLessThanOrEqualTo(
|
||||
[
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
assertThat(trace).isNotLessThanOrEqualTo([levels.ALL]);
|
||||
});
|
||||
|
||||
it('should be greater than ALL', function() {
|
||||
assertThat(trace).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
|
||||
assertThat(trace).isNotGreaterThanOrEqualTo(
|
||||
[
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should only be equal to TRACE', function() {
|
||||
assertThat(trace).isEqualTo([levels.toLevel("TRACE")]);
|
||||
assertThat(trace).isNotEqualTo(
|
||||
[
|
||||
levels.ALL,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('DEBUG', function() {
|
||||
var debug = levels.DEBUG;
|
||||
|
||||
it('should be less than INFO', function() {
|
||||
assertThat(debug).isLessThanOrEqualTo(
|
||||
[
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
assertThat(debug).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE]);
|
||||
});
|
||||
|
||||
it('should be greater than TRACE', function() {
|
||||
assertThat(debug).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
|
||||
assertThat(debug).isNotGreaterThanOrEqualTo(
|
||||
[
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should only be equal to DEBUG', function() {
|
||||
assertThat(debug).isEqualTo([levels.toLevel("DEBUG")]);
|
||||
assertThat(debug).isNotEqualTo(
|
||||
[
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('INFO', function() {
|
||||
var info = levels.INFO;
|
||||
|
||||
it('should be less than WARN', function() {
|
||||
assertThat(info).isLessThanOrEqualTo([
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
assertThat(info).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
|
||||
});
|
||||
|
||||
it('should be greater than DEBUG', function() {
|
||||
assertThat(info).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
|
||||
assertThat(info).isNotGreaterThanOrEqualTo([
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only be equal to INFO', function() {
|
||||
assertThat(info).isEqualTo([levels.toLevel("INFO")]);
|
||||
assertThat(info).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WARN', function() {
|
||||
var warn = levels.WARN;
|
||||
|
||||
it('should be less than ERROR', function() {
|
||||
assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
|
||||
assertThat(warn).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than INFO', function() {
|
||||
assertThat(warn).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO
|
||||
]);
|
||||
assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
|
||||
});
|
||||
|
||||
it('should only be equal to WARN', function() {
|
||||
assertThat(warn).isEqualTo([levels.toLevel("WARN")]);
|
||||
assertThat(warn).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERROR', function() {
|
||||
var error = levels.ERROR;
|
||||
|
||||
it('should be less than FATAL', function() {
|
||||
assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.OFF]);
|
||||
assertThat(error).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than WARN', function() {
|
||||
assertThat(error).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN
|
||||
]);
|
||||
assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.OFF]);
|
||||
});
|
||||
|
||||
it('should only be equal to ERROR', function() {
|
||||
assertThat(error).isEqualTo([levels.toLevel("ERROR")]);
|
||||
assertThat(error).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FATAL', function() {
|
||||
var fatal = levels.FATAL;
|
||||
|
||||
it('should be less than OFF', function() {
|
||||
assertThat(fatal).isLessThanOrEqualTo([levels.OFF]);
|
||||
assertThat(fatal).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than ERROR', function() {
|
||||
assertThat(fatal).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR
|
||||
]);
|
||||
assertThat(fatal).isNotGreaterThanOrEqualTo([levels.OFF]);
|
||||
});
|
||||
|
||||
it('should only be equal to FATAL', function() {
|
||||
assertThat(fatal).isEqualTo([levels.toLevel("FATAL")]);
|
||||
assertThat(fatal).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('OFF', function() {
|
||||
var off = levels.OFF;
|
||||
|
||||
it('should not be less than anything', function() {
|
||||
assertThat(off).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than everything', function() {
|
||||
assertThat(off).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only be equal to OFF', function() {
|
||||
assertThat(off).isEqualTo([levels.toLevel("OFF")]);
|
||||
assertThat(off).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isGreaterThanOrEqualTo', function() {
|
||||
var info = levels.INFO;
|
||||
it('should handle string arguments', function() {
|
||||
assertThat(info).isGreaterThanOrEqualTo(["all", "trace", "debug"]);
|
||||
assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLessThanOrEqualTo', function() {
|
||||
var info = levels.INFO;
|
||||
it('should handle string arguments', function() {
|
||||
assertThat(info).isNotLessThanOrEqualTo(["all", "trace", "debug"]);
|
||||
assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEqualTo', function() {
|
||||
var info = levels.INFO;
|
||||
it('should handle string arguments', function() {
|
||||
assertThat(info).isEqualTo(["info", "INFO", "iNfO"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toLevel', function() {
|
||||
it('should ignore the case of arguments', function() {
|
||||
levels.toLevel("debug").should.eql(levels.DEBUG);
|
||||
levels.toLevel("DEBUG").should.eql(levels.DEBUG);
|
||||
levels.toLevel("DeBuG").should.eql(levels.DEBUG);
|
||||
});
|
||||
|
||||
it('should return undefined when argument is not recognised', function() {
|
||||
should.not.exist(levels.toLevel("cheese"));
|
||||
});
|
||||
|
||||
it('should return the default value if argument is not recognised', function() {
|
||||
levels.toLevel("cheese", levels.DEBUG).should.eql(levels.DEBUG);
|
||||
});
|
||||
|
||||
it('should return the default value if argument is falsy', function() {
|
||||
levels.toLevel(undefined, levels.DEBUG).should.eql(levels.DEBUG);
|
||||
levels.toLevel(null, levels.DEBUG).should.eql(levels.DEBUG);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
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"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
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() {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
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);
|
||||
423
test/log4js-test.js
Normal file
423
test/log4js-test.js
Normal file
@@ -0,0 +1,423 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, fs = require('fs')
|
||||
, sandbox = require('sandboxed-module')
|
||||
, log4js = require('../lib/log4js');
|
||||
|
||||
describe('../lib/log4js', function() {
|
||||
describe('#getLogger', function() {
|
||||
it('should return a Logger', function() {
|
||||
log4js.getLogger().should.have.property('debug').be.a('function');
|
||||
log4js.getLogger().should.have.property('info').be.a('function');
|
||||
log4js.getLogger().should.have.property('error').be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#configure', function() {
|
||||
it('should require an object or a filename', function() {
|
||||
[
|
||||
undefined,
|
||||
null,
|
||||
true,
|
||||
42,
|
||||
function() {}
|
||||
].forEach(function(arg) {
|
||||
(function() { log4js.configure(arg); }).should.throw(
|
||||
"You must specify configuration as an object or a filename."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should complain if the file cannot be found', function() {
|
||||
(function() { log4js.configure("pants"); }).should.throw(
|
||||
"ENOENT, no such file or directory 'pants'"
|
||||
);
|
||||
});
|
||||
|
||||
it('should pick up the configuration filename from env.LOG4JS_CONFIG', function() {
|
||||
process.env.LOG4JS_CONFIG = 'made-up-file';
|
||||
(function() { log4js.configure(); }).should.throw(
|
||||
"ENOENT, no such file or directory 'made-up-file'"
|
||||
);
|
||||
delete process.env.LOG4JS_CONFIG;
|
||||
});
|
||||
|
||||
it('should complain if the config does not specify any appenders', function() {
|
||||
|
||||
(function() { log4js.configure({}); }).should.throw(
|
||||
"You must specify at least one appender."
|
||||
);
|
||||
|
||||
(function() { log4js.configure({ appenders: {} }); }).should.throw(
|
||||
"You must specify at least one appender."
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
it(
|
||||
'should complain if the config does not specify an appender for the default category',
|
||||
function() {
|
||||
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: {
|
||||
"console": { type: "console" }
|
||||
},
|
||||
categories: {}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"You must specify an appender for the default category"
|
||||
);
|
||||
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"console": { type: "console" }
|
||||
},
|
||||
categories: {
|
||||
"cheese": { level: "DEBUG", appenders: [ "console" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(
|
||||
"You must specify an appender for the default category"
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
it('should complain if a category does not specify level or appenders', function() {
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" } },
|
||||
categories: {
|
||||
"default": { thing: "thing" }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"You must specify a level for category 'default'."
|
||||
);
|
||||
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" } },
|
||||
categories: {
|
||||
"default": { level: "DEBUG" }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"You must specify an appender for category 'default'."
|
||||
);
|
||||
});
|
||||
|
||||
it('should complain if a category specifies a level that does not exist', function() {
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" }},
|
||||
categories: {
|
||||
"default": { level: "PICKLES" }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"Level 'PICKLES' is not valid for category 'default'. " +
|
||||
"Acceptable values are: OFF, TRACE, DEBUG, INFO, WARN, ERROR, FATAL."
|
||||
);
|
||||
});
|
||||
|
||||
it('should complain if a category specifies an appender that does not exist', function() {
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" }},
|
||||
categories: {
|
||||
"default": { level: "DEBUG", appenders: [ "cheese" ] }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"Appender 'cheese' for category 'default' does not exist. Known appenders are: console."
|
||||
);
|
||||
});
|
||||
|
||||
before(function(done) {
|
||||
fs.unlink("test.log", function (err) { done(); });
|
||||
});
|
||||
|
||||
it('should set up the included appenders', function(done) {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: "test.log" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
log4js.getLogger('test').debug("cheese");
|
||||
|
||||
setTimeout(function() {
|
||||
fs.readFile("test.log", "utf-8", function(err, contents) {
|
||||
contents.should.include("cheese");
|
||||
done(err);
|
||||
});
|
||||
}, 50);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
fs.unlink("test.log", function (err) { done(); });
|
||||
});
|
||||
|
||||
it('should set up third-party appenders', function() {
|
||||
var events = [], log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(evt) { events.push(evt); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"thing": { type: "cheese" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "thing" ] }
|
||||
}
|
||||
});
|
||||
log4js_sandbox.getLogger().info("edam");
|
||||
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("edam");
|
||||
|
||||
});
|
||||
|
||||
it('should only load third-party appenders once', function() {
|
||||
var moduleCalled = 0
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
moduleCalled += 1;
|
||||
return function() {
|
||||
return function() {};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"thing1": { type: "cheese" },
|
||||
"thing2": { type: "cheese" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "thing1", "thing2" ] }
|
||||
}
|
||||
});
|
||||
|
||||
moduleCalled.should.eql(1);
|
||||
});
|
||||
|
||||
it('should pass layouts and levels to appender modules', function() {
|
||||
var layouts
|
||||
, levels
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function(arg1, arg2) {
|
||||
layouts = arg1;
|
||||
levels = arg2;
|
||||
return function() {
|
||||
return function() {};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"thing": { type: "cheese" }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "thing" ] }
|
||||
}
|
||||
});
|
||||
|
||||
layouts.should.have.property("basicLayout");
|
||||
levels.should.have.property("toLevel");
|
||||
});
|
||||
|
||||
it('should pass config and appenderByName to appender makers', function() {
|
||||
var otherAppender = function() { /* I do nothing */ }
|
||||
, config
|
||||
, other
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'other': function() {
|
||||
return function() {
|
||||
return otherAppender;
|
||||
};
|
||||
},
|
||||
'cheese': function() {
|
||||
return function(arg1, arg2) {
|
||||
config = arg1;
|
||||
other = arg2("other");
|
||||
return function() {};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"other": { type: "other" },
|
||||
"thing": { type: "cheese", something: "something" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "debug", appenders: [ "other", "thing" ] }
|
||||
}
|
||||
});
|
||||
|
||||
other.should.equal(otherAppender);
|
||||
config.should.have.property("something", "something");
|
||||
|
||||
});
|
||||
|
||||
it('should complain about unknown appenders', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"thing": { type: "madeupappender" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "thing" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(
|
||||
"Could not load appender of type 'madeupappender'."
|
||||
);
|
||||
});
|
||||
|
||||
it('should read config from a file', function() {
|
||||
var events = [], log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js_sandbox.configure(__dirname + "/with-cheese.json");
|
||||
log4js_sandbox.getLogger().debug("gouda");
|
||||
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("gouda");
|
||||
});
|
||||
|
||||
it('should set up log levels for categories', function() {
|
||||
var events = []
|
||||
, noisyLogger
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js_sandbox.configure(__dirname + "/with-cheese.json");
|
||||
noisyLogger = log4js_sandbox.getLogger("noisy");
|
||||
noisyLogger.debug("pow");
|
||||
noisyLogger.info("crash");
|
||||
noisyLogger.warn("bang");
|
||||
noisyLogger.error("boom");
|
||||
noisyLogger.fatal("aargh");
|
||||
|
||||
events.should.have.length(2);
|
||||
events[0].data[0].should.eql("boom");
|
||||
events[1].data[0].should.eql("aargh");
|
||||
|
||||
});
|
||||
|
||||
it('should have a default log level for all categories', function() {
|
||||
var events = []
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//with-cheese.json only specifies categories noisy and default
|
||||
//unspecified categories should use the default category config
|
||||
log4js_sandbox.configure(__dirname + "/with-cheese.json");
|
||||
log4js_sandbox.getLogger("surprise").trace("not seen");
|
||||
log4js_sandbox.getLogger("surprise").info("should be seen");
|
||||
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("should be seen");
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with no configuration', function() {
|
||||
var events = []
|
||||
, log4js_sandboxed = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/console': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js_sandboxed.getLogger("blah").debug("goes to console");
|
||||
log4js_sandboxed.getLogger("yawn").trace("does not go to console");
|
||||
log4js_sandboxed.getLogger().error("also goes to console");
|
||||
|
||||
it('should log events of debug level and higher to console', function() {
|
||||
events.should.have.length(2);
|
||||
events[0].data[0].should.eql("goes to console");
|
||||
events[0].category.should.eql("blah");
|
||||
events[0].level.toString().should.eql("DEBUG");
|
||||
events[1].data[0].should.eql("also goes to console");
|
||||
events[1].category.should.eql("default");
|
||||
events[1].level.toString().should.eql("ERROR");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,69 +1,141 @@
|
||||
var vows = require('vows')
|
||||
, fs = require('fs')
|
||||
, assert = require('assert');
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, sandbox = require('sandboxed-module')
|
||||
, log4js = require('../lib/log4js');
|
||||
|
||||
function remove(filename) {
|
||||
try {
|
||||
fs.unlinkSync(filename);
|
||||
} catch (e) {
|
||||
//doesn't really matter if it failed
|
||||
}
|
||||
}
|
||||
describe('log level filter', function() {
|
||||
describe('when configured correctly', function() {
|
||||
var events = [], logger;
|
||||
|
||||
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');
|
||||
before(function() {
|
||||
var log4js_sandboxed = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{ requires:
|
||||
{ './appenders/console': function() {
|
||||
return function() {
|
||||
return function(evt) { events.push(evt); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'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);
|
||||
);
|
||||
log4js_sandboxed.configure({
|
||||
appenders: {
|
||||
"console": { type: "console", layout: { type: "messagePassThrough" } },
|
||||
"errors only": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "ERROR", "FATAL" ],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
'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']);
|
||||
}
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors only" ] }
|
||||
}
|
||||
}
|
||||
}).export(module);
|
||||
});
|
||||
logger = log4js_sandboxed.getLogger("test");
|
||||
});
|
||||
|
||||
it('should pass events to an appender if they match', function() {
|
||||
logger.error("oh no");
|
||||
logger.fatal("boom");
|
||||
|
||||
events.should.have.length(2);
|
||||
events[0].data[0].should.eql("oh no");
|
||||
events[1].data[0].should.eql("boom");
|
||||
});
|
||||
|
||||
it('should not pass events to the appender if they do not match', function() {
|
||||
events.should.have.length(2);
|
||||
logger.debug("cheese");
|
||||
events.should.have.length(2);
|
||||
logger.info("yawn");
|
||||
events.should.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should complain if it has no appender', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "ERROR", "FATAL" ]
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/Missing an appender\./);
|
||||
});
|
||||
|
||||
it('should complain if it has no list of allowed levels', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"console": { type: "console" },
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/No allowed log levels specified\./);
|
||||
});
|
||||
|
||||
it('should complain if the referenced appender does not exist', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "ERROR" ],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/Appender 'console' not found\./);
|
||||
});
|
||||
|
||||
it('should complain if the list of levels is not valid', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "cheese", "biscuits", "ERROR" ],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/Unrecognised log level 'cheese'\./);
|
||||
});
|
||||
|
||||
it('should complain if the list of levels is empty', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"console": { type: "console" },
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "debug", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/No allowed log levels specified\./);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
53
test/logger-test.js
Normal file
53
test/logger-test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, levels = require('../lib/levels')
|
||||
, Logger = require('../lib/logger');
|
||||
|
||||
describe('../lib/logger', function() {
|
||||
describe('Logger constructor', function() {
|
||||
it('must be passed a dispatch delegate and a category', function() {
|
||||
(function() { new Logger(); }).should.throw(
|
||||
"Logger must have a dispatch delegate."
|
||||
);
|
||||
(function() { new Logger(function() {}); }).should.throw(
|
||||
"Logger must have a category."
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Logger instance', function() {
|
||||
var event
|
||||
, logger = new Logger(
|
||||
function(evt) { event = evt; },
|
||||
"exciting category"
|
||||
);
|
||||
|
||||
beforeEach(function() {
|
||||
event = null;
|
||||
});
|
||||
|
||||
it('should be immutable', function() {
|
||||
logger.category = "rubbish";
|
||||
logger.debug("thing");
|
||||
|
||||
event.category.should.equal("exciting category");
|
||||
});
|
||||
|
||||
['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(function(level) {
|
||||
it('should have a ' + level + ' function', function() {
|
||||
logger[level].should.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
it('should send log events to the dispatch delegate', function() {
|
||||
logger.debug("interesting thing");
|
||||
event.should.have.property('category').equal('exciting category');
|
||||
event.should.have.property('level').equal(levels.DEBUG);
|
||||
event.should.have.property('data').eql(["interesting thing"]);
|
||||
event.should.have.property('startTime');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,578 +0,0 @@
|
||||
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('log4js').addBatch({
|
||||
'getLogger': {
|
||||
topic: function() {
|
||||
var log4js = require('../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
var logger = log4js.getLogger('tests');
|
||||
logger.setLevel("DEBUG");
|
||||
return logger;
|
||||
},
|
||||
|
||||
'should take a category and return a logger': function(logger) {
|
||||
assert.equal(logger.category, 'tests');
|
||||
assert.equal(logger.level.toString(), "DEBUG");
|
||||
assert.isFunction(logger.debug);
|
||||
assert.isFunction(logger.info);
|
||||
assert.isFunction(logger.warn);
|
||||
assert.isFunction(logger.error);
|
||||
assert.isFunction(logger.fatal);
|
||||
},
|
||||
|
||||
'log events' : {
|
||||
topic: function(logger) {
|
||||
var events = [];
|
||||
logger.addListener("log", function (logEvent) { events.push(logEvent); });
|
||||
logger.debug("Debug event");
|
||||
logger.trace("Trace event 1");
|
||||
logger.trace("Trace event 2");
|
||||
logger.warn("Warning event");
|
||||
logger.error("Aargh!", new Error("Pants are on fire!"));
|
||||
logger.error("Simulated CouchDB problem", { err: 127, cause: "incendiary underwear" });
|
||||
return events;
|
||||
},
|
||||
|
||||
'should emit log events': function(events) {
|
||||
assert.equal(events[0].level.toString(), 'DEBUG');
|
||||
assert.equal(events[0].data[0], 'Debug event');
|
||||
assert.instanceOf(events[0].startTime, Date);
|
||||
},
|
||||
|
||||
'should not emit events of a lower level': function(events) {
|
||||
assert.equal(events.length, 4);
|
||||
assert.equal(events[1].level.toString(), 'WARN');
|
||||
},
|
||||
|
||||
'should include the error if passed in': function (events) {
|
||||
assert.instanceOf(events[2].data[1], Error);
|
||||
assert.equal(events[2].data[1].message, 'Pants are on fire!');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
'invalid configuration': {
|
||||
'should throw an exception': function() {
|
||||
assert.throws(function() {
|
||||
require('log4js').configure({ "type": "invalid" });
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'configuration when passed as object': {
|
||||
topic: function() {
|
||||
var appenderConfig
|
||||
, log4js = sandbox.require(
|
||||
'../lib/log4js'
|
||||
, { requires:
|
||||
{ './appenders/file':
|
||||
{
|
||||
name: "file"
|
||||
, appender: function() {}
|
||||
, configure: function(configuration) {
|
||||
appenderConfig = configuration;
|
||||
return function() {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
, config = {
|
||||
"appenders": [
|
||||
{
|
||||
"type" : "file",
|
||||
"filename" : "cheesy-wotsits.log",
|
||||
"maxLogSize" : 1024,
|
||||
"backups" : 3
|
||||
}
|
||||
]
|
||||
};
|
||||
log4js.configure(config);
|
||||
return appenderConfig;
|
||||
},
|
||||
'should be passed to appender config': function(configuration) {
|
||||
assert.equal(configuration.filename, 'cheesy-wotsits.log');
|
||||
}
|
||||
},
|
||||
|
||||
'configuration when passed as filename': {
|
||||
topic: function() {
|
||||
var appenderConfig
|
||||
, configFilename
|
||||
, log4js = sandbox.require(
|
||||
'../lib/log4js'
|
||||
, { requires:
|
||||
{ 'fs':
|
||||
{
|
||||
statSync: function() {
|
||||
return { mtime: Date.now() };
|
||||
},
|
||||
readFileSync: function(filename) {
|
||||
configFilename = filename;
|
||||
return JSON.stringify({
|
||||
appenders: [
|
||||
{ type: "file"
|
||||
, filename: "whatever.log"
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
readdirSync: function() {
|
||||
return ['file'];
|
||||
}
|
||||
}
|
||||
, './appenders/file':
|
||||
{
|
||||
name: "file"
|
||||
, appender: function() {}
|
||||
, configure: function(configuration) {
|
||||
appenderConfig = configuration;
|
||||
return function() {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js.configure("/path/to/cheese.json");
|
||||
return [ configFilename, appenderConfig ];
|
||||
},
|
||||
'should read the config from a file': function(args) {
|
||||
assert.equal(args[0], '/path/to/cheese.json');
|
||||
},
|
||||
'should pass config to appender': function(args) {
|
||||
assert.equal(args[1].filename, "whatever.log");
|
||||
}
|
||||
},
|
||||
|
||||
'with no appenders defined' : {
|
||||
topic: function() {
|
||||
var logger
|
||||
, that = this
|
||||
, fakeConsoleAppender = {
|
||||
name: "console"
|
||||
, appender: function() {
|
||||
return function(evt) {
|
||||
that.callback(null, evt);
|
||||
}
|
||||
}
|
||||
, configure: function() {
|
||||
return fakeConsoleAppender.appender();
|
||||
}
|
||||
}
|
||||
, log4js = sandbox.require(
|
||||
'../lib/log4js'
|
||||
, {
|
||||
requires: {
|
||||
'./appenders/console': fakeConsoleAppender
|
||||
}
|
||||
}
|
||||
);
|
||||
logger = log4js.getLogger("some-logger");
|
||||
logger.debug("This is a test");
|
||||
},
|
||||
'should default to the console appender': function(evt) {
|
||||
assert.equal(evt.data[0], "This is a test");
|
||||
}
|
||||
},
|
||||
|
||||
'addAppender' : {
|
||||
topic: function() {
|
||||
var log4js = require('../lib/log4js');
|
||||
log4js.clearAppenders();
|
||||
return log4js;
|
||||
},
|
||||
'without a category': {
|
||||
'should register the function as a listener for all loggers': function (log4js) {
|
||||
var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests");
|
||||
log4js.addAppender(appender);
|
||||
logger.debug("This is a test");
|
||||
assert.equal(appenderEvent.data[0], "This is a test");
|
||||
assert.equal(appenderEvent.categoryName, "tests");
|
||||
assert.equal(appenderEvent.level.toString(), "DEBUG");
|
||||
},
|
||||
'should also register as an appender for loggers if an appender for that category is defined': function (log4js) {
|
||||
var otherEvent, appenderEvent, cheeseLogger;
|
||||
log4js.addAppender(function (evt) { appenderEvent = evt; });
|
||||
log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese');
|
||||
|
||||
cheeseLogger = log4js.getLogger('cheese');
|
||||
cheeseLogger.debug('This is a test');
|
||||
assert.deepEqual(appenderEvent, otherEvent);
|
||||
assert.equal(otherEvent.data[0], 'This is a test');
|
||||
assert.equal(otherEvent.categoryName, 'cheese');
|
||||
|
||||
otherEvent = undefined;
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('pants').debug("this should not be propagated to otherEvent");
|
||||
assert.isUndefined(otherEvent);
|
||||
assert.equal(appenderEvent.data[0], "this should not be propagated to otherEvent");
|
||||
}
|
||||
},
|
||||
|
||||
'with a category': {
|
||||
'should only register the function as a listener for that category': function(log4js) {
|
||||
var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests");
|
||||
log4js.addAppender(appender, 'tests');
|
||||
logger.debug('this is a category test');
|
||||
assert.equal(appenderEvent.data[0], 'this is a category test');
|
||||
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('some other category').debug('Cheese');
|
||||
assert.isUndefined(appenderEvent);
|
||||
}
|
||||
},
|
||||
|
||||
'with multiple categories': {
|
||||
'should register the function as a listener for all the categories': function(log4js) {
|
||||
var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger('tests');
|
||||
log4js.addAppender(appender, 'tests', 'biscuits');
|
||||
|
||||
logger.debug('this is a test');
|
||||
assert.equal(appenderEvent.data[0], 'this is a test');
|
||||
appenderEvent = undefined;
|
||||
|
||||
var otherLogger = log4js.getLogger('biscuits');
|
||||
otherLogger.debug("mmm... garibaldis");
|
||||
assert.equal(appenderEvent.data[0], "mmm... garibaldis");
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger("something else").debug("pants");
|
||||
assert.isUndefined(appenderEvent);
|
||||
},
|
||||
'should register the function when the list of categories is an array': function(log4js) {
|
||||
var appenderEvent, appender = function(evt) { appenderEvent = evt; };
|
||||
log4js.addAppender(appender, ['tests', 'pants']);
|
||||
|
||||
log4js.getLogger('tests').debug('this is a test');
|
||||
assert.equal(appenderEvent.data[0], 'this is a test');
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger('pants').debug("big pants");
|
||||
assert.equal(appenderEvent.data[0], "big pants");
|
||||
|
||||
appenderEvent = undefined;
|
||||
|
||||
log4js.getLogger("something else").debug("pants");
|
||||
assert.isUndefined(appenderEvent);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'default setup': {
|
||||
topic: function() {
|
||||
var appenderEvents = [],
|
||||
fakeConsole = {
|
||||
'name': 'console'
|
||||
, 'appender': function () {
|
||||
return function(evt) {
|
||||
appenderEvents.push(evt);
|
||||
}
|
||||
}
|
||||
, 'configure': function (config) {
|
||||
return fakeConsole.appender();
|
||||
}
|
||||
},
|
||||
globalConsole = {
|
||||
log: function() { }
|
||||
},
|
||||
log4js = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/console': fakeConsole
|
||||
},
|
||||
globals: {
|
||||
console: globalConsole
|
||||
}
|
||||
}
|
||||
),
|
||||
logger = log4js.getLogger('a-test');
|
||||
|
||||
logger.debug("this is a test");
|
||||
globalConsole.log("this should not be logged");
|
||||
|
||||
return appenderEvents;
|
||||
},
|
||||
|
||||
'should configure a console appender': function(appenderEvents) {
|
||||
assert.equal(appenderEvents[0].data[0], 'this is a test');
|
||||
},
|
||||
|
||||
'should not replace console.log with log4js version': function(appenderEvents) {
|
||||
assert.equal(appenderEvents.length, 1);
|
||||
}
|
||||
},
|
||||
|
||||
'console' : {
|
||||
topic: setupConsoleTest,
|
||||
|
||||
'when replaceConsole called': {
|
||||
topic: function(test) {
|
||||
test.log4js.replaceConsole();
|
||||
|
||||
test.fakeConsole.log("Some debug message someone put in a module");
|
||||
test.fakeConsole.debug("Some debug");
|
||||
test.fakeConsole.error("An error");
|
||||
test.fakeConsole.info("some info");
|
||||
test.fakeConsole.warn("a warning");
|
||||
|
||||
test.fakeConsole.log("cheese (%s) and biscuits (%s)", "gouda", "garibaldis");
|
||||
test.fakeConsole.log({ lumpy: "tapioca" });
|
||||
test.fakeConsole.log("count %d", 123);
|
||||
test.fakeConsole.log("stringify %j", { lumpy: "tapioca" });
|
||||
|
||||
return test.logEvents;
|
||||
},
|
||||
|
||||
'should replace console.log methods with log4js ones': function(logEvents) {
|
||||
assert.equal(logEvents.length, 9);
|
||||
assert.equal(logEvents[0].data[0], "Some debug message someone put in a module");
|
||||
assert.equal(logEvents[0].level.toString(), "INFO");
|
||||
assert.equal(logEvents[1].data[0], "Some debug");
|
||||
assert.equal(logEvents[1].level.toString(), "DEBUG");
|
||||
assert.equal(logEvents[2].data[0], "An error");
|
||||
assert.equal(logEvents[2].level.toString(), "ERROR");
|
||||
assert.equal(logEvents[3].data[0], "some info");
|
||||
assert.equal(logEvents[3].level.toString(), "INFO");
|
||||
assert.equal(logEvents[4].data[0], "a warning");
|
||||
assert.equal(logEvents[4].level.toString(), "WARN");
|
||||
assert.equal(logEvents[5].data[0], "cheese (%s) and biscuits (%s)");
|
||||
assert.equal(logEvents[5].data[1], "gouda");
|
||||
assert.equal(logEvents[5].data[2], "garibaldis");
|
||||
}
|
||||
},
|
||||
'when turned off': {
|
||||
topic: function(test) {
|
||||
test.log4js.restoreConsole();
|
||||
try {
|
||||
test.fakeConsole.log("This should cause the error described in the setup");
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
'should call the original console methods': function (err) {
|
||||
assert.instanceOf(err, Error);
|
||||
assert.equal(err.message, "this should not be called.");
|
||||
}
|
||||
},
|
||||
'configuration': {
|
||||
topic: function(test) {
|
||||
test.log4js.replaceConsole();
|
||||
test.log4js.configure({ replaceConsole: false });
|
||||
try {
|
||||
test.fakeConsole.log("This should cause the error described in the setup");
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
'should allow for turning off console replacement': function (err) {
|
||||
assert.instanceOf(err, Error);
|
||||
assert.equal(err.message, 'this should not be called.');
|
||||
}
|
||||
}
|
||||
},
|
||||
'configuration persistence' : {
|
||||
topic: function() {
|
||||
var logEvent,
|
||||
firstLog4js = require('../lib/log4js'),
|
||||
secondLog4js;
|
||||
|
||||
firstLog4js.clearAppenders();
|
||||
firstLog4js.addAppender(function(evt) { logEvent = evt; });
|
||||
|
||||
secondLog4js = require('../lib/log4js');
|
||||
secondLog4js.getLogger().info("This should go to the appender defined in firstLog4js");
|
||||
|
||||
return logEvent;
|
||||
},
|
||||
'should maintain appenders between requires': function (logEvent) {
|
||||
assert.equal(logEvent.data[0], "This should go to the appender defined in firstLog4js");
|
||||
}
|
||||
},
|
||||
'configuration reload with configuration 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' } },
|
||||
readdirSync: function(dir) {
|
||||
return require('fs').readdirSync(dir);
|
||||
},
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
'configuration reload with configuration 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' } },
|
||||
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");
|
||||
}
|
||||
}
|
||||
},
|
||||
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');
|
||||
}
|
||||
}
|
||||
}).export(module);
|
||||
@@ -1,241 +0,0 @@
|
||||
var vows = require('vows'),
|
||||
sandbox = require('sandboxed-module'),
|
||||
assert = require('assert');
|
||||
|
||||
function makeFakeNet() {
|
||||
return {
|
||||
logEvents: [],
|
||||
data: [],
|
||||
cbs: {},
|
||||
createConnectionCalled: 0,
|
||||
fakeAppender: function(logEvent) {
|
||||
this.logEvents.push(logEvent);
|
||||
},
|
||||
createConnection: function(port, host) {
|
||||
var fakeNet = this;
|
||||
this.port = port;
|
||||
this.host = host;
|
||||
this.createConnectionCalled += 1;
|
||||
return {
|
||||
on: function(evt, cb) {
|
||||
fakeNet.cbs[evt] = cb;
|
||||
},
|
||||
write: function(data, encoding) {
|
||||
fakeNet.data.push(data);
|
||||
fakeNet.encoding = encoding;
|
||||
},
|
||||
end: function() {
|
||||
fakeNet.closeCalled = true;
|
||||
}
|
||||
};
|
||||
},
|
||||
createServer: function(cb) {
|
||||
var fakeNet = this;
|
||||
cb({
|
||||
remoteAddress: '1.2.3.4',
|
||||
remotePort: '1234',
|
||||
setEncoding: function(encoding) {
|
||||
fakeNet.encoding = encoding;
|
||||
},
|
||||
on: function(event, cb) {
|
||||
fakeNet.cbs[event] = cb;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
listen: function(port, host) {
|
||||
fakeNet.port = port;
|
||||
fakeNet.host = host;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
vows.describe('Multiprocess Appender').addBatch({
|
||||
'worker': {
|
||||
topic: function() {
|
||||
var fakeNet = makeFakeNet(),
|
||||
appender = sandbox.require(
|
||||
'../lib/appenders/multiprocess',
|
||||
{
|
||||
requires: {
|
||||
'net': fakeNet
|
||||
}
|
||||
}
|
||||
).appender({ mode: 'worker', loggerPort: 1234, loggerHost: 'pants' });
|
||||
|
||||
//don't need a proper log event for the worker tests
|
||||
appender('before connect');
|
||||
fakeNet.cbs['connect']();
|
||||
appender('after connect');
|
||||
fakeNet.cbs['close'](true);
|
||||
appender('after error, before connect');
|
||||
fakeNet.cbs['connect']();
|
||||
appender('after error, after connect');
|
||||
|
||||
return fakeNet;
|
||||
},
|
||||
'should open a socket to the loggerPort and loggerHost': function(net) {
|
||||
assert.equal(net.port, 1234);
|
||||
assert.equal(net.host, 'pants');
|
||||
},
|
||||
'should buffer messages written before socket is connected': function(net) {
|
||||
assert.equal(net.data[0], JSON.stringify('before connect'));
|
||||
},
|
||||
'should write log messages to socket as json strings with a terminator string': function(net) {
|
||||
assert.equal(net.data[0], JSON.stringify('before connect'));
|
||||
assert.equal(net.data[1], '__LOG4JS__');
|
||||
assert.equal(net.data[2], JSON.stringify('after connect'));
|
||||
assert.equal(net.data[3], '__LOG4JS__');
|
||||
assert.equal(net.encoding, 'utf8');
|
||||
},
|
||||
'should attempt to re-open the socket on error': function(net) {
|
||||
assert.equal(net.data[4], JSON.stringify('after error, before connect'));
|
||||
assert.equal(net.data[5], '__LOG4JS__');
|
||||
assert.equal(net.data[6], JSON.stringify('after error, after connect'));
|
||||
assert.equal(net.data[7], '__LOG4JS__');
|
||||
assert.equal(net.createConnectionCalled, 2);
|
||||
}
|
||||
},
|
||||
'worker with timeout': {
|
||||
topic: function() {
|
||||
var fakeNet = makeFakeNet(),
|
||||
appender = sandbox.require(
|
||||
'../lib/appenders/multiprocess',
|
||||
{
|
||||
requires: {
|
||||
'net': fakeNet
|
||||
}
|
||||
}
|
||||
).appender({ mode: 'worker' });
|
||||
|
||||
//don't need a proper log event for the worker tests
|
||||
appender('before connect');
|
||||
fakeNet.cbs['connect']();
|
||||
appender('after connect');
|
||||
fakeNet.cbs['timeout']();
|
||||
appender('after timeout, before close');
|
||||
fakeNet.cbs['close']();
|
||||
appender('after close, before connect');
|
||||
fakeNet.cbs['connect']();
|
||||
appender('after close, after connect');
|
||||
|
||||
return fakeNet;
|
||||
},
|
||||
'should attempt to re-open the socket': function(net) {
|
||||
//skipping the __LOG4JS__ separators
|
||||
assert.equal(net.data[0], JSON.stringify('before connect'));
|
||||
assert.equal(net.data[2], JSON.stringify('after connect'));
|
||||
assert.equal(net.data[4], JSON.stringify('after timeout, before close'));
|
||||
assert.equal(net.data[6], JSON.stringify('after close, before connect'));
|
||||
assert.equal(net.data[8], JSON.stringify('after close, after connect'));
|
||||
assert.equal(net.createConnectionCalled, 2);
|
||||
}
|
||||
},
|
||||
'worker defaults': {
|
||||
topic: function() {
|
||||
var fakeNet = makeFakeNet(),
|
||||
appender = sandbox.require(
|
||||
'../lib/appenders/multiprocess',
|
||||
{
|
||||
requires: {
|
||||
'net': fakeNet
|
||||
}
|
||||
}
|
||||
).appender({ mode: 'worker' });
|
||||
|
||||
return fakeNet;
|
||||
},
|
||||
'should open a socket to localhost:5000': function(net) {
|
||||
assert.equal(net.port, 5000);
|
||||
assert.equal(net.host, 'localhost');
|
||||
}
|
||||
},
|
||||
'master': {
|
||||
topic: function() {
|
||||
var fakeNet = makeFakeNet(),
|
||||
appender = sandbox.require(
|
||||
'../lib/appenders/multiprocess',
|
||||
{
|
||||
requires: {
|
||||
'net': fakeNet
|
||||
}
|
||||
}
|
||||
).appender({ mode: 'master',
|
||||
loggerHost: 'server',
|
||||
loggerPort: 1234,
|
||||
actualAppender: fakeNet.fakeAppender.bind(fakeNet)
|
||||
});
|
||||
|
||||
appender('this should be sent to the actual appender directly');
|
||||
|
||||
return fakeNet;
|
||||
},
|
||||
'should listen for log messages on loggerPort and loggerHost': function(net) {
|
||||
assert.equal(net.port, 1234);
|
||||
assert.equal(net.host, 'server');
|
||||
},
|
||||
'should return the underlying appender': function(net) {
|
||||
assert.equal(net.logEvents[0], 'this should be sent to the actual appender directly');
|
||||
},
|
||||
'when a client connects': {
|
||||
topic: function(net) {
|
||||
var logString = JSON.stringify({ level: { level: 10000, levelStr: 'DEBUG' }, data: ['some debug']}) + '__LOG4JS__';
|
||||
|
||||
net.cbs['data'](JSON.stringify({ level: { level: 40000, levelStr: 'ERROR' }, data: ['an error message'] }) + '__LOG4JS__');
|
||||
net.cbs['data'](logString.substring(0, 10));
|
||||
net.cbs['data'](logString.substring(10));
|
||||
net.cbs['data'](logString + logString + logString);
|
||||
net.cbs['end'](JSON.stringify({ level: { level: 50000, levelStr: 'FATAL' }, data: ["that's all folks"] }) + '__LOG4JS__');
|
||||
net.cbs['data']('bad message__LOG4JS__');
|
||||
return net;
|
||||
},
|
||||
'should parse log messages into log events and send to appender': function(net) {
|
||||
assert.equal(net.logEvents[1].level.toString(), 'ERROR');
|
||||
assert.equal(net.logEvents[1].data[0], 'an error message');
|
||||
assert.equal(net.logEvents[1].remoteAddress, '1.2.3.4');
|
||||
assert.equal(net.logEvents[1].remotePort, '1234');
|
||||
},
|
||||
'should parse log messages split into multiple chunks': function(net) {
|
||||
assert.equal(net.logEvents[2].level.toString(), 'DEBUG');
|
||||
assert.equal(net.logEvents[2].data[0], 'some debug');
|
||||
assert.equal(net.logEvents[2].remoteAddress, '1.2.3.4');
|
||||
assert.equal(net.logEvents[2].remotePort, '1234');
|
||||
},
|
||||
'should parse multiple log messages in a single chunk': function(net) {
|
||||
assert.equal(net.logEvents[3].data[0], 'some debug');
|
||||
assert.equal(net.logEvents[4].data[0], 'some debug');
|
||||
assert.equal(net.logEvents[5].data[0], 'some debug');
|
||||
},
|
||||
'should handle log messages sent as part of end event': function(net) {
|
||||
assert.equal(net.logEvents[6].data[0], "that's all folks");
|
||||
},
|
||||
'should handle unparseable log messages': function(net) {
|
||||
assert.equal(net.logEvents[7].level.toString(), 'ERROR');
|
||||
assert.equal(net.logEvents[7].categoryName, 'log4js');
|
||||
assert.equal(net.logEvents[7].data[0], 'Unable to parse log:');
|
||||
assert.equal(net.logEvents[7].data[1], 'bad message');
|
||||
}
|
||||
}
|
||||
},
|
||||
'master defaults': {
|
||||
topic: function() {
|
||||
var fakeNet = makeFakeNet(),
|
||||
appender = sandbox.require(
|
||||
'../lib/appenders/multiprocess',
|
||||
{
|
||||
requires: {
|
||||
'net': fakeNet
|
||||
}
|
||||
}
|
||||
).appender({ mode: 'master' });
|
||||
|
||||
return fakeNet;
|
||||
},
|
||||
'should listen for log messages on localhost:5000': function(net) {
|
||||
assert.equal(net.port, 5000);
|
||||
assert.equal(net.host, 'localhost');
|
||||
}
|
||||
}
|
||||
}).exportTo(module);
|
||||
@@ -1,260 +0,0 @@
|
||||
var vows = require('vows')
|
||||
, assert = require('assert')
|
||||
, levels = require('../lib/levels');
|
||||
|
||||
function MockLogger() {
|
||||
|
||||
var that = this;
|
||||
this.messages = [];
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
function MockRequest(remoteAddr, method, originalUrl) {
|
||||
|
||||
this.socket = { remoteAddress: remoteAddr };
|
||||
this.originalUrl = originalUrl;
|
||||
this.method = method;
|
||||
this.httpVersionMajor = '5';
|
||||
this.httpVersionMinor = '0';
|
||||
this.headers = {};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'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() { });
|
||||
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 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() { });
|
||||
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);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}).export(module);
|
||||
@@ -1,79 +0,0 @@
|
||||
// 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).
|
||||
//
|
||||
|
||||
// Basic set up
|
||||
var vows = require('vows');
|
||||
var assert = require('assert');
|
||||
var log4js = require('../lib/log4js');
|
||||
var logger = log4js.getLogger('test-setLevel-asymmetry');
|
||||
|
||||
// uncomment one or other of the following to see progress (or not) while running the tests
|
||||
// var showProgress = console.log;
|
||||
var showProgress = function() {};
|
||||
|
||||
|
||||
// Define the array of levels as string to iterate over.
|
||||
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.
|
||||
strLevels.forEach(function(l) {
|
||||
log4jsLevels.push(log4js.levels.toLevel(l));
|
||||
});
|
||||
|
||||
|
||||
// We are going to iterate over this object's properties to define an exhaustive list of vows.
|
||||
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
|
||||
for (var type in levelTypes) {
|
||||
var context = 'is called with a '+type;
|
||||
var levelsToTest = levelTypes[type];
|
||||
showProgress('Setting up the vows context for '+context);
|
||||
|
||||
batch.setLevel[context]= {};
|
||||
levelsToTest.forEach( function(level) {
|
||||
var subContext = 'of '+level;
|
||||
var log4jsLevel=log4js.levels.toLevel(level.toString());
|
||||
|
||||
showProgress('Setting up the vows sub-context for '+subContext);
|
||||
batch.setLevel[context][subContext] = {topic: level};
|
||||
for (var comparisonType in levelTypes) {
|
||||
levelTypes[comparisonType].forEach(function(comparisonLevel) {
|
||||
var t = type;
|
||||
var ct = comparisonType;
|
||||
var expectedResult = log4jsLevel.isLessThanOrEqualTo(comparisonLevel);
|
||||
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('Running tests...');
|
||||
|
||||
vows.describe('log4js setLevel asymmetry fix').addBatch(batch).export(module);
|
||||
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
var vows = require('vows'),
|
||||
assert = require('assert'),
|
||||
log4js = require('../lib/log4js'),
|
||||
sandbox = require('sandboxed-module');
|
||||
|
||||
function setupLogging(category, options) {
|
||||
var msgs = [];
|
||||
|
||||
var fakeMailer = {
|
||||
createTransport: function (name, options) {
|
||||
return {
|
||||
config: options,
|
||||
sendMail: function (msg, callback) {
|
||||
msgs.push(msg);
|
||||
callback(null, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var smtpModule = sandbox.require('../lib/appenders/smtp', {
|
||||
requires: {
|
||||
'nodemailer': fakeMailer
|
||||
}
|
||||
});
|
||||
|
||||
log4js.addAppender(smtpModule.configure(options), category);
|
||||
|
||||
return {
|
||||
logger: log4js.getLogger(category),
|
||||
mailer: fakeMailer,
|
||||
results: msgs
|
||||
};
|
||||
}
|
||||
|
||||
function checkMessages (result, sender, subject) {
|
||||
for (var i = 0; i < result.results.length; ++i) {
|
||||
assert.equal(result.results[i].from, sender);
|
||||
assert.equal(result.results[i].to, 'recipient@domain.com');
|
||||
assert.equal(result.results[i].subject, subject ? subject : 'Log event #' + (i+1));
|
||||
assert.ok(new RegExp('.+Log event #' + (i+1) + '\n$').test(result.results[i].text));
|
||||
}
|
||||
}
|
||||
|
||||
log4js.clearAppenders();
|
||||
vows.describe('log4js smtpAppender').addBatch({
|
||||
'minimal config': {
|
||||
topic: function() {
|
||||
var setup = setupLogging('minimal config', {
|
||||
recipients: 'recipient@domain.com',
|
||||
transport: "SMTP",
|
||||
SMTP: {
|
||||
port: 25,
|
||||
auth: {
|
||||
user: 'user@domain.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
setup.logger.info('Log event #1');
|
||||
return setup;
|
||||
},
|
||||
'there should be one message only': function (result) {
|
||||
assert.equal(result.results.length, 1);
|
||||
},
|
||||
'message should contain proper data': function (result) {
|
||||
checkMessages(result);
|
||||
}
|
||||
},
|
||||
'fancy config': {
|
||||
topic: function() {
|
||||
var setup = setupLogging('fancy config', {
|
||||
recipients: 'recipient@domain.com',
|
||||
sender: 'sender@domain.com',
|
||||
subject: 'This is subject',
|
||||
transport: "SMTP",
|
||||
SMTP: {
|
||||
port: 25,
|
||||
auth: {
|
||||
user: 'user@domain.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
setup.logger.info('Log event #1');
|
||||
return setup;
|
||||
},
|
||||
'there should be one message only': function (result) {
|
||||
assert.equal(result.results.length, 1);
|
||||
},
|
||||
'message should contain proper data': function (result) {
|
||||
checkMessages(result, 'sender@domain.com', 'This is subject');
|
||||
}
|
||||
},
|
||||
'separate email for each event': {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
var setup = setupLogging('separate email for each event', {
|
||||
recipients: 'recipient@domain.com',
|
||||
transport: "SMTP",
|
||||
SMTP: {
|
||||
port: 25,
|
||||
auth: {
|
||||
user: 'user@domain.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(function () {
|
||||
setup.logger.info('Log event #1');
|
||||
}, 0);
|
||||
setTimeout(function () {
|
||||
setup.logger.info('Log event #2');
|
||||
}, 500);
|
||||
setTimeout(function () {
|
||||
setup.logger.info('Log event #3');
|
||||
}, 1050);
|
||||
setTimeout(function () {
|
||||
self.callback(null, setup);
|
||||
}, 2100);
|
||||
},
|
||||
'there should be three messages': function (result) {
|
||||
assert.equal(result.results.length, 3);
|
||||
},
|
||||
'messages should contain proper data': function (result) {
|
||||
checkMessages(result);
|
||||
}
|
||||
},
|
||||
'multiple events in one email': {
|
||||
topic: function() {
|
||||
var self = this;
|
||||
var setup = setupLogging('multiple events in one email', {
|
||||
recipients: 'recipient@domain.com',
|
||||
sendInterval: 1,
|
||||
transport: "SMTP",
|
||||
SMTP: {
|
||||
port: 25,
|
||||
auth: {
|
||||
user: 'user@domain.com'
|
||||
}
|
||||
}
|
||||
});
|
||||
setTimeout(function () {
|
||||
setup.logger.info('Log event #1');
|
||||
}, 0);
|
||||
setTimeout(function () {
|
||||
setup.logger.info('Log event #2');
|
||||
}, 500);
|
||||
setTimeout(function () {
|
||||
setup.logger.info('Log event #3');
|
||||
}, 1050);
|
||||
setTimeout(function () {
|
||||
self.callback(null, setup);
|
||||
}, 2100);
|
||||
},
|
||||
'there should be two messages': function (result) {
|
||||
assert.equal(result.results.length, 2);
|
||||
},
|
||||
'messages should contain proper data': function (result) {
|
||||
assert.equal(result.results[0].to, 'recipient@domain.com');
|
||||
assert.equal(result.results[0].subject, 'Log event #1');
|
||||
assert.equal(result.results[0].text.match(new RegExp('.+Log event #[1-2]$', 'gm')).length, 2);
|
||||
|
||||
assert.equal(result.results[1].to, 'recipient@domain.com');
|
||||
assert.equal(result.results[1].subject, 'Log event #3');
|
||||
assert.ok(new RegExp('.+Log event #3\n$').test(result.results[1].text));
|
||||
}
|
||||
}
|
||||
|
||||
}).export(module);
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
var vows = require('vows')
|
||||
, assert = require('assert')
|
||||
, fs = require('fs')
|
||||
, 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);
|
||||
};
|
||||
}
|
||||
|
||||
function now() {
|
||||
return testTime.getTime();
|
||||
}
|
||||
|
||||
vows.describe('DateRollingFileStream').addBatch({
|
||||
'arguments': {
|
||||
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-1', 'yyyy-mm-dd.hh'),
|
||||
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-1'),
|
||||
|
||||
'should take a filename and a pattern and return a 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');
|
||||
}
|
||||
},
|
||||
|
||||
'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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}).exportTo(module);
|
||||
@@ -1,134 +0,0 @@
|
||||
var vows = require('vows')
|
||||
, async = require('async')
|
||||
, assert = require('assert')
|
||||
, events = require('events')
|
||||
, fs = require('fs')
|
||||
, 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
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
},
|
||||
'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);
|
||||
}
|
||||
},
|
||||
'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);
|
||||
}
|
||||
},
|
||||
'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);
|
||||
}
|
||||
}
|
||||
},
|
||||
'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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}).exportTo(module);
|
||||
23
test/with-categoryFilter.json
Normal file
23
test/with-categoryFilter.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
9
test/with-cheese.json
Normal file
9
test/with-cheese.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"appenders": {
|
||||
"thing": { "type": "cheese" }
|
||||
},
|
||||
"categories": {
|
||||
"default": { "level": "DEBUG", "appenders": [ "thing" ] },
|
||||
"noisy": { "level": "ERROR", "appenders": [ "thing" ] }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"appenders": [
|
||||
{
|
||||
"category": "tests",
|
||||
"appenders": {
|
||||
"dateFile": {
|
||||
"type": "dateFile",
|
||||
"filename": "test/date-file-test.log",
|
||||
"pattern": "-from-MM-dd",
|
||||
@@ -9,9 +8,9 @@
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
"levels": {
|
||||
"tests": "WARN"
|
||||
"categories": {
|
||||
"default": { "level": "WARN", "appenders": [ "dateFile" ] }
|
||||
}
|
||||
}
|
||||
|
||||
81
writing-appenders.md
Normal file
81
writing-appenders.md
Normal file
@@ -0,0 +1,81 @@
|
||||
Writing Appenders For log4js
|
||||
============================
|
||||
|
||||
Loading appenders
|
||||
-----------------
|
||||
log4js supports loading appender modules from outside its own code. The [log4js-gelf](http://github.com/nomiddlename/log4js-gelf), [log4js-smtp](http://github.com/nomiddlename/log4js-smtp), and [log4js-hookio](http://github.com/nomiddlename/log4js-hookio) appenders are examples of this. In the configuration for an appender, log4js will first attempt to `require` the module from `./lib/appenders/ + type` within log4js - if that fails, it will `require` just using the type. e.g.
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"custom": { type: "log4js-gelf", hostname: "blah", port: 1234 }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: ["custom"] }
|
||||
}
|
||||
});
|
||||
|
||||
log4js will first attempt to `require('./appenders/' + log4js-gelf)`, this will fail. It will then attempt `require('log4js-gelf')`, which (assuming you have previously run `npm install log4js-gelf`) will pick up the gelf appender.
|
||||
|
||||
Writing your own custom appender
|
||||
--------------------------------
|
||||
This is easiest to explain with an example. Let's assume you want to write a [CouchDB](http://couchdb.apache.org) appender. CouchDB is a document database that you talk to via HTTP and JSON. Our log4js configuration is going to look something like this:
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"couch": {
|
||||
type: "log4js-couchdb",
|
||||
url: "http://mycouchhost:5984",
|
||||
db: "logs",
|
||||
layout: {
|
||||
type: "messagePassThrough"
|
||||
}
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: ["couch"] }
|
||||
}
|
||||
});
|
||||
|
||||
When processing this configuration, the first thing log4js will do is `require('log4js-couchdb')`. It expects this module to return a function that accepts two arguments
|
||||
|
||||
module.exports = function(layouts, levels) {
|
||||
...
|
||||
};
|
||||
|
||||
log4js will then call that function, passing in the `layouts` and `levels` sub-modules in case your appender might need to use them. Layouts contains functions which will format a log event as a string in various different ways. Levels contains the definitions of the log levels used by log4js - you might need this for mapping log4js levels to external definitions (the GELF appender does this). These are passed in so that appenders do not need to include a hard dependency on log4js (see below), and so that log4js does not need to expose these modules to the public API. The module function will only be called once per call to `log4js.configure`, even if there are multiple appenders of that type defined.
|
||||
|
||||
The module function should return another function, a configuration function, which will be called for each appender of that type defined in the config. That function should return an appender instance. For our CouchDB example, the calling process is roughly like this:
|
||||
|
||||
couchDbModule = require('log4js-couchdb');
|
||||
appenderMaker = couchDbModule(layouts, levels);
|
||||
appender = appenderMaker({
|
||||
type: "log4js-couchdb",
|
||||
url: "http://mycouchhost:5984",
|
||||
db: "logs",
|
||||
layout: {
|
||||
type: "messagePassThrough"
|
||||
}
|
||||
}, appenderByName)
|
||||
|
||||
Note that in addition to our couchdb appender config, the appenderMaker function gets an extra argument: `appenderByName`, a function which returns an appender when passed its name. This is used by appenders that wrap other appenders. The `logLevelFilter` is an example of this use.
|
||||
|
||||
The `layout` portion of the config can be passed directly to `layouts.layout(config.layout)` to generate a layout function.
|
||||
|
||||
The appender function returned after processing your config should just take one argument: a log event. This function will be called for every log event that should be handled by your appender. In our case, with the config above, every log event of DEBUG level and above will be sent to our appender.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
You should declare which version of log4js your appender works with by
|
||||
including a "peerDependencies" section in your package.json. e.g.
|
||||
|
||||
{
|
||||
"name": "my-cool-appender",
|
||||
"version": "0.0.1",
|
||||
...
|
||||
"peerDependencies": {
|
||||
"log4js": "0.7.x"
|
||||
}
|
||||
}
|
||||
|
||||
For more details on peer dependencies, see
|
||||
[this blog post](http://blog.nodejs.org/2013/02/07/peer-dependencies/).
|
||||
Reference in New Issue
Block a user