Compare commits

...

298 Commits

Author SHA1 Message Date
Francisco Dans
3bf2bade32 x-forwarded-for 2015-06-01 15:30:46 +02:00
Gareth Jones
0c74fbbf7d added node 0.12 to travis config 2015-05-13 08:06:16 +10:00
Gareth Jones
8dff114b49 0.6.25 2015-05-13 08:03:35 +10:00
Gareth Jones
c76fe098ae Merge pull request #294 from Skiggz/gelf-fix
Remove GELF flag when capturing custom fields
2015-05-13 07:54:33 +10:00
Skylar Lowery
1454ae5350 Use isUndefined test method vs equals undefined
*doh
2015-04-27 11:01:20 -06:00
Skylar Lowery
3cf1d697e8 Remove GELF flag when capturing custom fields
* Logstash (which supports GELF) drops messages with this key
* There's no particular need to keep this key
2015-04-24 11:14:17 -06:00
Gareth Jones
9fe32d06e3 0.6.24 2015-04-17 11:36:22 +10:00
Gareth Jones
b8e1d03928 Merge pull request #292 from hasegawa-jun/fix-reference-error
make sure to call require()
2015-04-17 11:35:28 +10:00
hasegawa-jun
ebfcf94db3 make sure to call require() 2015-04-17 10:08:00 +09:00
Gareth Jones
adeb714243 made calling logger.log safe if level is not provided as first argument (github issue #279) 2015-04-17 08:31:12 +10:00
Gareth Jones
1cdd37c488 0.6.23 2015-04-17 08:21:00 +10:00
Gareth Jones
eb87ccb78d Merge pull request #277 from hasegawa-jun/shutdown-smtp-appender
Implemented shutdown function for SMTP appender
2015-04-17 08:12:17 +10:00
Gareth Jones
8360bb732c Merge pull request #282 from qbrandon/master
Add appender-level timezone offset config
2015-04-17 08:10:15 +10:00
Gareth Jones
085a88d6fc upgraded semver package version (github issue #291) 2015-04-17 08:05:47 +10:00
Gareth Jones
957d5d369d Merge pull request #288 from alawatthe/nodemailermigration
The smtp appender now works with the current version of nodemailer - fixes #287
2015-04-17 07:54:57 +10:00
alawatthe
57fc9e7aa0 The smtp appender now works with the current version of nodemailer 2015-04-11 12:03:14 +02:00
Quentin Brandon
435f4f320f Bugfixes: get the unit tests passing again 2015-03-20 18:35:53 +09:00
Quentin Brandon
af69eddd1c Add optional timezoneOffset config for appenders
Example:
    log4js.configure({
        appenders: [{type: 'console', timezoneOffset: -540}],
        replaceConsole: true
    });

The expected value is the equivalent of (new Date).getTimezoneOffset()
In this example, -540 is the value for JST.
This allows machines members of world-wide-spread cluster to all report
log time-stamps using the same timezone (or adapt the timezone to a
local different from the system)
2015-03-20 11:51:23 +09:00
hasegawa-jun
4dfe14a0c2 added shutdownTimeout option 2015-03-17 09:25:22 +09:00
Gareth Jones
4a217afc37 Merge pull request #278 from cfogelberg/custom-connect-tokens
Custom connect tokens
2015-03-12 10:02:24 +11:00
Christo Fogelberg
41504a755d test/connect-logger-test.js - tests for custom tokens 2015-03-10 06:37:53 +00:00
Christo Fogelberg
f22621199f test/connect-logger-test.js - trailing whitespace Sublime removed 2015-03-10 06:24:57 +00:00
Christo Fogelberg
25c543f8ae lib/connect-logger.js - allow options.tokens 2015-03-10 06:18:02 +00:00
Christo Fogelberg
24268422cf lib/connect-logger.js - format takes tokens array instead of req, res 2015-03-10 06:13:49 +00:00
Christo Fogelberg
ba80dc1588 Trailing whitespace Sublime removed 2015-03-08 19:36:17 +00:00
hasegawa-jun
adfad9ad20 Implemented shutdown function for SMTP appender 2015-03-04 09:13:30 +09:00
Gareth Jones
35067af550 0.6.22 2015-01-10 15:40:03 +11:00
Gareth Jones
d31521bac0 Merge pull request #240 from vivocha/vivocha-0.6.20
Vivocha 0.6.20
2015-01-10 15:34:31 +11:00
Gareth Jones
6d7cab343d Merge pull request #260 from DerKnerd/patch-1
Update README.md
2015-01-10 15:29:53 +11:00
Gareth Jones
c624aef282 Merge pull request #261 from boljen/clusterpid
added cluster identifier support
2015-01-10 15:29:01 +11:00
Gareth Jones
0ae72ee424 Merge pull request #264 from FleetingClouds/master
Force bundling of appenders/console
2015-01-10 15:26:39 +11:00
Gareth Jones
a4be20f8e1 Merge pull request #266 from sc2bigjoe/patch-1
Update smtp.js
2015-01-10 15:23:28 +11:00
Gareth Jones
d10d40b572 Merge pull request #268 from askhogan/master
Allow for blank tokens due to dynamic data
2015-01-10 15:22:24 +11:00
Gareth Jones
51ab2963d0 Merge pull request #269 from Nekle/master
add options to reload function
2015-01-10 15:21:46 +11:00
Fuxian Ding
1f1442cb7c add options to reload function
if `cwd` is included in option, reload will not work
2014-12-27 12:48:24 +08:00
Patrick Hogan
f987990339 added null tests 2014-12-23 19:42:32 -08:00
Patrick Hogan
6b029e98fc Allow for blank tokens due to dynamic data
Metadata for users such as name, email, etc are not always present for users.  For example, I am running express and I want to log the %x{company}%x{username} so that when I look at my logs I can immediately understand which user this affects.  Or for example, I could log %x{payingOrTial} the type of user.  

This works well when the user is logged in.  However, my logger encompasses everything.  I log when the server boots up.  I log during the login screen where a user is not yet logged in.  In these circumstances there is no way to retrieve this metadata.

So for example

```
"username": function () {
            var session = require('continuation-local-storage').getNamespace('api.callinize');
            if(!session) session = require('continuation-local-storage').getNamespace('dashboard.callinize');
            var username = session && session.get('user') && session.get('user').username;
            if(!username) return "";
            return " " + username + " ";
        }
```

I try to get the metadata.  If I get no metdata I return a blank string.  Unfortunately, in the current implementation, due to the OR operator, even if I have a replacement of "" || matchedString, 

```
  replaceToken(conversionCharacter, loggingEvent, specifier) || 
          matchedString;
```

the blank string equals false and puts the token in the log instead of the blank string.  This makes the log lines get long with information that is not relevant.  The better thing to do is simply allow for blank strings.  This lets the user have control over their logs and also allows for more metadata to go in the logs, without having to pick only metadata that is always present.
2014-12-21 14:49:25 -08:00
sc2bigjoe
1629e01df9 Update smtp.js
added the ability for smtp appender to send message as html instead of plaintext. in your log4js.config file simply include "html": "true", to write out as html, otherwise it will send plaintext
2014-12-17 11:28:50 -05:00
Martin Bramwell
ec72c03f81 Force bundling of appenders/console
Changes to be committed:
	modified:   lib/log4js.js
2014-12-09 02:59:58 -05:00
Christophe Bol
3300dfae60 fixed error when logging from the clustered master 2014-12-01 15:34:09 +01:00
Christophe Bol
b694fd1d8d added cluster identifier support 2014-12-01 12:29:45 +01:00
Imanuel Ulbricht
e07adf2ca4 Update README.md 2014-11-28 17:49:46 +01:00
Gareth Jones
ec5f4485f8 Merge pull request #258 from osher/patch-4
fix bug: headers are changed after log entry emits
2014-11-16 09:19:47 +11:00
osher
2f44dbf53e 0.8 compatibility 2014-11-05 11:58:09 +02:00
osher
9da158f945 fix tests - they have to be async! 2014-11-05 11:49:21 +02:00
osher
cd3971cc03 fix bug: headers are changed after log entry emits
In the original version, the following operation looks synchronic, however it is not:

```
res.end = end;
res.end(data,enc);

//emit the log entry 
```

In fact, it starts a series of async operations, in which the request may yet be changed after the request log has already been emitted
(in our case - a change on request headers was observed, probably by some low level hook or hacky wrap of some http.ServerResponse method that's involved on the process - but called asynchronously)
what leads to situation that the request log does not capture valid data.
(observed by us:
 - request headers
 - calculated end time, when concerning big content
)

The fix just used `setTimeout(function() { /*emit the log entry*/ }, 1)`, but I'm afraid it may not hold true for big contents.
Well. maybe the headers part will, but the response time calculation will lie.

The fix relays on: http://nodejs.org/api/http.html#http_event_finish
2014-11-04 17:28:52 +02:00
Gareth Jones
176d44833e 0.6.21 2014-09-11 09:26:02 +10:00
Gareth Jones
988e9a41f6 added license info 2014-09-11 09:23:55 +10:00
Gareth Jones
39ce97d140 Merge pull request #242 from marcelog/marcelog_logstash_udp_appender
adding logstash UDP appender
2014-09-11 09:20:42 +10:00
Gareth Jones
c753fe37bb Merge pull request #243 from laenger/master
pass options to wrapped appender in logLevelFilter
2014-09-11 09:16:46 +10:00
Marcelo Gornstein
a7a0964803 adding logstash UDP appender 2014-09-10 13:03:21 -03:00
Christian Langer
82950eb965 pass options to wrapped appender in logLevelFilter 2014-09-09 03:28:02 +02:00
Luis Malheiro
1e999f36d7 Fix and test for MARK level. 2014-09-08 12:17:50 +02:00
Luis Malheiro
17c9b29ca5 Removed property 'level' from the file appender, because that functionality is provided by appender logLevelFilter. 2014-09-08 11:33:22 +02:00
Luis Malheiro
492c45d055 Test for appender using subcategories. 2014-09-08 11:14:00 +02:00
Luis Malheiro
ebbbea198d Added test to check loggers using sub-categories. 2014-09-02 14:33:51 +02:00
Gareth Jones
cacade0a37 Merge pull request #239 from lazutkin/gh-238
Closes #238 Updated async library
2014-09-02 19:33:58 +10:00
Luis Malheiro
02ea4831ea Test for the 'compress' option at the file appender. 2014-09-01 18:05:36 +02:00
Luis Malheiro
39a73586ed Fixed bug that failed test 'set level on all categories' 2014-09-01 14:37:25 +02:00
Luis Malheiro
036293db41 Log compression. 2014-08-29 16:33:41 +02:00
Luis Malheiro
6fa998408a Adds subcategories to the appenders and loggers. Adds property "level" at the file appender to limit the levels that a file appender accepts.
Creates a MARK category that always write to the log. That's useful to write things like '---- STARTED ----'.
2014-08-29 16:33:32 +02:00
Dmitry M. Lazutkin
7558a3c367 Closes #238 Updated async library in order to use lib in —use-strict mode 2014-08-28 12:04:02 +04:00
Gareth Jones
fb072dd70d 0.6.20 2014-08-26 09:59:10 +10:00
Gareth Jones
af1ce2933b Merge pull request #236 from jchmura/filter-max-level
Added option for maximum level in logLevelFilter
2014-08-26 09:55:13 +10:00
Jakub Chmura
ade6dd8ea0 Adjusting and adding new tests for logLevelFilter to handle the maxLevel option. 2014-08-25 20:56:37 +02:00
Jakub Chmura
00c62c7fa6 Adding maxLevel to levelFilter.
This optional parameter specifies what maximum level of event is accepted by the filter.
2014-08-25 19:48:15 +02:00
Gareth Jones
ae04cc9a4a 0.6.19 2014-08-21 21:21:16 +10:00
Gareth Jones
70a9444f4d Merge pull request #235 from j2ro/master
Pull request for #233
2014-08-21 21:20:27 +10:00
j2ro
3e78fcb630 #233: Adding ability to put pid in log layout pattern 2014-08-20 10:43:48 +01:00
j2ro
44687e1bd1 #233: Adding ability to put pid in log layout pattern 2014-08-20 10:23:31 +01:00
Gareth Jones
8e5754371a 0.6.18 2014-08-20 10:20:57 +01:00
Grégoire Charvet 黑瓜
feef9975c7 Remove test logging 2014-08-20 10:20:57 +01:00
Grégoire Charvet 黑瓜
93695fbfc4 Change spaces by tabs
Keeping constitency with the rest of the code.
2014-08-20 10:20:57 +01:00
Grégoire Charvet 黑瓜
0571089a8b Correctly send message, level and hostname
Fix issue #230 where the level would hold the message and the log level
information would be lost.
2014-08-20 10:20:57 +01:00
Gareth Jones
ab77895555 0.6.18 2014-08-20 09:12:13 +10:00
Gareth Jones
9637be8a41 Merge pull request #232 from GregoireDigbil/fix_loggly_formatting
Fix loggly formatting
2014-08-20 09:11:12 +10:00
Grégoire Charvet 黑瓜
0ecd729f49 Remove test logging 2014-08-18 07:32:23 +08:00
Grégoire Charvet 黑瓜
f9c2e78055 Change spaces by tabs
Keeping constitency with the rest of the code.
2014-08-18 07:31:33 +08:00
Grégoire Charvet 黑瓜
e7267ecf46 Correctly send message, level and hostname
Fix issue #230 where the level would hold the message and the log level
information would be lost.
2014-08-18 07:25:55 +08:00
Gareth Jones
c03185b563 Merge pull request #231 from eurekaa/buffered-logger
added getBufferedLogger function.
2014-08-17 11:00:10 +10:00
Stefano Graziato
c0aa8c5c86 gitignore 2014-08-16 14:52:00 +02:00
Stefano Graziato
59a6703549 getBufferedLogger tested. 2014-08-16 14:22:58 +02:00
Gareth Jones
ceffdf92e4 Removed hook.io appender, because hook.io does not build on systems without python 2014-08-16 12:23:50 +10:00
Stefano Graziato
c9e72d0f00 added getBufferedLogger function.
This function should be useful when you need to log during async
parallel operations, without having a mess in logs.
For example when you walk asynchronously a directory and you want logs
to be grouped by file.

It returns the same getLogger() object but messages are stored
internally and sent to appenders only when you call the flush() method
on it.
2014-08-15 13:42:08 +02:00
Gareth Jones
a27345461b altering the timings on some tests to make them slightly less flaky (maybe) 2014-08-15 20:06:35 +10:00
Gareth Jones
fb9948145c 0.6.17 2014-08-15 18:32:32 +10:00
Gareth Jones
0242bae78f Merge pull request #214 from GregoireDigbil/master
Format message before adding loggly metadata
2014-08-15 18:29:19 +10:00
Gareth Jones
b71f635267 Merge pull request #224 from idalv/express_fix
Update connect-logger.js to work correctly with express
2014-08-02 03:30:05 +10:00
mishless
66872d136d Update connect-logger.js to work correctly with express
When used with express levels are wrong since send() does not call writeHead, but sets responseCode on response.
2014-07-30 13:06:51 +03:00
Gareth Jones
49849a545a Merge pull request #219 from PSyton/fix_for_no_dir
Make some test windows friendly.
2014-07-18 08:43:27 +10:00
Pavel Sysolyatin
3c4c98bb0b Make some test windows friendly. 2014-07-17 14:29:03 +07:00
Gareth Jones
eaa77b0454 0.6.16 2014-07-12 17:46:03 +10:00
Gareth Jones
78c604b90d Merge branch 'mayconbordin-master' 2014-07-12 17:33:31 +10:00
Gareth Jones
4648e7a5e6 Merge branch 'master' of https://github.com/mayconbordin/log4js-node into mayconbordin-master 2014-07-12 17:18:07 +10:00
Gareth Jones
8f3e040786 Merge branch 'RolfKoenders-set-global-log-level' 2014-07-12 17:09:30 +10:00
Gareth Jones
60d2e093ae Merge branch 'set-global-log-level' of https://github.com/RolfKoenders/log4js-node into RolfKoenders-set-global-log-level 2014-07-12 17:08:39 +10:00
Gareth Jones
a06478632e Merge 2ion-gelf-spec-v1.1-compliance into master 2014-07-12 15:12:40 +10:00
Gareth Jones
a4d55b3339 fixed up test after merge 2014-07-12 15:11:21 +10:00
Gareth Jones
914ed5356b Merge pull request #216 from 2ion/gelf-no-full-message
GELF appender: don't send full_message field
2014-07-12 15:02:17 +10:00
Jens John
52b4aa1874 fix uninitialized field error 2014-07-11 19:19:19 +09:00
Jens John
2b0a58c2f2 reflect the above changes ; forgot a facility field 2014-07-11 13:02:06 +09:00
Jens John
fcafed9a7e fix gelfAppender-test.js to reflect the above changes 2014-07-11 12:52:23 +09:00
Jens John
7ff053f104 fix test/gelfAppender-test.js to reflect full_message being gone 2014-07-11 12:38:29 +09:00
Jens John
0f51ab1bb3 GELF appender: set version to 1.1 2014-07-10 14:26:27 +09:00
Jens John
5b2d840472 GELF appender: move facility key into a custom field if present
At the same time, don't assign a default value as the field is optional
according to the GELF spec v1.1.
2014-07-10 14:07:38 +09:00
Jens John
ca635fc4b9 GELF appender: don't send full_message field
According to the GELF spec v1.1 [1], the full_message field in GELF is
optional. The log4js implemention until now has sent identitical
short_message and full_message fields. Since this does not add any new
information to the log message, I suggest that full_message be dropped
from GELF.

--
[1] http://graylog2.org/gelf#specs
2014-07-10 12:17:35 +09:00
Grégoire Charvet 黑瓜
d65d053bc1 Format message before adding loggly metadata
Also removed a throw err inside an asynchronous callback.
2014-07-04 14:03:08 +08:00
Gareth Jones
f44af56f9e Added link to example app. 2014-07-03 08:44:24 +10:00
Gareth Jones
a703f2dc12 0.6.15 2014-07-03 08:16:07 +10:00
Gareth Jones
b2edbb1146 Merge pull request #175 from devotis/master
Loggly appender should not make use of any layout
2014-06-28 08:22:20 +10:00
Gareth Jones
99e7c0981d Merge pull request #189 from jengler/add-support-for-prerequired-appender-styles
Add support for prerequired appender styles
2014-06-28 08:20:12 +10:00
Gareth Jones
06bab894af Merge pull request #203 from idalv/master
Clusterred appender should consider the categories.
2014-06-28 08:17:16 +10:00
Gareth Jones
101739ebef Merge pull request #209 from Icehunter/patch-1
Update for "write after end" uncaught error.
2014-06-28 08:02:27 +10:00
Ryan Wilson
f8ffccffd5 epic day with a forgotten console 2014-06-20 13:23:57 -07:00
Ryan Wilson
7d50b4aeff removed check for writeable
caused broken unit tests
2014-06-20 13:23:09 -07:00
Ryan Wilson
b12200fabc Update for "write after end" uncaught error. 2014-06-20 13:16:23 -07:00
Vladimir Mitev
f8b6cc7c39 Revert "Update connect-logger.js to work correctly with express"
This reverts commit d18fb466fb.
2014-05-26 15:56:23 +03:00
idalv
6314e4a344 Merge pull request #1 from idalv/automation
Update connect-logger.js to work correctly with express
2014-05-26 15:46:07 +03:00
mishless
d18fb466fb Update connect-logger.js to work correctly with express
When used with express levels are wrong since send() does not call writeHead, but sets responseCode on response.
2014-05-23 13:58:52 +03:00
Vladimir Mitev
e638ff7271 Unit test.
Fixed the old unit tests.
Added validation for new functionality.

Signed-off-by: Vladimir Mitev <idalv@users.noreply.github.com>
2014-05-20 14:42:29 +03:00
Vladimir Mitev
2daf29b400 Clusterred appender should consider the categories.
It turns out that whenever the clusterred appender is used the log event is passed to all actual appenders.
The actual appender's category is ignored.

Signed-off-by: Vladimir Mitev <idalv@users.noreply.github.com>
2014-05-20 13:45:46 +03:00
=
8cf03507dd Add unit test to check if the level is set on all categories 2014-05-04 15:20:22 +02:00
Gareth Jones
ca5272aacc 0.6.14 2014-04-22 10:06:04 +10:00
Gareth Jones
614127bb10 added shutdown to datefile 2014-04-22 10:05:37 +10:00
Gareth Jones
a549df44b4 Merge pull request #199 from lulurun/fix_file_appender
fix shutdown method: make sure to callback when write completed immediat...
2014-04-22 09:58:03 +10:00
Gareth Jones
5e0982f0b1 Merge pull request #200 from lulurun/datefile_appender_fix
file won't get rolled if the process restarted daily
2014-04-22 09:29:15 +10:00
Xiaolu Liu
f5a76d9073 file won't get rolled if the process restarted daily 2014-04-24 00:07:11 +09:00
Xiaolu Liu
29d941f0a6 fix shutdown method: make sure to callback when write completed immediately 2014-04-21 16:27:51 +09:00
Gareth Jones
0c2baa9690 0.6.13 2014-04-09 07:44:23 +10:00
Gareth Jones
9b538ee8ed fixed timezone flakiness 2014-04-09 07:43:40 +10:00
Gareth Jones
e4d5228f2b Merge branch 'flush-on-exit' 2014-04-09 07:37:17 +10:00
Gareth Jones
6aacb0da0b Merge pull request #195 from jengler/flush-on-exit
Flush on exit
2014-04-09 07:35:25 +10:00
John Engler
6e3da6f44b Added error throwing when error loading test file.
This will hopefully give us better visibility into our Travis CI
build failures.
2014-04-08 12:40:27 -07:00
John Engler
3b5eb28115 Update dateFile EOL usage to be consistent with appender.
From the looks of the Travis CI failure, this could be the issue
causing failures. Not sure as I can't reproduce locally. However,
it is still an inconsistency and worth fixing.
2014-04-08 10:47:18 -07:00
John Engler
633ed3cddb Support for disabling log writes on shutdown.
Updated logger.js to support disabling all log writes.
Updated log4js.js shutdown function to disable log writes.
Added tests.
Update gitignore to ignore rolling date stream's test output.
2014-04-07 19:06:29 -07:00
John Engler
8ca092cdb9 Removed callback to write, as it is not needed. 2014-04-05 16:14:56 -07:00
John Engler
3ec9811b5e Update log4js module to expose a shutdown function.
loadAppender will check for a shutdown function exposed by
a loaded appender. If present, it will be cached so that the
shutdown function can execute it.

The intent here is that a Node application would not invoked
process.exit until after the log4js shutdown callback returns.
2014-04-05 15:12:45 -07:00
Gareth Jones
c852fceaf4 Update README.md 2014-04-01 11:09:01 +11:00
John Engler
c569919160 Simplied loadAppender logic. 2014-03-11 02:19:00 -07:00
John Engler
28f7c87a0e Allow adding of appenders as objects
Previously, appenders could only be added by specifying the filepath
to the appender. This required the appender's path to be specified
either relative to the log4js installation, relative to a NODE_PATH
token, or absolute. This creates a coupling between the log4js
configurer and the log4js installation location, or a coupling between
the log4js configurer and the global NODE_PATH. This commit removes
the coupling by allowing the loading of appenders to be done relative
to the log4js configurer.
2014-03-11 02:07:58 -07:00
Gareth Jones
492919b940 0.6.12 2014-03-05 13:17:15 +11:00
Gareth Jones
470baa6c09 Merge pull request #187 from jci-fox/addHasLogger
adding ability to check if a logger exists
2014-03-05 13:16:51 +11:00
Gareth Jones
cd2ee14bde 0.6.11 2014-03-05 09:25:03 +11:00
Gareth Jones
c09c11b147 Merge branch 'master' of https://github.com/nomiddlename/log4js-node 2014-03-05 09:24:09 +11:00
Gareth Jones
b74a514369 Merge pull request #186 from jci-fox/issue184_dynamicloglevels
Adding level checks on dynamic logging
2014-03-05 09:17:16 +11:00
jci-fox
fd05d90c2f adding ability to check if a logger exists
This allows for not accidentally adding a non-configured logger
2014-03-04 09:45:56 -06:00
jci-fox
73344ba79f fixing unit test
logger.log requires 2 params, and with the 1st being level and filtering the level, the log call must provide a level that will result in log messages
2014-03-04 09:27:04 -06:00
jci-fox
22c156582f Adding level checks on dynamic logging
using levels.toLevel and this.isLevelEnabled prior to emiting the event will prevent the appenders from being notified if the log level provided is below the loggers level.
2014-03-04 09:08:27 -06:00
Gareth Jones
72bfb5d980 0.6.10 2014-02-11 08:51:21 +11:00
Gareth Jones
83ad0babf3 changed my email address 2014-02-11 08:50:36 +11:00
Rolf Koenders
6ae01b2b84 Update log4js.js
Overlooked that there was already a var with '[all]'.
2014-01-23 10:34:56 +01:00
Rolf Koenders
e7c0b0da52 Changed to only the ‚levels’ option as discussed in #87 2014-01-22 20:13:36 +01:00
Rolf Koenders
25fa48ee8d Set the global level via the configuration object passed to
log4js.configure or target `all` in the `levels` object.
2014-01-21 23:39:54 +01:00
Christiaan Westerbeek
ae1a55fed9 Stop making use of any layout
Stop making use of any layout by default, because they are intended to
format a line for human reading. Loggly indexes the values (of all
properties of objects) and makes them available for querying.
2014-01-21 12:11:32 +01:00
Gareth Jones
94034e1226 Merge pull request #172 from devotis/master
Fork with Loggly appender here
2014-01-16 14:00:16 -08:00
Christiaan Westerbeek
9b4c7d1574 Fixes the error with test/logglyAppender-test
Refs #172

I will add more relevant tests later
2014-01-16 16:39:45 +01:00
Christiaan Westerbeek
770f2da627 Cleanup 2014-01-10 21:59:39 +01:00
Christiaan Westerbeek
eb51aa99be First working version
tried examples/loggly-appender.js [OK]
2014-01-10 21:51:08 +01:00
Christiaan Westerbeek
5286c50375 Added the basic files for Loggly appender
appender, example, test
not tested yet!
2014-01-10 21:18:16 +01:00
Christiaan Westerbeek
bb644a1632 Update README.md
Will try to add a appender for Loggly in this Fork. I will keep it in to this feature alone as outlined in the rules
2014-01-10 20:47:48 +01:00
Gareth Jones
a6efbf6273 Merge pull request #168 from macedigital/connectlogger-ip-fix
fix 'remote-addr' property in connect-logger
2013-12-30 12:40:40 -08:00
Matthias Adler
2118d8f7b3 fix 'remote-addr' property in connect-logger 2013-12-21 19:42:44 +01:00
Maycon Bordin
7fcdb2e651 fixed a issue with the encoding on node 0.8 2013-12-12 22:26:48 -02:00
Maycon Bordin
60a84f16cf added tests for the fileSync appender and changed the behavior of fileSync to create an empty log when called, just like the file appender does 2013-12-12 18:16:53 -02:00
Maycon Bordin
723dbec964 added a synchronous file appender 2013-12-06 12:48:49 -02:00
Gareth Jones
d2f044a451 0.6.9 2013-09-30 08:48:15 +10:00
Gareth Jones
d0661322aa Merge pull request #158 from emilecantin/master
Added logic to serialize Error objects correctly
2013-09-29 15:36:40 -07:00
Emile Cantin
8b8844694f Fixed unit tests, now with regexes. 2013-09-27 09:45:10 -04:00
Emile Cantin
abdba8e56f Added logic to serialize Error objects correctly
This should fix #97.
2013-09-26 14:55:20 -04:00
Gareth Jones
093f693232 Merge pull request #157 from karlvlam/gelf-timefix
GELF time precision should be millisecond level
2013-09-17 14:04:49 -07:00
Karl Lam
b9bba00d8c GELF time precision should be millisecond level 2013-09-16 18:31:23 +08:00
Gareth Jones
731e217505 removed dequeue from dependencies 2013-08-22 15:46:42 +10:00
Gareth Jones
3018a49bde 0.6.8 2013-08-22 11:52:25 +10:00
Gareth Jones
a5bb94a048 Merge pull request #152 from fb55/patch-1
Browserify support
2013-08-20 15:52:32 -07:00
Felix Böhm
7a1a895e46 browserify support 2013-08-20 18:48:27 +02:00
Gareth Jones
48dc22eb63 Merge pull request #150 from wood1986/master
layouts supports hostname and ISO8601_WITH_TZ_OFFSET_FORMAT
2013-08-20 03:50:36 -07:00
wood1986
7888381991 Update layouts.js 2013-08-18 01:43:48 +08:00
wood1986
cd286fa25f Update layouts-test.js 2013-08-18 01:39:37 +08:00
wood1986
6df4753822 Update layouts.js 2013-08-18 01:36:07 +08:00
wood1986
613474eb44 Update layouts-test.js 2013-08-15 22:45:56 +08:00
wood1986
112246dd55 Update layouts-test.js 2013-08-15 22:39:59 +08:00
wood1986
069ed31759 Update layouts-test.js 2013-08-15 22:37:01 +08:00
wood1986
9e72189574 Update date_format-test.js 2013-08-15 22:30:57 +08:00
wood1986
5a167d853a Update date_format-test.js 2013-08-15 22:29:52 +08:00
wood1986
5755faa7bb Update layouts-test.js 2013-08-15 22:29:36 +08:00
wood1986
1ed026a8d9 Update fileAppender-test.js 2013-08-14 17:35:47 +08:00
wood1986
2d177d517b Update date_format.js 2013-08-13 23:04:52 +08:00
wood1986
21aebbde33 Update layouts.js 2013-08-13 23:04:11 +08:00
Gareth Jones
49892f35d3 Merge pull request #149 from mkielar/master
Clustered appender for log4js.
2013-08-08 18:04:18 -07:00
Marcin Kielar
61beac28d3 Clustered appender for log4js.
+ lib/appenders/clustered.js
+ test/clusteredAppender-test.js

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

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

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

12
.bob.json Normal file
View File

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

5
.gitignore vendored
View File

@ -2,4 +2,7 @@
*.log?? *.log??
build build
node_modules node_modules
.bob/
test/streams/test-rolling-file-stream*
test/streams/test-rolling-stream-with-existing-files*
.idea

15
.jshintrc Normal file
View File

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

View File

@ -1,4 +1,6 @@
language: node_js language: node_js
node_js: node_js:
- 0.6 - "0.12"
- 0.8 - "0.10"
- "0.8"

View File

@ -1,7 +1,7 @@
# log4js-node [![Build Status](https://secure.travis-ci.org/nomiddlename/log4js-node.png?branch=master)](http://travis-ci.org/nomiddlename/log4js-node) # log4js-node [![Build Status](https://secure.travis-ci.org/nomiddlename/log4js-node.png?branch=master)](http://travis-ci.org/nomiddlename/log4js-node)
This is a conversion of the [log4js](http://log4js.berlios.de/index.html) This is a conversion of the [log4js](https://github.com/stritti/log4js)
framework to work with [node](http://nodejs.org). I've mainly stripped out the browser-specific code and tidied up some of the javascript. framework to work with [node](http://nodejs.org). I've mainly stripped out the browser-specific code and tidied up some of the javascript.
Out of the box it supports the following features: Out of the box it supports the following features:
@ -12,6 +12,8 @@ Out of the box it supports the following features:
* SMTP appender * SMTP appender
* GELF appender * GELF appender
* hook.io appender * hook.io appender
* Loggly appender
* Logstash UDP appender
* multiprocess appender (useful when you've got worker processes) * multiprocess appender (useful when you've got worker processes)
* a logger for connect/express servers * a logger for connect/express servers
* configurable log message layout/patterns * configurable log message layout/patterns
@ -106,8 +108,9 @@ For FileAppender you can also pass the path to the log directory as an option wh
log4js.configure('my_log4js_configuration.json', { cwd: '/absolute/path/to/log/dir' }); log4js.configure('my_log4js_configuration.json', { cwd: '/absolute/path/to/log/dir' });
``` ```
If you have already defined an absolute path for one of the FileAppenders in the configuration file, you could add a "absolute": true to the particular FileAppender to override the cwd option passed. Here is an example configuration file: If you have already defined an absolute path for one of the FileAppenders in the configuration file, you could add a "absolute": true to the particular FileAppender to override the cwd option passed. Here is an example configuration file:
```json
#### my_log4js_configuration.json #### #### my_log4js_configuration.json ####
```json
{ {
"appenders": [ "appenders": [
{ {
@ -128,13 +131,15 @@ If you have already defined an absolute path for one of the FileAppenders in the
] ]
} }
``` ```
Documentation for most of the core appenders can be found on the [wiki](log4js-node/wiki/Appenders), otherwise take a look at the tests and the examples. Documentation for most of the core appenders can be found on the [wiki](https://github.com/nomiddlename/log4js-node/wiki/Appenders), otherwise take a look at the tests and the examples.
## Documentation ## Documentation
See the [wiki](log4js-node/wiki). Improve the [wiki](log4js-node/wiki), please. See the [wiki](https://github.com/nomiddlename/log4js-node/wiki). Improve the [wiki](https://github.com/nomiddlename/log4js-node/wiki), please.
There's also [an example application](https://github.com/nomiddlename/log4js-example).
## Contributing ## Contributing
Contributions welcome, but take a look at the [rules](log4js-node/wiki/Contributing) first. Contributions welcome, but take a look at the [rules](https://github.com/nomiddlename/log4js-node/wiki/Contributing) first.
## License ## License

View File

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

View File

@ -1,4 +1,4 @@
var log4js = require('./lib/log4js'); var log4js = require('../lib/log4js');
//log the cheese logger messages to a file, and the console ones as well. //log the cheese logger messages to a file, and the console ones as well.
log4js.configure({ log4js.configure({
appenders: [ appenders: [
@ -35,11 +35,13 @@ logger.setLevel('ERROR');
//console logging methods have been replaced with log4js ones. //console logging methods have been replaced with log4js ones.
//so this will get coloured output on console, and appear in cheese.log //so this will get coloured output on console, and appear in cheese.log
console.error("AAArgh! Something went wrong", { some: "otherObject", useful_for: "debug purposes" }); console.error("AAArgh! Something went wrong", { some: "otherObject", useful_for: "debug purposes" });
console.log("This should appear as info output");
//these will not appear (logging level beneath error) //these will not appear (logging level beneath error)
logger.trace('Entering cheese testing'); logger.trace('Entering cheese testing');
logger.debug('Got cheese.'); logger.debug('Got cheese.');
logger.info('Cheese is Gouda.'); logger.info('Cheese is Gouda.');
logger.log('Something funny about cheese.');
logger.warn('Cheese is quite smelly.'); logger.warn('Cheese is quite smelly.');
//these end up on the console and in cheese.log //these end up on the console and in cheese.log
logger.error('Cheese %s is too ripe!', "gouda"); logger.error('Cheese %s is too ripe!', "gouda");

27
examples/flush-on-exit.js Normal file
View File

@ -0,0 +1,27 @@
/**
* run this, then "ab -c 10 -n 100 localhost:4444/" to test (in
* another shell)
*/
var log4js = require('../lib/log4js');
log4js.configure({
appenders: [
{ type: 'file', filename: 'cheese.log', category: 'cheese' },
{ type: 'console'}
]
});
var logger = log4js.getLogger('cheese');
logger.setLevel('INFO');
var http=require('http');
var server = http.createServer(function(request, response){
response.writeHead(200, {'Content-Type': 'text/plain'});
var rd = Math.random() * 50;
logger.info("hello " + rd);
response.write('hello ');
if (Math.floor(rd) == 30){
log4js.shutdown(function() { process.exit(1); });
}
response.end();
}).listen(4444);

View File

@ -1,4 +1,4 @@
var log4js = require('./lib/log4js') var log4js = require('../lib/log4js')
, log , log
, i = 0; , i = 0;
log4js.configure({ log4js.configure({

View File

@ -0,0 +1,24 @@
//Note that loggly appender needs node-loggly to work.
//If you haven't got node-loggly installed, you'll get cryptic
//"cannot find module" errors when using the loggly appender
var log4js = require('../lib/log4js');
log4js.configure({
"appenders": [
{
type: "console",
category: "test"
},
{
"type" : "loggly",
"token" : "12345678901234567890",
"subdomain": "your-subdomain",
"tags" : ["test"],
"category" : "loggly"
}
]
});
var logger = log4js.getLogger("loggly");
logger.info("Test log message");
//logger.debug("Test log message");

39
examples/logstashUDP.js Normal file
View File

@ -0,0 +1,39 @@
var log4js = require('../lib/log4js');
/*
Sample logstash config:
udp {
codec => json
port => 10001
queue_size => 2
workers => 2
type => myAppType
}
*/
log4js.configure({
"appenders": [
{
type: "console",
category: "myLogger"
},
{
"host": "127.0.0.1",
"port": 10001,
"type": "logstashUDP",
"logType": "myAppType", // Optional, defaults to 'category'
"fields": { // Optional, will be added to the 'fields' object in logstash
"field1": "value1",
"field2": "value2"
},
"layout": {
"type": "pattern",
"pattern": "%m"
},
"category": "myLogger"
}
]
});
var logger = log4js.getLogger("myLogger");
logger.info("Test log message %s", "arg1", "arg2");

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

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

View File

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

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

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

View File

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

View File

@ -1,9 +1,11 @@
var streams = require('../streams'), "use strict";
layouts = require('../layouts'), var streams = require('../streams')
path = require('path'), , layouts = require('../layouts')
os = require('os'), , async = require('async')
eol = os.EOL || '\n', , path = require('path')
openFiles = []; , os = require('os')
, eol = os.EOL || '\n'
, openFiles = [];
//close open files on process exit. //close open files on process exit.
process.on('exit', function() { process.on('exit', function() {
@ -18,15 +20,20 @@ process.on('exit', function() {
* @pattern the format that will be added to the end of filename when rolling, * @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' * also used to check when to roll files - defaults to '.yyyy-MM-dd'
* @layout layout function for log messages - defaults to basicLayout * @layout layout function for log messages - defaults to basicLayout
* @timezoneOffset optional timezone offset in minutes - defaults to system local
*/ */
function appender(filename, pattern, layout) { function appender(filename, pattern, alwaysIncludePattern, layout, timezoneOffset) {
layout = layout || layouts.basicLayout; layout = layout || layouts.basicLayout;
var logFile = new streams.BufferedWriteStream(new streams.DateRollingFileStream(filename, pattern)); var logFile = new streams.DateRollingFileStream(
filename,
pattern,
{ alwaysIncludePattern: alwaysIncludePattern }
);
openFiles.push(logFile); openFiles.push(logFile);
return function(logEvent) { return function(logEvent) {
logFile.write(layout(logEvent) + eol, "utf8"); logFile.write(layout(logEvent, timezoneOffset) + eol, "utf8");
}; };
} }
@ -38,12 +45,29 @@ function configure(config, options) {
layout = layouts.layout(config.layout.type, config.layout); layout = layouts.layout(config.layout.type, config.layout);
} }
if (!config.alwaysIncludePattern) {
config.alwaysIncludePattern = false;
}
if (options && options.cwd && !config.absolute) { if (options && options.cwd && !config.absolute) {
config.filename = path.join(options.cwd, config.filename); config.filename = path.join(options.cwd, config.filename);
} }
return appender(config.filename, config.pattern, layout); return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout, config.timezoneOffset);
}
function shutdown(cb) {
async.each(openFiles, function(file, done) {
if (!file.write(eol, "utf-8")) {
file.once('drain', function() {
file.end(done);
});
} else {
file.end(done);
}
}, cb);
} }
exports.appender = appender; exports.appender = appender;
exports.configure = configure; exports.configure = configure;
exports.shutdown = shutdown;

View File

@ -1,10 +1,13 @@
"use strict";
var layouts = require('../layouts') var layouts = require('../layouts')
, async = require('async')
, path = require('path') , path = require('path')
, fs = require('fs') , fs = require('fs')
, streams = require('../streams') , streams = require('../streams')
, os = require('os') , os = require('os')
, eol = os.EOL || '\n' , eol = os.EOL || '\n'
, openFiles = []; , openFiles = []
, levels = require('../levels');
//close open files on process exit. //close open files on process exit.
process.on('exit', function() { process.on('exit', function() {
@ -17,11 +20,16 @@ process.on('exit', function() {
* File Appender writing the logs to a text file. Supports rolling of logs by size. * File Appender writing the logs to a text file. Supports rolling of logs by size.
* *
* @param file file log messages will be written to * @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 layout a function that takes a logevent and returns a string
* @param logSize - the maximum size (in bytes) for a log file, if not provided then logs won't be rotated. * (defaults to basicLayout).
* @param numBackups - the number of log files to keep after logSize has been reached (default 5) * @param logSize - the maximum size (in bytes) for a log file,
* if not provided then logs won't be rotated.
* @param numBackups - the number of log files to keep after logSize
* has been reached (default 5)
* @param compress - flag that controls log file compression
* @param timezoneOffset - optional timezone offset in minutes (default system local)
*/ */
function fileAppender (file, layout, logSize, numBackups) { function fileAppender (file, layout, logSize, numBackups, compress, timezoneOffset) {
var bytesWritten = 0; var bytesWritten = 0;
file = path.normalize(file); file = path.normalize(file);
layout = layout || layouts.basicLayout; layout = layout || layouts.basicLayout;
@ -32,15 +40,19 @@ function fileAppender (file, layout, logSize, numBackups) {
function openTheStream(file, fileSize, numFiles) { function openTheStream(file, fileSize, numFiles) {
var stream; var stream;
if (fileSize) { if (fileSize) {
stream = new streams.BufferedWriteStream( stream = new streams.RollingFileStream(
new streams.RollingFileStream(
file, file,
fileSize, fileSize,
numFiles numFiles,
) { "compress": compress }
); );
} else { } else {
stream = new streams.BufferedWriteStream(fs.createWriteStream(file, { encoding: "utf8", mode: 0644, flags: 'a' })); stream = fs.createWriteStream(
file,
{ encoding: "utf8",
mode: parseInt('0644', 8),
flags: 'a' }
);
} }
stream.on("error", function (err) { stream.on("error", function (err) {
console.error("log4js.fileAppender - Writing to file %s, error happened ", file, err); console.error("log4js.fileAppender - Writing to file %s, error happened ", file, err);
@ -54,8 +66,9 @@ function fileAppender (file, layout, logSize, numBackups) {
openFiles.push(logFile); openFiles.push(logFile);
return function(loggingEvent) { return function(loggingEvent) {
logFile.write(layout(loggingEvent) + eol, "utf8"); logFile.write(layout(loggingEvent, timezoneOffset) + eol, "utf8");
}; };
} }
function configure(config, options) { function configure(config, options) {
@ -68,8 +81,21 @@ function configure(config, options) {
config.filename = path.join(options.cwd, config.filename); config.filename = path.join(options.cwd, config.filename);
} }
return fileAppender(config.filename, layout, config.maxLogSize, config.backups); return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.compress, config.timezoneOffset);
}
function shutdown(cb) {
async.each(openFiles, function(file, done) {
if (!file.write(eol, "utf-8")) {
file.once('drain', function() {
file.end(done);
});
} else {
file.end(done);
}
}, cb);
} }
exports.appender = fileAppender; exports.appender = fileAppender;
exports.configure = configure; exports.configure = configure;
exports.shutdown = shutdown;

189
lib/appenders/fileSync.js Executable file
View File

@ -0,0 +1,189 @@
"use strict";
var debug = require('../debug')('fileSync')
, layouts = require('../layouts')
, path = require('path')
, fs = require('fs')
, streams = require('../streams')
, os = require('os')
, eol = os.EOL || '\n'
;
function RollingFileSync (filename, size, backups, options) {
debug("In RollingFileStream");
function throwErrorIfArgumentsAreNotValid() {
if (!filename || !size || size <= 0) {
throw new Error("You must specify a filename and file size");
}
}
throwErrorIfArgumentsAreNotValid();
this.filename = filename;
this.size = size;
this.backups = backups || 1;
this.options = options || { encoding: 'utf8', mode: parseInt('0644', 8), flags: 'a' };
this.currentSize = 0;
function currentFileSize(file) {
var fileSize = 0;
try {
fileSize = fs.statSync(file).size;
} catch (e) {
// file does not exist
fs.appendFileSync(filename, '');
}
return fileSize;
}
this.currentSize = currentFileSize(this.filename);
}
RollingFileSync.prototype.shouldRoll = function() {
debug("should roll with current size %d, and max size %d", this.currentSize, this.size);
return this.currentSize >= this.size;
};
RollingFileSync.prototype.roll = function(filename) {
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) {
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
try {
fs.unlinkSync(filename + '.' + (idx+1));
} catch(e) {
//ignore err: if we could not delete, it's most likely that it doesn't exist
}
debug('Renaming ' + fileToRename + ' -> ' + filename + '.' + (idx+1));
fs.renameSync(path.join(path.dirname(filename), fileToRename), filename + '.' + (idx + 1));
}
}
function renameTheFiles() {
//roll the backups (rename file.n to file.n+1, where n <= numBackups)
debug("Renaming the old files");
var files = fs.readdirSync(path.dirname(filename));
files.filter(justTheseFiles).sort(byIndex).reverse().forEach(increaseFileIndex);
}
debug("Rolling, rolling, rolling");
renameTheFiles();
};
RollingFileSync.prototype.write = function(chunk, encoding) {
var that = this;
function writeTheChunk() {
debug("writing the chunk to the file");
that.currentSize += chunk.length;
fs.appendFileSync(that.filename, chunk);
}
debug("in write");
if (this.shouldRoll()) {
this.currentSize = 0;
this.roll(this.filename);
}
writeTheChunk();
};
/**
* File Appender writing the logs to a text file. Supports rolling of logs by size.
*
* @param file file log messages will be written to
* @param layout a function that takes a logevent and returns a string
* (defaults to basicLayout).
* @param logSize - the maximum size (in bytes) for a log file,
* if not provided then logs won't be rotated.
* @param numBackups - the number of log files to keep after logSize
* has been reached (default 5)
* @param timezoneOffset - optional timezone offset in minutes
* (default system local)
*/
function fileAppender (file, layout, logSize, numBackups, timezoneOffset) {
debug("fileSync appender created");
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 RollingFileSync(
file,
fileSize,
numFiles
);
} else {
stream = (function(f) {
// create file if it doesn't exist
if (!fs.existsSync(f))
fs.appendFileSync(f, '');
return {
write: function(data) {
fs.appendFileSync(f, data);
}
};
})(file);
}
return stream;
}
var logFile = openTheStream(file, logSize, numBackups);
return function(loggingEvent) {
logFile.write(layout(loggingEvent, timezoneOffset) + eol);
};
}
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, config.timezoneOffset);
}
exports.appender = fileAppender;
exports.configure = configure;

View File

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

View File

@ -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;

View File

@ -1,19 +1,22 @@
var levels = require('../levels'); "use strict";
var log4js = require('../log4js'); var levels = require('../levels')
, log4js = require('../log4js');
function logLevelFilter (levelString, appender) { function logLevelFilter (minLevelString, maxLevelString, appender) {
var level = levels.toLevel(levelString); var minLevel = levels.toLevel(minLevelString);
var maxLevel = levels.toLevel(maxLevelString, levels.FATAL);
return function(logEvent) { return function(logEvent) {
if (logEvent.level.isGreaterThanOrEqualTo(level)) { var eventLevel = logEvent.level;
if (eventLevel.isGreaterThanOrEqualTo(minLevel) && eventLevel.isLessThanOrEqualTo(maxLevel)) {
appender(logEvent); appender(logEvent);
} }
} };
} }
function configure(config) { function configure(config, options) {
log4js.loadAppender(config.appender.type); log4js.loadAppender(config.appender.type);
var appender = log4js.appenderMakers[config.appender.type](config.appender); var appender = log4js.appenderMakers[config.appender.type](config.appender, options);
return logLevelFilter(config.level, appender); return logLevelFilter(config.level, config.maxLevel, appender);
} }
exports.appender = logLevelFilter; exports.appender = logLevelFilter;

44
lib/appenders/loggly.js Normal file
View File

@ -0,0 +1,44 @@
'use strict';
var layouts = require('../layouts')
, loggly = require('loggly')
, os = require('os')
, passThrough = layouts.messagePassThroughLayout;
/**
* Loggly Appender. Sends logging events to Loggly using node-loggly
*
* @param config object with loggly configuration data
* {
* token: 'your-really-long-input-token',
* subdomain: 'your-subdomain',
* tags: ['loggly-tag1', 'loggly-tag2', .., 'loggly-tagn']
* }
* @param layout a function that takes a logevent and returns a string (defaults to objectLayout).
*/
function logglyAppender(config, layout) {
var client = loggly.createClient(config);
if(!layout) layout = passThrough;
return function(loggingEvent) {
var msg = layout(loggingEvent);
client.log({
msg: msg,
level: loggingEvent.level.levelStr,
category: loggingEvent.categoryName,
hostname: os.hostname().toString(),
});
}
}
function configure(config) {
var layout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return logglyAppender(config, layout);
}
exports.name = 'loggly';
exports.appender = logglyAppender;
exports.configure = configure;

View File

@ -0,0 +1,50 @@
"use strict";
var layouts = require('../layouts')
, dgram = require('dgram')
, util = require('util');
function logstashUDP (config, layout) {
var udp = dgram.createSocket('udp4');
var type = config.logType ? config.logType : config.category;
layout = layout || layouts.colouredLayout;
if(!config.fields) {
config.fields = {};
}
return function(loggingEvent) {
var logMessage = layout(loggingEvent);
var fields = {};
for(var i in config.fields) {
fields[i] = config.fields[i];
}
fields['level'] = loggingEvent.level.levelStr;
var logObject = {
'@timestamp': (new Date(loggingEvent.startTime)).toISOString(),
type: type,
message: logMessage,
fields: fields
};
sendLog(udp, config.host, config.port, logObject);
};
}
function sendLog(udp, host, port, logObject) {
var buffer = new Buffer(JSON.stringify(logObject));
udp.send(buffer, 0, buffer.length, port, host, function(err, bytes) {
if(err) {
console.error(
"log4js.logstashUDP - %s:%p Error: %s", host, port, util.inspect(err)
);
}
});
}
function configure(config) {
var layout;
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout);
}
return logstashUDP(config, layout);
}
exports.appender = logstashUDP;
exports.configure = configure;

View File

@ -1,6 +1,7 @@
var log4js = require('../log4js'), "use strict";
net = require('net'), var log4js = require('../log4js')
END_MSG = '__LOG4JS__'; , net = require('net')
, END_MSG = '__LOG4JS__';
/** /**
* Creates a server, listening on config.loggerPort, config.loggerHost. * Creates a server, listening on config.loggerPort, config.loggerHost.
@ -38,7 +39,6 @@ function logServer(config) {
var actualAppender = config.actualAppender, var actualAppender = config.actualAppender,
server = net.createServer(function serverCreated(clientSocket) { server = net.createServer(function serverCreated(clientSocket) {
clientSocket.setEncoding('utf8'); clientSocket.setEncoding('utf8');
clientSocket.on('connect', function clientConnected() {
var logMessage = ''; var logMessage = '';
function logTheMessage(msg) { function logTheMessage(msg) {
@ -62,7 +62,7 @@ function logServer(config) {
clientSocket.on('data', chunkReceived); clientSocket.on('data', chunkReceived);
clientSocket.on('end', chunkReceived); clientSocket.on('end', chunkReceived);
}); });
});
server.listen(config.loggerPort || 5000, config.loggerHost || 'localhost'); server.listen(config.loggerPort || 5000, config.loggerHost || 'localhost');
return actualAppender; return actualAppender;
@ -94,6 +94,11 @@ function workerAppender(config) {
} }
function write(loggingEvent) { function write(loggingEvent) {
// JSON.stringify(new Error('test')) returns {}, which is not really useful for us.
// The following allows us to serialize errors correctly.
if (loggingEvent && loggingEvent.stack && JSON.stringify(loggingEvent) === '{}') { // Validate that we really are in this case
loggingEvent = {stack : loggingEvent.stack};
}
socket.write(JSON.stringify(loggingEvent), 'utf8'); socket.write(JSON.stringify(loggingEvent), 'utf8');
socket.write(END_MSG, 'utf8'); socket.write(END_MSG, 'utf8');
} }

View File

@ -1,14 +1,21 @@
var layouts = require("../layouts"), "use strict";
mailer = require("nodemailer"), var layouts = require("../layouts")
os = require('os'); , mailer = require("nodemailer")
, os = require('os')
, async = require('async')
, unsentCount = 0
, shutdownTimeout;
/** /**
* SMTP Appender. Sends logging events using SMTP protocol. * SMTP Appender. Sends logging events using SMTP protocol.
* It can either send an email on each event or group several logging events gathered during specified interval. * It can either send an email on each event or group several
* logging events gathered during specified interval.
* *
* @param config appender configuration data * @param config appender configuration data
* config.sendInterval time between log emails (in seconds), if 0
* then every event sends an email
* config.shutdownTimeout time to give up remaining emails (in seconds; defaults to 5).
* @param layout a function that takes a logevent and returns a string (defaults to basicLayout). * @param layout a function that takes a logevent and returns a string (defaults to basicLayout).
* all events are buffered and sent in one email during this time; if 0 than every event sends an email
*/ */
function smtpAppender(config, layout) { function smtpAppender(config, layout) {
layout = layout || layouts.basicLayout; layout = layout || layouts.basicLayout;
@ -17,47 +24,62 @@ function smtpAppender(config, layout) {
var logEventBuffer = []; var logEventBuffer = [];
var sendTimer; var sendTimer;
var transport = mailer.createTransport(config.transport, config[config.transport]);
shutdownTimeout = ('shutdownTimeout' in config ? config.shutdownTimeout : 5) * 1000;
function sendBuffer() { function sendBuffer() {
if (logEventBuffer.length == 0) if (logEventBuffer.length > 0) {
return;
var transport = mailer.createTransport(config.SMTP);
var firstEvent = logEventBuffer[0]; var firstEvent = logEventBuffer[0];
var body = ""; var body = "";
var count = logEventBuffer.length;
while (logEventBuffer.length > 0) { while (logEventBuffer.length > 0) {
body += layout(logEventBuffer.shift()) + "\n"; body += layout(logEventBuffer.shift(), config.timezoneOffset) + "\n";
} }
var msg = { var msg = {
to: config.recipients, to: config.recipients,
subject: config.subject || subjectLayout(firstEvent), subject: config.subject || subjectLayout(firstEvent),
text: body,
headers: { "Hostname": os.hostname() } headers: { "Hostname": os.hostname() }
}; };
if (config.sender)
if (!config.html) {
msg.text = body;
} else {
msg.html = body;
}
if (config.sender) {
msg.from = config.sender; msg.from = config.sender;
}
transport.sendMail(msg, function(error, success) { transport.sendMail(msg, function(error, success) {
if (error) { if (error) {
console.error("log4js.smtpAppender - Error happened", error); console.error("log4js.smtpAppender - Error happened", error);
} }
transport.close();
unsentCount -= count;
}); });
}
} }
function scheduleSend() { function scheduleSend() {
if (!sendTimer) if (!sendTimer) {
sendTimer = setTimeout(function() { sendTimer = setTimeout(function() {
sendTimer = null; sendTimer = null;
sendBuffer(); sendBuffer();
}, sendInterval); }, sendInterval);
}
} }
return function(loggingEvent) { return function(loggingEvent) {
unsentCount++;
logEventBuffer.push(loggingEvent); logEventBuffer.push(loggingEvent);
if (sendInterval > 0) if (sendInterval > 0) {
scheduleSend(); scheduleSend();
else } else {
sendBuffer(); sendBuffer();
}
}; };
} }
@ -69,7 +91,19 @@ function configure(config) {
return smtpAppender(config, layout); return smtpAppender(config, layout);
} }
function shutdown(cb) {
if (shutdownTimeout > 0) {
setTimeout(function() { unsentCount = 0; }, shutdownTimeout);
}
async.whilst(function() {
return unsentCount > 0;
}, function(done) {
setTimeout(done, 100);
}, cb);
}
exports.name = "smtp"; exports.name = "smtp";
exports.appender = smtpAppender; exports.appender = smtpAppender;
exports.configure = configure; exports.configure = configure;
exports.shutdown = shutdown;

View File

@ -1,11 +1,17 @@
"use strict";
var levels = require("./levels"); var levels = require("./levels");
var _ = require('underscore');
var DEFAULT_FORMAT = ':remote-addr - -' +
' ":method :url HTTP/:http-version"' +
' :status :content-length ":referrer"' +
' ":user-agent"';
/** /**
* Log requests with the given `options` or a `format` string. * Log requests with the given `options` or a `format` string.
* *
* Options: * Options:
* *
* - `format` Format string, see below for tokens * - `format` Format string, see below for tokens
* - `level` A log4js levels instance. * - `level` A log4js levels instance. Supports also 'auto'
* *
* Tokens: * Tokens:
* *
@ -37,23 +43,20 @@ function getLogger(logger4js, options) {
var thislogger = logger4js var thislogger = logger4js
, level = levels.toLevel(options.level, levels.INFO) , level = levels.toLevel(options.level, levels.INFO)
, fmt = options.format || ':remote-addr - - ":method :url HTTP/:http-version" :status :content-length ":referrer" ":user-agent"' , fmt = options.format || DEFAULT_FORMAT
, nolog = options.nolog ? createNoLogCondition(options.nolog) : null; , nolog = options.nolog ? createNoLogCondition(options.nolog) : null;
return function (req, res, next) { return function (req, res, next) {
// mount safety // mount safety
if (req._logging) return next(); if (req._logging) return next();
// nologs // nologs
if (nolog && nolog.test(req.originalUrl)) return next(); if (nolog && nolog.test(req.originalUrl)) return next();
if (thislogger.isLevelEnabled(level) || options.level === 'auto') {
if (thislogger.isLevelEnabled(level)) { var start = new Date()
var start = +new Date
, statusCode , statusCode
, writeHead = res.writeHead , writeHead = res.writeHead
, end = res.end
, url = req.originalUrl; , url = req.originalUrl;
// flag as logging // flag as logging
@ -65,20 +68,36 @@ function getLogger(logger4js, options) {
res.writeHead(code, headers); res.writeHead(code, headers);
res.__statusCode = statusCode = code; res.__statusCode = statusCode = code;
res.__headers = headers || {}; res.__headers = headers || {};
};
// proxy end to output a line to the provided logger. //status code response level handling
res.end = function(chunk, encoding) { if(options.level === 'auto'){
res.end = end; level = levels.INFO;
res.end(chunk, encoding); if(code >= 300) level = levels.WARN;
res.responseTime = +new Date - start; if(code >= 400) level = levels.ERROR;
if ('function' == typeof fmt) {
var line = fmt(req, res, function(str){ return format(str, req, res); });
if (line) thislogger.log(level, line);
} else { } else {
thislogger.log(level, format(fmt, req, res)); level = levels.toLevel(options.level, levels.INFO);
} }
}; };
//hook on end request to emit the log entry of the HTTP request.
res.on('finish', function() {
res.responseTime = new Date() - start;
//status code response level handling
if(res.statusCode && options.level === 'auto'){
level = levels.INFO;
if(res.statusCode >= 300) level = levels.WARN;
if(res.statusCode >= 400) level = levels.ERROR;
}
if (thislogger.isLevelEnabled(level)) {
var combined_tokens = assemble_tokens(req, res, options.tokens || []);
if (typeof fmt === 'function') {
var line = fmt(req, res, function(str){ return format(str, combined_tokens); });
if (line) thislogger.log(level, line);
} else {
thislogger.log(level, format(fmt, combined_tokens));
}
}
});
} }
//ensure next gets always called //ensure next gets always called
@ -86,6 +105,52 @@ function getLogger(logger4js, options) {
}; };
} }
/**
* Adds custom {token, replacement} objects to defaults, overwriting the defaults if any tokens clash
*
* @param {IncomingMessage} req
* @param {ServerResponse} res
* @param {Array} custom_tokens [{ token: string-or-regexp, replacement: string-or-replace-function }]
* @return {Array}
*/
function assemble_tokens(req, res, custom_tokens) {
var array_unique_tokens = function(array) {
var a = array.concat();
for(var i=0; i<a.length; ++i) {
for(var j=i+1; j<a.length; ++j) {
if(a[i].token == a[j].token) { // not === because token can be regexp object
a.splice(j--, 1);
}
}
}
return a;
};
var default_tokens = [];
default_tokens.push({ token: ':url', replacement: req.originalUrl });
default_tokens.push({ token: ':method', replacement: req.method });
default_tokens.push({ token: ':status', replacement: res.__statusCode || res.statusCode });
default_tokens.push({ token: ':response-time', replacement: res.responseTime });
default_tokens.push({ token: ':date', replacement: new Date().toUTCString() });
default_tokens.push({ token: ':referrer', replacement: req.headers.referer || req.headers.referrer || '' });
default_tokens.push({ token: ':http-version', replacement: req.httpVersionMajor + '.' + req.httpVersionMinor });
default_tokens.push({ token: ':remote-addr', replacement: req.headers['x-forwarded-for'] || req.ip || req._remoteAddress ||
(req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress))) });
default_tokens.push({ token: ':user-agent', replacement: req.headers['user-agent'] });
default_tokens.push({ token: ':content-length', replacement: (res._headers && res._headers['content-length']) ||
(res.__headers && res.__headers['Content-Length']) || '-' });
default_tokens.push({ token: /:req\[([^\]]+)\]/g, replacement: function(_, field) {
return req.headers[field.toLowerCase()];
} });
default_tokens.push({ token: /:res\[([^\]]+)\]/g, replacement: function(_, field) {
return res._headers ?
(res._headers[field.toLowerCase()] || res.__headers[field])
: (res.__headers && res.__headers[field]);
} });
return array_unique_tokens(custom_tokens.concat(default_tokens));
};
/** /**
* Return formatted log line. * Return formatted log line.
* *
@ -96,24 +161,10 @@ function getLogger(logger4js, options) {
* @api private * @api private
*/ */
function format(str, req, res) { function format(str, tokens) {
return str return _.reduce(tokens, function(current_string, token) {
.replace(':url', req.originalUrl) return current_string.replace(token.token, token.replacement);
.replace(':method', req.method) }, str);
.replace(':status', res.__statusCode || res.statusCode)
.replace(':response-time', res.responseTime)
.replace(':date', new Date().toUTCString())
.replace(':referrer', req.headers['referer'] || req.headers['referrer'] || '')
.replace(':http-version', req.httpVersionMajor + '.' + req.httpVersionMinor)
.replace(':remote-addr', req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress)))
.replace(':user-agent', req.headers['user-agent'] || '')
.replace(':content-length', (res._headers && res._headers['content-length']) || (res.__headers && res.__headers['Content-Length']) || '-')
.replace(/:req\[([^\]]+)\]/g, function(_, field){ return req.headers[field.toLowerCase()]; })
.replace(/:res\[([^\]]+)\]/g, function(_, field){
return res._headers
? (res._headers[field.toLowerCase()] || res.__headers[field])
: (res.__headers && res.__headers[field]);
});
} }
/** /**
@ -122,17 +173,17 @@ function format(str, req, res) {
* @param {String} nolog * @param {String} nolog
* @return {RegExp} * @return {RegExp}
* @api private * @api private
*/ *
/**
* syntax * syntax
* 1. String * 1. String
* 1.1 "\\.gif" * 1.1 "\\.gif"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga * NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.gif?fuga
* LOGGING http://example.com/hoge.agif * LOGGING http://example.com/hoge.agif
* 1.2 in "\\.gif|\\.jpg$" * 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 * NOT LOGGING http://example.com/hoge.gif and
* LOGGING http://example.com/hoge.agif, http://example.com/hoge.ajpg and http://example.com/hoge.jpg?hoge * 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)$" * 1.3 in "\\.(gif|jpe?g|png)$"
* NOT LOGGING http://example.com/hoge.gif and http://example.com/hoge.jpeg * 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 * LOGGING http://example.com/hoge.gif?uid=2 and http://example.com/hoge.jpg?pid=3
@ -143,26 +194,29 @@ function format(str, req, res) {
* 3.1 ["\\.jpg$", "\\.png", "\\.gif"] * 3.1 ["\\.jpg$", "\\.png", "\\.gif"]
* SAME AS "\\.jpg|\\.png|\\.gif" * SAME AS "\\.jpg|\\.png|\\.gif"
*/ */
function createNoLogCondition(nolog, type) { function createNoLogCondition(nolog) {
if(!nolog) return null; var regexp = null;
type = type || '';
if (nolog) {
if (nolog instanceof RegExp) { if (nolog instanceof RegExp) {
if(type === 'string') regexp = nolog;
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')}); if (typeof nolog === 'string') {
return new RegExp(regexps.join('|')); regexp = new RegExp(nolog);
} }
if (Array.isArray(nolog)) {
var regexpsAsStrings = nolog.map(
function convertToStrings(o) {
return o.source ? o.source : o;
}
);
regexp = new RegExp(regexpsAsStrings.join('|'));
}
}
return regexp;
} }
exports.connectLogger = getLogger; exports.connectLogger = getLogger;

View File

@ -1,36 +1,9 @@
"use strict";
exports.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS"; exports.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS";
exports.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO"; exports.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO";
exports.DATETIME_FORMAT = "dd MM yyyy hh:mm:ss.SSS"; exports.DATETIME_FORMAT = "dd MM yyyy hh:mm:ss.SSS";
exports.ABSOLUTETIME_FORMAT = "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) { function padWithZeros(vNumber, width) {
var numAsString = vNumber + ""; var numAsString = vNumber + "";
while (numAsString.length < width) { while (numAsString.length < width) {
@ -48,13 +21,52 @@ exports.asString = function(/*format,*/ date) {
* Thanks to http://www.svendtofte.com/code/date_format/ * Thanks to http://www.svendtofte.com/code/date_format/
* @private * @private
*/ */
function offset(date) { function offset(timezoneOffset) {
// Difference to Greenwich time (GMT) in hours // Difference to Greenwich time (GMT) in hours
var os = Math.abs(date.getTimezoneOffset()); var os = Math.abs(timezoneOffset);
var h = String(Math.floor(os/60)); var h = String(Math.floor(os/60));
var m = String(os%60); var m = String(os%60);
h.length == 1? h = "0"+h:1; if (h.length == 1) {
m.length == 1? m = "0"+m:1; h = "0" + h;
return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
} }
if (m.length == 1) {
m = "0" + m;
}
return timezoneOffset < 0 ? "+"+h+m : "-"+h+m;
}
exports.asString = function(/*format,*/ date, timezoneOffset) {
var format = exports.ISO8601_FORMAT;
if (typeof(date) === "string") {
format = arguments[0];
date = arguments[1];
timezoneOffset = arguments[2];
}
// make the date independent of the system timezone by working with UTC
if (timezoneOffset === undefined) {
timezoneOffset = date.getTimezoneOffset();
}
date.setUTCMinutes(date.getUTCMinutes() - timezoneOffset);
var vDay = addZero(date.getUTCDate());
var vMonth = addZero(date.getUTCMonth()+1);
var vYearLong = addZero(date.getUTCFullYear());
var vYearShort = addZero(date.getUTCFullYear().toString().substring(2,4));
var vYear = (format.indexOf("yyyy") > -1 ? vYearLong : vYearShort);
var vHour = addZero(date.getUTCHours());
var vMinute = addZero(date.getUTCMinutes());
var vSecond = addZero(date.getUTCSeconds());
var vMillisecond = padWithZeros(date.getUTCMilliseconds(), 3);
var vTimeZone = offset(timezoneOffset);
date.setUTCMinutes(date.getUTCMinutes() + timezoneOffset);
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;
}; };

15
lib/debug.js Normal file
View File

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

View File

@ -1,61 +1,42 @@
"use strict";
var dateFormat = require('./date_format') var dateFormat = require('./date_format')
, os = require('os') , os = require('os')
, eol = os.EOL || '\n' , eol = os.EOL || '\n'
, util = require('util') , util = require('util')
, replacementRegExp = /%[sdj]/g , replacementRegExp = /%[sdj]/g
, layoutMakers = { , layoutMakers = {
"messagePassThrough": function() { return messagePassThroughLayout; } "messagePassThrough": function() { return messagePassThroughLayout; },
, "basic": function() { return basicLayout; } "basic": function() { return basicLayout; },
, "colored": function() { return colouredLayout; } "colored": function() { return colouredLayout; },
, "coloured": function() { return colouredLayout; } "coloured": function() { return colouredLayout; },
, "pattern": function (config) { "pattern": function (config) {
var pattern = config.pattern || undefined; return patternLayout(config && config.pattern, config && config.tokens);
var tokens = config.tokens || undefined;
return patternLayout(pattern, tokens);
} }
} }
, colours = { , colours = {
ALL: "grey" ALL: "grey",
, TRACE: "blue" TRACE: "blue",
, DEBUG: "cyan" DEBUG: "cyan",
, INFO: "green" INFO: "green",
, WARN: "yellow" WARN: "yellow",
, ERROR: "red" ERROR: "red",
, FATAL: "magenta" FATAL: "magenta",
, OFF: "grey" 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) { function formatLogData(logData) {
var output = "" var data = Array.isArray(logData) ? logData : Array.prototype.slice.call(arguments);
, data = Array.isArray(logData) ? logData.slice() : Array.prototype.slice.call(arguments) return util.format.apply(util, wrapErrorsWithInspect(data));
, 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 styles = { var styles = {
@ -78,10 +59,10 @@ var styles = {
}; };
function colorizeStart(style) { function colorizeStart(style) {
return style ? '\033[' + styles[style][0] + 'm' : ''; return style ? '\x1B[' + styles[style][0] + 'm' : '';
} }
function colorizeEnd(style) { 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) * Taken from masylum's fork (https://github.com/masylum/log4js-node)
@ -90,11 +71,11 @@ function colorize (str, style) {
return colorizeStart(style) + str + colorizeEnd(style); return colorizeStart(style) + str + colorizeEnd(style);
} }
function timestampLevelAndCategory(loggingEvent, colour) { function timestampLevelAndCategory(loggingEvent, colour, timezoneOffest) {
var output = colorize( var output = colorize(
formatLogData( formatLogData(
'[%s] [%s] %s - ' '[%s] [%s] %s - '
, dateFormat.asString(loggingEvent.startTime) , dateFormat.asString(loggingEvent.startTime, timezoneOffest)
, loggingEvent.level , loggingEvent.level
, loggingEvent.categoryName , loggingEvent.categoryName
) )
@ -112,16 +93,20 @@ function timestampLevelAndCategory(loggingEvent, colour) {
* *
* @author Stephan Strittmatter * @author Stephan Strittmatter
*/ */
function basicLayout (loggingEvent) { function basicLayout (loggingEvent, timezoneOffset) {
return timestampLevelAndCategory(loggingEvent) + formatLogData(loggingEvent.data); return timestampLevelAndCategory(loggingEvent, undefined, timezoneOffset) + formatLogData(loggingEvent.data);
} }
/** /**
* colouredLayout - taken from masylum's fork. * colouredLayout - taken from masylum's fork.
* same as basicLayout, but with colours. * same as basicLayout, but with colours.
*/ */
function colouredLayout (loggingEvent) { function colouredLayout (loggingEvent, timezoneOffset) {
return timestampLevelAndCategory(loggingEvent, colours[loggingEvent.level.toString()]) + formatLogData(loggingEvent.data); return timestampLevelAndCategory(
loggingEvent,
colours[loggingEvent.level.toString()],
timezoneOffset
) + formatLogData(loggingEvent.data);
} }
function messagePassThroughLayout (loggingEvent) { function messagePassThroughLayout (loggingEvent) {
@ -136,10 +121,12 @@ function messagePassThroughLayout (loggingEvent) {
* - %r time in toLocaleTimeString format * - %r time in toLocaleTimeString format
* - %p log level * - %p log level
* - %c log category * - %c log category
* - %h hostname
* - %m log data * - %m log data
* - %d date in various formats * - %d date in various formats
* - %% % * - %% %
* - %n newline * - %n newline
* - %z pid
* - %x{<tokenname>} add dynamic tokens to your log. Tokens are specified in the tokens parameter * - %x{<tokenname>} add dynamic tokens to your log. Tokens are specified in the tokens parameter
* You can use %[ and %] to define a colored block. * You can use %[ and %] to define a colored block.
* *
@ -153,16 +140,162 @@ function messagePassThroughLayout (loggingEvent) {
* Takes a pattern string, array of tokens and returns a layout function. * Takes a pattern string, array of tokens and returns a layout function.
* @param {String} Log format pattern String * @param {String} Log format pattern String
* @param {object} map object of different tokens * @param {object} map object of different tokens
* @param {number} timezone offset in minutes
* @return {Function} * @return {Function}
* @author Stephan Strittmatter * @author Stephan Strittmatter
* @author Jan Schmidle * @author Jan Schmidle
*/ */
function patternLayout (pattern, tokens) { function patternLayout (pattern, tokens, timezoneOffset) {
var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n"; var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdmnprx%])(\{([^\}]+)\})?|([^%]+)/; var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdhmnprzxy%])(\{([^\}]+)\})?|([^%]+)/;
pattern = pattern || TTCC_CONVERSION_PATTERN; pattern = pattern || TTCC_CONVERSION_PATTERN;
function categoryName(loggingEvent, specifier) {
var loggerName = loggingEvent.categoryName;
if (specifier) {
var precision = parseInt(specifier, 10);
var loggerNameBits = loggerName.split(".");
if (precision < loggerNameBits.length) {
loggerName = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
}
}
return loggerName;
}
function formatAsDate(loggingEvent, specifier) {
var format = dateFormat.ISO8601_FORMAT;
if (specifier) {
format = specifier;
// Pick up special cases
if (format == "ISO8601") {
format = dateFormat.ISO8601_FORMAT;
} else if (format == "ISO8601_WITH_TZ_OFFSET") {
format = dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT;
} else if (format == "ABSOLUTE") {
format = dateFormat.ABSOLUTETIME_FORMAT;
} else if (format == "DATE") {
format = dateFormat.DATETIME_FORMAT;
}
}
// Format the date
return dateFormat.asString(format, loggingEvent.startTime, timezoneOffset);
}
function hostname() {
return os.hostname().toString();
}
function formatMessage(loggingEvent) {
return formatLogData(loggingEvent.data);
}
function endOfLine() {
return eol;
}
function logLevel(loggingEvent) {
return loggingEvent.level.toString();
}
function startTime(loggingEvent) {
return dateFormat.asString('hh:mm:ss', loggingEvent.startTime, timezoneOffset);
}
function startColour(loggingEvent) {
return colorizeStart(colours[loggingEvent.level.toString()]);
}
function endColour(loggingEvent) {
return colorizeEnd(colours[loggingEvent.level.toString()]);
}
function percent() {
return '%';
}
function pid(loggingEvent) {
if (loggingEvent && loggingEvent.pid) {
return loggingEvent.pid;
} else {
return process.pid;
}
}
function clusterInfo(loggingEvent, specifier) {
if (loggingEvent.cluster && specifier) {
return specifier
.replace('%m', loggingEvent.cluster.master)
.replace('%w', loggingEvent.cluster.worker)
.replace('%i', loggingEvent.cluster.workerId);
} else if (loggingEvent.cluster) {
return loggingEvent.cluster.worker+'@'+loggingEvent.cluster.master;
} else {
return pid();
}
}
function userDefined(loggingEvent, specifier) {
if (typeof(tokens[specifier]) !== 'undefined') {
if (typeof(tokens[specifier]) === 'function') {
return tokens[specifier](loggingEvent);
} else {
return tokens[specifier];
}
}
return null;
}
var replacers = {
'c': categoryName,
'd': formatAsDate,
'h': hostname,
'm': formatMessage,
'n': endOfLine,
'p': logLevel,
'r': startTime,
'[': startColour,
']': endColour,
'y': clusterInfo,
'z': pid,
'%': 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) { return function(loggingEvent) {
var formattedString = ""; var formattedString = "";
var result; var result;
@ -182,100 +315,12 @@ function patternLayout (pattern, tokens) {
} else { } else {
// Create a raw replacement string based on the conversion // Create a raw replacement string based on the conversion
// character and specifier // character and specifier
var replacement = ""; var replacement = replaceToken(conversionCharacter, loggingEvent, specifier);
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 // Format the replacement according to any padding or
// truncation specified // truncation specified
replacement = truncate(truncation, replacement);
var len; replacement = pad(padding, replacement);
// 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; formattedString += replacement;
} }
searchString = searchString.substr(result.index + result[0].length); searchString = searchString.substr(result.index + result[0].length);
@ -283,15 +328,15 @@ function patternLayout (pattern, tokens) {
return formattedString; return formattedString;
}; };
}; }
module.exports = { module.exports = {
basicLayout: basicLayout basicLayout: basicLayout,
, messagePassThroughLayout: messagePassThroughLayout messagePassThroughLayout: messagePassThroughLayout,
, patternLayout: patternLayout patternLayout: patternLayout,
, colouredLayout: colouredLayout colouredLayout: colouredLayout,
, coloredLayout: colouredLayout coloredLayout: colouredLayout,
, layout: function(name, config) { layout: function(name, config) {
return layoutMakers[name] && layoutMakers[name](config); return layoutMakers[name] && layoutMakers[name](config);
} }
}; };

View File

@ -1,3 +1,5 @@
"use strict";
function Level(level, levelStr) { function Level(level, levelStr) {
this.level = level; this.level = level;
this.levelStr = levelStr; this.levelStr = levelStr;
@ -26,8 +28,7 @@ function toLevel(sArg, defaultLevel) {
} }
return toLevel(sArg.toString()); return toLevel(sArg.toString());
}
};
Level.prototype.toString = function() { Level.prototype.toString = function() {
return this.levelStr; return this.levelStr;
@ -52,16 +53,17 @@ Level.prototype.isEqualTo = function(otherLevel) {
otherLevel = toLevel(otherLevel); otherLevel = toLevel(otherLevel);
} }
return this.level === otherLevel.level; return this.level === otherLevel.level;
} };
module.exports = { module.exports = {
ALL: new Level(Number.MIN_VALUE, "ALL") ALL: new Level(Number.MIN_VALUE, "ALL"),
, TRACE: new Level(5000, "TRACE") TRACE: new Level(5000, "TRACE"),
, DEBUG: new Level(10000, "DEBUG") DEBUG: new Level(10000, "DEBUG"),
, INFO: new Level(20000, "INFO") INFO: new Level(20000, "INFO"),
, WARN: new Level(30000, "WARN") WARN: new Level(30000, "WARN"),
, ERROR: new Level(40000, "ERROR") ERROR: new Level(40000, "ERROR"),
, FATAL: new Level(50000, "FATAL") FATAL: new Level(50000, "FATAL"),
, OFF: new Level(Number.MAX_VALUE, "OFF") MARK: new Level(9007199254740992, "MARK"), // 2^53
, toLevel: toLevel OFF: new Level(Number.MAX_VALUE, "OFF"),
toLevel: toLevel
}; };

View File

@ -1,3 +1,4 @@
"use strict";
/* /*
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,8 +13,6 @@
* limitations under the License. * limitations under the License.
*/ */
/*jsl:option explicit*/
/** /**
* @fileoverview log4js is a library to log in JavaScript in similar manner * @fileoverview log4js is a library to log in JavaScript in similar manner
* than in log4j for Java. The API should be nearly the same. * than in log4j for Java. The API should be nearly the same.
@ -45,17 +44,20 @@
* Website: http://log4js.berlios.de * Website: http://log4js.berlios.de
*/ */
var events = require('events') var events = require('events')
, async = require('async')
, fs = require('fs') , fs = require('fs')
, path = require('path') , path = require('path')
, util = require('util') , util = require('util')
, layouts = require('./layouts') , layouts = require('./layouts')
, levels = require('./levels') , levels = require('./levels')
, LoggingEvent = require('./logger').LoggingEvent , loggerModule = require('./logger')
, Logger = require('./logger').Logger , LoggingEvent = loggerModule.LoggingEvent
, Logger = loggerModule.Logger
, ALL_CATEGORIES = '[all]' , ALL_CATEGORIES = '[all]'
, appenders = {} , appenders = {}
, loggers = {} , loggers = {}
, appenderMakers = {} , appenderMakers = {}
, appenderShutdowns = {}
, defaultConfig = { , defaultConfig = {
appenders: [ appenders: [
{ type: "console" } { type: "console" }
@ -63,38 +65,103 @@ var events = require('events')
replaceConsole: false replaceConsole: false
}; };
require('./appenders/console');
function hasLogger(logger) {
return loggers.hasOwnProperty(logger);
}
function getBufferedLogger(categoryName) {
var base_logger = getLogger(categoryName);
var logger = {};
logger.temp = [];
logger.target = base_logger;
logger.flush = function () {
for (var i = 0; i < logger.temp.length; i++) {
var log = logger.temp[i];
logger.target[log.level](log.message);
delete logger.temp[i];
}
};
logger.trace = function (message) { logger.temp.push({level: 'trace', message: message}); };
logger.debug = function (message) { logger.temp.push({level: 'debug', message: message}); };
logger.info = function (message) { logger.temp.push({level: 'info', message: message}); };
logger.warn = function (message) { logger.temp.push({level: 'warn', message: message}); };
logger.error = function (message) { logger.temp.push({level: 'error', message: message}); };
logger.fatal = function (message) { logger.temp.push({level: 'fatal', message: message}); };
return logger;
}
function normalizeCategory (category) {
return category + '.';
}
function doesLevelEntryContainsLogger (levelCategory, loggerCategory) {
var normalizedLevelCategory = normalizeCategory(levelCategory);
var normalizedLoggerCategory = normalizeCategory(loggerCategory);
return normalizedLoggerCategory.substring(0, normalizedLevelCategory.length) == normalizedLevelCategory;
}
function doesAppenderContainsLogger (appenderCategory, loggerCategory) {
var normalizedAppenderCategory = normalizeCategory(appenderCategory);
var normalizedLoggerCategory = normalizeCategory(loggerCategory);
return normalizedLoggerCategory.substring(0, normalizedAppenderCategory.length) == normalizedAppenderCategory;
}
/** /**
* Get a logger instance. Instance is cached on categoryName level. * Get a logger instance. Instance is cached on categoryName level.
* @param {String} categoryName name of category to log to. * @param {String} categoryName name of category to log to.
* @return {Logger} instance of logger for the category * @return {Logger} instance of logger for the category
* @static * @static
*/ */
function getLogger (categoryName) { function getLogger (loggerCategoryName) {
// Use default logger if categoryName is not specified or invalid // Use default logger if categoryName is not specified or invalid
if (!(typeof categoryName == "string")) { if (typeof loggerCategoryName !== "string") {
categoryName = Logger.DEFAULT_CATEGORY; loggerCategoryName = Logger.DEFAULT_CATEGORY;
}
if (!hasLogger(loggerCategoryName)) {
var level = undefined;
// If there's a "levels" entry in the configuration
if (levels.config) {
// Goes through the categories in the levels configuration entry, starting by the "higher" ones.
var keys = Object.keys(levels.config).sort();
for (var idx = 0; idx < keys.length; idx++) {
var levelCategory = keys[idx];
if (doesLevelEntryContainsLogger(levelCategory, loggerCategoryName)) {
// level for the logger
level = levels.config[levelCategory];
}
}
} }
var appenderList;
if (!loggers[categoryName]) {
// Create the logger for this name if it doesn't already exist // Create the logger for this name if it doesn't already exist
loggers[categoryName] = new Logger(categoryName); loggers[loggerCategoryName] = new Logger(loggerCategoryName, level);
if (appenders[categoryName]) {
appenderList = appenders[categoryName]; var appenderList;
for(var appenderCategory in appenders) {
if (doesAppenderContainsLogger(appenderCategory, loggerCategoryName)) {
appenderList = appenders[appenderCategory];
appenderList.forEach(function(appender) { appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender); loggers[loggerCategoryName].addListener("log", appender);
}); });
} }
}
if (appenders[ALL_CATEGORIES]) { if (appenders[ALL_CATEGORIES]) {
appenderList = appenders[ALL_CATEGORIES]; appenderList = appenders[ALL_CATEGORIES];
appenderList.forEach(function(appender) { appenderList.forEach(function(appender) {
loggers[categoryName].addListener("log", appender); loggers[loggerCategoryName].addListener("log", appender);
}); });
} }
} }
return loggers[categoryName]; return loggers[loggerCategoryName];
} }
/** /**
@ -103,7 +170,7 @@ function getLogger (categoryName) {
function addAppender () { function addAppender () {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
var appender = args.shift(); var appender = args.shift();
if (args.length == 0 || args[0] === undefined) { if (args.length === 0 || args[0] === undefined) {
args = [ ALL_CATEGORIES ]; args = [ ALL_CATEGORIES ];
} }
//argument may already be an array //argument may already be an array
@ -111,28 +178,42 @@ function addAppender () {
args = args[0]; args = args[0];
} }
args.forEach(function(category) { args.forEach(function(appenderCategory) {
addAppenderToCategory(appender, appenderCategory);
if (appenderCategory === ALL_CATEGORIES) {
addAppenderToAllLoggers(appender);
} else {
for(var loggerCategory in loggers) {
if (doesAppenderContainsLogger(appenderCategory,loggerCategory)) {
loggers[loggerCategory].addListener("log", appender);
}
}
}
});
}
function addAppenderToAllLoggers(appender) {
for (var logger in loggers) {
if (hasLogger(logger)) {
loggers[logger].addListener("log", appender);
}
}
}
function addAppenderToCategory(appender, category) {
if (!appenders[category]) { if (!appenders[category]) {
appenders[category] = []; appenders[category] = [];
} }
appenders[category].push(appender); appenders[category].push(appender);
if (category === ALL_CATEGORIES) {
for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) {
loggers[logger].addListener("log", appender);
}
}
} else if (loggers[category]) {
loggers[category].addListener("log", appender);
}
});
} }
function clearAppenders () { function clearAppenders () {
appenders = {}; appenders = {};
for (var logger in loggers) { for (var logger in loggers) {
if (loggers.hasOwnProperty(logger)) { if (hasLogger(logger)) {
loggers[logger].removeAllListeners("log"); loggers[logger].removeAllListeners("log");
} }
} }
@ -145,21 +226,29 @@ function configureAppenders(appenderList, options) {
loadAppender(appenderConfig.type); loadAppender(appenderConfig.type);
var appender; var appender;
appenderConfig.makers = appenderMakers; appenderConfig.makers = appenderMakers;
try {
appender = appenderMakers[appenderConfig.type](appenderConfig, options); appender = appenderMakers[appenderConfig.type](appenderConfig, options);
if (appender) {
addAppender(appender, appenderConfig.category); addAppender(appender, appenderConfig.category);
} else { } catch(e) {
throw new Error("log4js configuration problem for "+util.inspect(appenderConfig)); throw new Error("log4js configuration problem for " + util.inspect(appenderConfig), e);
} }
}); });
} }
} }
function configureLevels(levels) { function configureLevels(_levels) {
if (levels) { levels.config = _levels; // Keep it so we can create loggers later using this cfg
for (var category in levels) { if (_levels) {
if (levels.hasOwnProperty(category)) { var keys = Object.keys(levels.config).sort();
getLogger(category).setLevel(levels[category]); for (var idx in keys) {
var category = keys[idx];
if(category === ALL_CATEGORIES) {
setGlobalLogLevel(_levels[category]);
}
for(var loggerCategory in loggers) {
if (doesLevelEntryContainsLogger(category, loggerCategory)) {
loggers[loggerCategory].setLevel(_levels[category]);
}
} }
} }
} }
@ -181,10 +270,7 @@ function getDefaultLogger () {
var configState = {}; var configState = {};
function loadConfigurationFile(filename) { function loadConfigurationFile(filename) {
if (filename && (!configState.lastFilename || filename !== configState.lastFilename || if (filename) {
!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 JSON.parse(fs.readFileSync(filename, "utf8"));
} }
return undefined; return undefined;
@ -193,8 +279,8 @@ function loadConfigurationFile(filename) {
function configureOnceOff(config, options) { function configureOnceOff(config, options) {
if (config) { if (config) {
try { try {
configureAppenders(config.appenders, options);
configureLevels(config.levels); configureLevels(config.levels);
configureAppenders(config.appenders, options);
if (config.replaceConsole) { if (config.replaceConsole) {
replaceConsole(); replaceConsole();
@ -202,31 +288,32 @@ function configureOnceOff(config, options) {
restoreConsole(); restoreConsole();
} }
} catch (e) { } catch (e) {
throw new Error("Problem reading log4js config " + util.inspect(config) + ". Error was \"" + e.message + "\" ("+e.stack+")"); throw new Error(
"Problem reading log4js config " + util.inspect(config) +
". Error was \"" + e.message + "\" (" + e.stack + ")"
);
} }
} }
} }
function reloadConfiguration() { function reloadConfiguration(options) {
var filename = configState.filename, var mtime = getMTime(configState.filename);
mtime; if (!mtime) return;
if (!filename) {
// can't find anything to reload if (configState.lastMTime && (mtime.getTime() > configState.lastMTime.getTime())) {
return; configureOnceOff(loadConfigurationFile(configState.filename), options);
} }
configState.lastMTime = mtime;
}
function getMTime(filename) {
var mtime;
try { try {
mtime = fs.statSync(filename).mtime; mtime = fs.statSync(configState.filename).mtime;
} catch (e) { } catch (e) {
getLogger('log4js').warn('Failed to load configuration file ' + filename); getLogger('log4js').warn('Failed to load configuration file ' + filename);
return;
}
if (configState.lastFilename && configState.lastFilename === filename) {
if (mtime.getTime() > configState.lastMTime.getTime()) {
configureOnceOff(loadConfigurationFile(filename));
}
} else {
configureOnceOff(loadConfigurationFile(filename));
} }
return mtime;
} }
function initReloadConfiguration(filename, options) { function initReloadConfiguration(filename, options) {
@ -235,7 +322,8 @@ function initReloadConfiguration(filename, options) {
delete configState.timerId; delete configState.timerId;
} }
configState.filename = filename; configState.filename = filename;
configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000); configState.lastMTime = getMTime(filename);
configState.timerId = setInterval(reloadConfiguration, options.reloadSecs*1000, options);
} }
function configure(configurationFileOrObject, options) { function configure(configurationFileOrObject, options) {
@ -250,7 +338,9 @@ function configure(configurationFileOrObject, options) {
config = loadConfigurationFile(config) || defaultConfig; config = loadConfigurationFile(config) || defaultConfig;
} else { } else {
if (options.reloadSecs) { if (options.reloadSecs) {
getLogger('log4js').warn('Ignoring configuration reload parameter for "object" configuration.'); getLogger('log4js').warn(
'Ignoring configuration reload parameter for "object" configuration.'
);
} }
} }
configureOnceOff(config, options); configureOnceOff(config, options);
@ -268,7 +358,7 @@ function replaceConsole(logger) {
function replaceWith(fn) { function replaceWith(fn) {
return function() { return function() {
fn.apply(logger, arguments); fn.apply(logger, arguments);
} };
} }
logger = logger || getLogger("console"); logger = logger || getLogger("console");
['log','debug','info','warn','error'].forEach(function (item) { ['log','debug','info','warn','error'].forEach(function (item) {
@ -282,25 +372,90 @@ function restoreConsole() {
}); });
} }
function loadAppender(appender) { /**
* Load an appenderModule based on the provided appender filepath. Will first
* check if the appender path is a subpath of the log4js "lib/appenders" directory.
* If not, it will attempt to load the the appender as complete path.
*
* @param {string} appender The filepath for the appender.
* @returns {Object|null} The required appender or null if appender could not be loaded.
* @private
*/
function requireAppender(appender) {
var appenderModule; var appenderModule;
try { try {
appenderModule = require('./appenders/' + appender); appenderModule = require('./appenders/' + appender);
} catch (e) { } catch (e) {
appenderModule = require(appender); appenderModule = require(appender);
} }
return appenderModule;
}
/**
* Load an appender. Provided the appender path to be loaded. If appenderModule is defined,
* it will be used in place of requiring the appender module.
*
* @param {string} appender The path to the appender module.
* @param {Object|void} [appenderModule] The pre-required appender module. When provided,
* instead of requiring the appender by its path, this object will be used.
* @returns {void}
* @private
*/
function loadAppender(appender, appenderModule) {
appenderModule = appenderModule || requireAppender(appender);
if (!appenderModule) {
throw new Error("Invalid log4js appender: " + util.inspect(appender));
}
module.exports.appenders[appender] = appenderModule.appender.bind(appenderModule); module.exports.appenders[appender] = appenderModule.appender.bind(appenderModule);
if (appenderModule.shutdown) {
appenderShutdowns[appender] = appenderModule.shutdown.bind(appenderModule);
}
appenderMakers[appender] = appenderModule.configure.bind(appenderModule); appenderMakers[appender] = appenderModule.configure.bind(appenderModule);
} }
/**
* Shutdown all log appenders. This will first disable all writing to appenders
* and then call the shutdown function each appender.
*
* @params {Function} cb - The callback to be invoked once all appenders have
* shutdown. If an error occurs, the callback will be given the error object
* as the first argument.
* @returns {void}
*/
function shutdown(cb) {
// First, disable all writing to appenders. This prevents appenders from
// not being able to be drained because of run-away log writes.
loggerModule.disableAllLogWrites();
// Next, get all the shutdown functions for appenders as an array.
var shutdownFunctions = Object.keys(appenderShutdowns).reduce(
function(accum, category) {
return accum.concat(appenderShutdowns[category]);
}, []);
// Call each of the shutdown functions.
async.each(
shutdownFunctions,
function(shutdownFn, done) {
shutdownFn(done);
},
cb
);
}
module.exports = { module.exports = {
getBufferedLogger: getBufferedLogger,
getLogger: getLogger, getLogger: getLogger,
getDefaultLogger: getDefaultLogger, getDefaultLogger: getDefaultLogger,
hasLogger: hasLogger,
addAppender: addAppender, addAppender: addAppender,
loadAppender: loadAppender, loadAppender: loadAppender,
clearAppenders: clearAppenders, clearAppenders: clearAppenders,
configure: configure, configure: configure,
shutdown: shutdown,
replaceConsole: replaceConsole, replaceConsole: replaceConsole,
restoreConsole: restoreConsole, restoreConsole: restoreConsole,

View File

@ -1,7 +1,10 @@
var levels = require('./levels'), "use strict";
util = require('util'), var levels = require('./levels')
events = require('events'), , util = require('util')
DEFAULT_CATEGORY = '[default]'; , events = require('events')
, DEFAULT_CATEGORY = '[default]';
var logWritesEnabled = true;
/** /**
* Models a logging event. * Models a logging event.
@ -30,12 +33,13 @@ function LoggingEvent (categoryName, level, data, logger) {
function Logger (name, level) { function Logger (name, level) {
this.category = name || DEFAULT_CATEGORY; this.category = name || DEFAULT_CATEGORY;
if (! this.level) { if (level) {
this.__proto__.level = levels.TRACE; this.setLevel(level);
} }
} }
util.inherits(Logger, events.EventEmitter); util.inherits(Logger, events.EventEmitter);
Logger.DEFAULT_CATEGORY = DEFAULT_CATEGORY; Logger.DEFAULT_CATEGORY = DEFAULT_CATEGORY;
Logger.prototype.level = levels.TRACE;
Logger.prototype.setLevel = function(level) { Logger.prototype.setLevel = function(level) {
this.level = levels.toLevel(level, this.level || levels.TRACE); this.level = levels.toLevel(level, this.level || levels.TRACE);
@ -47,16 +51,19 @@ Logger.prototype.removeLevel = function() {
Logger.prototype.log = function() { Logger.prototype.log = function() {
var args = Array.prototype.slice.call(arguments) var args = Array.prototype.slice.call(arguments)
, logLevel = args.shift() , logLevel = levels.toLevel(args.shift(), levels.INFO)
, loggingEvent = new LoggingEvent(this.category, logLevel, args, this); , loggingEvent;
if (this.isLevelEnabled(logLevel)) {
loggingEvent = new LoggingEvent(this.category, logLevel, args, this);
this.emit("log", loggingEvent); this.emit("log", loggingEvent);
}
}; };
Logger.prototype.isLevelEnabled = function(otherLevel) { Logger.prototype.isLevelEnabled = function(otherLevel) {
return this.level.isLessThanOrEqualTo(otherLevel); return this.level.isLessThanOrEqualTo(otherLevel);
}; };
['Trace','Debug','Info','Warn','Error','Fatal'].forEach( ['Trace','Debug','Info','Warn','Error','Fatal', 'Mark'].forEach(
function(levelString) { function(levelString) {
var level = levels.toLevel(levelString); var level = levels.toLevel(levelString);
Logger.prototype['is'+levelString+'Enabled'] = function() { Logger.prototype['is'+levelString+'Enabled'] = function() {
@ -64,7 +71,7 @@ Logger.prototype.isLevelEnabled = function(otherLevel) {
}; };
Logger.prototype[levelString.toLowerCase()] = function () { Logger.prototype[levelString.toLowerCase()] = function () {
if (this.isLevelEnabled(level)) { if (logWritesEnabled && this.isLevelEnabled(level)) {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
args.unshift(level); args.unshift(level);
Logger.prototype.log.apply(this, args); Logger.prototype.log.apply(this, args);
@ -73,6 +80,23 @@ Logger.prototype.isLevelEnabled = function(otherLevel) {
} }
); );
/**
* Disable all log writes.
* @returns {void}
*/
function disableAllLogWrites() {
logWritesEnabled = false;
}
/**
* Enable log writes.
* @returns {void}
*/
function enableAllLogWrites() {
logWritesEnabled = true;
}
exports.LoggingEvent = LoggingEvent; exports.LoggingEvent = LoggingEvent;
exports.Logger = Logger; exports.Logger = Logger;
exports.disableAllLogWrites = disableAllLogWrites;
exports.enableAllLogWrites = enableAllLogWrites;

View File

@ -1,21 +1,27 @@
var fs = require('fs'), "use strict";
util = require('util'); var fs = require('fs')
, stream
, debug = require('../debug')('BaseRollingFileStream')
, util = require('util')
, semver = require('semver');
function debug(message) { if (semver.satisfies(process.version, '>=0.10.0')) {
// console.log(message); stream = require('stream');
} else {
stream = require('readable-stream');
} }
module.exports = BaseRollingFileStream; module.exports = BaseRollingFileStream;
function BaseRollingFileStream(filename, options) { function BaseRollingFileStream(filename, options) {
debug("In BaseRollingFileStream"); debug("In BaseRollingFileStream");
this.filename = filename; this.filename = filename;
this.options = options || { encoding: 'utf8', mode: 0644, flags: 'a' }; this.options = options || {};
this.rolling = false; this.options.encoding = this.options.encoding || 'utf8';
this.writesWhileRolling = []; this.options.mode = this.options.mode || parseInt('0644', 8);
this.options.flags = this.options.flags || 'a';
this.currentSize = 0; this.currentSize = 0;
this.rollBeforeWrite = false;
function currentFileSize(file) { function currentFileSize(file) {
var fileSize = 0; var fileSize = 0;
@ -35,58 +41,47 @@ debug("In BaseRollingFileStream");
throwErrorIfArgumentsAreNotValid(); throwErrorIfArgumentsAreNotValid();
debug("Calling BaseRollingFileStream.super"); debug("Calling BaseRollingFileStream.super");
BaseRollingFileStream.super_.call(this, this.filename, this.options); BaseRollingFileStream.super_.call(this);
this.openTheStream();
this.currentSize = currentFileSize(this.filename); this.currentSize = currentFileSize(this.filename);
} }
util.inherits(BaseRollingFileStream, fs.FileWriteStream); util.inherits(BaseRollingFileStream, stream.Writable);
BaseRollingFileStream.prototype.initRolling = function() { BaseRollingFileStream.prototype._write = function(chunk, encoding, callback) {
var that = this; var that = this;
function writeTheChunk() {
function emptyRollingQueue() { debug("writing the chunk to the underlying stream");
debug("emptying the rolling queue"); that.currentSize += chunk.length;
var toWrite; try {
while ((toWrite = that.writesWhileRolling.shift())) { that.theStream.write(chunk, encoding, callback);
BaseRollingFileStream.super_.prototype.write.call(that, toWrite.data, toWrite.encoding);
that.currentSize += toWrite.data.length;
if (that.shouldRoll()) {
that.flush();
return true;
} }
catch (err){
debug(err);
callback();
} }
that.flush();
return false;
} }
this.rolling = true; debug("in _write");
this.roll(this.filename, function() {
that.currentSize = 0; if (this.shouldRoll()) {
that.rolling = emptyRollingQueue(); this.currentSize = 0;
if (that.rolling) { this.roll(this.filename, writeTheChunk);
process.nextTick(function() { that.initRolling(); }); } else {
writeTheChunk();
} }
});
}; };
BaseRollingFileStream.prototype.write = function(data, encoding) { BaseRollingFileStream.prototype.openTheStream = function(cb) {
var canWrite = false; debug("opening the underlying stream");
if (this.rolling) { this.theStream = fs.createWriteStream(this.filename, this.options);
this.writesWhileRolling.push({ data: data, encoding: encoding }); if (cb) {
} else { this.theStream.on("open", cb);
if (this.rollBeforeWrite && this.shouldRoll()) { }
this.writesWhileRolling.push({ data: data, encoding: encoding }); };
this.initRolling();
} else {
canWrite = BaseRollingFileStream.super_.prototype.write.call(this, data, encoding);
this.currentSize += data.length;
debug('current size = ' + this.currentSize);
if (!this.rollBeforeWrite && this.shouldRoll()) { BaseRollingFileStream.prototype.closeTheStream = function(cb) {
this.initRolling(); debug("closing the underlying stream");
} this.theStream.end(cb);
}
}
return canWrite;
}; };
BaseRollingFileStream.prototype.shouldRoll = function() { BaseRollingFileStream.prototype.shouldRoll = function() {

View File

@ -1,78 +0,0 @@
var events = require('events'),
Dequeue = require('dequeue'),
util = require('util');
module.exports = BufferedWriteStream;
function BufferedWriteStream(stream) {
var that = this;
this.stream = stream;
this.buffer = new Dequeue();
this.canWrite = false;
this.bytes = 0;
this.stream.on("open", function() {
that.canWrite = true;
that.flushBuffer();
});
this.stream.on("error", function (err) {
that.emit("error", err);
});
this.stream.on("drain", function() {
that.canWrite = true;
that.flushBuffer();
});
}
util.inherits(BufferedWriteStream, events.EventEmitter);
Object.defineProperty(
BufferedWriteStream.prototype,
"fd",
{
get: function() { return this.stream.fd; },
set: function(newFd) {
this.stream.fd = newFd;
this.bytes = 0;
}
}
);
Object.defineProperty(
BufferedWriteStream.prototype,
"bytesWritten",
{
get: function() { return this.bytes; }
}
);
BufferedWriteStream.prototype.write = function(data, encoding) {
this.buffer.push({ data: data, encoding: encoding });
this.flushBuffer();
};
BufferedWriteStream.prototype.end = function(data, encoding) {
if (data) {
this.buffer.push({ data: data, encoding: encoding });
}
this.flushBufferEvenIfCannotWrite();
};
BufferedWriteStream.prototype.writeToStream = function(toWrite) {
this.bytes += toWrite.data.length;
this.canWrite = this.stream.write(toWrite.data, toWrite.encoding);
};
BufferedWriteStream.prototype.flushBufferEvenIfCannotWrite = function() {
while (this.buffer.length > 0) {
this.writeToStream(this.buffer.shift());
}
};
BufferedWriteStream.prototype.flushBuffer = function() {
while (this.buffer.length > 0 && this.canWrite) {
this.writeToStream(this.buffer.shift());
}
};

View File

@ -1,15 +1,13 @@
var BaseRollingFileStream = require('./BaseRollingFileStream'), "use strict";
format = require('../date_format'), var BaseRollingFileStream = require('./BaseRollingFileStream')
async = require('async'), , debug = require('../debug')('DateRollingFileStream')
fs = require('fs'), , format = require('../date_format')
util = require('util'); , async = require('async')
, fs = require('fs')
, util = require('util');
module.exports = DateRollingFileStream; module.exports = DateRollingFileStream;
function debug(message) {
// console.log(message);
}
function DateRollingFileStream(filename, pattern, options, now) { function DateRollingFileStream(filename, pattern, options, now) {
debug("Now is " + now); debug("Now is " + now);
if (pattern && typeof(pattern) === 'object') { if (pattern && typeof(pattern) === 'object') {
@ -19,20 +17,39 @@ function DateRollingFileStream(filename, pattern, options, now) {
} }
this.pattern = pattern || '.yyyy-MM-dd'; this.pattern = pattern || '.yyyy-MM-dd';
this.now = now || Date.now; this.now = now || Date.now;
if (fs.existsSync(filename)) {
var stat = fs.statSync(filename);
this.lastTimeWeWroteSomething = format.asString(this.pattern, stat.mtime);
} else {
this.lastTimeWeWroteSomething = format.asString(this.pattern, new Date(this.now())); this.lastTimeWeWroteSomething = format.asString(this.pattern, new Date(this.now()));
}
this.baseFilename = filename;
this.alwaysIncludePattern = false;
if (options) {
if (options.alwaysIncludePattern) {
this.alwaysIncludePattern = true;
filename = this.baseFilename + this.lastTimeWeWroteSomething;
}
delete options.alwaysIncludePattern;
if (Object.keys(options).length === 0) {
options = null;
}
}
debug("this.now is " + this.now + ", now is " + now); debug("this.now is " + this.now + ", now is " + now);
DateRollingFileStream.super_.call(this, filename, options); DateRollingFileStream.super_.call(this, filename, options);
this.rollBeforeWrite = true;
} }
util.inherits(DateRollingFileStream, BaseRollingFileStream); util.inherits(DateRollingFileStream, BaseRollingFileStream);
DateRollingFileStream.prototype.shouldRoll = function() { DateRollingFileStream.prototype.shouldRoll = function() {
var lastTime = this.lastTimeWeWroteSomething, var lastTime = this.lastTimeWeWroteSomething,
thisTime = format.asString(this.pattern, new Date(this.now())); thisTime = format.asString(this.pattern, new Date(this.now()));
debug("DateRollingFileStream.shouldRoll with now = " + this.now() + ", thisTime = " + thisTime + ", lastTime = " + lastTime); debug("DateRollingFileStream.shouldRoll with now = " +
this.now() + ", thisTime = " + thisTime + ", lastTime = " + lastTime);
this.lastTimeWeWroteSomething = thisTime; this.lastTimeWeWroteSomething = thisTime;
this.previousTime = lastTime; this.previousTime = lastTime;
@ -41,19 +58,25 @@ DateRollingFileStream.prototype.shouldRoll = function() {
}; };
DateRollingFileStream.prototype.roll = function(filename, callback) { DateRollingFileStream.prototype.roll = function(filename, callback) {
var that = this, var that = this;
newFilename = filename + this.previousTime;
debug("Starting roll"); debug("Starting roll");
debug("Queueing up data until we've finished rolling");
debug("Flushing underlying stream");
this.flush();
if (this.alwaysIncludePattern) {
this.filename = this.baseFilename + this.lastTimeWeWroteSomething;
async.series([ async.series([
this.closeTheStream.bind(this),
this.openTheStream.bind(this)
], callback);
} else {
var newFilename = this.baseFilename + this.previousTime;
async.series([
this.closeTheStream.bind(this),
deleteAnyExistingFile, deleteAnyExistingFile,
renameTheCurrentFile, renameTheCurrentFile,
openANewFile this.openTheStream.bind(this)
], callback); ], callback);
}
function deleteAnyExistingFile(cb) { function deleteAnyExistingFile(cb) {
//on windows, you can get a EEXIST error if you rename a file to an existing file //on windows, you can get a EEXIST error if you rename a file to an existing file
@ -69,21 +92,4 @@ DateRollingFileStream.prototype.roll = function(filename, callback) {
fs.rename(filename, newFilename, cb); fs.rename(filename, newFilename, cb);
} }
function openANewFile(cb) {
debug("Opening a new file");
fs.open(
filename,
that.options.flags,
that.options.mode,
function (err, fd) {
debug("opened new file");
var oldLogFileFD = that.fd;
that.fd = fd;
that.writable = true;
fs.close(oldLogFileFD, cb);
}
);
}
}; };

View File

@ -1,13 +1,12 @@
var BaseRollingFileStream = require('./BaseRollingFileStream'), "use strict";
util = require('util'), var BaseRollingFileStream = require('./BaseRollingFileStream')
path = require('path'), , debug = require('../debug')('RollingFileStream')
fs = require('fs'), , util = require('util')
async = require('async'); , path = require('path')
, child_process = require('child_process')
function debug(message) { , zlib = require("zlib")
// util.debug(message); , fs = require('fs')
// console.log(message); , async = require('async');
}
module.exports = RollingFileStream; module.exports = RollingFileStream;
@ -28,6 +27,7 @@ function RollingFileStream (filename, size, backups, options) {
util.inherits(RollingFileStream, BaseRollingFileStream); util.inherits(RollingFileStream, BaseRollingFileStream);
RollingFileStream.prototype.shouldRoll = function() { RollingFileStream.prototype.shouldRoll = function() {
debug("should roll with current size " + this.currentSize + " and max size " + this.size);
return this.currentSize >= this.size; return this.currentSize >= this.size;
}; };
@ -40,6 +40,7 @@ RollingFileStream.prototype.roll = function(filename, callback) {
} }
function index(filename_) { function index(filename_) {
debug('Calculating index of '+filename_);
return parseInt(filename_.substring((path.basename(filename) + '.').length), 10) || 0; return parseInt(filename_.substring((path.basename(filename) + '.').length), 10) || 0;
} }
@ -53,16 +54,42 @@ RollingFileStream.prototype.roll = function(filename, callback) {
} }
} }
function compress (filename, cb) {
var gzip = zlib.createGzip();
var inp = fs.createReadStream(filename);
var out = fs.createWriteStream(filename+".gz");
inp.pipe(gzip).pipe(out);
fs.unlink(filename, cb);
}
function increaseFileIndex (fileToRename, cb) { function increaseFileIndex (fileToRename, cb) {
var idx = index(fileToRename); var idx = index(fileToRename);
debug('Index of ' + fileToRename + ' is ' + idx); debug('Index of ' + fileToRename + ' is ' + idx);
if (idx < that.backups) { if (idx < that.backups) {
var ext = path.extname(fileToRename);
var destination = filename + '.' + (idx+1);
if (that.options.compress && /^gz$/.test(ext.substring(1))) {
destination+=ext;
}
//on windows, you can get a EEXIST error if you rename a file to an existing file //on windows, you can get a EEXIST error if you rename a file to an existing file
//so, we'll try to delete the file we're renaming to first //so, we'll try to delete the file we're renaming to first
fs.unlink(filename + '.' + (idx+1), function (err) { fs.unlink(destination, function (err) {
//ignore err: if we could not delete, it's most likely that it doesn't exist //ignore err: if we could not delete, it's most likely that it doesn't exist
debug('Renaming ' + fileToRename + ' -> ' + filename + '.' + (idx+1)); debug('Renaming ' + fileToRename + ' -> ' + destination);
fs.rename(path.join(path.dirname(filename), fileToRename), filename + '.' + (idx + 1), cb); fs.rename(path.join(path.dirname(filename), fileToRename), destination, function(err) {
if (err) {
cb(err);
} else {
if (that.options.compress && ext!=".gz") {
compress(destination, cb);
} else {
cb();
}
}
});
}); });
} else { } else {
cb(); cb();
@ -73,7 +100,7 @@ RollingFileStream.prototype.roll = function(filename, callback) {
//roll the backups (rename file.n to file.n+1, where n <= numBackups) //roll the backups (rename file.n to file.n+1, where n <= numBackups)
debug("Renaming the old files"); debug("Renaming the old files");
fs.readdir(path.dirname(filename), function (err, files) { fs.readdir(path.dirname(filename), function (err, files) {
async.forEachSeries( async.eachSeries(
files.filter(justTheseFiles).sort(byIndex).reverse(), files.filter(justTheseFiles).sort(byIndex).reverse(),
increaseFileIndex, increaseFileIndex,
cb cb
@ -81,30 +108,11 @@ RollingFileStream.prototype.roll = function(filename, callback) {
}); });
} }
function openANewFile(cb) { debug("Rolling, rolling, rolling");
debug("Opening a new file");
fs.open(
filename,
that.options.flags,
that.options.mode,
function (err, fd) {
debug("opened new file");
var oldLogFileFD = that.fd;
that.fd = fd;
that.writable = true;
fs.close(oldLogFileFD, cb);
}
);
}
debug("Starting roll");
debug("Queueing up data until we've finished rolling");
debug("Flushing underlying stream");
this.flush();
async.series([ async.series([
this.closeTheStream.bind(this),
renameTheFiles, renameTheFiles,
openANewFile this.openTheStream.bind(this)
], callback); ], callback);
}; };

View File

@ -1,3 +1,2 @@
exports.BufferedWriteStream = require('./BufferedWriteStream');
exports.RollingFileStream = require('./RollingFileStream'); exports.RollingFileStream = require('./RollingFileStream');
exports.DateRollingFileStream = require('./DateRollingFileStream'); exports.DateRollingFileStream = require('./DateRollingFileStream');

View File

@ -1,6 +1,6 @@
{ {
"name": "log4js", "name": "log4js",
"version": "0.5.6", "version": "0.6.25",
"description": "Port of Log4js to work with node.", "description": "Port of Log4js to work with node.",
"keywords": [ "keywords": [
"logging", "logging",
@ -8,8 +8,9 @@
"log4j", "log4j",
"node" "node"
], ],
"license": "Apache-2.0",
"main": "./lib/log4js", "main": "./lib/log4js",
"author": "Gareth Jones <gareth.jones@sensis.com.au>", "author": "Gareth Jones <gareth.nomiddlename@gmail.com>",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/nomiddlename/log4js-node.git" "url": "https://github.com/nomiddlename/log4js-node.git"
@ -17,7 +18,9 @@
"bugs": { "bugs": {
"url": "http://github.com/nomiddlename/log4js-node/issues" "url": "http://github.com/nomiddlename/log4js-node/issues"
}, },
"engines": [ "node >=0.6" ], "engines": {
"node": ">=0.8"
},
"scripts": { "scripts": {
"test": "vows" "test": "vows"
}, },
@ -26,13 +29,17 @@
"lib": "lib" "lib": "lib"
}, },
"dependencies": { "dependencies": {
"async": "0.1.15", "async": "~0.2.0",
"dequeue": "1.0.3" "readable-stream": "~1.0.2",
"semver": "~4.3.3",
"underscore": "1.8.2"
}, },
"devDependencies": { "devDependencies": {
"vows": "0.6.2", "vows": "0.7.0",
"sandboxed-module": "0.1.3", "sandboxed-module": "0.1.3",
"hook.io": "0.8.10",
"underscore": "1.2.1" "underscore": "1.2.1"
},
"browser": {
"os": false
} }
} }

View File

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

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

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

View File

@ -1,6 +1,7 @@
var assert = require('assert'), "use strict";
vows = require('vows'), var assert = require('assert')
sandbox = require('sandboxed-module'); , vows = require('vows')
, sandbox = require('sandboxed-module');
function makeTestAppender() { function makeTestAppender() {
return { return {
@ -12,7 +13,7 @@ function makeTestAppender() {
}, },
appender: function() { appender: function() {
var self = this; var self = this;
return function(logEvt) { self.logEvt = logEvt; } return function(logEvt) { self.logEvt = logEvt; };
} }
}; };
} }
@ -85,6 +86,21 @@ vows.describe('log4js configure').addBatch({
assert.isFunction(log4js.appenderMakers['some/other/external']); assert.isFunction(log4js.appenderMakers['some/other/external']);
} }
}, },
'when appender object loaded via loadAppender': {
topic: function() {
var testAppender = makeTestAppender(),
log4js = sandbox.require('../lib/log4js');
log4js.loadAppender('some/other/external', testAppender);
return log4js;
},
'should load appender with provided object': 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': { 'when configuration file loaded via LOG4JS_CONFIG environment variable': {
topic: function() { topic: function() {
process.env.LOG4JS_CONFIG = 'some/path/to/mylog4js.json'; process.env.LOG4JS_CONFIG = 'some/path/to/mylog4js.json';
@ -114,12 +130,14 @@ vows.describe('log4js configure').addBatch({
} }
} }
}, },
log4js = sandbox.require('../lib/log4js', log4js = sandbox.require(
'../lib/log4js',
{ {
requires: { requires: {
'fs': fakeFS, 'fs': fakeFS,
} }
}); }
);
delete process.env.LOG4JS_CONFIG; delete process.env.LOG4JS_CONFIG;
return fileRead; return fileRead;
}, },

View File

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

View File

@ -1,5 +1,9 @@
/* jshint maxparams:7 */
"use strict";
var vows = require('vows') var vows = require('vows')
, assert = require('assert') , assert = require('assert')
, util = require('util')
, EE = require('events').EventEmitter
, levels = require('../lib/levels'); , levels = require('../lib/levels');
function MockLogger() { function MockLogger() {
@ -19,25 +23,41 @@ function MockLogger() {
} }
function MockRequest(remoteAddr, method, originalUrl) { function MockRequest(remoteAddr, method, originalUrl, headers) {
this.socket = { remoteAddress: remoteAddr }; this.socket = { remoteAddress: remoteAddr };
this.originalUrl = originalUrl; this.originalUrl = originalUrl;
this.method = method; this.method = method;
this.httpVersionMajor = '5'; this.httpVersionMajor = '5';
this.httpVersionMinor = '0'; this.httpVersionMinor = '0';
this.headers = {} this.headers = headers || {};
var self = this;
Object.keys(this.headers).forEach(function(key) {
self.headers[key.toLowerCase()] = self.headers[key];
});
} }
function MockResponse(statusCode) { function MockResponse() {
var r = this;
this.statusCode = statusCode;
this.end = function(chunk, encoding) { this.end = function(chunk, encoding) {
r.emit('finish');
};
this.writeHead = function(code, headers) {
this.statusCode = code;
this._headers = headers;
};
} }
util.inherits(MockResponse, EE);
function request(cl, method, url, code, reqHeaders, resHeaders) {
var req = new MockRequest('my.remote.addr', method, url, reqHeaders);
var res = new MockResponse();
cl(req, res, function() {});
res.writeHead(code, resHeaders);
res.end('chunk','encoding');
} }
vows.describe('log4js connect logger').addBatch({ vows.describe('log4js connect logger').addBatch({
@ -67,11 +87,11 @@ vows.describe('log4js connect logger').addBatch({
topic: function(clm) { topic: function(clm) {
var ml = new MockLogger(); var ml = new MockLogger();
var cl = clm.connectLogger(ml); var cl = clm.connectLogger(ml);
var req = new MockRequest('my.remote.addr', 'GET', 'http://url'); var cb = this.callback;
var res = new MockResponse(200); request(cl, 'GET', 'http://url', 200);
cl(req, res, function() { }); setTimeout(function() {
res.end('chunk', 'encoding'); cb(null, ml.messages);
return ml.messages; },10);
}, },
'check message': function(messages) { 'check message': function(messages) {
@ -90,10 +110,7 @@ vows.describe('log4js connect logger').addBatch({
var ml = new MockLogger(); var ml = new MockLogger();
ml.level = levels.FATAL; ml.level = levels.FATAL;
var cl = clm.connectLogger(ml); var cl = clm.connectLogger(ml);
var req = new MockRequest('my.remote.addr', 'GET', 'http://url'); request(cl, 'GET', 'http://url', 200);
var res = new MockResponse(200);
cl(req, res, function() { });
res.end('chunk', 'encoding');
return ml.messages; return ml.messages;
}, },
@ -106,14 +123,13 @@ vows.describe('log4js connect logger').addBatch({
'log events with non-default level and custom format' : { 'log events with non-default level and custom format' : {
topic: function(clm) { topic: function(clm) {
var ml = new MockLogger(); var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO; ml.level = levels.INFO;
var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url' } ); var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url' } );
var req = new MockRequest('my.remote.addr', 'GET', 'http://url'); request(cl, 'GET', 'http://url', 200);
var res = new MockResponse(200); setTimeout(function() {
cl(req, res, function() { }); cb(null, ml.messages);
res.end('chunk', 'encoding'); },10); },
return ml.messages;
},
'check message': function(messages) { 'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
@ -121,8 +137,159 @@ vows.describe('log4js connect logger').addBatch({
assert.ok(levels.INFO.isEqualTo(messages[0].level)); assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.equal(messages[0].message, 'GET http://url'); assert.equal(messages[0].message, 'GET http://url');
} }
} },
'logger with options as string': {
topic: function(clm) {
var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, ':method :url');
request(cl, 'POST', 'http://meh', 200);
setTimeout(function() {
cb(null, ml.messages);
},10);
},
'should use the passed in format': function(messages) {
assert.equal(messages[0].message, 'POST http://meh');
} }
},
'auto log levels': {
topic: function(clm) {
var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, { level: 'auto', format: ':method :url' });
request(cl, 'GET', 'http://meh', 200);
request(cl, 'GET', 'http://meh', 201);
request(cl, 'GET', 'http://meh', 302);
request(cl, 'GET', 'http://meh', 404);
request(cl, 'GET', 'http://meh', 500);
setTimeout(function() {
cb(null, ml.messages);
},10);
},
'should use INFO for 2xx': function(messages) {
assert.ok(levels.INFO.isEqualTo(messages[0].level));
assert.ok(levels.INFO.isEqualTo(messages[1].level));
},
'should use WARN for 3xx': function(messages) {
assert.ok(levels.WARN.isEqualTo(messages[2].level));
},
'should use ERROR for 4xx': function(messages) {
assert.ok(levels.ERROR.isEqualTo(messages[3].level));
},
'should use ERROR for 5xx': function(messages) {
assert.ok(levels.ERROR.isEqualTo(messages[4].level));
}
},
'format using a function': {
topic: function(clm) {
var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, function(req, res, formatFn) { return "I was called"; });
request(cl, 'GET', 'http://blah', 200);
setTimeout(function() {
cb(null, ml.messages);
},10);
},
'should call the format function': function(messages) {
assert.equal(messages[0].message, 'I was called');
}
},
'format that includes request headers': {
topic: function(clm) {
var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, ':req[Content-Type]');
request(
cl,
'GET', 'http://blah', 200,
{ 'Content-Type': 'application/json' }
);
setTimeout(function() {
cb(null, ml.messages);
},10);
},
'should output the request header': function(messages) {
assert.equal(messages[0].message, 'application/json');
}
},
'format that includes response headers': {
topic: function(clm) {
var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, ':res[Content-Type]');
request(
cl,
'GET', 'http://blah', 200,
null,
{ 'Content-Type': 'application/cheese' }
);
setTimeout(function() {
cb(null, ml.messages);
},10);
},
'should output the response header': function(messages) {
assert.equal(messages[0].message, 'application/cheese');
}
},
'log events with custom token' : {
topic: function(clm) {
var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url :custom_string', tokens: [{
token: ':custom_string', replacement: 'fooBAR'
}] } );
request(cl, 'GET', 'http://url', 200);
setTimeout(function() {
cb(null, ml.messages);
},10);
},
'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 fooBAR');
}
},
'log events with custom override token' : {
topic: function(clm) {
var ml = new MockLogger();
var cb = this.callback;
ml.level = levels.INFO;
var cl = clm.connectLogger(ml, { level: levels.INFO, format: ':method :url :date', tokens: [{
token: ':date', replacement: "20150310"
}] } );
request(cl, 'GET', 'http://url', 200);
setTimeout(function() {
cb(null, ml.messages);
},10);
},
'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 20150310');
}
}
}
}).export(module); }).export(module);

View File

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

View File

@ -1,8 +1,11 @@
var vows = require('vows'), "use strict";
assert = require('assert'), var vows = require('vows')
path = require('path'), , assert = require('assert')
fs = require('fs'), , path = require('path')
log4js = require('../lib/log4js'); , fs = require('fs')
, sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js')
, EOL = require('os').EOL || '\n';
function removeFile(filename) { function removeFile(filename) {
return function() { return function() {
@ -40,6 +43,46 @@ vows.describe('../lib/appenders/dateFile').addBatch({
'should only add one `exit` listener': function (initialCount) { 'should only add one `exit` listener': function (initialCount) {
assert.equal(process.listeners('exit').length, initialCount + 1); assert.equal(process.listeners('exit').length, initialCount + 1);
},
},
'exit listener': {
topic: function() {
var exitListener
, openedFiles = []
, dateFileAppender = sandbox.require(
'../lib/appenders/dateFile',
{
globals: {
process: {
on: function(evt, listener) {
exitListener = listener;
}
}
},
requires: {
'../streams': {
DateRollingFileStream: function(filename) {
openedFiles.push(filename);
this.end = function() {
openedFiles.shift();
};
}
}
}
}
);
for (var i=0; i < 5; i += 1) {
dateFileAppender.appender('test' + i);
}
assert.isNotEmpty(openedFiles);
exitListener();
return openedFiles;
},
'should close all open files': function(openedFiles) {
assert.isEmpty(openedFiles);
} }
}, },
@ -66,7 +109,10 @@ vows.describe('../lib/appenders/dateFile').addBatch({
}, },
'should use the basic layout': function(contents) { '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 - /); assert.match(
contents,
/\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /
);
} }
} }
@ -89,9 +135,88 @@ vows.describe('../lib/appenders/dateFile').addBatch({
teardown: removeFile('date-file-test.log'), teardown: removeFile('date-file-test.log'),
'should load appender configuration from a json file': function(err, contents) { 'should load appender configuration from a json file': function(err, contents) {
assert.include(contents, 'this should be written to the file\n'); if (err) {
throw err;
}
assert.include(contents, 'this should be written to the file' + EOL);
assert.equal(contents.indexOf('this should not be written to the file'), -1); assert.equal(contents.indexOf('this should not be written to the file'), -1);
} }
},
'with options.alwaysIncludePattern': {
topic: function() {
var self = this
, log4js = require('../lib/log4js')
, format = require('../lib/date_format')
, logger
, options = {
"appenders": [
{
"category": "tests",
"type": "dateFile",
"filename": "test/date-file-test",
"pattern": "-from-MM-dd.log",
"alwaysIncludePattern": true,
"layout": {
"type": "messagePassThrough"
}
}
]
}
, thisTime = format.asString(options.appenders[0].pattern, new Date());
fs.writeFileSync(
path.join(__dirname, 'date-file-test' + thisTime),
"this is existing data" + EOL,
'utf8'
);
log4js.clearAppenders();
log4js.configure(options);
logger = log4js.getLogger('tests');
logger.warn('this should be written to the file with the appended date');
this.teardown = removeFile('date-file-test' + thisTime);
//wait for filesystem to catch up
setTimeout(function() {
fs.readFile(path.join(__dirname, 'date-file-test' + thisTime), 'utf8', self.callback);
}, 100);
},
'should create file with the correct pattern': function(contents) {
assert.include(contents, 'this should be written to the file with the appended date');
},
'should not overwrite the file on open (bug found in issue #132)': function(contents) {
assert.include(contents, 'this is existing data');
}
},
'with cwd option': {
topic: function () {
var fileOpened,
appender = sandbox.require(
'../lib/appenders/dateFile',
{ requires:
{ '../streams':
{ DateRollingFileStream:
function(file) {
fileOpened = file;
return {
on: function() {},
end: function() {}
};
}
}
}
}
);
appender.configure(
{
filename: "whatever.log",
maxLogSize: 10
},
{ cwd: '/absolute/path/to' }
);
return fileOpened;
},
'should prepend options.cwd to config.filename': function (fileOpened) {
var expected = path.sep + path.join("absolute", "path", "to", "whatever.log");
assert.equal(fileOpened, expected);
}
} }
} }

View File

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

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

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

View File

@ -1,8 +1,12 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, fs = require('fs') , fs = require('fs')
, path = require('path') , path = require('path')
, sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js') , log4js = require('../lib/log4js')
, assert = require('assert'); , assert = require('assert')
, zlib = require('zlib')
, EOL = require('os').EOL || '\n';
log4js.clearAppenders(); log4js.clearAppenders();
@ -29,18 +33,59 @@ vows.describe('log4js fileAppender').addBatch({
return listenersCount; return listenersCount;
}, },
'does not adds more than one `exit` listeners': function (initialCount) { 'does not add more than one `exit` listeners': function (initialCount) {
assert.ok(process.listeners('exit').length <= initialCount + 1); assert.ok(process.listeners('exit').length <= initialCount + 1);
} }
}, },
'exit listener': {
topic: function() {
var exitListener
, openedFiles = []
, fileAppender = sandbox.require(
'../lib/appenders/file',
{
globals: {
process: {
on: function(evt, listener) {
exitListener = listener;
}
}
},
requires: {
'../streams': {
RollingFileStream: function(filename) {
openedFiles.push(filename);
this.end = function() {
openedFiles.shift();
};
this.on = function() {};
}
}
}
}
);
for (var i=0; i < 5; i += 1) {
fileAppender.appender('test' + i, null, 100);
}
assert.isNotEmpty(openedFiles);
exitListener();
return openedFiles;
},
'should close all open files': function(openedFiles) {
assert.isEmpty(openedFiles);
}
},
'with default fileAppender settings': { 'with default fileAppender settings': {
topic: function() { topic: function() {
var that = this var that = this
, testFile = path.join(__dirname, '/fa-default-test.log') , testFile = path.join(__dirname, '/fa-default-test.log')
, logger = log4js.getLogger('default-settings'); , logger = log4js.getLogger('default-settings');
remove(testFile); remove(testFile);
//log4js.configure({ appenders:[ { type: "file", filename: testFile, category: 'default-settings' } ] });
log4js.clearAppenders(); log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/file').appender(testFile), 'default-settings'); log4js.addAppender(require('../lib/appenders/file').appender(testFile), 'default-settings');
@ -51,10 +96,77 @@ vows.describe('log4js fileAppender').addBatch({
}, 100); }, 100);
}, },
'should write log messages to the file': function (err, fileContents) { 'should write log messages to the file': function (err, fileContents) {
assert.include(fileContents, "This should be in the file.\n"); assert.include(fileContents, "This should be in the file." + EOL);
}, },
'log messages should be in the basic layout format': function(err, fileContents) { '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 - /); assert.match(
fileContents,
/\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /
);
}
},
'fileAppender subcategories': {
topic: function() {
var that = this;
log4js.clearAppenders();
function addAppender(cat) {
var testFile = path.join(__dirname, '/fa-subcategories-test-'+cat.join('-').replace(/\./g, "_")+'.log');
remove(testFile);
log4js.addAppender(require('../lib/appenders/file').appender(testFile), cat);
return testFile;
}
var file_sub1 = addAppender([ 'sub1']);
var file_sub1_sub12$sub1_sub13 = addAppender([ 'sub1.sub12', 'sub1.sub13' ]);
var file_sub1_sub12 = addAppender([ 'sub1.sub12' ]);
var logger_sub1_sub12_sub123 = log4js.getLogger('sub1.sub12.sub123');
var logger_sub1_sub13_sub133 = log4js.getLogger('sub1.sub13.sub133');
var logger_sub1_sub14 = log4js.getLogger('sub1.sub14');
var logger_sub2 = log4js.getLogger('sub2');
logger_sub1_sub12_sub123.info('sub1_sub12_sub123');
logger_sub1_sub13_sub133.info('sub1_sub13_sub133');
logger_sub1_sub14.info('sub1_sub14');
logger_sub2.info('sub2');
setTimeout(function() {
that.callback(null, {
file_sub1: fs.readFileSync(file_sub1).toString(),
file_sub1_sub12$sub1_sub13: fs.readFileSync(file_sub1_sub12$sub1_sub13).toString(),
file_sub1_sub12: fs.readFileSync(file_sub1_sub12).toString()
});
}, 3000);
},
'check file contents': function (err, fileContents) {
// everything but category 'sub2'
assert.match(fileContents.file_sub1, /^(\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] (sub1.sub12.sub123 - sub1_sub12_sub123|sub1.sub13.sub133 - sub1_sub13_sub133|sub1.sub14 - sub1_sub14)[\s\S]){3}$/);
assert.ok(fileContents.file_sub1.match(/sub123/) && fileContents.file_sub1.match(/sub133/) && fileContents.file_sub1.match(/sub14/));
assert.ok(!fileContents.file_sub1.match(/sub2/));
// only catgories starting with 'sub1.sub12' and 'sub1.sub13'
assert.match(fileContents.file_sub1_sub12$sub1_sub13, /^(\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] (sub1.sub12.sub123 - sub1_sub12_sub123|sub1.sub13.sub133 - sub1_sub13_sub133)[\s\S]){2}$/);
assert.ok(fileContents.file_sub1_sub12$sub1_sub13.match(/sub123/) && fileContents.file_sub1_sub12$sub1_sub13.match(/sub133/));
assert.ok(!fileContents.file_sub1_sub12$sub1_sub13.match(/sub14|sub2/));
// only catgories starting with 'sub1.sub12'
assert.match(fileContents.file_sub1_sub12, /^(\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] (sub1.sub12.sub123 - sub1_sub12_sub123)[\s\S]){1}$/);
assert.ok(!fileContents.file_sub1_sub12.match(/sub14|sub2|sub13/));
} }
}, },
'with a max file size and no backups': { 'with a max file size and no backups': {
@ -66,7 +178,10 @@ vows.describe('log4js fileAppender').addBatch({
remove(testFile + '.1'); remove(testFile + '.1');
//log file of 100 bytes maximum, no backups //log file of 100 bytes maximum, no backups
log4js.clearAppenders(); log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 100, 0), 'max-file-size'); 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 the first log message.");
logger.info("This is an intermediate log message."); logger.info("This is an intermediate log message.");
logger.info("This is the second log message."); logger.info("This is the second log message.");
@ -76,7 +191,7 @@ vows.describe('log4js fileAppender').addBatch({
}, 100); }, 100);
}, },
'log file should only contain the second message': function(err, fileContents) { 'log file should only contain the second message': function(err, fileContents) {
assert.include(fileContents, "This is the second log message.\n"); assert.include(fileContents, "This is the second log message.");
assert.equal(fileContents.indexOf("This is the first log message."), -1); assert.equal(fileContents.indexOf("This is the first log message."), -1);
}, },
'the number of files': { 'the number of files': {
@ -85,7 +200,9 @@ vows.describe('log4js fileAppender').addBatch({
}, },
'starting with the test file name should be two': function(err, files) { '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 //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; }); var logFiles = files.filter(
function(file) { return file.indexOf('fa-maxFileSize-test.log') > -1; }
);
assert.equal(logFiles.length, 2); assert.equal(logFiles.length, 2);
} }
} }
@ -100,7 +217,10 @@ vows.describe('log4js fileAppender').addBatch({
//log file of 50 bytes maximum, 2 backups //log file of 50 bytes maximum, 2 backups
log4js.clearAppenders(); log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 50, 2), 'max-file-size-backups'); 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 first log message.");
logger.info("This is the second log message."); logger.info("This is the second log message.");
logger.info("This is the third log message."); logger.info("This is the third log message.");
@ -108,42 +228,127 @@ vows.describe('log4js fileAppender').addBatch({
var that = this; var that = this;
//give the system a chance to open the stream //give the system a chance to open the stream
setTimeout(function() { setTimeout(function() {
fs.readdir(__dirname, that.callback); fs.readdir(__dirname, function(err, files) {
if (files) {
that.callback(null, files.sort());
} else {
that.callback(err, files);
}
});
}, 200); }, 200);
}, },
'the log files': { 'the log files': {
topic: function(files) { topic: function(files) {
var logFiles = files.filter(function(file) { return file.indexOf('fa-maxFileSize-with-backups-test.log') > -1; }); var logFiles = files.filter(
function(file) { return file.indexOf('fa-maxFileSize-with-backups-test.log') > -1; }
);
return logFiles; return logFiles;
}, },
'should be 3': function (files) { 'should be 3': function (files) {
assert.equal(files.length, 3); assert.equal(files.length, 3);
}, },
'should be named in sequence': function (files) { 'should be named in sequence': function (files) {
assert.deepEqual(files.sort(), ['fa-maxFileSize-with-backups-test.log', 'fa-maxFileSize-with-backups-test.log.1', 'fa-maxFileSize-with-backups-test.log.2']); 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': { 'and the contents of the first file': {
topic: function(logFiles) { topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[0]), "utf8", this.callback); fs.readFile(path.join(__dirname, logFiles[0]), "utf8", this.callback);
}, },
'should be empty because the last log message triggers rolling': function(contents) { 'should be the last log message': function(contents) {
assert.isEmpty(contents); assert.include(contents, 'This is the fourth log message.');
} }
}, },
'and the contents of the second file': { 'and the contents of the second file': {
topic: function(logFiles) { topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[1]), "utf8", this.callback); fs.readFile(path.join(__dirname, logFiles[1]), "utf8", this.callback);
}, },
'should be the last log message': function(contents) { 'should be the third log message': function(contents) {
assert.include(contents, 'This is the fourth log message.'); assert.include(contents, 'This is the third log message.');
} }
}, },
'and the contents of the third file': { 'and the contents of the third file': {
topic: function(logFiles) { topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[2]), "utf8", this.callback); 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.');
}
}
}
},
'with a max file size and 2 compressed backups': {
topic: function() {
var testFile = path.join(__dirname, '/fa-maxFileSize-with-backups-compressed-test.log')
, logger = log4js.getLogger('max-file-size-backups');
remove(testFile);
remove(testFile+'.1.gz');
remove(testFile+'.2.gz');
//log file of 50 bytes maximum, 2 backups
log4js.clearAppenders();
log4js.addAppender(
require('../lib/appenders/file').appender(testFile, log4js.layouts.basicLayout, 50, 2, true),
'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);
}
});
}, 1000);
},
'the log files': {
topic: function(files) {
var logFiles = files.filter(
function(file) { return file.indexOf('fa-maxFileSize-with-backups-compressed-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-compressed-test.log',
'fa-maxFileSize-with-backups-compressed-test.log.1.gz',
'fa-maxFileSize-with-backups-compressed-test.log.2.gz'
]);
},
'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) {
zlib.gunzip(fs.readFileSync(path.join(__dirname, logFiles[1])), this.callback);
},
'should be the third log message': function(contents) { 'should be the third log message': function(contents) {
assert.include(contents, 'This is the third log message.'); assert.include(contents.toString('utf8'), 'This is the third log message.');
}
},
'and the contents of the third file': {
topic: function(logFiles) {
zlib.gunzip(fs.readFileSync(path.join(__dirname, logFiles[2])), this.callback);
},
'should be the second log message': function(contents) {
assert.include(contents.toString('utf8'), 'This is the second log message.');
} }
} }
} }
@ -156,7 +361,7 @@ vows.describe('log4js fileAppender').addBatch({
, logger; , logger;
//this config file defines one file appender (to ./tmp-tests.log) //this config file defines one file appender (to ./tmp-tests.log)
//and sets the log level for "tests" to WARN //and sets the log level for "tests" to WARN
log4js.configure('test/log4js.json'); log4js.configure('./test/log4js.json');
logger = log4js.getLogger('tests'); logger = log4js.getLogger('tests');
logger.info('this should not be written to the file'); logger.info('this should not be written to the file');
logger.warn('this should be written to the file'); logger.warn('this should be written to the file');
@ -164,10 +369,51 @@ vows.describe('log4js fileAppender').addBatch({
fs.readFile('tmp-tests.log', 'utf8', this.callback); fs.readFile('tmp-tests.log', 'utf8', this.callback);
}, },
'should load appender configuration from a json file': function (err, contents) { 'should load appender configuration from a json file': function (err, contents) {
assert.include(contents, 'this should be written to the file\n'); assert.include(contents, 'this should be written to the file' + EOL);
assert.equal(contents.indexOf('this should not be written to the file'), -1); assert.equal(contents.indexOf('this should not be written to the file'), -1);
} }
} }
} }
}).addBatch({
'when underlying stream errors': {
topic: function() {
var consoleArgs
, errorHandler
, fileAppender = sandbox.require(
'../lib/appenders/file',
{
globals: {
console: {
error: function() {
consoleArgs = Array.prototype.slice.call(arguments);
}
}
},
requires: {
'../streams': {
RollingFileStream: function(filename) {
this.end = function() {};
this.on = function(evt, cb) {
if (evt === 'error') {
errorHandler = cb;
}
};
}
}
}
}
);
fileAppender.appender('test1.log', null, 100);
errorHandler({ error: 'aargh' });
return consoleArgs;
},
'should log the error to console.error': function(consoleArgs) {
assert.isNotEmpty(consoleArgs);
assert.equal(consoleArgs[0], 'log4js.fileAppender - Writing to file %s, error happened ');
assert.equal(consoleArgs[1], 'test1.log');
assert.equal(consoleArgs[2].error, 'aargh');
}
}
}).export(module); }).export(module);

182
test/fileSyncAppender-test.js Executable file
View File

@ -0,0 +1,182 @@
"use strict";
var vows = require('vows')
, fs = require('fs')
, path = require('path')
, sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js')
, assert = require('assert')
, EOL = require('os').EOL || '\n';
log4js.clearAppenders();
function remove(filename) {
try {
fs.unlinkSync(filename);
} catch (e) {
//doesn't really matter if it failed
}
}
vows.describe('log4js fileSyncAppender').addBatch({
'with default fileSyncAppender settings': {
topic: function() {
var that = this
, testFile = path.join(__dirname, '/fa-default-sync-test.log')
, logger = log4js.getLogger('default-settings');
remove(testFile);
log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/fileSync').appender(testFile), 'default-settings');
logger.info("This should be in the file.");
fs.readFile(testFile, "utf8", that.callback);
},
'should write log messages to the file': function (err, fileContents) {
assert.include(fileContents, "This should be in the file." + EOL);
},
'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-sync-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/fileSync').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.");
fs.readFile(testFile, "utf8", that.callback);
},
'log file should only contain the second message': function (err, fileContents) {
assert.include(fileContents, "This is the second log message." + EOL);
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-sync-test.log') > -1; }
);
assert.equal(logFiles.length, 2);
}
}
},
'with a max file size and 2 backups': {
topic: function() {
var testFile = path.join(__dirname, '/fa-maxFileSize-with-backups-sync-test.log')
, logger = log4js.getLogger('max-file-size-backups');
remove(testFile);
remove(testFile+'.1');
remove(testFile+'.2');
//log file of 50 bytes maximum, 2 backups
log4js.clearAppenders();
log4js.addAppender(
require('../lib/appenders/fileSync').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;
fs.readdir(__dirname, function(err, files) {
if (files) {
that.callback(null, files.sort());
} else {
that.callback(err, files);
}
});
},
'the log files': {
topic: function(files) {
var logFiles = files.filter(
function(file) { return file.indexOf('fa-maxFileSize-with-backups-sync-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-sync-test.log',
'fa-maxFileSize-with-backups-sync-test.log.1',
'fa-maxFileSize-with-backups-sync-test.log.2'
]);
},
'and the contents of the first file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[0]), "utf8", this.callback);
},
'should be the last log message': function(contents) {
assert.include(contents, 'This is the fourth log message.');
}
},
'and the contents of the second file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[1]), "utf8", this.callback);
},
'should be the third log message': function(contents) {
assert.include(contents, 'This is the third log message.');
}
},
'and the contents of the third file': {
topic: function(logFiles) {
fs.readFile(path.join(__dirname, logFiles[2]), "utf8", this.callback);
},
'should be the second log message': function(contents) {
assert.include(contents, 'This is the second log message.');
}
}
}
}
}).addBatch({
'configure' : {
'with fileSyncAppender': {
topic: function() {
var log4js = require('../lib/log4js')
, logger;
//this config defines one file appender (to ./tmp-sync-tests.log)
//and sets the log level for "tests" to WARN
log4js.configure({
appenders: [{
category: "tests",
type: "file",
filename: "tmp-sync-tests.log",
layout: { type: "messagePassThrough" }
}],
levels: { tests: "WARN" }
});
logger = log4js.getLogger('tests');
logger.info('this should not be written to the file');
logger.warn('this should be written to the file');
fs.readFile('tmp-sync-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' + EOL);
assert.equal(contents.indexOf('this should not be written to the file'), -1);
}
}
}
}).export(module);

View File

@ -1,13 +1,17 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, assert = require('assert') , assert = require('assert')
, sandbox = require('sandboxed-module') , sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js') , log4js = require('../lib/log4js')
, realLayouts = require('../lib/layouts')
, setupLogging = function(options, category, compressedLength) { , setupLogging = function(options, category, compressedLength) {
var fakeDgram = { var fakeDgram = {
sent: false, sent: false,
socket: { socket: {
packetLength: 0, packetLength: 0,
closed: false,
close: function() { close: function() {
this.closed = true;
}, },
send: function(pkt, offset, pktLength, port, host) { send: function(pkt, offset, pktLength, port, host) {
fakeDgram.sent = true; fakeDgram.sent = true;
@ -26,6 +30,11 @@ var vows = require('vows')
, fakeZlib = { , fakeZlib = {
gzip: function(objectToCompress, callback) { gzip: function(objectToCompress, callback) {
fakeZlib.uncompressed = objectToCompress; fakeZlib.uncompressed = objectToCompress;
if (this.shouldError) {
callback({ stack: "oh noes" });
return;
}
if (compressedLength) { if (compressedLength) {
callback(null, { length: compressedLength }); callback(null, { length: compressedLength });
} else { } else {
@ -33,10 +42,35 @@ var vows = require('vows')
} }
} }
} }
, exitHandler
, fakeConsole = {
error: function(message) {
this.message = message;
}
}
, fakeLayouts = {
layout: function(type, options) {
this.type = type;
this.options = options;
return realLayouts.messagePassThroughLayout;
},
messagePassThroughLayout: realLayouts.messagePassThroughLayout
}
, appender = sandbox.require('../lib/appenders/gelf', { , appender = sandbox.require('../lib/appenders/gelf', {
requires: { requires: {
dgram: fakeDgram, dgram: fakeDgram,
zlib: fakeZlib zlib: fakeZlib,
'../layouts': fakeLayouts
},
globals: {
process: {
on: function(evt, handler) {
if (evt === 'exit') {
exitHandler = handler;
}
}
},
console: fakeConsole
} }
}); });
@ -45,12 +79,13 @@ var vows = require('vows')
return { return {
dgram: fakeDgram, dgram: fakeDgram,
compress: fakeZlib, compress: fakeZlib,
exitHandler: exitHandler,
console: fakeConsole,
layouts: fakeLayouts,
logger: log4js.getLogger(category || "gelf-test") logger: log4js.getLogger(category || "gelf-test")
}; };
}; };
//log4js.configure({ doNotReplaceConsole: true });
vows.describe('log4js gelfAppender').addBatch({ vows.describe('log4js gelfAppender').addBatch({
'with default gelfAppender settings': { 'with default gelfAppender settings': {
@ -80,12 +115,10 @@ vows.describe('log4js gelfAppender').addBatch({
return message; return message;
}, },
'should be in the gelf format': function(message) { 'should be in the gelf format': function(message) {
assert.equal(message.version, '1.0'); assert.equal(message.version, '1.1');
assert.equal(message.host, require('os').hostname()); assert.equal(message.host, require('os').hostname());
assert.equal(message.level, 6); //INFO assert.equal(message.level, 6); //INFO
assert.equal(message.facility, 'nodejs-server'); assert.equal(message.short_message, 'This is a test');
assert.equal(message.full_message, message.short_message);
assert.equal(message.full_message, 'This is a test');
} }
} }
}, },
@ -131,8 +164,94 @@ vows.describe('log4js gelfAppender').addBatch({
}, },
'should pick up the options': function(message) { 'should pick up the options': function(message) {
assert.equal(message.host, 'cheese'); assert.equal(message.host, 'cheese');
assert.equal(message.facility, 'nonsense'); assert.equal(message._facility, 'nonsense');
}
}
},
'on process.exit': {
topic: function() {
var setup = setupLogging();
setup.exitHandler();
return setup;
},
'should close open sockets': function(setup) {
assert.isTrue(setup.dgram.socket.closed);
}
},
'on zlib error': {
topic: function() {
var setup = setupLogging();
setup.compress.shouldError = true;
setup.logger.info('whatever');
return setup;
},
'should output to console.error': function(setup) {
assert.equal(setup.console.message, 'oh noes');
}
},
'with layout in configuration': {
topic: function() {
var setup = setupLogging({
layout: {
type: 'madeuplayout',
earlgrey: 'yes, please'
}
});
return setup;
},
'should pass options to layout': function(setup) {
assert.equal(setup.layouts.type, 'madeuplayout');
assert.equal(setup.layouts.options.earlgrey, 'yes, please');
}
},
'with custom fields options': {
topic: function() {
var setup = setupLogging({
host: 'somewhere',
port: 12345,
hostname: 'cheese',
facility: 'nonsense',
customFields: {
_every1: 'Hello every one',
_every2: 'Hello every two'
}
});
var myFields = {
GELF: true,
_every2: 'Overwritten!',
_myField: 'This is my field!'
};
setup.logger.debug(myFields, "Just testing.");
return setup;
},
'the dgram packet': {
topic: function(setup) {
return setup.dgram;
},
'should pick up the options': function(dgram) {
assert.equal(dgram.socket.host, 'somewhere');
assert.equal(dgram.socket.port, 12345);
}
},
'the uncompressed packet': {
topic: function(setup) {
var message = JSON.parse(setup.compress.uncompressed);
return message;
},
'should pick up the options': function(message) {
assert.equal(message.host, 'cheese');
assert.isUndefined(message.GELF); // make sure flag was removed
assert.equal(message._facility, 'nonsense');
assert.equal(message._every1, 'Hello every one'); // the default value
assert.equal(message._every2, 'Overwritten!'); // the overwritten value
assert.equal(message._myField, 'This is my field!'); // the value for this message only
assert.equal(message.short_message, 'Just testing.'); // skip the field object
} }
} }
} }
}).export(module); }).export(module);

View File

@ -1,5 +1,6 @@
var vows = require('vows'), "use strict";
assert = require('assert'); var vows = require('vows')
, assert = require('assert');
vows.describe('log4js global loglevel').addBatch({ vows.describe('log4js global loglevel').addBatch({
'global loglevel' : { 'global loglevel' : {
@ -80,6 +81,41 @@ vows.describe('log4js global loglevel').addBatch({
log2.removeLevel(); log2.removeLevel();
assert.equal(log1.level.toString(), oldLevel); assert.equal(log1.level.toString(), oldLevel);
assert.equal(log2.level.toString(), oldLevel); assert.equal(log2.level.toString(), oldLevel);
},
'set level on all categories': function(log4js) {
// Get 2 loggers
var log1 = log4js.getLogger('log1');
var log2 = log4js.getLogger('log2');
// First a test with 2 categories with different levels
var config = {
'levels': {
'log1': 'ERROR',
'log2': 'WARN'
}
};
log4js.configure(config);
// Check if the levels are set correctly
assert.equal('ERROR', log1.level.toString());
assert.equal('WARN', log2.level.toString());
log1.removeLevel();
log2.removeLevel();
// Almost identical test, but now we set
// level on all categories
var config2 = {
'levels': {
'[all]': 'DEBUG'
}
};
log4js.configure(config2);
// Check if the loggers got the DEBUG level
assert.equal('DEBUG', log1.level.toString());
assert.equal('DEBUG', log2.level.toString());
} }
} }
}).export(module); }).export(module);

View File

@ -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);

View File

@ -1,5 +1,8 @@
var vows = require('vows'), "use strict";
assert = require('assert'); var vows = require('vows')
, assert = require('assert')
, os = require('os')
, EOL = os.EOL || '\n';
//used for patternLayout tests. //used for patternLayout tests.
function test(args, pattern, value) { function test(args, pattern, value) {
@ -25,9 +28,8 @@ vows.describe('log4js layouts').addBatch({
toString: function() { return "ERROR"; } toString: function() { return "ERROR"; }
} }
}); });
assert.equal(output, '\033[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \033[39mnonsense'); assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mnonsense');
}, },
'should support the console.log format for the message': function(layout) { 'should support the console.log format for the message': function(layout) {
var output = layout({ var output = layout({
data: ["thing %d", 2], data: ["thing %d", 2],
@ -37,7 +39,7 @@ vows.describe('log4js layouts').addBatch({
toString: function() { return "ERROR"; } toString: function() { return "ERROR"; }
} }
}); });
assert.equal(output, '\033[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \033[39mthing 2'); assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mthing 2');
} }
}, },
@ -58,51 +60,50 @@ vows.describe('log4js layouts').addBatch({
}, },
'should support the console.log format for the message' : function(layout) { 'should support the console.log format for the message' : function(layout) {
assert.equal(layout({ assert.equal(layout({
data: ["thing %d", 1, "cheese"] data: ["thing %d", 1, "cheese"],
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45) startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
, categoryName: "cheese" categoryName: "cheese",
, level : { level : {
colour: "green" colour: "green",
, toString: function() { return "ERROR"; } toString: function() { return "ERROR"; }
} }
}), "thing 1 'cheese'"); }), "thing 1 cheese");
}, },
'should output the first item even if it is not a string': function(layout) { 'should output the first item even if it is not a string': function(layout) {
assert.equal(layout({ assert.equal(layout({
data: [ { thing: 1} ] data: [ { thing: 1} ],
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45) startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
, categoryName: "cheese" categoryName: "cheese",
, level: { level: {
colour: "green" colour: "green",
, toString: function() { return "ERROR"; } toString: function() { return "ERROR"; }
} }
}), "{ thing: 1 }"); }), "{ thing: 1 }");
}, },
'should print the stacks of a passed error objects': function(layout) { 'should print the stacks of a passed error objects': function(layout) {
assert.isArray(layout({ assert.isArray(layout({
data: [ new Error() ] data: [ new Error() ],
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45) startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
, categoryName: "cheese" categoryName: "cheese",
, level: { level: {
colour: "green" colour: "green",
, toString: function() { return "ERROR"; } toString: function() { return "ERROR"; }
} }
}).match(/Error\s+at Object\..*\s+\((.*)test[\\\/]layouts-test\.js\:\d+\:\d+\)\s+at runTest/) }).match(/Error\s+at Object\..*\s+\((.*)test[\\\/]layouts-test\.js\:\d+\:\d+\)\s+at runTest/)
, 'regexp did not return a match'); , 'regexp did not return a match');
}, },
'with passed augmented errors': 'with passed augmented errors': {
{ topic: topic: function(layout){
function(layout){
var e = new Error("My Unique Error Message"); var e = new Error("My Unique Error Message");
e.augmented = "My Unique attribute value" e.augmented = "My Unique attribute value";
e.augObj = { at1: "at2" } e.augObj = { at1: "at2" };
return layout({ return layout({
data: [ e ] data: [ e ],
, startTime: new Date(2010, 11, 5, 14, 18, 30, 45) startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
, categoryName: "cheese" categoryName: "cheese",
, level: { level: {
colour: "green" colour: "green",
, toString: function() { return "ERROR"; } toString: function() { return "ERROR"; }
} }
}); });
}, },
@ -150,7 +151,10 @@ vows.describe('log4js layouts').addBatch({
lines = output.split(/\n/); lines = output.split(/\n/);
assert.equal(lines.length - 1, stack.length); assert.equal(lines.length - 1, stack.length);
assert.equal(lines[0], "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test [Error: Some made-up error]"); assert.equal(
lines[0],
"[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test [Error: Some made-up error]"
);
for (var i = 1; i < stack.length; i++) { for (var i = 1; i < stack.length; i++) {
assert.equal(lines[i+2], stack[i+1]); assert.equal(lines[i+2], stack[i+1]);
@ -163,7 +167,11 @@ vows.describe('log4js layouts').addBatch({
message: 'Gorgonzola smells.' message: 'Gorgonzola smells.'
}]; }];
output = layout(event); output = layout(event);
assert.equal(output, "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test { name: 'Cheese', message: 'Gorgonzola smells.' }"); assert.equal(
output,
"[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test " +
"{ name: 'Cheese', message: 'Gorgonzola smells.' }"
);
} }
}, },
@ -171,7 +179,7 @@ vows.describe('log4js layouts').addBatch({
topic: function() { topic: function() {
var event = { var event = {
data: ['this is a test'], data: ['this is a test'],
startTime: new Date(2010, 11, 5, 14, 18, 30, 45), startTime: new Date('2010-12-05T14:18:30.045Z'), //new Date(2010, 11, 5, 14, 18, 30, 45),
categoryName: "multiple.levels.of.tests", categoryName: "multiple.levels.of.tests",
level: { level: {
toString: function() { return "DEBUG"; } toString: function() { return "DEBUG"; }
@ -179,13 +187,17 @@ vows.describe('log4js layouts').addBatch({
}, layout = require('../lib/layouts').patternLayout }, layout = require('../lib/layouts').patternLayout
, tokens = { , tokens = {
testString: 'testStringToken', testString: 'testStringToken',
testFunction: function() { return 'testFunctionToken'; } testFunction: function() { return 'testFunctionToken'; },
fnThatUsesLogEvent: function(logEvent) { return logEvent.level.toString(); }
}; };
//override getTimezoneOffset
event.startTime.getTimezoneOffset = function() { return 0; };
return [layout, event, tokens]; return [layout, event, tokens];
}, },
'should default to "time logLevel loggerName - message"': function(args) { '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"); test(args, null, "14:18:30 DEBUG multiple.levels.of.tests - this is a test" + EOL);
}, },
'%r should output time only': function(args) { '%r should output time only': function(args) {
test(args, '%r', '14:18:30'); test(args, '%r', '14:18:30');
@ -200,7 +212,13 @@ vows.describe('log4js layouts').addBatch({
test(args, '%m', 'this is a test'); test(args, '%m', 'this is a test');
}, },
'%n should output a new line': function(args) { '%n should output a new line': function(args) {
test(args, '%n', '\n'); test(args, '%n', EOL);
},
'%h should output hostname' : function(args) {
test(args, '%h', os.hostname().toString());
},
'%z should output pid' : function(args) {
test(args, '%z', process.pid);
}, },
'%c should handle category names like java-style package names': function(args) { '%c should handle category names like java-style package names': function(args) {
test(args, '%c{1}', 'tests'); test(args, '%c{1}', 'tests');
@ -214,9 +232,11 @@ vows.describe('log4js layouts').addBatch({
test(args, '%d', '2010-12-05 14:18:30.045'); test(args, '%d', '2010-12-05 14:18:30.045');
}, },
'%d should allow for format specification': function(args) { '%d should allow for format specification': function(args) {
test(args, '%d{ISO8601_WITH_TZ_OFFSET}', '2010-12-05T14:18:30-0000');
test(args, '%d{ISO8601}', '2010-12-05 14:18:30.045'); test(args, '%d{ISO8601}', '2010-12-05 14:18:30.045');
test(args, '%d{ABSOLUTE}', '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{DATE}', '05 12 2010 14:18:30.045');
test(args, '%d{yy MM dd hh mm ss}', '10 12 05 14 18 30');
test(args, '%d{yyyy MM dd}', '2010 12 05'); test(args, '%d{yyyy MM dd}', '2010 12 05');
test(args, '%d{yyyy MM dd hh mm ss SSS}', '2010 12 05 14 18 30 045'); test(args, '%d{yyyy MM dd hh mm ss SSS}', '2010 12 05 14 18 30 045');
}, },
@ -226,10 +246,13 @@ vows.describe('log4js layouts').addBatch({
'should output anything not preceded by % as literal': function(args) { 'should output anything not preceded by % as literal': function(args) {
test(args, 'blah blah blah', 'blah blah blah'); test(args, 'blah blah blah', 'blah blah blah');
}, },
'should output the original string if no replacer matches the token': function(args) {
test(args, '%a{3}', 'a{3}');
},
'should handle complicated patterns': function(args) { 'should handle complicated patterns': function(args) {
test(args, test(args,
'%m%n %c{2} at %d{ABSOLUTE} cheese %p%n', '%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' 'this is a test'+ EOL +' of.tests at 14:18:30.045 cheese DEBUG' + EOL
); );
}, },
'should truncate fields if specified': function(args) { 'should truncate fields if specified': function(args) {
@ -250,7 +273,7 @@ vows.describe('log4js layouts').addBatch({
test(args, '%-10p', 'DEBUG '); test(args, '%-10p', 'DEBUG ');
}, },
'%[%r%] should output colored time': function(args) { '%[%r%] should output colored time': function(args) {
test(args, '%[%r%]', '\033[36m14:18:30\033[39m'); test(args, '%[%r%]', '\x1B[36m14:18:30\x1B[39m');
}, },
'%x{testString} should output the string stored in tokens': function(args) { '%x{testString} should output the string stored in tokens': function(args) {
test(args, '%x{testString}', 'testStringToken'); test(args, '%x{testString}', 'testStringToken');
@ -259,10 +282,23 @@ vows.describe('log4js layouts').addBatch({
test(args, '%x{testFunction}', 'testFunctionToken'); test(args, '%x{testFunction}', 'testFunctionToken');
}, },
'%x{doesNotExist} should output the string stored in tokens': function(args) { '%x{doesNotExist} should output the string stored in tokens': function(args) {
test(args, '%x{doesNotExist}', '%x{doesNotExist}'); test(args, '%x{doesNotExist}', 'null');
},
'%x{fnThatUsesLogEvent} should be able to use the logEvent': function(args) {
test(args, '%x{fnThatUsesLogEvent}', 'DEBUG');
}, },
'%x should output the string stored in tokens': function(args) { '%x should output the string stored in tokens': function(args) {
test(args, '%x', '%x'); test(args, '%x', 'null');
}
}, },
'layout makers': {
topic: require('../lib/layouts'),
'should have a maker for each layout': function(layouts) {
assert.ok(layouts.layout("messagePassThrough"));
assert.ok(layouts.layout("basic"));
assert.ok(layouts.layout("colored"));
assert.ok(layouts.layout("coloured"));
assert.ok(layouts.layout("pattern"));
}
} }
}).export(module); }).export(module);

View File

@ -1,3 +1,4 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, assert = require('assert') , assert = require('assert')
, levels = require('../lib/levels'); , levels = require('../lib/levels');
@ -42,122 +43,367 @@ vows.describe('levels').addBatch({
assert.isNotNull(levels.WARN); assert.isNotNull(levels.WARN);
assert.isNotNull(levels.ERROR); assert.isNotNull(levels.ERROR);
assert.isNotNull(levels.FATAL); assert.isNotNull(levels.FATAL);
assert.isNotNull(levels.MARK);
assert.isNotNull(levels.OFF); assert.isNotNull(levels.OFF);
}, },
'ALL': { 'ALL': {
topic: levels.ALL, topic: levels.ALL,
'should be less than the other levels': function(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]); assertThat(all).isLessThanOrEqualTo(
[
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
}, },
'should be greater than no levels': function(all) { '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]); assertThat(all).isNotGreaterThanOrEqualTo(
[
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
}, },
'should only be equal to ALL': function(all) { 'should only be equal to ALL': function(all) {
assertThat(all).isEqualTo([levels.toLevel("ALL")]); assertThat(all).isEqualTo([levels.toLevel("ALL")]);
assertThat(all).isNotEqualTo([levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(all).isNotEqualTo(
[
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
} }
}, },
'TRACE': { 'TRACE': {
topic: levels.TRACE, topic: levels.TRACE,
'should be less than DEBUG': function(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).isLessThanOrEqualTo(
[
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
assertThat(trace).isNotLessThanOrEqualTo([levels.ALL]); assertThat(trace).isNotLessThanOrEqualTo([levels.ALL]);
}, },
'should be greater than ALL': function(trace) { 'should be greater than ALL': function(trace) {
assertThat(trace).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]); assertThat(trace).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
assertThat(trace).isNotGreaterThanOrEqualTo([levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(trace).isNotGreaterThanOrEqualTo(
[
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
}, },
'should only be equal to TRACE': function(trace) { 'should only be equal to TRACE': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("TRACE")]); assertThat(trace).isEqualTo([levels.toLevel("TRACE")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(trace).isNotEqualTo(
[
levels.ALL,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
} }
}, },
'DEBUG': { 'DEBUG': {
topic: levels.DEBUG, topic: levels.DEBUG,
'should be less than INFO': function(debug) { 'should be less than INFO': function(debug) {
assertThat(debug).isLessThanOrEqualTo([levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(debug).isLessThanOrEqualTo(
[
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
assertThat(debug).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE]); assertThat(debug).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE]);
}, },
'should be greater than TRACE': function(debug) { 'should be greater than TRACE': function(debug) {
assertThat(debug).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]); assertThat(debug).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
assertThat(debug).isNotGreaterThanOrEqualTo([levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(debug).isNotGreaterThanOrEqualTo(
[
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
}, },
'should only be equal to DEBUG': function(trace) { 'should only be equal to DEBUG': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("DEBUG")]); assertThat(trace).isEqualTo([levels.toLevel("DEBUG")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(trace).isNotEqualTo(
[
levels.ALL,
levels.TRACE,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]
);
} }
}, },
'INFO': { 'INFO': {
topic: levels.INFO, topic: levels.INFO,
'should be less than WARN': function(info) { 'should be less than WARN': function(info) {
assertThat(info).isLessThanOrEqualTo([levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(info).isLessThanOrEqualTo([
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]);
assertThat(info).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]); assertThat(info).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
}, },
'should be greater than DEBUG': function(info) { 'should be greater than DEBUG': function(info) {
assertThat(info).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]); assertThat(info).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
assertThat(info).isNotGreaterThanOrEqualTo([levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(info).isNotGreaterThanOrEqualTo([
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]);
}, },
'should only be equal to INFO': function(trace) { 'should only be equal to INFO': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("INFO")]); assertThat(trace).isEqualTo([levels.toLevel("INFO")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.WARN, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(trace).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK,
levels.OFF
]);
} }
}, },
'WARN': { 'WARN': {
topic: levels.WARN, topic: levels.WARN,
'should be less than ERROR': function(warn) { 'should be less than ERROR': function(warn) {
assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]); assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.MARK, levels.OFF]);
assertThat(warn).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO]); assertThat(warn).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO
]);
}, },
'should be greater than INFO': function(warn) { 'should be greater than INFO': function(warn) {
assertThat(warn).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO]); assertThat(warn).isGreaterThanOrEqualTo([
assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]); levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO
]);
assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.MARK, levels.OFF]);
}, },
'should only be equal to WARN': function(trace) { 'should only be equal to WARN': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("WARN")]); assertThat(trace).isEqualTo([levels.toLevel("WARN")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.ERROR, levels.FATAL, levels.OFF]); assertThat(trace).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.ERROR,
levels.FATAL,
levels.OFF
]);
} }
}, },
'ERROR': { 'ERROR': {
topic: levels.ERROR, topic: levels.ERROR,
'should be less than FATAL': function(error) { 'should be less than FATAL': function(error) {
assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.OFF]); assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.MARK, levels.OFF]);
assertThat(error).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN]); assertThat(error).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN
]);
}, },
'should be greater than WARN': function(error) { 'should be greater than WARN': function(error) {
assertThat(error).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN]); assertThat(error).isGreaterThanOrEqualTo([
assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.OFF]); levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN
]);
assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.MARK, levels.OFF]);
}, },
'should only be equal to ERROR': function(trace) { 'should only be equal to ERROR': function(trace) {
assertThat(trace).isEqualTo([levels.toLevel("ERROR")]); assertThat(trace).isEqualTo([levels.toLevel("ERROR")]);
assertThat(trace).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.FATAL, levels.OFF]); assertThat(trace).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.FATAL,
levels.MARK,
levels.OFF
]);
} }
}, },
'FATAL': { 'FATAL': {
topic: levels.FATAL, topic: levels.FATAL,
'should be less than OFF': function(fatal) { 'should be less than OFF': function(fatal) {
assertThat(fatal).isLessThanOrEqualTo([levels.OFF]); assertThat(fatal).isLessThanOrEqualTo([levels.MARK, levels.OFF]);
assertThat(fatal).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR]); assertThat(fatal).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR
]);
}, },
'should be greater than ERROR': function(fatal) { 'should be greater than ERROR': function(fatal) {
assertThat(fatal).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR]); assertThat(fatal).isGreaterThanOrEqualTo([
assertThat(fatal).isNotGreaterThanOrEqualTo([levels.OFF]); levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR
]);
assertThat(fatal).isNotGreaterThanOrEqualTo([levels.MARK, levels.OFF]);
}, },
'should only be equal to FATAL': function(fatal) { 'should only be equal to FATAL': function(fatal) {
assertThat(fatal).isEqualTo([levels.toLevel("FATAL")]); assertThat(fatal).isEqualTo([levels.toLevel("FATAL")]);
assertThat(fatal).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.OFF]); assertThat(fatal).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.MARK,
levels.OFF
]);
}
},
'MARK': {
topic: levels.MARK,
'should be less than OFF': function(mark) {
assertThat(mark).isLessThanOrEqualTo([levels.OFF]);
assertThat(mark).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.FATAL,
levels.ERROR
]);
},
'should be greater than FATAL': function(mark) {
assertThat(mark).isGreaterThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL
]);
assertThat(mark).isNotGreaterThanOrEqualTo([levels.OFF]);
},
'should only be equal to MARK': function(mark) {
assertThat(mark).isEqualTo([levels.toLevel("MARK")]);
assertThat(mark).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.OFF
]);
} }
}, },
'OFF': { 'OFF': {
topic: levels.OFF, topic: levels.OFF,
'should not be less than anything': function(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]); assertThat(off).isNotLessThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK
]);
}, },
'should be greater than everything': function(off) { 'should be greater than everything': function(off) {
assertThat(off).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL]); assertThat(off).isGreaterThanOrEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK
]);
}, },
'should only be equal to OFF': function(off) { 'should only be equal to OFF': function(off) {
assertThat(off).isEqualTo([levels.toLevel("OFF")]); assertThat(off).isEqualTo([levels.toLevel("OFF")]);
assertThat(off).isNotEqualTo([levels.ALL, levels.TRACE, levels.DEBUG, levels.INFO, levels.WARN, levels.ERROR, levels.FATAL]); assertThat(off).isNotEqualTo([
levels.ALL,
levels.TRACE,
levels.DEBUG,
levels.INFO,
levels.WARN,
levels.ERROR,
levels.FATAL,
levels.MARK
]);
} }
} }
}, },
@ -165,14 +411,20 @@ vows.describe('levels').addBatch({
topic: levels.INFO, topic: levels.INFO,
'should handle string arguments': function(info) { 'should handle string arguments': function(info) {
assertThat(info).isGreaterThanOrEqualTo(["all", "trace", "debug"]); assertThat(info).isGreaterThanOrEqualTo(["all", "trace", "debug"]);
assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']); assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'MARK', 'off']);
} }
}, },
'isLessThanOrEqualTo': { 'isLessThanOrEqualTo': {
topic: levels.INFO, topic: levels.INFO,
'should handle string arguments': function(info) { 'should handle string arguments': function(info) {
assertThat(info).isNotLessThanOrEqualTo(["all", "trace", "debug"]); assertThat(info).isNotLessThanOrEqualTo(["all", "trace", "debug"]);
assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']); assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'MARK', 'off']);
}
},
'isEqualTo': {
topic: levels.INFO,
'should handle string arguments': function(info) {
assertThat(info).isEqualTo(["info", "INFO", "iNfO"]);
} }
}, },
'toLevel': { 'toLevel': {

View File

@ -1,5 +1,7 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, assert = require('assert') , assert = require('assert')
, path = require('path')
, sandbox = require('sandboxed-module'); , sandbox = require('sandboxed-module');
vows.describe('log4js-abspath').addBatch({ vows.describe('log4js-abspath').addBatch({
@ -10,8 +12,7 @@ vows.describe('log4js-abspath').addBatch({
'../lib/log4js', '../lib/log4js',
{ requires: { requires:
{ './appenders/fake': { './appenders/fake':
{ { name: "fake",
name: "fake",
appender: function() {}, appender: function() {},
configure: function(configuration, options) { configure: function(configuration, options) {
appenderOptions = options; appenderOptions = options;
@ -47,22 +48,30 @@ vows.describe('log4js-abspath').addBatch({
'../lib/appenders/file', '../lib/appenders/file',
{ requires: { requires:
{ '../streams': { '../streams':
{ { RollingFileStream:
RollingFileStream: function(file) { function(file) {
fileOpened = file; fileOpened = file;
}, return {
BufferedWriteStream: function(other) { on: function() {},
return { on: function() { }, end: function() {} } end: function() {}
};
} }
} }
} }
} }
); );
fileAppender.configure({ filename: "whatever.log", maxLogSize: 10 }, { cwd: '/absolute/path/to' }); fileAppender.configure(
{
filename: "whatever.log",
maxLogSize: 10
},
{ cwd: '/absolute/path/to' }
);
return fileOpened; return fileOpened;
}, },
'should prepend options.cwd to config.filename': function(fileOpened) { 'should prepend options.cwd to config.filename': function(fileOpened) {
assert.equal(fileOpened, "/absolute/path/to/whatever.log"); var expected = path.sep + path.join("absolute", "path", "to", "whatever.log");
assert.equal(fileOpened, expected);
} }
}, },
}).export(module); }).export(module);

View File

@ -1,6 +1,9 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, fs = require('fs') , fs = require('fs')
, assert = require('assert'); , assert = require('assert')
, os = require('os')
, EOL = require('os').EOL || '\n';
function remove(filename) { function remove(filename) {
try { try {
@ -15,7 +18,16 @@ vows.describe('log4js logLevelFilter').addBatch({
topic: function() { topic: function() {
var log4js = require('../lib/log4js'), logEvents = [], logger; var log4js = require('../lib/log4js'), logEvents = [], logger;
log4js.clearAppenders(); log4js.clearAppenders();
log4js.addAppender(require('../lib/appenders/logLevelFilter').appender('ERROR', function(evt) { logEvents.push(evt); }), "logLevelTest"); log4js.addAppender(
require('../lib/appenders/logLevelFilter')
.appender(
'ERROR',
undefined,
function(evt) { logEvents.push(evt); }
),
"logLevelTest"
);
logger = log4js.getLogger("logLevelTest"); logger = log4js.getLogger("logLevelTest");
logger.debug('this should not trigger an event'); logger.debug('this should not trigger an event');
logger.warn('neither should this'); logger.warn('neither should this');
@ -37,23 +49,26 @@ vows.describe('log4js logLevelFilter').addBatch({
remove(__dirname + '/logLevelFilter.log'); remove(__dirname + '/logLevelFilter.log');
remove(__dirname + '/logLevelFilter-warnings.log'); remove(__dirname + '/logLevelFilter-warnings.log');
remove(__dirname + '/logLevelFilter-debugs.log');
log4js.configure('test/with-logLevelFilter.json'); log4js.configure('test/with-logLevelFilter.json');
logger = log4js.getLogger("tests"); logger = log4js.getLogger("tests");
logger.info('main'); logger.debug('debug');
logger.error('both'); logger.info('info');
logger.warn('both'); logger.error('error');
logger.debug('main'); logger.warn('warn');
logger.debug('debug');
logger.trace('trace');
//wait for the file system to catch up //wait for the file system to catch up
setTimeout(this.callback, 100); setTimeout(this.callback, 500);
}, },
'tmp-tests.log': { 'tmp-tests.log': {
topic: function() { topic: function() {
fs.readFile(__dirname + '/logLevelFilter.log', 'utf8', this.callback); fs.readFile(__dirname + '/logLevelFilter.log', 'utf8', this.callback);
}, },
'should contain all log messages': function (contents) { 'should contain all log messages': function (contents) {
var messages = contents.trim().split('\n'); var messages = contents.trim().split(EOL);
assert.deepEqual(messages, ['main','both','both','main']); assert.deepEqual(messages, ['debug','info','error','warn','debug','trace']);
} }
}, },
'tmp-tests-warnings.log': { 'tmp-tests-warnings.log': {
@ -61,8 +76,17 @@ vows.describe('log4js logLevelFilter').addBatch({
fs.readFile(__dirname + '/logLevelFilter-warnings.log','utf8',this.callback); fs.readFile(__dirname + '/logLevelFilter-warnings.log','utf8',this.callback);
}, },
'should contain only error and warning log messages': function(contents) { 'should contain only error and warning log messages': function(contents) {
var messages = contents.trim().split('\n'); var messages = contents.trim().split(EOL);
assert.deepEqual(messages, ['both','both']); assert.deepEqual(messages, ['error','warn']);
}
},
'tmp-tests-debugs.log': {
topic: function() {
fs.readFile(__dirname + '/logLevelFilter-debugs.log','utf8',this.callback);
},
'should contain only trace and debug log messages': function(contents) {
var messages = contents.trim().split(EOL);
assert.deepEqual(messages, ['debug','debug','trace']);
} }
} }
} }

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

@ -0,0 +1,81 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, levels = require('../lib/levels')
, loggerModule = require('../lib/logger')
, Logger = loggerModule.Logger;
vows.describe('../lib/logger').addBatch({
'constructor with no parameters': {
topic: new Logger(),
'should use default category': function(logger) {
assert.equal(logger.category, Logger.DEFAULT_CATEGORY);
},
'should use TRACE log level': function(logger) {
assert.equal(logger.level, levels.TRACE);
}
},
'constructor with category': {
topic: new Logger('cheese'),
'should use category': function(logger) {
assert.equal(logger.category, 'cheese');
},
'should use TRACE log level': function(logger) {
assert.equal(logger.level, levels.TRACE);
}
},
'constructor with category and level': {
topic: new Logger('cheese', 'debug'),
'should use category': function(logger) {
assert.equal(logger.category, 'cheese');
},
'should use level': function(logger) {
assert.equal(logger.level, levels.DEBUG);
}
},
'isLevelEnabled': {
topic: new Logger('cheese', 'info'),
'should provide a level enabled function for all levels': function(logger) {
assert.isFunction(logger.isTraceEnabled);
assert.isFunction(logger.isDebugEnabled);
assert.isFunction(logger.isInfoEnabled);
assert.isFunction(logger.isWarnEnabled);
assert.isFunction(logger.isErrorEnabled);
assert.isFunction(logger.isFatalEnabled);
},
'should return the right values': function(logger) {
assert.isFalse(logger.isTraceEnabled());
assert.isFalse(logger.isDebugEnabled());
assert.isTrue(logger.isInfoEnabled());
assert.isTrue(logger.isWarnEnabled());
assert.isTrue(logger.isErrorEnabled());
assert.isTrue(logger.isFatalEnabled());
}
},
'should emit log events': {
topic: function() {
var events = [],
logger = new Logger();
logger.addListener('log', function (logEvent) { events.push(logEvent); });
logger.debug('Event 1');
loggerModule.disableAllLogWrites();
logger.debug('Event 2');
loggerModule.enableAllLogWrites();
logger.debug('Event 3');
return events;
},
'when log writes are enabled': function(events) {
assert.equal(events[0].data[0], 'Event 1');
},
'but not when log writes are disabled': function(events) {
assert.equal(events.length, 2);
assert.equal(events[1].data[0], 'Event 3');
}
}
}).exportTo(module);

View File

@ -1,3 +1,4 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, assert = require('assert') , assert = require('assert')
, sandbox = require('sandboxed-module'); , sandbox = require('sandboxed-module');
@ -14,8 +15,8 @@ function setupConsoleTest() {
}); });
log4js = sandbox.require( log4js = sandbox.require(
'../lib/log4js' '../lib/log4js',
, { {
globals: { globals: {
console: fakeConsole console: fakeConsole
} }
@ -31,6 +32,72 @@ function setupConsoleTest() {
} }
vows.describe('log4js').addBatch({ vows.describe('log4js').addBatch({
'getBufferedLogger': {
topic: function () {
var log4js = require('../lib/log4js');
log4js.clearAppenders();
var logger = log4js.getBufferedLogger('tests');
return logger;
},
'should take a category and return a logger': function (logger) {
assert.equal(logger.target.category, 'tests');
assert.isFunction(logger.flush);
assert.isFunction(logger.trace);
assert.isFunction(logger.debug);
assert.isFunction(logger.info);
assert.isFunction(logger.warn);
assert.isFunction(logger.error);
assert.isFunction(logger.fatal);
},
'cache events': {
topic: function () {
var log4js = require('../lib/log4js');
log4js.clearAppenders();
var logger = log4js.getBufferedLogger('tests1');
var events = [];
logger.target.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 not emit log events if .flush() is not called.': function (events) {
assert.equal(events.length, 0);
}
},
'log events after flush() is called': {
topic: function () {
var log4js = require('../lib/log4js');
log4js.clearAppenders();
var logger = log4js.getBufferedLogger('tests2');
logger.target.setLevel("TRACE");
var events = [];
logger.target.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" });
logger.flush();
return events;
},
'should emit log events when .flush() is called.': function (events) {
assert.equal(events.length, 6);
}
}
},
'getLogger': { 'getLogger': {
topic: function() { topic: function() {
var log4js = require('../lib/log4js'); var log4js = require('../lib/log4js');
@ -78,9 +145,61 @@ vows.describe('log4js').addBatch({
assert.instanceOf(events[2].data[1], Error); assert.instanceOf(events[2].data[1], Error);
assert.equal(events[2].data[1].message, 'Pants are on fire!'); assert.equal(events[2].data[1].message, 'Pants are on fire!');
} }
}
}, },
'when shutdown is called': {
topic: function() {
var events = {
appenderShutdownCalled: false,
shutdownCallbackCalled: false
},
log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'./appenders/file':
{
name: "file",
appender: function() {},
configure: function(configuration) {
return function() {};
},
shutdown: function(cb) {
events.appenderShutdownCalled = true;
cb();
}
}
}
}
),
shutdownCallback = function() {
events.shutdownCallbackCalled = true;
},
config = { appenders:
[ { "type" : "file",
"filename" : "cheesy-wotsits.log",
"maxLogSize" : 1024,
"backups" : 3
}
]
};
log4js.configure(config);
log4js.shutdown(shutdownCallback);
// Re-enable log writing so other tests that use logger are not
// affected.
require('../lib/logger').enableAllLogWrites();
return events;
},
'should invoke appender shutdowns': function(events) {
assert.ok(events.appenderShutdownCalled);
},
'should call callback': function(events) {
assert.ok(events.shutdownCallbackCalled);
}
}, },
'invalid configuration': { 'invalid configuration': {
@ -93,26 +212,25 @@ vows.describe('log4js').addBatch({
'configuration when passed as object': { 'configuration when passed as object': {
topic: function() { topic: function() {
var appenderConfig var appenderConfig,
, log4js = sandbox.require( log4js = sandbox.require(
'../lib/log4js' '../lib/log4js',
, { requires:
{ './appenders/file':
{ {
name: "file" requires: {
, appender: function() {} './appenders/file':
, configure: function(configuration) { {
name: "file",
appender: function() {},
configure: function(configuration) {
appenderConfig = configuration; appenderConfig = configuration;
return function() {}; return function() {};
} }
} }
} }
} }
) ),
, config = { config = { appenders:
"appenders": [ [ { "type" : "file",
{
"type" : "file",
"filename" : "cheesy-wotsits.log", "filename" : "cheesy-wotsits.log",
"maxLogSize" : 1024, "maxLogSize" : 1024,
"backups" : 3 "backups" : 3
@ -127,19 +245,56 @@ vows.describe('log4js').addBatch({
} }
}, },
'configuration that causes an error': {
topic: function() {
var log4js = sandbox.require(
'../lib/log4js',
{
requires: {
'./appenders/file':
{
name: "file",
appender: function() {},
configure: function(configuration) {
throw new Error("oh noes");
}
}
}
}
),
config = { appenders:
[ { "type" : "file",
"filename" : "cheesy-wotsits.log",
"maxLogSize" : 1024,
"backups" : 3
}
]
};
try {
log4js.configure(config);
} catch (e) {
return e;
}
},
'should wrap error in a meaningful message': function(e) {
assert.ok(e.message.indexOf('log4js configuration problem for') > -1);
}
},
'configuration when passed as filename': { 'configuration when passed as filename': {
topic: function() { topic: function() {
var appenderConfig var appenderConfig,
, configFilename configFilename,
, log4js = sandbox.require( log4js = sandbox.require(
'../lib/log4js' '../lib/log4js',
, { requires: { requires:
{ 'fs': { 'fs':
{ { statSync:
statSync: function() { function() {
return { mtime: Date.now() }; return { mtime: Date.now() };
}, },
readFileSync: function(filename) { readFileSync:
function(filename) {
configFilename = filename; configFilename = filename;
return JSON.stringify({ return JSON.stringify({
appenders: [ appenders: [
@ -149,15 +304,15 @@ vows.describe('log4js').addBatch({
] ]
}); });
}, },
readdirSync: function() { readdirSync:
function() {
return ['file']; return ['file'];
} }
} },
, './appenders/file': './appenders/file':
{ { name: "file",
name: "file" appender: function() {},
, appender: function() {} configure: function(configuration) {
, configure: function(configuration) {
appenderConfig = configuration; appenderConfig = configuration;
return function() {}; return function() {};
} }
@ -178,22 +333,22 @@ vows.describe('log4js').addBatch({
'with no appenders defined' : { 'with no appenders defined' : {
topic: function() { topic: function() {
var logger var logger,
, that = this that = this,
, fakeConsoleAppender = { fakeConsoleAppender = {
name: "console" name: "console",
, appender: function() { appender: function() {
return function(evt) { return function(evt) {
that.callback(null, evt); that.callback(null, evt);
} };
} },
, configure: function() { configure: function() {
return fakeConsoleAppender.appender(); return fakeConsoleAppender.appender();
} }
} },
, log4js = sandbox.require( log4js = sandbox.require(
'../lib/log4js' '../lib/log4js',
, { {
requires: { requires: {
'./appenders/console': fakeConsoleAppender './appenders/console': fakeConsoleAppender
} }
@ -215,15 +370,22 @@ vows.describe('log4js').addBatch({
}, },
'without a category': { 'without a category': {
'should register the function as a listener for all loggers': function (log4js) { 'should register the function as a listener for all loggers': function (log4js) {
var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests"); var appenderEvent,
appender = function(evt) { appenderEvent = evt; },
logger = log4js.getLogger("tests");
log4js.addAppender(appender); log4js.addAppender(appender);
logger.debug("This is a test"); logger.debug("This is a test");
assert.equal(appenderEvent.data[0], "This is a test"); assert.equal(appenderEvent.data[0], "This is a test");
assert.equal(appenderEvent.categoryName, "tests"); assert.equal(appenderEvent.categoryName, "tests");
assert.equal(appenderEvent.level.toString(), "DEBUG"); assert.equal(appenderEvent.level.toString(), "DEBUG");
}, },
'should also register as an appender for loggers if an appender for that category is defined': function (log4js) { 'if an appender for a category is defined': {
var otherEvent, appenderEvent, cheeseLogger; 'should register for that category': function (log4js) {
var otherEvent,
appenderEvent,
cheeseLogger;
log4js.addAppender(function (evt) { appenderEvent = evt; }); log4js.addAppender(function (evt) { appenderEvent = evt; });
log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese'); log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese');
@ -239,11 +401,15 @@ vows.describe('log4js').addBatch({
assert.isUndefined(otherEvent); assert.isUndefined(otherEvent);
assert.equal(appenderEvent.data[0], "this should not be propagated to otherEvent"); assert.equal(appenderEvent.data[0], "this should not be propagated to otherEvent");
} }
}
}, },
'with a category': { 'with a category': {
'should only register the function as a listener for that category': function(log4js) { 'should only register the function as a listener for that category': function(log4js) {
var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger("tests"); var appenderEvent,
appender = function(evt) { appenderEvent = evt; },
logger = log4js.getLogger("tests");
log4js.addAppender(appender, 'tests'); log4js.addAppender(appender, 'tests');
logger.debug('this is a category test'); logger.debug('this is a category test');
assert.equal(appenderEvent.data[0], 'this is a category test'); assert.equal(appenderEvent.data[0], 'this is a category test');
@ -256,7 +422,10 @@ vows.describe('log4js').addBatch({
'with multiple categories': { 'with multiple categories': {
'should register the function as a listener for all the categories': function(log4js) { 'should register the function as a listener for all the categories': function(log4js) {
var appenderEvent, appender = function(evt) { appenderEvent = evt; }, logger = log4js.getLogger('tests'); var appenderEvent,
appender = function(evt) { appenderEvent = evt; },
logger = log4js.getLogger('tests');
log4js.addAppender(appender, 'tests', 'biscuits'); log4js.addAppender(appender, 'tests', 'biscuits');
logger.debug('this is a test'); logger.debug('this is a test');
@ -273,7 +442,9 @@ vows.describe('log4js').addBatch({
assert.isUndefined(appenderEvent); assert.isUndefined(appenderEvent);
}, },
'should register the function when the list of categories is an array': function(log4js) { 'should register the function when the list of categories is an array': function(log4js) {
var appenderEvent, appender = function(evt) { appenderEvent = evt; }; var appenderEvent,
appender = function(evt) { appenderEvent = evt; };
log4js.addAppender(appender, ['tests', 'pants']); log4js.addAppender(appender, ['tests', 'pants']);
log4js.getLogger('tests').debug('this is a test'); log4js.getLogger('tests').debug('this is a test');
@ -296,13 +467,13 @@ vows.describe('log4js').addBatch({
topic: function() { topic: function() {
var appenderEvents = [], var appenderEvents = [],
fakeConsole = { fakeConsole = {
'name': 'console' 'name': 'console',
, 'appender': function () { 'appender': function () {
return function(evt) { return function(evt) {
appenderEvents.push(evt); appenderEvents.push(evt);
} };
} },
, 'configure': function (config) { 'configure': function (config) {
return fakeConsole.appender(); return fakeConsole.appender();
} }
}, },
@ -388,8 +559,11 @@ vows.describe('log4js').addBatch({
assert.instanceOf(err, Error); assert.instanceOf(err, Error);
assert.equal(err.message, "this should not be called."); assert.equal(err.message, "this should not be called.");
} }
}
}, },
'configuration': { 'console configuration': {
topic: setupConsoleTest,
'when disabled': {
topic: function(test) { topic: function(test) {
test.log4js.replaceConsole(); test.log4js.replaceConsole();
test.log4js.configure({ replaceConsole: false }); test.log4js.configure({ replaceConsole: false });
@ -403,6 +577,25 @@ vows.describe('log4js').addBatch({
assert.instanceOf(err, Error); assert.instanceOf(err, Error);
assert.equal(err.message, 'this should not be called.'); assert.equal(err.message, 'this should not be called.');
} }
},
'when enabled': {
topic: function(test) {
test.log4js.restoreConsole();
test.log4js.configure({ replaceConsole: true });
//log4js.configure clears all appenders
test.log4js.addAppender(function(evt) {
test.logEvents.push(evt);
});
test.fakeConsole.debug("Some debug");
return test.logEvents;
},
'should allow for turning on console replacement': function (logEvents) {
assert.equal(logEvents.length, 1);
assert.equal(logEvents[0].level.toString(), "DEBUG");
assert.equal(logEvents[0].data[0], "Some debug");
}
} }
}, },
'configuration persistence' : { 'configuration persistence' : {
@ -423,156 +616,15 @@ vows.describe('log4js').addBatch({
assert.equal(logEvent.data[0], "This should go to the appender defined in firstLog4js"); assert.equal(logEvent.data[0], "This should go to the appender defined in firstLog4js");
} }
}, },
'configuration reload with configuration changing' : {
'getDefaultLogger': {
topic: function() { topic: function() {
var pathsChecked = [], return require('../lib/log4js').getDefaultLogger();
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) { 'should return a logger': function(logger) {
assert.equal(file, modulePath); assert.ok(logger.info);
assert.equal(encoding, 'utf8'); assert.ok(logger.debug);
return JSON.stringify(fakeFS.config); assert.ok(logger.error);
},
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); }).export(module);

View File

@ -0,0 +1,82 @@
"use strict";
var vows = require('vows')
, assert = require('assert')
, log4js = require('../lib/log4js')
, sandbox = require('sandboxed-module')
;
function setupLogging(category, options) {
var msgs = [];
var fakeLoggly = {
createClient: function (options) {
return {
config: options,
log: function (msg, tags) {
msgs.push({
msg: msg,
tags: tags
});
}
};
}
};
var fakeLayouts = {
layout: function(type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
};
var fakeConsole = {
errors: [],
error: function(msg, value) {
this.errors.push({ msg: msg, value: value });
}
};
var logglyModule = sandbox.require('../lib/appenders/loggly', {
requires: {
'loggly': fakeLoggly,
'../layouts': fakeLayouts
},
globals: {
console: fakeConsole
}
});
log4js.addAppender(logglyModule.configure(options), category);
return {
logger: log4js.getLogger(category),
loggly: fakeLoggly,
layouts: fakeLayouts,
console: fakeConsole,
results: msgs
};
}
log4js.clearAppenders();
vows.describe('log4js logglyAppender').addBatch({
'minimal config': {
topic: function() {
var setup = setupLogging('loggly', {
token: 'your-really-long-input-token',
subdomain: 'your-subdomain',
tags: ['loggly-tag1', 'loggly-tag2', 'loggly-tagn']
});
setup.logger.log('trace', 'Log event #1');
return setup;
},
'there should be one message only': function (topic) {
//console.log('topic', topic);
assert.equal(topic.results.length, 1);
}
}
}).export(module);

106
test/logstashUDP-test.js Normal file
View File

@ -0,0 +1,106 @@
"use strict";
var sys = require("sys");
var vows = require('vows')
, assert = require('assert')
, log4js = require('../lib/log4js')
, sandbox = require('sandboxed-module')
;
function setupLogging(category, options) {
var udpSent = {};
var fakeDgram = {
createSocket: function (type) {
return {
send: function(buffer, offset, length, port, host, callback) {
udpSent.date = new Date();
udpSent.host = host;
udpSent.port = port;
udpSent.length = length;
udpSent.offset = 0;
udpSent.buffer = buffer;
callback(undefined, length);
}
};
}
};
var logstashModule = sandbox.require('../lib/appenders/logstashUDP', {
requires: {
'dgram': fakeDgram
}
});
log4js.clearAppenders();
log4js.addAppender(logstashModule.configure(options), category);
return {
logger: log4js.getLogger(category),
results: udpSent
};
}
vows.describe('logstashUDP appender').addBatch({
'when logging with logstash via UDP': {
topic: function() {
var setup = setupLogging('logstashUDP', {
"host": "127.0.0.1",
"port": 10001,
"type": "logstashUDP",
"logType": "myAppType",
"category": "myLogger",
"fields": {
"field1": "value1",
"field2": "value2"
},
"layout": {
"type": "pattern",
"pattern": "%m"
}
});
setup.logger.log('trace', 'Log event #1');
return setup;
},
'an UDP packet should be sent': function (topic) {
assert.equal(topic.results.host, "127.0.0.1");
assert.equal(topic.results.port, 10001);
assert.equal(topic.results.offset, 0);
var json = JSON.parse(topic.results.buffer.toString());
assert.equal(json.type, 'myAppType');
var fields = {
field1: 'value1',
field2: 'value2',
level: 'TRACE'
};
assert.equal(JSON.stringify(json.fields), JSON.stringify(fields));
assert.equal(json.message, 'Log event #1');
// Assert timestamp, up to hours resolution.
var date = new Date(json['@timestamp']);
assert.equal(
date.toISOString().substring(0, 14),
topic.results.date.toISOString().substring(0, 14)
);
}
},
'when missing some options': {
topic: function() {
var setup = setupLogging('myLogger', {
"host": "127.0.0.1",
"port": 10001,
"type": "logstashUDP",
"category": "myLogger",
"layout": {
"type": "pattern",
"pattern": "%m"
}
});
setup.logger.log('trace', 'Log event #1');
return setup;
},
'it sets some defaults': function (topic) {
var json = JSON.parse(topic.results.buffer.toString());
assert.equal(json.type, 'myLogger');
assert.equal(JSON.stringify(json.fields), JSON.stringify({'level': 'TRACE'}));
}
}
}).export(module);

View File

@ -1,6 +1,8 @@
var vows = require('vows'), "use strict";
sandbox = require('sandboxed-module'), var vows = require('vows')
assert = require('assert'); , sandbox = require('sandboxed-module')
, assert = require('assert')
;
function makeFakeNet() { function makeFakeNet() {
return { return {
@ -67,12 +69,13 @@ vows.describe('Multiprocess Appender').addBatch({
//don't need a proper log event for the worker tests //don't need a proper log event for the worker tests
appender('before connect'); appender('before connect');
fakeNet.cbs['connect'](); fakeNet.cbs.connect();
appender('after connect'); appender('after connect');
fakeNet.cbs['close'](true); fakeNet.cbs.close(true);
appender('after error, before connect'); appender('after error, before connect');
fakeNet.cbs['connect'](); fakeNet.cbs.connect();
appender('after error, after connect'); appender('after error, after connect');
appender(new Error('Error test'));
return fakeNet; return fakeNet;
}, },
@ -96,6 +99,13 @@ vows.describe('Multiprocess Appender').addBatch({
assert.equal(net.data[6], JSON.stringify('after error, after connect')); assert.equal(net.data[6], JSON.stringify('after error, after connect'));
assert.equal(net.data[7], '__LOG4JS__'); assert.equal(net.data[7], '__LOG4JS__');
assert.equal(net.createConnectionCalled, 2); assert.equal(net.createConnectionCalled, 2);
},
'should serialize an Error correctly': function(net) {
assert(JSON.parse(net.data[8]).stack, "Expected:\n\n" + net.data[8] + "\n\n to have a 'stack' property");
var actual = JSON.parse(net.data[8]).stack;
var expectedRegex = /^Error: Error test/;
assert(actual.match(expectedRegex), "Expected: \n\n " + actual + "\n\n to match " + expectedRegex);
} }
}, },
'worker with timeout': { 'worker with timeout': {
@ -112,13 +122,13 @@ vows.describe('Multiprocess Appender').addBatch({
//don't need a proper log event for the worker tests //don't need a proper log event for the worker tests
appender('before connect'); appender('before connect');
fakeNet.cbs['connect'](); fakeNet.cbs.connect();
appender('after connect'); appender('after connect');
fakeNet.cbs['timeout'](); fakeNet.cbs.timeout();
appender('after timeout, before close'); appender('after timeout, before close');
fakeNet.cbs['close'](); fakeNet.cbs.close();
appender('after close, before connect'); appender('after close, before connect');
fakeNet.cbs['connect'](); fakeNet.cbs.connect();
appender('after close, after connect'); appender('after close, after connect');
return fakeNet; return fakeNet;
@ -181,15 +191,27 @@ vows.describe('Multiprocess Appender').addBatch({
}, },
'when a client connects': { 'when a client connects': {
topic: function(net) { topic: function(net) {
var logString = JSON.stringify({ level: { level: 10000, levelStr: 'DEBUG' }, data: ['some debug']}) + '__LOG4JS__'; var logString = JSON.stringify(
{ level: { level: 10000, levelStr: 'DEBUG' }
, data: ['some debug']}
) + '__LOG4JS__';
net.cbs['connect'](); net.cbs.data(
net.cbs['data'](JSON.stringify({ level: { level: 40000, levelStr: 'ERROR' }, data: ['an error message'] }) + '__LOG4JS__'); JSON.stringify(
net.cbs['data'](logString.substring(0, 10)); { level: { level: 40000, levelStr: 'ERROR' }
net.cbs['data'](logString.substring(10)); , data: ['an error message'] }
net.cbs['data'](logString + logString + logString); ) + '__LOG4JS__'
net.cbs['end'](JSON.stringify({ level: { level: 50000, levelStr: 'FATAL' }, data: ["that's all folks"] }) + '__LOG4JS__'); );
net.cbs['data']('bad message__LOG4JS__'); net.cbs.data(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; return net;
}, },
'should parse log messages into log events and send to appender': function(net) { 'should parse log messages into log events and send to appender': function(net) {
@ -239,4 +261,51 @@ vows.describe('Multiprocess Appender').addBatch({
assert.equal(net.host, 'localhost'); assert.equal(net.host, 'localhost');
} }
} }
}).addBatch({
'configure': {
topic: function() {
var results = {}
, fakeNet = makeFakeNet()
, appender = sandbox.require(
'../lib/appenders/multiprocess',
{
requires: {
'net': fakeNet,
'../log4js': {
loadAppender: function(app) {
results.appenderLoaded = app;
},
appenderMakers: {
'madeupappender': function(config, options) {
results.config = config;
results.options = options;
}
}
}
}
}
).configure(
{
mode: 'master',
appender: {
type: 'madeupappender',
cheese: 'gouda'
}
},
{ crackers: 'jacobs' }
);
return results;
},
'should load underlying appender for master': function(results) {
assert.equal(results.appenderLoaded, 'madeupappender');
},
'should pass config to underlying appender': function(results) {
assert.equal(results.config.cheese, 'gouda');
},
'should pass options to underlying appender': function(results) {
assert.equal(results.options.crackers, 'jacobs');
}
}
}).exportTo(module); }).exportTo(module);

View File

@ -1,5 +1,8 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, assert = require('assert') , assert = require('assert')
, util = require('util')
, EE = require('events').EventEmitter
, levels = require('../lib/levels'); , levels = require('../lib/levels');
function MockLogger() { function MockLogger() {
@ -30,13 +33,14 @@ function MockRequest(remoteAddr, method, originalUrl) {
} }
function MockResponse(statusCode) { function MockResponse(statusCode) {
var r = this;
this.statusCode = statusCode; this.statusCode = statusCode;
this.end = function(chunk, encoding) { this.end = function(chunk, encoding) {
r.emit('finish');
}; };
} }
util.inherits(MockResponse, EE);
vows.describe('log4js connect logger').addBatch({ vows.describe('log4js connect logger').addBatch({
'getConnectLoggerModule': { 'getConnectLoggerModule': {
@ -60,11 +64,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d){ topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages);
, 'check message': function(messages){ },10);
},
'check message': function(messages){
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 1); assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level)); assert.ok(levels.INFO.isEqualTo(messages[0].level));
@ -80,11 +87,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d) { topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages);
, 'check message': function(messages) { },10);
},
'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 0); assert.equal(messages.length, 0);
} }
@ -102,11 +112,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d){ topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages){ }, 10);
},
'check message': function(messages){
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 1); assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level)); assert.ok(levels.INFO.isEqualTo(messages[0].level));
@ -122,11 +135,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d) { topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages) { }, 10);
},
'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 0); assert.equal(messages.length, 0);
} }
@ -135,11 +151,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d) { topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages) { }, 10);
},
'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 0); assert.equal(messages.length, 0);
} }
@ -156,11 +175,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d){ topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages){ }, 10);
},
'check message': function(messages){
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 1); assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level)); assert.ok(levels.INFO.isEqualTo(messages[0].level));
@ -176,11 +198,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d) { topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages) { }, 10);
},
'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 0); assert.equal(messages.length, 0);
} }
@ -190,11 +215,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d) { topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages) { }, 10);
},
'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 0); assert.equal(messages.length, 0);
} }
@ -211,11 +239,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d){ topic: function(d){
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.png'); // not gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages){ }, 10);
},
'check message': function(messages){
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 1); assert.equal(messages.length, 1);
assert.ok(levels.INFO.isEqualTo(messages[0].level)); assert.ok(levels.INFO.isEqualTo(messages[0].level));
@ -231,11 +262,14 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d) { topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.gif'); // gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages) { }, 10);
},
'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 0); assert.equal(messages.length, 0);
} }
@ -245,15 +279,18 @@ vows.describe('log4js connect logger').addBatch({
topic: function(d) { topic: function(d) {
var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif var req = new MockRequest('my.remote.addr', 'GET', 'http://url/hoge.jpeg'); // gif
var res = new MockResponse(200); var res = new MockResponse(200);
var cb = this.callback;
d.cl(req, res, function() { }); d.cl(req, res, function() { });
res.end('chunk', 'encoding'); res.end('chunk', 'encoding');
return d.ml.messages; setTimeout(function() {
} cb(null, d.ml.messages)
, 'check message': function(messages) { }, 10);
},
'check message': function(messages) {
assert.isArray(messages); assert.isArray(messages);
assert.equal(messages.length, 0); assert.equal(messages.length, 0);
} }
}, }
} }
} }

View File

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

View File

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

View File

@ -1,7 +1,9 @@
var vows = require('vows'), "use strict";
assert = require('assert'), var vows = require('vows')
log4js = require('../lib/log4js'), , assert = require('assert')
sandbox = require('sandboxed-module'); , log4js = require('../lib/log4js')
, sandbox = require('sandboxed-module')
;
function setupLogging(category, options) { function setupLogging(category, options) {
var msgs = []; var msgs = [];
@ -13,14 +15,36 @@ function setupLogging(category, options) {
sendMail: function (msg, callback) { sendMail: function (msg, callback) {
msgs.push(msg); msgs.push(msg);
callback(null, true); callback(null, true);
},
close: function() {}
};
} }
}; };
var fakeLayouts = {
layout: function(type, config) {
this.type = type;
this.config = config;
return log4js.layouts.messagePassThroughLayout;
},
basicLayout: log4js.layouts.basicLayout,
messagePassThroughLayout: log4js.layouts.messagePassThroughLayout
};
var fakeConsole = {
errors: [],
error: function(msg, value) {
this.errors.push({ msg: msg, value: value });
} }
}; };
var smtpModule = sandbox.require('../lib/appenders/smtp', { var smtpModule = sandbox.require('../lib/appenders/smtp', {
requires: { requires: {
'nodemailer': fakeMailer 'nodemailer': fakeMailer,
'../layouts': fakeLayouts
},
globals: {
console: fakeConsole
} }
}); });
@ -29,6 +53,8 @@ function setupLogging(category, options) {
return { return {
logger: log4js.getLogger(category), logger: log4js.getLogger(category),
mailer: fakeMailer, mailer: fakeMailer,
layouts: fakeLayouts,
console: fakeConsole,
results: msgs results: msgs
}; };
} }
@ -48,7 +74,6 @@ vows.describe('log4js smtpAppender').addBatch({
topic: function() { topic: function() {
var setup = setupLogging('minimal config', { var setup = setupLogging('minimal config', {
recipients: 'recipient@domain.com', recipients: 'recipient@domain.com',
transport: "SMTP",
SMTP: { SMTP: {
port: 25, port: 25,
auth: { auth: {
@ -72,7 +97,6 @@ vows.describe('log4js smtpAppender').addBatch({
recipients: 'recipient@domain.com', recipients: 'recipient@domain.com',
sender: 'sender@domain.com', sender: 'sender@domain.com',
subject: 'This is subject', subject: 'This is subject',
transport: "SMTP",
SMTP: { SMTP: {
port: 25, port: 25,
auth: { auth: {
@ -90,12 +114,24 @@ vows.describe('log4js smtpAppender').addBatch({
checkMessages(result, 'sender@domain.com', 'This is subject'); checkMessages(result, 'sender@domain.com', 'This is subject');
} }
}, },
'config with layout': {
topic: function() {
var setup = setupLogging('config with layout', {
layout: {
type: "tester"
}
});
return setup;
},
'should configure layout': function(result) {
assert.equal(result.layouts.type, 'tester');
}
},
'separate email for each event': { 'separate email for each event': {
topic: function() { topic: function() {
var self = this; var self = this;
var setup = setupLogging('separate email for each event', { var setup = setupLogging('separate email for each event', {
recipients: 'recipient@domain.com', recipients: 'recipient@domain.com',
transport: "SMTP",
SMTP: { SMTP: {
port: 25, port: 25,
auth: { auth: {
@ -111,10 +147,10 @@ vows.describe('log4js smtpAppender').addBatch({
}, 500); }, 500);
setTimeout(function () { setTimeout(function () {
setup.logger.info('Log event #3'); setup.logger.info('Log event #3');
}, 1050); }, 1100);
setTimeout(function () { setTimeout(function () {
self.callback(null, setup); self.callback(null, setup);
}, 2100); }, 3000);
}, },
'there should be three messages': function (result) { 'there should be three messages': function (result) {
assert.equal(result.results.length, 3); assert.equal(result.results.length, 3);
@ -129,7 +165,6 @@ vows.describe('log4js smtpAppender').addBatch({
var setup = setupLogging('multiple events in one email', { var setup = setupLogging('multiple events in one email', {
recipients: 'recipient@domain.com', recipients: 'recipient@domain.com',
sendInterval: 1, sendInterval: 1,
transport: "SMTP",
SMTP: { SMTP: {
port: 25, port: 25,
auth: { auth: {
@ -142,13 +177,13 @@ vows.describe('log4js smtpAppender').addBatch({
}, 0); }, 0);
setTimeout(function () { setTimeout(function () {
setup.logger.info('Log event #2'); setup.logger.info('Log event #2');
}, 500); }, 100);
setTimeout(function () { setTimeout(function () {
setup.logger.info('Log event #3'); setup.logger.info('Log event #3');
}, 1050); }, 1500);
setTimeout(function () { setTimeout(function () {
self.callback(null, setup); self.callback(null, setup);
}, 2100); }, 3000);
}, },
'there should be two messages': function (result) { 'there should be two messages': function (result) {
assert.equal(result.results.length, 2); assert.equal(result.results.length, 2);
@ -157,12 +192,35 @@ vows.describe('log4js smtpAppender').addBatch({
assert.equal(result.results[0].to, 'recipient@domain.com'); assert.equal(result.results[0].to, 'recipient@domain.com');
assert.equal(result.results[0].subject, 'Log event #1'); 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[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].to, 'recipient@domain.com');
assert.equal(result.results[1].subject, 'Log event #3'); assert.equal(result.results[1].subject, 'Log event #3');
assert.ok(new RegExp('.+Log event #3\n$').test(result.results[1].text)); assert.ok(new RegExp('.+Log event #3\n$').test(result.results[1].text));
} }
},
'error when sending email': {
topic: function() {
var setup = setupLogging('error when sending email', {
recipients: 'recipient@domain.com',
sendInterval: 0,
SMTP: { port: 25, auth: { user: 'user@domain.com' } }
});
setup.mailer.createTransport = function() {
return {
sendMail: function(msg, cb) {
cb({ message: "oh noes" });
},
close: function() { }
};
};
setup.logger.info("This will break");
return setup.console;
},
'should be logged to console': function(cons) {
assert.equal(cons.errors.length, 1);
assert.equal(cons.errors[0].msg, "log4js.smtpAppender - Error happened");
assert.equal(cons.errors[0].value.message, 'oh noes');
}
} }
}).export(module); }).export(module);

View File

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

View File

@ -1,8 +1,18 @@
var vows = require('vows'), "use strict";
assert = require('assert'), var vows = require('vows')
fs = require('fs'), , assert = require('assert')
DateRollingFileStream = require('../../lib/streams').DateRollingFileStream, , fs = require('fs')
testTime = new Date(2012, 8, 12, 10, 37, 11); , 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) { function cleanUp(filename) {
return function() { return function() {
@ -16,18 +26,22 @@ function now() {
vows.describe('DateRollingFileStream').addBatch({ vows.describe('DateRollingFileStream').addBatch({
'arguments': { 'arguments': {
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-1', 'yyyy-mm-dd.hh'), topic: new DateRollingFileStream(
__dirname + '/test-date-rolling-file-stream-1',
'yyyy-mm-dd.hh'
),
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-1'), teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-1'),
'should take a filename and a pattern and return a FileWriteStream': function(stream) { '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.filename, __dirname + '/test-date-rolling-file-stream-1');
assert.equal(stream.pattern, 'yyyy-mm-dd.hh'); assert.equal(stream.pattern, 'yyyy-mm-dd.hh');
assert.instanceOf(stream, fs.FileWriteStream); assert.instanceOf(stream, streams.Writable);
}, },
'with default settings for the underlying stream': function(stream) { 'with default settings for the underlying stream': function(stream) {
assert.equal(stream.mode, 420); assert.equal(stream.theStream.mode, 420);
assert.equal(stream.flags, 'a'); assert.equal(stream.theStream.flags, 'a');
assert.equal(stream.encoding, 'utf8'); //encoding is not available on the underlying stream
//assert.equal(stream.encoding, 'utf8');
} }
}, },
@ -41,20 +55,27 @@ vows.describe('DateRollingFileStream').addBatch({
}, },
'with stream arguments': { 'with stream arguments': {
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-3', 'yyyy-MM-dd', { mode: 0666 }), topic: new DateRollingFileStream(
__dirname + '/test-date-rolling-file-stream-3',
'yyyy-MM-dd',
{ mode: parseInt('0666', 8) }
),
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-3'), teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-3'),
'should pass them to the underlying stream': function(stream) { 'should pass them to the underlying stream': function(stream) {
assert.equal(stream.mode, 0666); assert.equal(stream.theStream.mode, parseInt('0666', 8));
} }
}, },
'with stream arguments but no pattern': { 'with stream arguments but no pattern': {
topic: new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-4', { mode: 0666 }), topic: new DateRollingFileStream(
__dirname + '/test-date-rolling-file-stream-4',
{ mode: parseInt('0666', 8) }
),
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-4'), teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-4'),
'should pass them to the underlying stream': function(stream) { 'should pass them to the underlying stream': function(stream) {
assert.equal(stream.mode, 0666); assert.equal(stream.theStream.mode, parseInt('0666', 8));
}, },
'should use default pattern': function(stream) { 'should use default pattern': function(stream) {
assert.equal(stream.pattern, '.yyyy-MM-dd'); assert.equal(stream.pattern, '.yyyy-MM-dd');
@ -64,10 +85,12 @@ vows.describe('DateRollingFileStream').addBatch({
'with a pattern of .yyyy-MM-dd': { 'with a pattern of .yyyy-MM-dd': {
topic: function() { topic: function() {
var that = this, var that = this,
stream = new DateRollingFileStream(__dirname + '/test-date-rolling-file-stream-5', '.yyyy-MM-dd', null, now); stream = new DateRollingFileStream(
stream.on("open", function() { __dirname + '/test-date-rolling-file-stream-5', '.yyyy-MM-dd',
stream.write("First message\n"); null,
//wait for the file system to catch up with us now
);
stream.write("First message\n", 'utf8', function() {
that.callback(null, stream); that.callback(null, stream);
}); });
}, },
@ -85,8 +108,7 @@ vows.describe('DateRollingFileStream').addBatch({
'when the day changes': { 'when the day changes': {
topic: function(stream) { topic: function(stream) {
testTime = new Date(2012, 8, 13, 0, 10, 12); testTime = new Date(2012, 8, 13, 0, 10, 12);
stream.write("Second message\n"); stream.write("Second message\n", 'utf8', this.callback);
setTimeout(this.callback, 100);
}, },
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-5.2012-09-12'), teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-5.2012-09-12'),
@ -96,7 +118,14 @@ vows.describe('DateRollingFileStream').addBatch({
fs.readdir(__dirname, this.callback); fs.readdir(__dirname, this.callback);
}, },
'should be two': function(files) { 'should be two': function(files) {
assert.equal(files.filter(function(file) { return file.indexOf('test-date-rolling-file-stream-5') > -1; }).length, 2); assert.equal(
files.filter(
function(file) {
return file.indexOf('test-date-rolling-file-stream-5') > -1;
}
).length,
2
);
} }
}, },
@ -118,6 +147,81 @@ vows.describe('DateRollingFileStream').addBatch({
} }
} }
} }
},
'with alwaysIncludePattern': {
topic: function() {
var that = this,
testTime = new Date(2012, 8, 12, 0, 10, 12),
stream = new DateRollingFileStream(
__dirname + '/test-date-rolling-file-stream-pattern',
'.yyyy-MM-dd',
{alwaysIncludePattern: true},
now
);
stream.write("First message\n", 'utf8', function() {
that.callback(null, stream);
});
},
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-pattern.2012-09-12'),
'should create a file with the pattern set': {
topic: function(stream) {
fs.readFile(__dirname + '/test-date-rolling-file-stream-pattern.2012-09-12', this.callback);
},
'file should contain first message': function(result) {
assert.equal(result.toString(), "First message\n");
}
},
'when the day changes': {
topic: function(stream) {
testTime = new Date(2012, 8, 13, 0, 10, 12);
stream.write("Second message\n", 'utf8', this.callback);
},
teardown: cleanUp(__dirname + '/test-date-rolling-file-stream-pattern.2012-09-13'),
'the number of files': {
topic: function() {
fs.readdir(__dirname, this.callback);
},
'should be two': function(files) {
assert.equal(
files.filter(
function(file) {
return file.indexOf('test-date-rolling-file-stream-pattern') > -1;
}
).length,
2
);
}
},
'the file with the later date': {
topic: function() {
fs.readFile(
__dirname + '/test-date-rolling-file-stream-pattern.2012-09-13',
this.callback
);
},
'should contain the second message': function(contents) {
assert.equal(contents.toString(), "Second message\n");
}
},
'the file with the date': {
topic: function() {
fs.readFile(
__dirname + '/test-date-rolling-file-stream-pattern.2012-09-12',
this.callback
);
},
'should contain the first message': function(contents) {
assert.equal(contents.toString(), "First message\n");
}
}
}
} }
}).exportTo(module); }).exportTo(module);

View File

@ -1,130 +0,0 @@
var vows = require('vows')
, assert = require('assert')
, events = require('events')
, BufferedWriteStream = require('../../lib/streams').BufferedWriteStream;
function FakeStream() {
this.writes = [];
this.canWrite = false;
this.callbacks = {};
}
FakeStream.prototype.on = function(event, callback) {
this.callbacks[event] = callback;
}
FakeStream.prototype.write = function(data, encoding) {
assert.equal("utf8", encoding);
this.writes.push(data);
return this.canWrite;
}
FakeStream.prototype.emit = function(event, payload) {
this.callbacks[event](payload);
}
FakeStream.prototype.block = function() {
this.canWrite = false;
}
FakeStream.prototype.unblock = function() {
this.canWrite = true;
this.emit("drain");
}
vows.describe('BufferedWriteStream').addBatch({
'stream': {
topic: new BufferedWriteStream(new FakeStream()),
'should take a stream as an argument and return a stream': function(stream) {
assert.instanceOf(stream, events.EventEmitter);
}
},
'before stream is open': {
topic: function() {
var fakeStream = new FakeStream(),
stream = new BufferedWriteStream(fakeStream);
stream.write("Some data", "utf8");
stream.write("Some more data", "utf8");
return fakeStream.writes;
},
'should buffer writes': function(writes) {
assert.equal(writes.length, 0);
}
},
'when stream is open': {
topic: function() {
var fakeStream = new FakeStream(),
stream = new BufferedWriteStream(fakeStream);
stream.write("Some data", "utf8");
fakeStream.canWrite = true;
fakeStream.emit("open");
stream.write("Some more data", "utf8");
return fakeStream.writes;
},
'should write data to stream from before stream was open': function (writes) {
assert.equal(writes[0], "Some data");
},
'should write data to stream from after stream was open': function (writes) {
assert.equal(writes[1], "Some more data");
}
},
'when stream is blocked': {
topic: function() {
var fakeStream = new FakeStream(),
stream = new BufferedWriteStream(fakeStream);
fakeStream.emit("open");
fakeStream.block();
stream.write("will not know it is blocked until first write", "utf8");
stream.write("so this one will be buffered, but not the previous one", "utf8");
return fakeStream.writes;
},
'should buffer writes': function (writes) {
assert.equal(writes.length, 1);
assert.equal(writes[0], "will not know it is blocked until first write");
}
},
'when stream is unblocked': {
topic: function() {
var fakeStream = new FakeStream(),
stream = new BufferedWriteStream(fakeStream);
fakeStream.emit("open");
fakeStream.block();
stream.write("will not know it is blocked until first write", "utf8");
stream.write("so this one will be buffered, but not the previous one", "utf8");
fakeStream.unblock();
return fakeStream.writes;
},
'should send buffered data': function (writes) {
assert.equal(writes.length, 2);
assert.equal(writes[1], "so this one will be buffered, but not the previous one");
}
},
'when stream is closed': {
topic: function() {
var fakeStream = new FakeStream(),
stream = new BufferedWriteStream(fakeStream);
fakeStream.emit("open");
fakeStream.block();
stream.write("first write to notice stream is blocked", "utf8");
stream.write("data while blocked", "utf8");
stream.end();
return fakeStream.writes;
},
'should send any buffered writes to the stream': function (writes) {
assert.equal(writes.length, 2);
assert.equal(writes[1], "data while blocked");
}
},
'when stream errors': {
topic: function() {
var fakeStream = new FakeStream(),
stream = new BufferedWriteStream(fakeStream);
stream.on("error", this.callback);
fakeStream.emit("error", "oh noes!");
},
'should emit error': function(err, value) {
assert.equal(err, "oh noes!");
}
}
}).exportTo(module);

View File

@ -1,8 +1,19 @@
"use strict";
var vows = require('vows') var vows = require('vows')
, async = require('async')
, assert = require('assert') , assert = require('assert')
, events = require('events') , events = require('events')
, fs = require('fs') , fs = require('fs')
, RollingFileStream = require('../../lib/streams').RollingFileStream; , semver = require('semver')
, streams
, RollingFileStream;
if (semver.satisfies(process.version, '>=0.10.0')) {
streams = require('stream');
} else {
streams = require('readable-stream');
}
RollingFileStream = require('../../lib/streams').RollingFileStream;
function remove(filename) { function remove(filename) {
try { try {
@ -12,31 +23,41 @@ function remove(filename) {
} }
} }
function create(filename) {
fs.writeFileSync(filename, "test file");
}
vows.describe('RollingFileStream').addBatch({ vows.describe('RollingFileStream').addBatch({
'arguments': { 'arguments': {
topic: function() { topic: function() {
remove(__dirname + "/test-rolling-file-stream"); remove(__dirname + "/test-rolling-file-stream");
return new RollingFileStream("test-rolling-file-stream", 1024, 5); return new RollingFileStream("test-rolling-file-stream", 1024, 5);
}, },
'should take a filename, file size in bytes, number of backups as arguments and return a FileWriteStream': function(stream) { 'should take a filename, file size (bytes), no. backups, return Writable': function(stream) {
assert.instanceOf(stream, fs.FileWriteStream); assert.instanceOf(stream, streams.Writable);
assert.equal(stream.filename, "test-rolling-file-stream"); assert.equal(stream.filename, "test-rolling-file-stream");
assert.equal(stream.size, 1024); assert.equal(stream.size, 1024);
assert.equal(stream.backups, 5); assert.equal(stream.backups, 5);
}, },
'with default settings for the underlying stream': function(stream) { 'with default settings for the underlying stream': function(stream) {
assert.equal(stream.mode, 420); assert.equal(stream.theStream.mode, 420);
assert.equal(stream.flags, 'a'); assert.equal(stream.theStream.flags, 'a');
assert.equal(stream.encoding, 'utf8'); //encoding isn't a property on the underlying stream
//assert.equal(stream.theStream.encoding, 'utf8');
} }
}, },
'with stream arguments': { 'with stream arguments': {
topic: function() { topic: function() {
remove(__dirname + '/test-rolling-file-stream'); remove(__dirname + '/test-rolling-file-stream');
return new RollingFileStream('test-rolling-file-stream', 1024, 5, { mode: 0666 }); return new RollingFileStream(
'test-rolling-file-stream',
1024,
5,
{ mode: parseInt('0666', 8) }
);
}, },
'should pass them to the underlying stream': function(stream) { 'should pass them to the underlying stream': function(stream) {
assert.equal(stream.mode, 0666); assert.equal(stream.theStream.mode, parseInt('0666', 8));
} }
}, },
'without size': { 'without size': {
@ -63,14 +84,15 @@ vows.describe('RollingFileStream').addBatch({
'writing less than the file size': { 'writing less than the file size': {
topic: function() { topic: function() {
remove(__dirname + "/test-rolling-file-stream-write-less"); remove(__dirname + "/test-rolling-file-stream-write-less");
var that = this, stream = new RollingFileStream(__dirname + "/test-rolling-file-stream-write-less", 100); var that = this
stream.on("open", function() { that.callback(null, stream); }); , stream = new RollingFileStream(
}, __dirname + "/test-rolling-file-stream-write-less",
'(when open)': { 100
topic: function(stream) { );
stream.write("cheese", "utf8"); stream.write("cheese", "utf8", function() {
stream.end(); stream.end();
fs.readFile(__dirname + "/test-rolling-file-stream-write-less", "utf8", this.callback); fs.readFile(__dirname + "/test-rolling-file-stream-write-less", "utf8", that.callback);
});
}, },
'should write to the file': function(contents) { 'should write to the file': function(contents) {
assert.equal(contents, "cheese"); assert.equal(contents, "cheese");
@ -80,8 +102,14 @@ vows.describe('RollingFileStream').addBatch({
fs.readdir(__dirname, this.callback); fs.readdir(__dirname, this.callback);
}, },
'should be one': function(files) { 'should be one': function(files) {
assert.equal(files.filter(function(file) { return file.indexOf('test-rolling-file-stream-write-less') > -1; }).length, 1); assert.equal(
files.filter(
function(file) {
return file.indexOf('test-rolling-file-stream-write-less') > -1;
} }
).length,
1
);
} }
} }
}, },
@ -89,21 +117,32 @@ vows.describe('RollingFileStream').addBatch({
topic: function() { topic: function() {
remove(__dirname + "/test-rolling-file-stream-write-more"); remove(__dirname + "/test-rolling-file-stream-write-more");
remove(__dirname + "/test-rolling-file-stream-write-more.1"); remove(__dirname + "/test-rolling-file-stream-write-more.1");
var that = this, stream = new RollingFileStream(__dirname + "/test-rolling-file-stream-write-more", 45); var that = this
stream.on("open", function() { , stream = new RollingFileStream(
for (var i=0; i < 7; i++) { __dirname + "/test-rolling-file-stream-write-more",
stream.write(i +".cheese\n", "utf8"); 45
);
async.each(
[0, 1, 2, 3, 4, 5, 6],
function(i, cb) {
stream.write(i +".cheese\n", "utf8", cb);
},
function() {
stream.end();
that.callback();
} }
//wait for the file system to catch up with us );
setTimeout(that.callback, 100);
});
}, },
'the number of files': { 'the number of files': {
topic: function() { topic: function() {
fs.readdir(__dirname, this.callback); fs.readdir(__dirname, this.callback);
}, },
'should be two': function(files) { 'should be two': function(files) {
assert.equal(files.filter(function(file) { return file.indexOf('test-rolling-file-stream-write-more') > -1; }).length, 2); assert.equal(files.filter(
function(file) {
return file.indexOf('test-rolling-file-stream-write-more') > -1;
}
).length, 2);
} }
}, },
'the first file': { 'the first file': {
@ -122,5 +161,50 @@ vows.describe('RollingFileStream').addBatch({
assert.equal(contents, '0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n'); assert.equal(contents, '0.cheese\n1.cheese\n2.cheese\n3.cheese\n4.cheese\n');
} }
} }
},
'when many files already exist': {
topic: function() {
remove(__dirname + '/test-rolling-stream-with-existing-files.11');
remove(__dirname + '/test-rolling-stream-with-existing-files.20');
remove(__dirname + '/test-rolling-stream-with-existing-files.-1');
remove(__dirname + '/test-rolling-stream-with-existing-files.1.1');
remove(__dirname + '/test-rolling-stream-with-existing-files.1');
create(__dirname + '/test-rolling-stream-with-existing-files.11');
create(__dirname + '/test-rolling-stream-with-existing-files.20');
create(__dirname + '/test-rolling-stream-with-existing-files.-1');
create(__dirname + '/test-rolling-stream-with-existing-files.1.1');
create(__dirname + '/test-rolling-stream-with-existing-files.1');
var that = this
, stream = new RollingFileStream(
__dirname + "/test-rolling-stream-with-existing-files",
45,
5
);
async.each(
[0, 1, 2, 3, 4, 5, 6],
function(i, cb) {
stream.write(i +".cheese\n", "utf8", cb);
},
function() {
stream.end();
that.callback();
}
);
},
'the files': {
topic: function() {
fs.readdir(__dirname, this.callback);
},
'should be rolled': function(files) {
assert.include(files, 'test-rolling-stream-with-existing-files');
assert.include(files, 'test-rolling-stream-with-existing-files.1');
assert.include(files, 'test-rolling-stream-with-existing-files.2');
assert.include(files, 'test-rolling-stream-with-existing-files.11');
assert.include(files, 'test-rolling-stream-with-existing-files.20');
}
}
} }
}).exportTo(module); }).exportTo(module);

View File

@ -0,0 +1,86 @@
"use strict";
var assert = require('assert')
, vows = require('vows')
, sandbox = require('sandboxed-module')
, log4js = require('../lib/log4js')
, levels = require('../lib/levels');
vows.describe('subcategories').addBatch({
'loggers created after levels configuration is loaded': {
topic: function() {
log4js.configure({
"levels": {
"sub1": "WARN",
"sub1.sub11": "TRACE",
"sub1.sub11.sub111": "WARN",
"sub1.sub12": "INFO"
}
}, { reloadSecs: 30 })
return {
"sub1": log4js.getLogger('sub1'), // WARN
"sub11": log4js.getLogger('sub1.sub11'), // TRACE
"sub111": log4js.getLogger('sub1.sub11.sub111'), // WARN
"sub12": log4js.getLogger('sub1.sub12'), // INFO
"sub13": log4js.getLogger('sub1.sub13'), // Inherits sub1: WARN
"sub112": log4js.getLogger('sub1.sub11.sub112'), // Inherits sub1.sub11: TRACE
"sub121": log4js.getLogger('sub1.sub12.sub121'), // Inherits sub12: INFO
"sub0": log4js.getLogger('sub0') // Not defined, not inherited: TRACE
};
},
'check logger levels': function(loggers) {
assert.equal(loggers.sub1.level, levels.WARN);
assert.equal(loggers.sub11.level, levels.TRACE);
assert.equal(loggers.sub111.level, levels.WARN);
assert.equal(loggers.sub12.level, levels.INFO);
assert.equal(loggers.sub13.level, levels.WARN);
assert.equal(loggers.sub112.level, levels.TRACE);
assert.equal(loggers.sub121.level, levels.INFO);
assert.equal(loggers.sub0.level, levels.TRACE);
}
},
'loggers created before levels configuration is loaded': {
topic: function() {
var loggers = {
"sub1": log4js.getLogger('sub1'), // WARN
"sub11": log4js.getLogger('sub1.sub11'), // TRACE
"sub111": log4js.getLogger('sub1.sub11.sub111'), // WARN
"sub12": log4js.getLogger('sub1.sub12'), // INFO
"sub13": log4js.getLogger('sub1.sub13'), // Inherits sub1: WARN
"sub112": log4js.getLogger('sub1.sub11.sub112'), // Inherits sub1.sub11: TRACE
"sub121": log4js.getLogger('sub1.sub12.sub121'), // Inherits sub12: INFO
"sub0": log4js.getLogger('sub0') // Not defined, not inherited: TRACE
};
log4js.configure({
"levels": {
"sub1": "WARN",
"sub1.sub11": "TRACE",
"sub1.sub11.sub111": "WARN",
"sub1.sub12": "INFO"
}
}, { reloadSecs: 30 })
return loggers;
},
'check logger levels': function(loggers) {
assert.equal(loggers.sub1.level, levels.WARN);
assert.equal(loggers.sub11.level, levels.TRACE);
assert.equal(loggers.sub111.level, levels.WARN);
assert.equal(loggers.sub12.level, levels.INFO);
assert.equal(loggers.sub13.level, levels.WARN);
assert.equal(loggers.sub112.level, levels.TRACE);
assert.equal(loggers.sub121.level, levels.INFO);
assert.equal(loggers.sub0.level, levels.TRACE);
}
}
}).exportTo(module);

View File

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

View File

@ -12,6 +12,19 @@
} }
} }
}, },
{
"category": "tests",
"type": "logLevelFilter",
"level": "TRACE",
"maxLevel": "DEBUG",
"appender": {
"type": "file",
"filename": "test/logLevelFilter-debugs.log",
"layout": {
"type": "messagePassThrough"
}
}
},
{ {
"category": "tests", "category": "tests",
"type": "file", "type": "file",
@ -23,6 +36,6 @@
], ],
"levels": { "levels": {
"tests": "DEBUG" "tests": "TRACE"
} }
} }