Compare commits
397 Commits
refactorin
...
logger-dec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a136404021 | ||
|
|
d1c6ad6f39 | ||
|
|
aad2bb1c2e | ||
|
|
5c13469bf6 | ||
|
|
491c2709e7 | ||
|
|
245b2e3b1a | ||
|
|
6dabcf5ee5 | ||
|
|
3142497e98 | ||
|
|
09d4300875 | ||
|
|
80f8f5a795 | ||
|
|
72c4fb48db | ||
|
|
5e144e4004 | ||
|
|
46ad57b4e0 | ||
|
|
fe1f1228ca | ||
|
|
d43d49d83d | ||
|
|
3312724d7d | ||
|
|
045b0dda2b | ||
|
|
b6dc0b9557 | ||
|
|
7c0cfbdcfd | ||
|
|
25e9835521 | ||
|
|
be1a9ca411 | ||
|
|
b2569c6d9d | ||
|
|
04d0113224 | ||
|
|
ac4fd2a7fc | ||
|
|
50074842ad | ||
|
|
5a2771cfed | ||
|
|
3b4a30587a | ||
|
|
50a8164b4b | ||
|
|
eabcaf8aef | ||
|
|
d8cf8cb2dc | ||
|
|
9afbbb580e | ||
|
|
6c09a6fb71 | ||
|
|
5f68db41b4 | ||
|
|
731e217505 | ||
|
|
3018a49bde | ||
|
|
4e6e51e9fa | ||
|
|
7f38837f9b | ||
|
|
b70e2e6220 | ||
|
|
834e084e5a | ||
|
|
270ba0fcc6 | ||
|
|
bdb56e4256 | ||
|
|
b12a39ac79 | ||
|
|
c3d3a8c363 | ||
|
|
7bad76d8ec | ||
|
|
a0a5a3aa99 | ||
|
|
a5bb94a048 | ||
|
|
631ca75e57 | ||
|
|
ab1c81a61c | ||
|
|
a0d0373480 | ||
|
|
eb875b6d98 | ||
|
|
a8679aced1 | ||
|
|
7a1a895e46 | ||
|
|
48dc22eb63 | ||
|
|
7888381991 | ||
|
|
cd286fa25f | ||
|
|
6df4753822 | ||
|
|
613474eb44 | ||
|
|
112246dd55 | ||
|
|
069ed31759 | ||
|
|
9e72189574 | ||
|
|
5a167d853a | ||
|
|
5755faa7bb | ||
|
|
1ed026a8d9 | ||
|
|
2d177d517b | ||
|
|
21aebbde33 | ||
|
|
49892f35d3 | ||
|
|
61beac28d3 | ||
|
|
c60d629608 | ||
|
|
8ad1cd67e2 | ||
|
|
c67ab855bb | ||
|
|
4905761f60 | ||
|
|
9897dcbc93 | ||
|
|
9c510f7705 | ||
|
|
5bd7ce3ab9 | ||
|
|
1e17f88ded | ||
|
|
3b55aefe6f | ||
|
|
d25e1abd48 | ||
|
|
dde2e69948 | ||
|
|
351a912a86 | ||
|
|
c5fd75dac3 | ||
|
|
4dd5989d27 | ||
|
|
46721465a1 | ||
|
|
76ff7aa5fa | ||
|
|
be5fa838be | ||
|
|
a86bed975c | ||
|
|
baaebef2ed | ||
|
|
837d007de3 | ||
|
|
be754f0c0e | ||
|
|
946b216a79 | ||
|
|
508dbdadf8 | ||
|
|
2e7f6e5a66 | ||
|
|
cbadb5fa19 | ||
|
|
c258470cda | ||
|
|
2b070e5470 | ||
|
|
4cd546e8b3 | ||
|
|
0e5da1d361 | ||
|
|
fc7f686f65 | ||
|
|
4a8f0580de | ||
|
|
f50fab2b86 | ||
|
|
f1c0767ca3 | ||
|
|
652888944b | ||
|
|
efc4e36317 | ||
|
|
d2f30b473f | ||
|
|
fa179ecba2 | ||
|
|
dd25d30228 | ||
|
|
11fe5bde5f | ||
|
|
41ddf5eea7 | ||
|
|
81fa9c3568 | ||
|
|
7ca517b5ed | ||
|
|
6368de1094 | ||
|
|
94dbd22c71 | ||
|
|
0a2a6c0769 | ||
|
|
5d6f00eda4 | ||
|
|
f998d7e81a | ||
|
|
46ae1a586d | ||
|
|
516320c79a | ||
|
|
40ec9e98e4 | ||
|
|
cc2e94cf11 | ||
|
|
2de838bc76 | ||
|
|
87dc7cf5aa | ||
|
|
913c748ee0 | ||
|
|
def0e8e371 | ||
|
|
20f80ff775 | ||
|
|
f24db59523 | ||
|
|
07869b915f | ||
|
|
2cd27e4293 | ||
|
|
3d11cbc0ad | ||
|
|
e5dba219d1 | ||
|
|
9853e13429 | ||
|
|
4fd138f87d | ||
|
|
1ad4977aec | ||
|
|
7cb7e6df72 | ||
|
|
2192a094b6 | ||
|
|
6a9441d261 | ||
|
|
50b676dec5 | ||
|
|
8b3c036245 | ||
|
|
b356dec318 | ||
|
|
8383dfc4f4 | ||
|
|
4e8fb26099 | ||
|
|
8492519e3b | ||
|
|
fdc9d253c9 | ||
|
|
18e21ca473 | ||
|
|
ab8c7ed89d | ||
|
|
aa4f7c071b | ||
|
|
dc632f4705 | ||
|
|
ac6284add1 | ||
|
|
2da01cc611 | ||
|
|
ad8229145e | ||
|
|
8c12c948d9 | ||
|
|
af6ae7af98 | ||
|
|
936ad4da8e | ||
|
|
097ae3d7f1 | ||
|
|
04de4ed8d3 | ||
|
|
29b02921b6 | ||
|
|
48ed5d1222 | ||
|
|
7844b0d2e4 | ||
|
|
8b49ba9f3d | ||
|
|
ed7462885f | ||
|
|
36c5175a55 | ||
|
|
22160f90b3 | ||
|
|
73437ecb40 | ||
|
|
107e33c0d1 | ||
|
|
6352632fb2 | ||
|
|
0544342e9f | ||
|
|
1d1153d32f | ||
|
|
e58cf201ca | ||
|
|
83271e47fc | ||
|
|
f3271a3997 | ||
|
|
4b7cf589a2 | ||
|
|
c8f401c47d | ||
|
|
ecbf41bc83 | ||
|
|
65e490cbd2 | ||
|
|
eb21e10208 | ||
|
|
f272e3fd0a | ||
|
|
5e242c9dc9 | ||
|
|
50eefcc701 | ||
|
|
8e53c6213e | ||
|
|
a15a628311 | ||
|
|
b75e3660f4 | ||
|
|
22da6226e5 | ||
|
|
c9a890b37b | ||
|
|
a3bdac8e14 | ||
|
|
af428c5669 | ||
|
|
5c75ba9468 | ||
|
|
bec0d05847 | ||
|
|
e4bf405f20 | ||
|
|
95568f352b | ||
|
|
6da6f3c90e | ||
|
|
7f57d14e70 | ||
|
|
f478793da3 | ||
|
|
0dbc4921a3 | ||
|
|
ec2f8fec3b | ||
|
|
0167c84ea5 | ||
|
|
3e1a27e522 | ||
|
|
8b42e46071 | ||
|
|
4a7a90ed53 | ||
|
|
a9307fd6da | ||
|
|
4739c65c68 | ||
|
|
892181f88f | ||
|
|
bdfa7f9a9b | ||
|
|
ad63b801f7 | ||
|
|
2bfad6362a | ||
|
|
2b889fe776 | ||
|
|
9ac61e37f4 | ||
|
|
185f343e68 | ||
|
|
be1272cd7c | ||
|
|
cbc1dd32f9 | ||
|
|
a6fb26efb1 | ||
|
|
012b0d5ed7 | ||
|
|
de72005e7e | ||
|
|
c6a0e58409 | ||
|
|
f832a2ba79 | ||
|
|
3f10b68c30 | ||
|
|
54c311842c | ||
|
|
f948b5f5cd | ||
|
|
54e420eb58 | ||
|
|
40ba24a55d | ||
|
|
e3a20a1746 | ||
|
|
7a02f39921 | ||
|
|
b6ba3bce00 | ||
|
|
638ce187bb | ||
|
|
3cbae96a97 | ||
|
|
a33e48cb07 | ||
|
|
df491c0b14 | ||
|
|
6ff1a2499f | ||
|
|
ce2d7df8df | ||
|
|
1b12265800 | ||
|
|
32e9045334 | ||
|
|
1aed671137 | ||
|
|
68b47dd51c | ||
|
|
8f9b4444f6 | ||
|
|
e49f7107fb | ||
|
|
077302c772 | ||
|
|
6f0dfa0c5f | ||
|
|
82a6bee331 | ||
|
|
ad7e844d68 | ||
|
|
bef2075c60 | ||
|
|
a046523804 | ||
|
|
0ed1a137d6 | ||
|
|
33a92b5dd6 | ||
|
|
0901794b35 | ||
|
|
05d5265554 | ||
|
|
9a29d6222e | ||
|
|
38a89dcf3d | ||
|
|
754ac2c5ac | ||
|
|
ccc4976206 | ||
|
|
6e7348f8d8 | ||
|
|
61078e88ef | ||
|
|
613a077a61 | ||
|
|
68d1c8fa07 | ||
|
|
216937637d | ||
|
|
ff5b8d2939 | ||
|
|
6a20efb965 | ||
|
|
872bc791c7 | ||
|
|
2c7b56853b | ||
|
|
c8157cef5c | ||
|
|
352653dcbe | ||
|
|
cff6928761 | ||
|
|
1fb8962b83 | ||
|
|
d276bbc2f8 | ||
|
|
e78f4e33ce | ||
|
|
53367785b4 | ||
|
|
cff20b99e3 | ||
|
|
0a422e5749 | ||
|
|
37b94cf195 | ||
|
|
0c04c6807c | ||
|
|
b4ca201a91 | ||
|
|
2ab6f5fa24 | ||
|
|
9bad070b8a | ||
|
|
5aaa9fcd50 | ||
|
|
b7e77b11ad | ||
|
|
615b534b56 | ||
|
|
788de0cac3 | ||
|
|
4d484ad752 | ||
|
|
449893fd24 | ||
|
|
5bdeaf68d7 | ||
|
|
a5b09b3ead | ||
|
|
05c4c59c20 | ||
|
|
b4a5227fc0 | ||
|
|
b152618dbc | ||
|
|
a999d8fc00 | ||
|
|
78de73a274 | ||
|
|
4cf1d1cfa4 | ||
|
|
e5d0b3348f | ||
|
|
f10a6e164e | ||
|
|
cea3dc97d1 | ||
|
|
a3a0c55322 | ||
|
|
51d48165fd | ||
|
|
7d50c45801 | ||
|
|
40c5f5ee70 | ||
|
|
1d769fdf33 | ||
|
|
bc665b875e | ||
|
|
154c0dc299 | ||
|
|
050fae5230 | ||
|
|
342286e062 | ||
|
|
537f1058b9 | ||
|
|
283a403a11 | ||
|
|
ae8aaa5376 | ||
|
|
a95117c0d3 | ||
|
|
097390bc89 | ||
|
|
0a0119300b | ||
|
|
fde66f92f5 | ||
|
|
516659f733 | ||
|
|
5aabebbdb7 | ||
|
|
8b376eb46e | ||
|
|
ced570413c | ||
|
|
b2827076da | ||
|
|
07e920cc1b | ||
|
|
89f3659825 | ||
|
|
23a2758a6d | ||
|
|
25aa075fad | ||
|
|
d099a9fc3f | ||
|
|
7bc460e8e0 | ||
|
|
681decf51f | ||
|
|
b93691b82a | ||
|
|
f82ecf8f2a | ||
|
|
3b77a42706 | ||
|
|
b5bc9c8322 | ||
|
|
c7d3ac4fe1 | ||
|
|
0aca64623e | ||
|
|
ff68e46858 | ||
|
|
f9768eb56e | ||
|
|
75e5584060 | ||
|
|
b78fd77015 | ||
|
|
2a06048114 | ||
|
|
9a34d9edfd | ||
|
|
12e71bda4e | ||
|
|
53a481d4da | ||
|
|
8d7b5513fb | ||
|
|
d13b2fb3b4 | ||
|
|
4f7d73bc97 | ||
|
|
163db0e5fd | ||
|
|
71f9eef6fe | ||
|
|
623bc1859f | ||
|
|
b72182c0cf | ||
|
|
ef9fe3a4b1 | ||
|
|
3b241095cb | ||
|
|
545681287f | ||
|
|
80474c6881 | ||
|
|
7aa076c278 | ||
|
|
e6b69ff7f2 | ||
|
|
69e64932b1 | ||
|
|
4b32456db7 | ||
|
|
ec21ec63f0 | ||
|
|
a9a698cf09 | ||
|
|
925c280c68 | ||
|
|
d0b4563ba0 | ||
|
|
aac8ca0eb0 | ||
|
|
0968c6709f | ||
|
|
800f0d6bf6 | ||
|
|
71fe001278 | ||
|
|
3d27140a9d | ||
|
|
d64d4ca0ca | ||
|
|
b338b34fd6 | ||
|
|
3691648cd0 | ||
|
|
d7ffa59434 | ||
|
|
5868856a7d | ||
|
|
f89d54b66e | ||
|
|
e121ca345a | ||
|
|
8767cda15f | ||
|
|
8eaff77974 | ||
|
|
d7a97366cb | ||
|
|
a1681f5579 | ||
|
|
1fa9d029a2 | ||
|
|
ffdfca7d99 | ||
|
|
1ee8cfcd74 | ||
|
|
f386f003be | ||
|
|
9f4878d82c | ||
|
|
d9bfc5db44 | ||
|
|
a50c02a3e5 | ||
|
|
59f7e0af3c | ||
|
|
3f95e02cba | ||
|
|
540a683566 | ||
|
|
c74120e499 | ||
|
|
6f79694904 | ||
|
|
47fcb2233d | ||
|
|
fb8b4554e1 | ||
|
|
0258fda93c | ||
|
|
80e3ed7174 | ||
|
|
838f0c8f28 | ||
|
|
9364a8a442 | ||
|
|
76fea28bbb | ||
|
|
fc3d50846d | ||
|
|
f21fa2bcf8 | ||
|
|
71459ab6d3 | ||
|
|
079edd19c8 | ||
|
|
a876dfbe9c | ||
|
|
c6dd2398ab | ||
|
|
cf7d5f681a | ||
|
|
612d9eeb23 | ||
|
|
c870289928 | ||
|
|
c2f9ccce73 | ||
|
|
2e3843205a | ||
|
|
80305ca376 | ||
|
|
7e7961330d | ||
|
|
682d95db69 | ||
|
|
a7006444b3 |
12
.bob.json
Normal file
12
.bob.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"build": "clean lint test coverage",
|
||||
"lint": {
|
||||
"type": "jshint"
|
||||
},
|
||||
"coverage": {
|
||||
"type": "mocha-istanbul"
|
||||
},
|
||||
"test": {
|
||||
"type": "mocha"
|
||||
}
|
||||
}
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.log
|
||||
*.log??
|
||||
build
|
||||
node_modules
|
||||
.bob/
|
||||
test/streams/test-rolling-file-stream*
|
||||
18
.jshintrc
Normal file
18
.jshintrc
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": true,
|
||||
"laxcomma": true,
|
||||
"indent": 2,
|
||||
"globalstrict": true,
|
||||
"maxparams": 5,
|
||||
"maxdepth": 3,
|
||||
"maxstatements": 20,
|
||||
"maxcomplexity": 5,
|
||||
"maxlen": 100,
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"it": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"after": true
|
||||
}
|
||||
}
|
||||
2
.npmignore
Normal file
2
.npmignore
Normal file
@@ -0,0 +1,2 @@
|
||||
*.log
|
||||
*.log??
|
||||
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.8"
|
||||
|
||||
38
0.7-changes
Normal file
38
0.7-changes
Normal file
@@ -0,0 +1,38 @@
|
||||
changes
|
||||
=======
|
||||
LogEvent.categoryName -> LogEvent.category
|
||||
Logger is immutable (no setLevel any more)
|
||||
Log levels defined in configure call, nowhere else
|
||||
References to Loggers not retained
|
||||
Clustered appender, multiprocess appender removed - core handles clusters now
|
||||
Default category needs to be defined, with appender
|
||||
connect logger, gelf, smtp, hookio appenders removed from core.
|
||||
reload configuration removed from core - use 'watchr' or something instead
|
||||
appenders now only need to provide configure function
|
||||
log4js.configure now only takes single argument (no options)
|
||||
tests use mocha not vows
|
||||
replaced my debug lib with tjholowaychuk's debug (more of a standard)
|
||||
options.cwd removed - filenames should always be specified in full, not relative
|
||||
loglevelfilter changed to accept a list of log levels it allows
|
||||
appenders that wrap other appenders must reference them by name
|
||||
extracted streams to streamroller
|
||||
extracted date_format.js to date-format
|
||||
console.log replacement has been removed.
|
||||
|
||||
to-do
|
||||
=====
|
||||
documentation pages (gh-pages)
|
||||
* configuration
|
||||
* file appenders
|
||||
* layouts
|
||||
* optional components
|
||||
* writing your own appender (use couchdb as example)
|
||||
readme
|
||||
* getting started
|
||||
* typical config - file with max size, file with date rolling
|
||||
* optional components
|
||||
fix and publish the optional components
|
||||
* connect
|
||||
* smtp
|
||||
* gelf
|
||||
* hookio ?
|
||||
202
LICENSE.txt
202
LICENSE.txt
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
147
README.md
147
README.md
@@ -1,67 +1,130 @@
|
||||
# log4js-node
|
||||
# log4js-node [](http://travis-ci.org/nomiddlename/log4js-node)
|
||||
|
||||
This is a conversion of the [log4js](http://log4js.berlios.de/index.html)
|
||||
framework to work with [node](http://nodejs.org). I've mainly stripped out the browser-specific code
|
||||
and tidied up some of the javascript. It includes a basic file logger, with log rolling based on file size.
|
||||
|
||||
NOTE: since v0.2.0 require('log4js') returns a function, so you need to call that function in your code before you can use it. I've done this to make testing easier (allows dependency injection).
|
||||
This was a conversion of the [log4js](http://log4js.berlios.de/index.html)
|
||||
framework to work with [node](http://nodejs.org). It's changed a lot since then, but there are still plenty of the original parts involved.
|
||||
|
||||
Out of the box it supports the following features:
|
||||
|
||||
* coloured console logging
|
||||
* file appender, with log rolling based on file size or date
|
||||
* multi-process logging (works fine with node's clusters)
|
||||
* configurable log message layout/patterns
|
||||
* different log levels for different log categories (make some parts of your app log as DEBUG, others only ERRORS, etc.)
|
||||
|
||||
NOTE: There have been a lot of changes in version 0.7.x, if you're upgrading from an older version, you should read [0.7-changes](http://github.com/nomiddlename/log4js-node/0.7-changes)
|
||||
|
||||
## installation
|
||||
|
||||
npm install log4js
|
||||
npm install log4js
|
||||
|
||||
## tests
|
||||
|
||||
Tests now use [vows](http://vowsjs.org), run with `vows test/logging.js`. I am slowly porting the previous tests from jspec (run those with `node tests.js`), since jspec is no longer maintained.
|
||||
|
||||
## usage
|
||||
|
||||
Minimalist version:
|
||||
var log4js = require('log4js')();
|
||||
var logger = log4js.getLogger();
|
||||
logger.debug("Some debug messages");
|
||||
|
||||
var log4js = require('log4js');
|
||||
var logger = log4js.getLogger();
|
||||
logger.debug("Some debug messages");
|
||||
|
||||
By default, log4js outputs to stdout with the coloured layout (thanks to [masylum](http://github.com/masylum)), so for the above you would see:
|
||||
[2010-01-17 11:43:37.987] [DEBUG] [default] - Some debug messages
|
||||
|
||||
See example.js:
|
||||
[2010-01-17 11:43:37.987] [DEBUG] default - Some debug messages
|
||||
|
||||
var log4js = require('log4js')(); //note the need to call the function
|
||||
log4js.addAppender(log4js.consoleAppender());
|
||||
log4js.addAppender(log4js.fileAppender('logs/cheese.log'), 'cheese');
|
||||
|
||||
var logger = log4js.getLogger('cheese');
|
||||
logger.setLevel('ERROR');
|
||||
|
||||
logger.trace('Entering cheese testing');
|
||||
logger.debug('Got cheese.');
|
||||
logger.info('Cheese is Gouda.');
|
||||
logger.warn('Cheese is quite smelly.');
|
||||
logger.error('Cheese is too ripe!');
|
||||
logger.fatal('Cheese was breeding ground for listeria.');
|
||||
See the examples directory for lots of sample setup and usage code.
|
||||
|
||||
## API
|
||||
Log4js exposes two public functions: `configure` and `getLogger`. If
|
||||
you're writing your own appender, your code will get access to some
|
||||
internal APIs, see
|
||||
[writing-appenders](http://github.com/nomiddlename/log4js-node/writing-appenders.md).
|
||||
|
||||
### log4js.configure(config)
|
||||
Configure takes a single argument. If that argument is a string, it is
|
||||
considered the path to a JSON file containing the configuration
|
||||
object. If the argument is an object, it must have the following
|
||||
fields:
|
||||
|
||||
* `appenders` (Object) - this should be a map of named appenders to
|
||||
their configuration. At least one appender must be defined.
|
||||
* `categories` (Object) - this should be a map of logger categories to
|
||||
their levels and configuration. The "default" logger category must
|
||||
be defined, as this is used to route all log events that do not have
|
||||
an explicit category defined in the config. Category objects have
|
||||
two fields:
|
||||
* `level` - (String) the log level for that category: "trace",
|
||||
"debug", "info", "warn", "error", "fatal", "off"
|
||||
* `appenders` - (Array) the list of appender names to which log
|
||||
events for this category should be sent
|
||||
|
||||
The default configuration for log4js, the one used if `configure` is
|
||||
not called, looks like this:
|
||||
|
||||
{
|
||||
"appenders": {
|
||||
"console": { "type": "console" }
|
||||
},
|
||||
"categories": {
|
||||
"default": { level: "TRACE", appenders: [ "console" ] }
|
||||
}
|
||||
}
|
||||
|
||||
Use of the default configuration can be overridden by setting the
|
||||
`LOG4JS_CONFIG` environment variable to the location of a JSON
|
||||
configuration file. log4js will use this file in preference to the
|
||||
defaults, if `configure` is not called. An example file can be found
|
||||
in `test/log4js.json`. An example config file with log rolling is in
|
||||
`test/with-log-rolling.json`.
|
||||
|
||||
### log4js.getLogger([category])
|
||||
|
||||
* `category` (String), optional. Category to use for log events
|
||||
generated by the Logger.
|
||||
|
||||
Output
|
||||
[2010-01-17 11:43:37.987] [ERROR] cheese - Cheese is too ripe!
|
||||
[2010-01-17 11:43:37.990] [FATAL] cheese - Cheese was breeding ground for listeria.
|
||||
Returns a Logger instance. Unlike in previous versions, log4js
|
||||
does not hold a reference to Loggers so feel free to use as many as
|
||||
you like.
|
||||
|
||||
|
||||
## configuration
|
||||
### Logger
|
||||
|
||||
You can either configure the appenders and log levels manually (as above), or provide a
|
||||
configuration file (`log4js.configure('path/to/file.json')`) explicitly, or just let log4js look for a file called `log4js.json` (it looks in the current directory first, then the require paths, and finally looks for the default config included in the same directory as the `log4js.js` file).
|
||||
An example file can be found in `test/log4js.json`. An example config file with log rolling is in `test/with-log-rolling.json`
|
||||
Loggers provide the following functions:
|
||||
|
||||
## todo
|
||||
* `trace`
|
||||
* `debug`
|
||||
* `info`
|
||||
* `warn`
|
||||
* `error`
|
||||
* `fatal`
|
||||
|
||||
patternLayout has no tests. This is mainly because I haven't found a use for it yet,
|
||||
and am not entirely sure what it was supposed to do. It is more-or-less intact from
|
||||
the original log4js.
|
||||
All can take a variable list of arguments which are used to construct
|
||||
a log event. They work the same way as console.log, so you can pass a
|
||||
format string with placeholders. e.g.
|
||||
|
||||
## author (of this node version)
|
||||
logger.debug("number of widgets is %d", widgets);
|
||||
|
||||
|
||||
Gareth Jones (csausdev - gareth.jones@sensis.com.au)
|
||||
## Appenders
|
||||
|
||||
Log4js comes with file appenders included, which can be configured to
|
||||
roll over based on a time or a file size. Other appenders are
|
||||
available as separate modules:
|
||||
|
||||
* [log4js-gelf](http://github.com/nomiddlename/log4js-gelf)
|
||||
* [log4js-smtp](http://github.com/nomiddlename/log4js-smtp)
|
||||
* [log4js-hookio](http://github.com/nomiddlename/log4js-hookio)
|
||||
|
||||
There's also
|
||||
[log4js-connect](http://github.com/nomiddlename/log4s-connect), for
|
||||
logging http access in connect-based servers, like express.
|
||||
|
||||
## Documentation
|
||||
See the [wiki](https://github.com/nomiddlename/log4js-node/wiki). Improve the [wiki](https://github.com/nomiddlename/log4js-node/wiki), please.
|
||||
|
||||
## Contributing
|
||||
Contributions welcome, but take a look at the [rules](https://github.com/nomiddlename/log4js-node/wiki/Contributing) first.
|
||||
|
||||
## License
|
||||
|
||||
The original log4js was distributed under the Apache 2.0 License, and so is this. I've tried to
|
||||
keep the original copyright and author credits in place, except in sections that I have rewritten
|
||||
keep the original copyright and author credits in place, except in sections that I have rewritten
|
||||
extensively.
|
||||
|
||||
13
example.js
13
example.js
@@ -1,13 +0,0 @@
|
||||
var log4js = require('./lib/log4js')();
|
||||
log4js.addAppender(log4js.consoleAppender());
|
||||
log4js.addAppender(log4js.fileAppender('cheese.log'), 'cheese');
|
||||
|
||||
var logger = log4js.getLogger('cheese');
|
||||
logger.setLevel('ERROR');
|
||||
|
||||
logger.trace('Entering cheese testing');
|
||||
logger.debug('Got cheese.');
|
||||
logger.info('Cheese is Gouda.');
|
||||
logger.warn('Cheese is quite smelly.');
|
||||
logger.error('Cheese is too ripe!');
|
||||
logger.fatal('Cheese was breeding ground for listeria.');
|
||||
46
examples/example-connect-logger.js
Normal file
46
examples/example-connect-logger.js
Normal file
@@ -0,0 +1,46 @@
|
||||
//The connect/express logger was added to log4js by danbell. This allows connect/express servers to log using log4js.
|
||||
//https://github.com/nomiddlename/log4js-node/wiki/Connect-Logger
|
||||
|
||||
// load modules
|
||||
var log4js = require('log4js');
|
||||
var express = require("express");
|
||||
var app = express();
|
||||
|
||||
//config
|
||||
log4js.configure({
|
||||
appenders: [
|
||||
{ type: 'console' },
|
||||
{ type: 'file', filename: 'logs/log4jsconnect.log', category: 'log4jslog' }
|
||||
]
|
||||
});
|
||||
|
||||
//define logger
|
||||
var logger = log4js.getLogger('log4jslog');
|
||||
|
||||
// set at which time msg is logged print like: only on error & above
|
||||
// logger.setLevel('ERROR');
|
||||
|
||||
//express app
|
||||
app.configure(function() {
|
||||
app.use(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' }));
|
||||
});
|
||||
|
||||
//route
|
||||
app.get('/', function(req,res) {
|
||||
res.send('hello world');
|
||||
});
|
||||
|
||||
//start app
|
||||
app.listen(5000);
|
||||
|
||||
console.log('server runing at localhost:5000');
|
||||
console.log('Simulation of normal response: goto localhost:5000');
|
||||
console.log('Simulation of error response: goto localhost:5000/xxx');
|
||||
45
examples/example-socket.js
Normal file
45
examples/example-socket.js
Normal file
@@ -0,0 +1,45 @@
|
||||
var log4js = require('./lib/log4js')
|
||||
, cluster = require('cluster')
|
||||
, numCPUs = require('os').cpus().length
|
||||
, i = 0;
|
||||
|
||||
if (cluster.isMaster) {
|
||||
log4js.configure({
|
||||
appenders: [
|
||||
{
|
||||
type: "multiprocess",
|
||||
mode: "master",
|
||||
appender: {
|
||||
type: "console"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
console.info("Master creating %d workers", numCPUs);
|
||||
for (i=0; i < numCPUs; i++) {
|
||||
cluster.fork();
|
||||
}
|
||||
|
||||
cluster.on('death', function(worker) {
|
||||
console.info("Worker %d died.", worker.pid);
|
||||
});
|
||||
} else {
|
||||
log4js.configure({
|
||||
appenders: [
|
||||
{
|
||||
type: "multiprocess",
|
||||
mode: "worker"
|
||||
}
|
||||
]
|
||||
});
|
||||
var logger = log4js.getLogger('example-socket');
|
||||
|
||||
console.info("Worker %d started.", process.pid);
|
||||
for (i=0; i < 1000; i++) {
|
||||
logger.info("Worker %d - logging something %d", process.pid, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
58
examples/example.js
Normal file
58
examples/example.js
Normal file
@@ -0,0 +1,58 @@
|
||||
var log4js = require('../lib/log4js');
|
||||
//log the cheese logger messages to a file, and the console ones as well.
|
||||
log4js.configure({
|
||||
appenders: [
|
||||
{
|
||||
type: "file",
|
||||
filename: "cheese.log",
|
||||
category: [ 'cheese','console' ]
|
||||
},
|
||||
{
|
||||
type: "console"
|
||||
}
|
||||
],
|
||||
replaceConsole: true
|
||||
});
|
||||
|
||||
//to add an appender programmatically, and without clearing other appenders
|
||||
//loadAppender is only necessary if you haven't already configured an appender of this type
|
||||
log4js.loadAppender('file');
|
||||
log4js.addAppender(log4js.appenders.file('pants.log'), 'pants');
|
||||
//a custom logger outside of the log4js/lib/appenders directory can be accessed like so
|
||||
//log4js.loadAppender('what/you/would/put/in/require');
|
||||
//log4js.addAppender(log4js.appenders['what/you/would/put/in/require'](args));
|
||||
//or through configure as:
|
||||
//log4js.configure({
|
||||
// appenders: [ { type: 'what/you/would/put/in/require', otherArgs: 'blah' } ]
|
||||
//});
|
||||
|
||||
var logger = log4js.getLogger('cheese');
|
||||
//only errors and above get logged.
|
||||
//you can also set this log level in the config object
|
||||
//via the levels field.
|
||||
logger.setLevel('ERROR');
|
||||
|
||||
//console logging methods have been replaced with log4js ones.
|
||||
//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" });
|
||||
|
||||
//these will not appear (logging level beneath error)
|
||||
logger.trace('Entering cheese testing');
|
||||
logger.debug('Got cheese.');
|
||||
logger.info('Cheese is Gouda.');
|
||||
logger.warn('Cheese is quite smelly.');
|
||||
//these end up on the console and in cheese.log
|
||||
logger.error('Cheese %s is too ripe!', "gouda");
|
||||
logger.fatal('Cheese was breeding ground for listeria.');
|
||||
|
||||
//these don't end up in cheese.log, but will appear on the console
|
||||
var anotherLogger = log4js.getLogger('another');
|
||||
anotherLogger.debug("Just checking");
|
||||
|
||||
//one for pants.log
|
||||
//will also go to console, since that's configured for all categories
|
||||
var pantsLog = log4js.getLogger('pants');
|
||||
pantsLog.debug("Something for pants");
|
||||
|
||||
|
||||
|
||||
19
examples/fromreadme.js
Normal file
19
examples/fromreadme.js
Normal file
@@ -0,0 +1,19 @@
|
||||
//remember to change the require to just 'log4js' if you've npm install'ed it
|
||||
var log4js = require('./lib/log4js');
|
||||
//by default the console appender is loaded
|
||||
//log4js.loadAppender('console');
|
||||
//you'd only need to add the console appender if you
|
||||
//had previously called log4js.clearAppenders();
|
||||
//log4js.addAppender(log4js.appenders.console());
|
||||
log4js.loadAppender('file');
|
||||
log4js.addAppender(log4js.appenders.file('cheese.log'), 'cheese');
|
||||
|
||||
var logger = log4js.getLogger('cheese');
|
||||
logger.setLevel('ERROR');
|
||||
|
||||
logger.trace('Entering cheese testing');
|
||||
logger.debug('Got cheese.');
|
||||
logger.info('Cheese is Gouda.');
|
||||
logger.warn('Cheese is quite smelly.');
|
||||
logger.error('Cheese is too ripe!');
|
||||
logger.fatal('Cheese was breeding ground for listeria.');
|
||||
27
examples/log-rolling.js
Normal file
27
examples/log-rolling.js
Normal file
@@ -0,0 +1,27 @@
|
||||
var log4js = require('../lib/log4js')
|
||||
, log
|
||||
, i = 0;
|
||||
log4js.configure({
|
||||
"appenders": [
|
||||
{
|
||||
type: "console"
|
||||
, category: "console"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"filename": "tmp-test.log",
|
||||
"maxLogSize": 1024,
|
||||
"backups": 3,
|
||||
"category": "test"
|
||||
}
|
||||
]
|
||||
});
|
||||
log = log4js.getLogger("test");
|
||||
|
||||
function doTheLogging(x) {
|
||||
log.info("Logging something %d", x);
|
||||
}
|
||||
|
||||
for ( ; i < 5000; i++) {
|
||||
doTheLogging(i);
|
||||
}
|
||||
37
examples/memory-test.js
Normal file
37
examples/memory-test.js
Normal file
@@ -0,0 +1,37 @@
|
||||
var log4js = require('./lib/log4js')
|
||||
, logger
|
||||
, usage
|
||||
, i;
|
||||
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: [
|
||||
{
|
||||
category: "memory-test"
|
||||
, type: "file"
|
||||
, filename: "memory-test.log"
|
||||
},
|
||||
{
|
||||
type: "console"
|
||||
, category: "memory-usage"
|
||||
},
|
||||
{
|
||||
type: "file"
|
||||
, filename: "memory-usage.log"
|
||||
, category: "memory-usage"
|
||||
, layout: {
|
||||
type: "messagePassThrough"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
logger = log4js.getLogger("memory-test");
|
||||
usage = log4js.getLogger("memory-usage");
|
||||
|
||||
for (i=0; i < 1000000; i++) {
|
||||
if ( (i % 5000) === 0) {
|
||||
usage.info("%d %d", i, process.memoryUsage().rss);
|
||||
}
|
||||
logger.info("Doing something.");
|
||||
}
|
||||
21
examples/patternLayout-tokens.js
Normal file
21
examples/patternLayout-tokens.js
Normal file
@@ -0,0 +1,21 @@
|
||||
var log4js = require('./lib/log4js');
|
||||
|
||||
var config = {
|
||||
"appenders": [
|
||||
{
|
||||
"type": "console",
|
||||
"layout": {
|
||||
"type": "pattern",
|
||||
"pattern": "%[%r (%x{pid}) %p %c -%] %m%n",
|
||||
"tokens": {
|
||||
"pid" : function() { return process.pid; }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
log4js.configure(config, {});
|
||||
|
||||
var logger = log4js.getLogger("app");
|
||||
logger.info("Test log message");
|
||||
43
examples/smtp-appender.js
Normal file
43
examples/smtp-appender.js
Normal file
@@ -0,0 +1,43 @@
|
||||
//Note that smtp appender needs nodemailer to work.
|
||||
//If you haven't got nodemailer installed, you'll get cryptic
|
||||
//"cannot find module" errors when using the smtp appender
|
||||
var log4js = require('../lib/log4js')
|
||||
, log
|
||||
, logmailer
|
||||
, i = 0;
|
||||
log4js.configure({
|
||||
"appenders": [
|
||||
{
|
||||
type: "console",
|
||||
category: "test"
|
||||
},
|
||||
{
|
||||
"type": "smtp",
|
||||
"recipients": "logfilerecipient@logging.com",
|
||||
"sendInterval": 5,
|
||||
"transport": "SMTP",
|
||||
"SMTP": {
|
||||
"host": "smtp.gmail.com",
|
||||
"secureConnection": true,
|
||||
"port": 465,
|
||||
"auth": {
|
||||
"user": "someone@gmail",
|
||||
"pass": "********************"
|
||||
},
|
||||
"debug": true
|
||||
},
|
||||
"category": "mailer"
|
||||
}
|
||||
]
|
||||
});
|
||||
log = log4js.getLogger("test");
|
||||
logmailer = log4js.getLogger("mailer");
|
||||
|
||||
function doTheLogging(x) {
|
||||
log.info("Logging something %d", x);
|
||||
logmailer.info("Logging something %d", x);
|
||||
}
|
||||
|
||||
for ( ; i < 500; i++) {
|
||||
doTheLogging(i);
|
||||
}
|
||||
21
lib/appenders/console.js
Normal file
21
lib/appenders/console.js
Normal file
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
var consoleLog = console.log.bind(console);
|
||||
|
||||
module.exports = function(layouts, levels) {
|
||||
|
||||
function consoleAppender (layout) {
|
||||
layout = layout || layouts.colouredLayout;
|
||||
return function(loggingEvent) {
|
||||
consoleLog(layout(loggingEvent));
|
||||
};
|
||||
}
|
||||
|
||||
return function configure(config) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
return consoleAppender(layout);
|
||||
};
|
||||
|
||||
};
|
||||
53
lib/appenders/dateFile.js
Normal file
53
lib/appenders/dateFile.js
Normal file
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
var streams = require('streamroller')
|
||||
, path = require('path')
|
||||
, os = require('os')
|
||||
, eol = os.EOL || '\n'
|
||||
, openFiles = [];
|
||||
|
||||
//close open files on process exit.
|
||||
process.on('exit', function() {
|
||||
openFiles.forEach(function (file) {
|
||||
file.end();
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = function(layouts, levels) {
|
||||
/**
|
||||
* File appender that rolls files according to a date pattern.
|
||||
* @filename base filename.
|
||||
* @pattern the format that will be added to the end of filename when rolling,
|
||||
* also used to check when to roll files - defaults to '.yyyy-MM-dd'
|
||||
* @layout layout function for log messages - defaults to basicLayout
|
||||
*/
|
||||
function appender(filename, pattern, alwaysIncludePattern, layout) {
|
||||
layout = layout || layouts.basicLayout;
|
||||
|
||||
var logFile = new streams.DateRollingFileStream(
|
||||
filename,
|
||||
pattern,
|
||||
{ alwaysIncludePattern: alwaysIncludePattern }
|
||||
);
|
||||
openFiles.push(logFile);
|
||||
|
||||
return function(logEvent) {
|
||||
logFile.write(layout(logEvent) + eol, "utf8");
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return function configure(config) {
|
||||
var layout;
|
||||
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
|
||||
if (!config.alwaysIncludePattern) {
|
||||
config.alwaysIncludePattern = false;
|
||||
}
|
||||
|
||||
return appender(config.filename, config.pattern, config.alwaysIncludePattern, layout);
|
||||
};
|
||||
|
||||
};
|
||||
78
lib/appenders/file.js
Normal file
78
lib/appenders/file.js
Normal file
@@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
var path = require('path')
|
||||
, fs = require('fs')
|
||||
, streams = require('streamroller')
|
||||
, os = require('os')
|
||||
, eol = os.EOL || '\n'
|
||||
, openFiles = [];
|
||||
|
||||
//close open files on process exit.
|
||||
process.on('exit', function() {
|
||||
openFiles.forEach(function (file) {
|
||||
file.end();
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = function(layouts, levels) {
|
||||
|
||||
/**
|
||||
* File Appender writing the logs to a text file. Supports rolling of logs by size.
|
||||
*
|
||||
* @param file file log messages will be written to
|
||||
* @param layout a function that takes a logevent and returns a string
|
||||
* (defaults to basicLayout).
|
||||
* @param logSize - the maximum size (in bytes) for a log file,
|
||||
* if not provided then logs won't be rotated.
|
||||
* @param numBackups - the number of log files to keep after logSize
|
||||
* has been reached (default 5)
|
||||
*/
|
||||
function fileAppender (file, layout, logSize, numBackups) {
|
||||
var bytesWritten = 0;
|
||||
file = path.normalize(file);
|
||||
layout = layout || layouts.basicLayout;
|
||||
numBackups = numBackups === undefined ? 5 : numBackups;
|
||||
//there has to be at least one backup if logSize has been specified
|
||||
numBackups = numBackups === 0 ? 1 : numBackups;
|
||||
|
||||
function openTheStream(file, fileSize, numFiles) {
|
||||
var stream;
|
||||
if (fileSize) {
|
||||
stream = new streams.RollingFileStream(
|
||||
file,
|
||||
fileSize,
|
||||
numFiles
|
||||
);
|
||||
} else {
|
||||
stream = fs.createWriteStream(
|
||||
file,
|
||||
{ encoding: "utf8",
|
||||
mode: parseInt('0644', 8),
|
||||
flags: 'a' }
|
||||
);
|
||||
}
|
||||
stream.on("error", function (err) {
|
||||
console.error("log4js.fileAppender - Writing to file %s, error happened ", file, err);
|
||||
});
|
||||
return stream;
|
||||
}
|
||||
|
||||
var logFile = openTheStream(file, logSize, numBackups);
|
||||
|
||||
// push file to the stack of open handlers
|
||||
openFiles.push(logFile);
|
||||
|
||||
return function(loggingEvent) {
|
||||
logFile.write(layout(loggingEvent) + eol, "utf8");
|
||||
};
|
||||
}
|
||||
|
||||
return function configure(config) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layouts.layout(config.layout.type, config.layout);
|
||||
}
|
||||
|
||||
return fileAppender(config.filename, layout, config.maxLogSize, config.backups);
|
||||
};
|
||||
|
||||
};
|
||||
40
lib/appenders/logLevelFilter.js
Normal file
40
lib/appenders/logLevelFilter.js
Normal file
@@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
var debug = require('debug')('log4js:logLevelFilter');
|
||||
|
||||
module.exports = function(layouts, levels) {
|
||||
|
||||
function logLevelFilter(allowedLevels, appender) {
|
||||
return function(logEvent) {
|
||||
debug("Checking ", logEvent.level, " against ", allowedLevels);
|
||||
if (allowedLevels.some(function(item) { return item.level === logEvent.level.level; })) {
|
||||
debug("Sending ", logEvent, " to appender ", appender);
|
||||
appender(logEvent);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return function configure(config, appenderByName) {
|
||||
if (!Array.isArray(config.allow)) {
|
||||
throw new Error("No allowed log levels specified.");
|
||||
}
|
||||
|
||||
var allowedLevels = config.allow.map(function(allowed) {
|
||||
var level = levels.toLevel(allowed);
|
||||
if (!level) {
|
||||
throw new Error("Unrecognised log level '" + allowed + "'.");
|
||||
}
|
||||
return level;
|
||||
});
|
||||
|
||||
if (allowedLevels.length === 0) {
|
||||
throw new Error("No allowed log levels specified.");
|
||||
}
|
||||
|
||||
if (!config.appender) {
|
||||
throw new Error("Missing an appender.");
|
||||
}
|
||||
|
||||
return logLevelFilter(allowedLevels, appenderByName(config.appender));
|
||||
};
|
||||
|
||||
};
|
||||
315
lib/layouts.js
Normal file
315
lib/layouts.js
Normal file
@@ -0,0 +1,315 @@
|
||||
"use strict";
|
||||
var dateFormat = require('date-format')
|
||||
, os = require('os')
|
||||
, eol = os.EOL || '\n'
|
||||
, util = require('util')
|
||||
, replacementRegExp = /%[sdj]/g
|
||||
, layoutMakers = {
|
||||
"messagePassThrough": function() { return messagePassThroughLayout; },
|
||||
"basic": function() { return basicLayout; },
|
||||
"colored": function() { return colouredLayout; },
|
||||
"coloured": function() { return colouredLayout; },
|
||||
"pattern": function (config) {
|
||||
return patternLayout(config && config.pattern, config && config.tokens);
|
||||
}
|
||||
}
|
||||
, colours = {
|
||||
ALL: "grey",
|
||||
TRACE: "blue",
|
||||
DEBUG: "cyan",
|
||||
INFO: "green",
|
||||
WARN: "yellow",
|
||||
ERROR: "red",
|
||||
FATAL: "magenta",
|
||||
OFF: "grey"
|
||||
};
|
||||
|
||||
function wrapErrorsWithInspect(items) {
|
||||
return items.map(function(item) {
|
||||
if ((item instanceof Error) && item.stack) {
|
||||
return { inspect: function() { return util.format(item) + '\n' + item.stack; } };
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function formatLogData(logData) {
|
||||
var data = Array.isArray(logData) ? logData : Array.prototype.slice.call(arguments);
|
||||
return util.format.apply(util, wrapErrorsWithInspect(data));
|
||||
}
|
||||
|
||||
var styles = {
|
||||
//styles
|
||||
'bold' : [1, 22],
|
||||
'italic' : [3, 23],
|
||||
'underline' : [4, 24],
|
||||
'inverse' : [7, 27],
|
||||
//grayscale
|
||||
'white' : [37, 39],
|
||||
'grey' : [90, 39],
|
||||
'black' : [90, 39],
|
||||
//colors
|
||||
'blue' : [34, 39],
|
||||
'cyan' : [36, 39],
|
||||
'green' : [32, 39],
|
||||
'magenta' : [35, 39],
|
||||
'red' : [31, 39],
|
||||
'yellow' : [33, 39]
|
||||
};
|
||||
|
||||
function colorizeStart(style) {
|
||||
return style ? '\x1B[' + styles[style][0] + 'm' : '';
|
||||
}
|
||||
function colorizeEnd(style) {
|
||||
return style ? '\x1B[' + styles[style][1] + 'm' : '';
|
||||
}
|
||||
/**
|
||||
* Taken from masylum's fork (https://github.com/masylum/log4js-node)
|
||||
*/
|
||||
function colorize (str, style) {
|
||||
return colorizeStart(style) + str + colorizeEnd(style);
|
||||
}
|
||||
|
||||
function timestampLevelAndCategory(loggingEvent, colour) {
|
||||
var output = colorize(
|
||||
formatLogData(
|
||||
'[%s] [%s] %s - '
|
||||
, dateFormat.asString(loggingEvent.startTime)
|
||||
, loggingEvent.level
|
||||
, loggingEvent.category
|
||||
)
|
||||
, colour
|
||||
);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* BasicLayout is a simple layout for storing the logs. The logs are stored
|
||||
* in following format:
|
||||
* <pre>
|
||||
* [startTime] [logLevel] category - message\n
|
||||
* </pre>
|
||||
*
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
function basicLayout (loggingEvent) {
|
||||
return timestampLevelAndCategory(loggingEvent) + formatLogData(loggingEvent.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* colouredLayout - taken from masylum's fork.
|
||||
* same as basicLayout, but with colours.
|
||||
*/
|
||||
function colouredLayout (loggingEvent) {
|
||||
return timestampLevelAndCategory(
|
||||
loggingEvent,
|
||||
colours[loggingEvent.level.toString()]
|
||||
) + formatLogData(loggingEvent.data);
|
||||
}
|
||||
|
||||
function messagePassThroughLayout (loggingEvent) {
|
||||
return formatLogData(loggingEvent.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* PatternLayout
|
||||
* Format for specifiers is %[padding].[truncation][field]{[format]}
|
||||
* e.g. %5.10p - left pad the log level by 5 characters, up to a max of 10
|
||||
* Fields can be any of:
|
||||
* - %r time in toLocaleTimeString format
|
||||
* - %p log level
|
||||
* - %c log category
|
||||
* - %h hostname
|
||||
* - %m log data
|
||||
* - %d date in various formats
|
||||
* - %% %
|
||||
* - %n newline
|
||||
* - %x{<tokenname>} add dynamic tokens to your log. Tokens are specified in the tokens parameter
|
||||
* You can use %[ and %] to define a colored block.
|
||||
*
|
||||
* Tokens are specified as simple key:value objects.
|
||||
* The key represents the token name whereas the value can be a string or function
|
||||
* which is called to extract the value to put in the log message. If token is not
|
||||
* found, it doesn't replace the field.
|
||||
*
|
||||
* A sample token would be: { "pid" : function() { return process.pid; } }
|
||||
*
|
||||
* Takes a pattern string, array of tokens and returns a layout function.
|
||||
* @param {String} Log format pattern String
|
||||
* @param {object} map object of different tokens
|
||||
* @return {Function}
|
||||
* @author Stephan Strittmatter
|
||||
* @author Jan Schmidle
|
||||
*/
|
||||
function patternLayout (pattern, tokens) {
|
||||
var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
|
||||
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdhmnprx%])(\{([^\}]+)\})?|([^%]+)/;
|
||||
|
||||
pattern = pattern || TTCC_CONVERSION_PATTERN;
|
||||
|
||||
function category(loggingEvent, specifier) {
|
||||
var loggerName = loggingEvent.category;
|
||||
if (specifier) {
|
||||
var precision = parseInt(specifier, 10);
|
||||
var loggerNameBits = loggerName.split(".");
|
||||
if (precision < loggerNameBits.length) {
|
||||
loggerName = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
|
||||
}
|
||||
}
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
var formats = {
|
||||
"ISO8601": dateFormat.ISO8601_FORMAT,
|
||||
"ISO8601_WITH_TZ_OFFSET": dateFormat.ISO8601_WITH_TZ_OFFSET_FORMAT,
|
||||
"ABSOLUTE": dateFormat.ABSOLUTETIME_FORMAT,
|
||||
"DATE": dateFormat.DATETIME_FORMAT
|
||||
};
|
||||
|
||||
function formatAsDate(loggingEvent, specifier) {
|
||||
var format = dateFormat.ISO8601_FORMAT;
|
||||
if (specifier) {
|
||||
format = formats[specifier] || specifier;
|
||||
}
|
||||
// Format the date
|
||||
return dateFormat.asString(format, loggingEvent.startTime);
|
||||
}
|
||||
|
||||
function hostname() {
|
||||
return os.hostname().toString();
|
||||
}
|
||||
|
||||
function formatMessage(loggingEvent) {
|
||||
return formatLogData(loggingEvent.data);
|
||||
}
|
||||
|
||||
function endOfLine() {
|
||||
return eol;
|
||||
}
|
||||
|
||||
function logLevel(loggingEvent) {
|
||||
return loggingEvent.level.toString();
|
||||
}
|
||||
|
||||
function startTime(loggingEvent) {
|
||||
return "" + loggingEvent.startTime.toLocaleTimeString();
|
||||
}
|
||||
|
||||
function startColour(loggingEvent) {
|
||||
return colorizeStart(colours[loggingEvent.level.toString()]);
|
||||
}
|
||||
|
||||
function endColour(loggingEvent) {
|
||||
return colorizeEnd(colours[loggingEvent.level.toString()]);
|
||||
}
|
||||
|
||||
function percent() {
|
||||
return '%';
|
||||
}
|
||||
|
||||
function userDefined(loggingEvent, specifier) {
|
||||
if (typeof(tokens[specifier]) !== 'undefined') {
|
||||
if (typeof(tokens[specifier]) === 'function') {
|
||||
return tokens[specifier](loggingEvent);
|
||||
} else {
|
||||
return tokens[specifier];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var replacers = {
|
||||
'c': category,
|
||||
'd': formatAsDate,
|
||||
'h': hostname,
|
||||
'm': formatMessage,
|
||||
'n': endOfLine,
|
||||
'p': logLevel,
|
||||
'r': startTime,
|
||||
'[': startColour,
|
||||
']': endColour,
|
||||
'%': percent,
|
||||
'x': userDefined
|
||||
};
|
||||
|
||||
function replaceToken(conversionCharacter, loggingEvent, specifier) {
|
||||
return replacers[conversionCharacter](loggingEvent, specifier);
|
||||
}
|
||||
|
||||
function truncate(truncation, toTruncate) {
|
||||
var len;
|
||||
if (truncation) {
|
||||
len = parseInt(truncation.substr(1), 10);
|
||||
return toTruncate.substring(0, len);
|
||||
}
|
||||
|
||||
return toTruncate;
|
||||
}
|
||||
|
||||
function pad(padding, toPad) {
|
||||
var len;
|
||||
if (padding) {
|
||||
if (padding.charAt(0) == "-") {
|
||||
len = parseInt(padding.substr(1), 10);
|
||||
// Right pad with spaces
|
||||
while (toPad.length < len) {
|
||||
toPad += " ";
|
||||
}
|
||||
} else {
|
||||
len = parseInt(padding, 10);
|
||||
// Left pad with spaces
|
||||
while (toPad.length < len) {
|
||||
toPad = " " + toPad;
|
||||
}
|
||||
}
|
||||
}
|
||||
return toPad;
|
||||
}
|
||||
|
||||
return function(loggingEvent) {
|
||||
var formattedString = "";
|
||||
var result;
|
||||
var searchString = pattern;
|
||||
|
||||
while ((result = regex.exec(searchString))) {
|
||||
var matchedString = result[0];
|
||||
var padding = result[1];
|
||||
var truncation = result[2];
|
||||
var conversionCharacter = result[3];
|
||||
var specifier = result[5];
|
||||
var text = result[6];
|
||||
|
||||
// Check if the pattern matched was just normal text
|
||||
if (text) {
|
||||
formattedString += "" + text;
|
||||
} else {
|
||||
// Create a raw replacement string based on the conversion
|
||||
// character and specifier
|
||||
var replacement =
|
||||
replaceToken(conversionCharacter, loggingEvent, specifier) ||
|
||||
matchedString;
|
||||
|
||||
// Format the replacement according to any padding or
|
||||
// truncation specified
|
||||
replacement = truncate(truncation, replacement);
|
||||
replacement = pad(padding, replacement);
|
||||
formattedString += replacement;
|
||||
}
|
||||
searchString = searchString.substr(result.index + result[0].length);
|
||||
}
|
||||
return formattedString;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
basicLayout: basicLayout,
|
||||
messagePassThroughLayout: messagePassThroughLayout,
|
||||
patternLayout: patternLayout,
|
||||
colouredLayout: colouredLayout,
|
||||
coloredLayout: colouredLayout,
|
||||
layout: function(name, config) {
|
||||
return layoutMakers[name] && layoutMakers[name](config);
|
||||
}
|
||||
};
|
||||
84
lib/levels.js
Normal file
84
lib/levels.js
Normal file
@@ -0,0 +1,84 @@
|
||||
"use strict";
|
||||
|
||||
function Level(level, levelStr) {
|
||||
this.level = level;
|
||||
this.levelStr = levelStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts given String to corresponding Level
|
||||
* @param {String} sArg String value of Level OR Log4js.Level
|
||||
* @param {Log4js.Level} defaultLevel default Level, if no String representation
|
||||
* @return Level object
|
||||
* @type Log4js.Level
|
||||
*/
|
||||
function toLevel(sArg, defaultLevel) {
|
||||
|
||||
if (!sArg) {
|
||||
return defaultLevel;
|
||||
}
|
||||
|
||||
if (typeof sArg == "string") {
|
||||
var s = sArg.toUpperCase();
|
||||
if (module.exports[s]) {
|
||||
return module.exports[s];
|
||||
} else {
|
||||
return defaultLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return toLevel(sArg.toString());
|
||||
}
|
||||
|
||||
Level.prototype.toString = function() {
|
||||
return this.levelStr;
|
||||
};
|
||||
|
||||
function convertAndCompare(comparison) {
|
||||
return function(otherLevel) {
|
||||
if (typeof otherLevel === "string") {
|
||||
otherLevel = toLevel(otherLevel);
|
||||
}
|
||||
return comparison.call(this, otherLevel);
|
||||
};
|
||||
}
|
||||
|
||||
Level.prototype.isLessThanOrEqualTo = convertAndCompare(
|
||||
function(otherLevel) {
|
||||
return this.level <= otherLevel.level;
|
||||
}
|
||||
);
|
||||
|
||||
Level.prototype.isGreaterThanOrEqualTo = convertAndCompare(
|
||||
function(otherLevel) {
|
||||
return this.level >= otherLevel.level;
|
||||
}
|
||||
);
|
||||
|
||||
Level.prototype.isEqualTo = convertAndCompare(
|
||||
function(otherLevel) {
|
||||
return this.level === otherLevel.level;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
exports.ALL = new Level(Number.MIN_VALUE, "ALL");
|
||||
exports.TRACE = new Level(5000, "TRACE");
|
||||
exports.DEBUG = new Level(10000, "DEBUG");
|
||||
exports.INFO = new Level(20000, "INFO");
|
||||
exports.WARN = new Level(30000, "WARN");
|
||||
exports.ERROR = new Level(40000, "ERROR");
|
||||
exports.FATAL = new Level(50000, "FATAL");
|
||||
exports.OFF = new Level(Number.MAX_VALUE, "OFF");
|
||||
|
||||
exports.levels = [
|
||||
exports.OFF,
|
||||
exports.TRACE,
|
||||
exports.DEBUG,
|
||||
exports.INFO,
|
||||
exports.WARN,
|
||||
exports.ERROR,
|
||||
exports.FATAL
|
||||
];
|
||||
|
||||
exports.toLevel = toLevel;
|
||||
911
lib/log4js.js
911
lib/log4js.js
@@ -1,3 +1,4 @@
|
||||
"use strict";
|
||||
/*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -12,31 +13,32 @@
|
||||
* 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.
|
||||
*
|
||||
* This file contains all log4js code and is the only file required for logging.
|
||||
*
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
* <pre>
|
||||
* var logging = require('log4js-node')();
|
||||
* //add an appender that logs all messages to stdout.
|
||||
* logging.addAppender(logging.consoleAppender());
|
||||
* //add an appender that logs "some-category" to a file
|
||||
* logging.addAppender(logging.fileAppender("file.log"), "some-category");
|
||||
* var logging = require('log4js');
|
||||
* logging.configure({
|
||||
* appenders: {
|
||||
* "errorFile": { type: "file", filename: "error.log" }
|
||||
* },
|
||||
* categories: {
|
||||
* "default": { level: "ERROR", appenders: [ "errorFile" ] }
|
||||
* }
|
||||
* });
|
||||
* //get a logger
|
||||
* var log = logging.getLogger("some-category");
|
||||
* log.setLevel(logging.levels.TRACE); //set the Level
|
||||
*
|
||||
* var log = logging.getLogger("some-category");
|
||||
*
|
||||
* ...
|
||||
*
|
||||
*
|
||||
* //call the log
|
||||
* log.trace("trace me" );
|
||||
* log.error("oh noes");
|
||||
* </pre>
|
||||
*
|
||||
* NOTE: the authors below are the original browser-based log4js authors
|
||||
* don't try to contact them about bugs in this version :)
|
||||
* @version 1.0
|
||||
* @author Stephan Strittmatter - http://jroller.com/page/stritti
|
||||
* @author Seth Chisamore - http://www.chisamore.com
|
||||
@@ -44,656 +46,231 @@
|
||||
* @static
|
||||
* Website: http://log4js.berlios.de
|
||||
*/
|
||||
module.exports = function (fileSystem, standardOutput, configPaths) {
|
||||
var fs = fileSystem || require('fs'),
|
||||
standardOutput = standardOutput || console.log,
|
||||
configPaths = configPaths || require.paths,
|
||||
sys = require('sys'),
|
||||
events = require('events'),
|
||||
path = require('path'),
|
||||
DEFAULT_CATEGORY = '[default]',
|
||||
ALL_CATEGORIES = '[all]',
|
||||
loggers = {},
|
||||
appenders = {},
|
||||
levels = {
|
||||
ALL: new Level(Number.MIN_VALUE, "ALL", "grey"),
|
||||
TRACE: new Level(5000, "TRACE", "blue"),
|
||||
DEBUG: new Level(10000, "DEBUG", "cyan"),
|
||||
INFO: new Level(20000, "INFO", "green"),
|
||||
WARN: new Level(30000, "WARN", "yellow"),
|
||||
ERROR: new Level(40000, "ERROR", "red"),
|
||||
FATAL: new Level(50000, "FATAL", "magenta"),
|
||||
OFF: new Level(Number.MAX_VALUE, "OFF", "grey")
|
||||
},
|
||||
appenderMakers = {
|
||||
"file": function(config) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layoutMakers[config.layout.type](config.layout);
|
||||
}
|
||||
return fileAppender(config.filename, layout, config.maxLogSize, config.backups, config.pollInterval);
|
||||
},
|
||||
"console": function(config) {
|
||||
var layout;
|
||||
if (config.layout) {
|
||||
layout = layoutMakers[config.layout.type](config.layout);
|
||||
}
|
||||
return consoleAppender(layout);
|
||||
},
|
||||
"logLevelFilter": function(config) {
|
||||
var appender = appenderMakers[config.appender.type](config.appender);
|
||||
return logLevelFilter(config.level, appender);
|
||||
}
|
||||
},
|
||||
layoutMakers = {
|
||||
"messagePassThrough": function() { return messagePassThroughLayout; },
|
||||
"basic": function() { return basicLayout; },
|
||||
"pattern": function (config) {
|
||||
var pattern = config.pattern || undefined;
|
||||
return patternLayout(pattern);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a logger instance. Instance is cached on categoryName level.
|
||||
* @param {String} categoryName name of category to log to.
|
||||
* @return {Logger} instance of logger for the category
|
||||
* @static
|
||||
*/
|
||||
function getLogger (categoryName) {
|
||||
|
||||
// Use default logger if categoryName is not specified or invalid
|
||||
if (!(typeof categoryName == "string")) {
|
||||
categoryName = DEFAULT_CATEGORY;
|
||||
}
|
||||
|
||||
var appenderList;
|
||||
if (!loggers[categoryName]) {
|
||||
// Create the logger for this name if it doesn't already exist
|
||||
loggers[categoryName] = new Logger(categoryName);
|
||||
if (appenders[categoryName]) {
|
||||
appenderList = appenders[categoryName];
|
||||
appenderList.forEach(function(appender) {
|
||||
loggers[categoryName].addListener("log", appender);
|
||||
});
|
||||
}
|
||||
if (appenders[ALL_CATEGORIES]) {
|
||||
appenderList = appenders[ALL_CATEGORIES];
|
||||
appenderList.forEach(function(appender) {
|
||||
loggers[categoryName].addListener("log", appender);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return loggers[categoryName];
|
||||
}
|
||||
|
||||
/**
|
||||
* args are appender, then zero or more categories
|
||||
*/
|
||||
function addAppender () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var appender = args.shift();
|
||||
if (args.length == 0 || args[0] === undefined) {
|
||||
args = [ ALL_CATEGORIES ];
|
||||
}
|
||||
//argument may already be an array
|
||||
if (args[0].forEach) {
|
||||
args = args[0];
|
||||
}
|
||||
|
||||
args.forEach(function(category) {
|
||||
if (!appenders[category]) {
|
||||
appenders[category] = [];
|
||||
}
|
||||
appenders[category].push(appender);
|
||||
|
||||
if (category === ALL_CATEGORIES) {
|
||||
for (var logger in loggers) {
|
||||
if (loggers.hasOwnProperty(logger)) {
|
||||
loggers[logger].addListener("log", appender);
|
||||
}
|
||||
}
|
||||
} else if (loggers[category]) {
|
||||
loggers[category].addListener("log", appender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearAppenders () {
|
||||
appenders = [];
|
||||
for (var logger in loggers) {
|
||||
if (loggers.hasOwnProperty(logger)) {
|
||||
loggers[logger].removeAllListeners("log");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function configure (configurationFile) {
|
||||
if (configurationFile) {
|
||||
try {
|
||||
var config = JSON.parse(fs.readFileSync(configurationFile, "utf8"));
|
||||
configureAppenders(config.appenders);
|
||||
configureLevels(config.levels);
|
||||
} catch (e) {
|
||||
throw new Error("Problem reading log4js config file " + configurationFile + ". Error was " + e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findConfiguration() {
|
||||
//add current directory onto the list of configPaths
|
||||
var paths = ['.'].concat(configPaths);
|
||||
//add this module's directory to the end of the list, so that we pick up the default config
|
||||
paths.push(__dirname);
|
||||
var pathsWithConfig = paths.filter( function (pathToCheck) {
|
||||
try {
|
||||
fs.statSync(path.join(pathToCheck, "log4js.json"));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (pathsWithConfig.length > 0) {
|
||||
return path.join(pathsWithConfig[0], 'log4js.json');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function configureAppenders(appenderList) {
|
||||
clearAppenders();
|
||||
if (appenderList) {
|
||||
appenderList.forEach(function(appenderConfig) {
|
||||
var appender = appenderMakers[appenderConfig.type](appenderConfig);
|
||||
if (appender) {
|
||||
addAppender(appender, appenderConfig.category);
|
||||
} else {
|
||||
throw new Error("log4js configuration problem for "+sys.inspect(appenderConfig));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
addAppender(consoleAppender);
|
||||
}
|
||||
}
|
||||
|
||||
function configureLevels(levels) {
|
||||
if (levels) {
|
||||
for (var category in levels) {
|
||||
if (levels.hasOwnProperty(category)) {
|
||||
getLogger(category).setLevel(levels[category]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Level(level, levelStr, colour) {
|
||||
this.level = level;
|
||||
this.levelStr = levelStr;
|
||||
this.colour = colour;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts given String to corresponding Level
|
||||
* @param {String} sArg String value of Level
|
||||
* @param {Log4js.Level} defaultLevel default Level, if no String representation
|
||||
* @return Level object
|
||||
* @type Log4js.Level
|
||||
*/
|
||||
Level.toLevel = function(sArg, defaultLevel) {
|
||||
|
||||
if (sArg === null) {
|
||||
return defaultLevel;
|
||||
}
|
||||
|
||||
if (typeof sArg == "string") {
|
||||
var s = sArg.toUpperCase();
|
||||
if (levels[s]) {
|
||||
return levels[s];
|
||||
}
|
||||
}
|
||||
return defaultLevel;
|
||||
};
|
||||
|
||||
Level.prototype.toString = function() {
|
||||
return this.levelStr;
|
||||
};
|
||||
|
||||
Level.prototype.isLessThanOrEqualTo = function(otherLevel) {
|
||||
return this.level <= otherLevel.level;
|
||||
};
|
||||
|
||||
Level.prototype.isGreaterThanOrEqualTo = function(otherLevel) {
|
||||
return this.level >= otherLevel.level;
|
||||
};
|
||||
|
||||
/**
|
||||
* Models a logging event.
|
||||
* @constructor
|
||||
* @param {String} categoryName name of category
|
||||
* @param {Log4js.Level} level level of message
|
||||
* @param {String} message message to log
|
||||
* @param {Log4js.Logger} logger the associated logger
|
||||
* @author Seth Chisamore
|
||||
*/
|
||||
function LoggingEvent (categoryName, level, message, exception, logger) {
|
||||
this.startTime = new Date();
|
||||
this.categoryName = categoryName;
|
||||
this.message = message;
|
||||
this.exception = exception;
|
||||
this.level = level;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger to log messages.
|
||||
* use {@see Log4js#getLogger(String)} to get an instance.
|
||||
* @constructor
|
||||
* @param name name of category to log to
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
function Logger (name, level) {
|
||||
this.category = name || DEFAULT_CATEGORY;
|
||||
this.level = Level.toLevel(level, levels.TRACE);
|
||||
}
|
||||
sys.inherits(Logger, events.EventEmitter);
|
||||
|
||||
Logger.prototype.setLevel = function(level) {
|
||||
this.level = Level.toLevel(level, levels.TRACE);
|
||||
};
|
||||
|
||||
Logger.prototype.log = function(logLevel, message, exception) {
|
||||
var loggingEvent = new LoggingEvent(this.category, logLevel, message, exception, this);
|
||||
this.emit("log", loggingEvent);
|
||||
};
|
||||
|
||||
Logger.prototype.isLevelEnabled = function(otherLevel) {
|
||||
return this.level.isLessThanOrEqualTo(otherLevel);
|
||||
};
|
||||
|
||||
['Trace','Debug','Info','Warn','Error','Fatal'].forEach(
|
||||
function(levelString) {
|
||||
var level = Level.toLevel(levelString);
|
||||
Logger.prototype['is'+levelString+'Enabled'] = function() {
|
||||
return this.isLevelEnabled(level);
|
||||
};
|
||||
|
||||
Logger.prototype[levelString.toLowerCase()] = function (message, exception) {
|
||||
if (this.isLevelEnabled(level)) {
|
||||
this.log(level, message, exception);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the default logger instance.
|
||||
* @return {Logger} instance of default logger
|
||||
* @static
|
||||
*/
|
||||
function getDefaultLogger () {
|
||||
return getLogger(DEFAULT_CATEGORY);
|
||||
}
|
||||
|
||||
function consoleAppender (layout) {
|
||||
layout = layout || colouredLayout;
|
||||
return function(loggingEvent) {
|
||||
standardOutput(layout(loggingEvent));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 filePollInterval - the time in seconds between file size checks (default 30s)
|
||||
*/
|
||||
function fileAppender (file, layout, logSize, numBackups, filePollInterval) {
|
||||
layout = layout || basicLayout;
|
||||
//syncs are generally bad, but we need
|
||||
//the file to be open before we start doing any writing.
|
||||
var logFile = fs.openSync(file, 'a', 0644);
|
||||
|
||||
if (logSize > 0) {
|
||||
setupLogRolling(logFile, file, logSize, numBackups || 5, (filePollInterval * 1000) || 30000);
|
||||
}
|
||||
|
||||
return function(loggingEvent) {
|
||||
fs.write(logFile, layout(loggingEvent)+'\n', null, "utf8");
|
||||
};
|
||||
}
|
||||
|
||||
function setupLogRolling (logFile, filename, logSize, numBackups, filePollInterval) {
|
||||
fs.watchFile(filename,
|
||||
{
|
||||
persistent: false,
|
||||
interval: filePollInterval
|
||||
},
|
||||
function (curr, prev) {
|
||||
if (curr.size >= logSize) {
|
||||
rollThatLog(logFile, filename, numBackups);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function rollThatLog (logFile, filename, numBackups) {
|
||||
//doing all of this fs stuff sync, because I don't want to lose any log events.
|
||||
//first close the current one.
|
||||
fs.closeSync(logFile);
|
||||
//roll the backups (rename file.n-1 to file.n, where n <= numBackups)
|
||||
for (var i=numBackups; i > 0; i--) {
|
||||
if (i > 1) {
|
||||
if (fileExists(filename + '.' + (i-1))) {
|
||||
fs.renameSync(filename+'.'+(i-1), filename+'.'+i);
|
||||
}
|
||||
} else {
|
||||
fs.renameSync(filename, filename+'.1');
|
||||
}
|
||||
}
|
||||
//open it up again
|
||||
logFile = fs.openSync(filename, 'a', 0644);
|
||||
}
|
||||
|
||||
function fileExists (filename) {
|
||||
try {
|
||||
fs.statSync(filename);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function logLevelFilter (levelString, appender) {
|
||||
var level = Level.toLevel(levelString);
|
||||
return function(logEvent) {
|
||||
if (logEvent.level.isGreaterThanOrEqualTo(level)) {
|
||||
appender(logEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BasicLayout is a simple layout for storing the logs. The logs are stored
|
||||
* in following format:
|
||||
* <pre>
|
||||
* [startTime] [logLevel] categoryName - message\n
|
||||
* </pre>
|
||||
*
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
function basicLayout (loggingEvent) {
|
||||
var timestampLevelAndCategory = '[' + loggingEvent.startTime.toFormattedString() + '] ';
|
||||
timestampLevelAndCategory += '[' + loggingEvent.level.toString() + '] ';
|
||||
timestampLevelAndCategory += loggingEvent.categoryName + ' - ';
|
||||
|
||||
var output = timestampLevelAndCategory + loggingEvent.message;
|
||||
|
||||
if (loggingEvent.exception) {
|
||||
output += '\n'
|
||||
output += timestampLevelAndCategory;
|
||||
if (loggingEvent.exception.stack) {
|
||||
output += loggingEvent.exception.stack;
|
||||
} else {
|
||||
output += loggingEvent.exception.name + ': '+loggingEvent.exception.message;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from masylum's fork (https://github.com/masylum/log4js-node)
|
||||
*/
|
||||
function colorize (str, style) {
|
||||
var styles = {
|
||||
//styles
|
||||
'bold' : [1, 22],
|
||||
'italic' : [3, 23],
|
||||
'underline' : [4, 24],
|
||||
'inverse' : [7, 27],
|
||||
//grayscale
|
||||
'white' : [37, 39],
|
||||
'grey' : [90, 39],
|
||||
'black' : [90, 39],
|
||||
//colors
|
||||
'blue' : [34, 39],
|
||||
'cyan' : [36, 39],
|
||||
'green' : [32, 39],
|
||||
'magenta' : [35, 39],
|
||||
'red' : [31, 39],
|
||||
'yellow' : [33, 39]
|
||||
};
|
||||
return '\033[' + styles[style][0] + 'm' + str +
|
||||
'\033[' + styles[style][1] + 'm';
|
||||
}
|
||||
|
||||
/**
|
||||
* colouredLayout - taken from masylum's fork.
|
||||
* same as basicLayout, but with colours.
|
||||
*/
|
||||
function colouredLayout (loggingEvent) {
|
||||
var timestampLevelAndCategory = colorize('[' + loggingEvent.startTime.toFormattedString() + '] ', 'grey');
|
||||
timestampLevelAndCategory += colorize(
|
||||
'[' + loggingEvent.level.toString() + '] ', loggingEvent.level.colour
|
||||
);
|
||||
timestampLevelAndCategory += colorize(loggingEvent.categoryName + ' - ', 'grey');
|
||||
|
||||
var output = timestampLevelAndCategory + loggingEvent.message;
|
||||
|
||||
if (loggingEvent.exception) {
|
||||
output += '\n'
|
||||
output += timestampLevelAndCategory;
|
||||
if (loggingEvent.exception.stack) {
|
||||
output += loggingEvent.exception.stack;
|
||||
} else {
|
||||
output += loggingEvent.exception.name + ': '+loggingEvent.exception.message;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function messagePassThroughLayout (loggingEvent) {
|
||||
return loggingEvent.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* PatternLayout
|
||||
* Takes a pattern string and returns a layout function.
|
||||
* @author Stephan Strittmatter
|
||||
*/
|
||||
function patternLayout (pattern) {
|
||||
var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
|
||||
var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([cdmnpr%])(\{([^\}]+)\})?|([^%]+)/;
|
||||
|
||||
pattern = pattern || patternLayout.TTCC_CONVERSION_PATTERN;
|
||||
|
||||
return function(loggingEvent) {
|
||||
var formattedString = "";
|
||||
var result;
|
||||
var searchString = this.pattern;
|
||||
|
||||
while ((result = regex.exec(searchString))) {
|
||||
var matchedString = result[0];
|
||||
var padding = result[1];
|
||||
var truncation = result[2];
|
||||
var conversionCharacter = result[3];
|
||||
var specifier = result[5];
|
||||
var text = result[6];
|
||||
|
||||
// Check if the pattern matched was just normal text
|
||||
if (text) {
|
||||
formattedString += "" + text;
|
||||
} else {
|
||||
// Create a raw replacement string based on the conversion
|
||||
// character and specifier
|
||||
var replacement = "";
|
||||
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 dateFormat = Date.ISO8601_FORMAT;
|
||||
if (specifier) {
|
||||
dateFormat = specifier;
|
||||
// Pick up special cases
|
||||
if (dateFormat == "ISO8601") {
|
||||
dateFormat = Date.ISO8601_FORMAT;
|
||||
} else if (dateFormat == "ABSOLUTE") {
|
||||
dateFormat = Date.ABSOLUTETIME_FORMAT;
|
||||
} else if (dateFormat == "DATE") {
|
||||
dateFormat = Date.DATETIME_FORMAT;
|
||||
}
|
||||
}
|
||||
// Format the date
|
||||
replacement = loggingEvent.startTime.toFormattedString(dateFormat);
|
||||
break;
|
||||
case "m":
|
||||
replacement = loggingEvent.message;
|
||||
break;
|
||||
case "n":
|
||||
replacement = "\n";
|
||||
break;
|
||||
case "p":
|
||||
replacement = loggingEvent.level.toString();
|
||||
break;
|
||||
case "r":
|
||||
replacement = "" + loggingEvent.startTime.toLocaleTimeString();
|
||||
break;
|
||||
case "%":
|
||||
replacement = "%";
|
||||
break;
|
||||
default:
|
||||
replacement = matchedString;
|
||||
break;
|
||||
}
|
||||
// Format the replacement according to any padding or
|
||||
// truncation specified
|
||||
|
||||
var len;
|
||||
|
||||
// First, truncation
|
||||
if (truncation) {
|
||||
len = parseInt(truncation.substr(1), 10);
|
||||
replacement = replacement.substring(0, len);
|
||||
}
|
||||
// Next, padding
|
||||
if (padding) {
|
||||
if (padding.charAt(0) == "-") {
|
||||
len = parseInt(padding.substr(1), 10);
|
||||
// Right pad with spaces
|
||||
while (replacement.length < len) {
|
||||
replacement += " ";
|
||||
}
|
||||
} else {
|
||||
len = parseInt(padding, 10);
|
||||
// Left pad with spaces
|
||||
while (replacement.length < len) {
|
||||
replacement = " " + replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
formattedString += replacement;
|
||||
}
|
||||
searchString = searchString.substr(result.index + result[0].length);
|
||||
}
|
||||
return formattedString;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
//set ourselves up if we can find a default log4js.json
|
||||
configure(findConfiguration());
|
||||
|
||||
return {
|
||||
getLogger: getLogger,
|
||||
getDefaultLogger: getDefaultLogger,
|
||||
|
||||
addAppender: addAppender,
|
||||
clearAppenders: clearAppenders,
|
||||
configure: configure,
|
||||
|
||||
levels: levels,
|
||||
|
||||
consoleAppender: consoleAppender,
|
||||
fileAppender: fileAppender,
|
||||
logLevelFilter: logLevelFilter,
|
||||
|
||||
basicLayout: basicLayout,
|
||||
messagePassThroughLayout: messagePassThroughLayout,
|
||||
patternLayout: patternLayout,
|
||||
colouredLayout: colouredLayout,
|
||||
coloredLayout: colouredLayout
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Date.ISO8601_FORMAT = "yyyy-MM-dd hh:mm:ss.SSS";
|
||||
Date.ISO8601_WITH_TZ_OFFSET_FORMAT = "yyyy-MM-ddThh:mm:ssO";
|
||||
Date.DATETIME_FORMAT = "dd MMM YYYY hh:mm:ss.SSS";
|
||||
Date.ABSOLUTETIME_FORMAT = "hh:mm:ss.SSS";
|
||||
|
||||
Date.prototype.toFormattedString = function(format) {
|
||||
format = format || Date.ISO8601_FORMAT;
|
||||
|
||||
var vDay = addZero(this.getDate());
|
||||
var vMonth = addZero(this.getMonth()+1);
|
||||
var vYearLong = addZero(this.getFullYear());
|
||||
var vYearShort = addZero(this.getFullYear().toString().substring(3,4));
|
||||
var vYear = (format.indexOf("yyyy") > -1 ? vYearLong : vYearShort);
|
||||
var vHour = addZero(this.getHours());
|
||||
var vMinute = addZero(this.getMinutes());
|
||||
var vSecond = addZero(this.getSeconds());
|
||||
var vMillisecond = padWithZeros(this.getMilliseconds(), 3);
|
||||
var vTimeZone = offset(this);
|
||||
var formatted = format
|
||||
.replace(/dd/g, vDay)
|
||||
.replace(/MM/g, vMonth)
|
||||
.replace(/y{1,4}/g, vYear)
|
||||
.replace(/hh/g, vHour)
|
||||
.replace(/mm/g, vMinute)
|
||||
.replace(/ss/g, vSecond)
|
||||
.replace(/SSS/g, vMillisecond)
|
||||
.replace(/O/g, vTimeZone);
|
||||
return formatted;
|
||||
|
||||
function padWithZeros(vNumber, width) {
|
||||
var numAsString = vNumber + "";
|
||||
while (numAsString.length < width) {
|
||||
numAsString = "0" + numAsString;
|
||||
}
|
||||
return numAsString;
|
||||
}
|
||||
|
||||
function addZero(vNumber) {
|
||||
return padWithZeros(vNumber, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the TimeOffest
|
||||
* Thanks to http://www.svendtofte.com/code/date_format/
|
||||
* @private
|
||||
*/
|
||||
function offset(date) {
|
||||
// Difference to Greenwich time (GMT) in hours
|
||||
var os = Math.abs(date.getTimezoneOffset());
|
||||
var h = String(Math.floor(os/60));
|
||||
var m = String(os%60);
|
||||
h.length == 1? h = "0"+h:1;
|
||||
m.length == 1? m = "0"+m:1;
|
||||
return date.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m;
|
||||
}
|
||||
var debug = require('debug')('log4js:core')
|
||||
, fs = require('fs')
|
||||
, cluster = require('cluster')
|
||||
, util = require('util')
|
||||
, layouts = require('./layouts')
|
||||
, levels = require('./levels')
|
||||
, Logger = require('./logger')
|
||||
, appenders = {}
|
||||
, categories = {}
|
||||
, appenderMakers = {}
|
||||
, defaultConfig = {
|
||||
appenders: {
|
||||
console: { type: "console" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: levels.DEBUG, appenders: [ "console" ] }
|
||||
}
|
||||
};
|
||||
|
||||
function serialise(event) {
|
||||
return JSON.stringify(event);
|
||||
}
|
||||
|
||||
function deserialise(serialised) {
|
||||
var event;
|
||||
try {
|
||||
event = JSON.parse(serialised);
|
||||
event.startTime = new Date(event.startTime);
|
||||
event.level = levels.toLevel(event.level.levelStr);
|
||||
} catch(e) {
|
||||
event = {
|
||||
startTime: new Date(),
|
||||
category: 'log4js',
|
||||
level: levels.ERROR,
|
||||
data: [ 'Unable to parse log:', serialised ]
|
||||
};
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
//in a multi-process node environment, worker loggers will use
|
||||
//process.send
|
||||
cluster.on('fork', function(worker) {
|
||||
debug('listening to worker: ', worker);
|
||||
worker.on('message', function(message) {
|
||||
if (message.type && message.type === '::log4js-message') {
|
||||
debug("received message: ", message.event);
|
||||
dispatch(deserialise(message.event));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a logger instance.
|
||||
* @param {String} category to log to.
|
||||
* @return {Logger} instance of logger for the category
|
||||
* @static
|
||||
*/
|
||||
function getLogger (category) {
|
||||
debug("getLogger(", category, ")");
|
||||
|
||||
return new Logger(
|
||||
cluster.isMaster ? dispatch : workerDispatch,
|
||||
category || 'default'
|
||||
);
|
||||
}
|
||||
|
||||
function workerDispatch(event) {
|
||||
process.send({ type: "::log4js-message", event: serialise(event) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Log event routing to appenders
|
||||
* This would be a good place to implement category hierarchies/wildcards, etc
|
||||
*/
|
||||
function dispatch(event) {
|
||||
debug("event is ", event);
|
||||
var category = categories[event.category] || categories.default;
|
||||
debug(
|
||||
"category.level[",
|
||||
category.level,
|
||||
"] <= ",
|
||||
event.level,
|
||||
" ? ",
|
||||
category.level.isLessThanOrEqualTo(event.level)
|
||||
);
|
||||
|
||||
if (category.level.isLessThanOrEqualTo(event.level)) {
|
||||
category.appenders.forEach(function(appender) {
|
||||
appenders[appender](event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function load(file) {
|
||||
debug("loading ", file);
|
||||
var contents = fs.readFileSync(file, "utf-8");
|
||||
debug("file contents ", contents);
|
||||
return JSON.parse(contents);
|
||||
}
|
||||
|
||||
function configure(configurationFileOrObject) {
|
||||
debug("configure(", configurationFileOrObject, ")");
|
||||
debug("process.env.LOG4JS_CONFIG = ", process.env.LOG4JS_CONFIG);
|
||||
|
||||
var filename, config = process.env.LOG4JS_CONFIG || configurationFileOrObject;
|
||||
|
||||
debug("config ", config);
|
||||
|
||||
if (!config || !(typeof config === 'string' || typeof config === 'object')) {
|
||||
throw new Error("You must specify configuration as an object or a filename.");
|
||||
}
|
||||
|
||||
if (typeof config === 'string') {
|
||||
debug("config is string");
|
||||
filename = config;
|
||||
config = load(filename);
|
||||
}
|
||||
|
||||
if (!config.appenders || !Object.keys(config.appenders).length) {
|
||||
throw new Error("You must specify at least one appender.");
|
||||
}
|
||||
|
||||
configureAppenders(config.appenders);
|
||||
|
||||
validateCategories(config.categories);
|
||||
categories = config.categories;
|
||||
|
||||
}
|
||||
|
||||
function validateCategories(cats) {
|
||||
if (!cats || !cats.default) {
|
||||
throw new Error("You must specify an appender for the default category");
|
||||
}
|
||||
|
||||
Object.keys(cats).forEach(function(categoryName) {
|
||||
var category = cats[categoryName], inputLevel = category.level;
|
||||
if (!category.level) {
|
||||
throw new Error("You must specify a level for category '" + categoryName + "'.");
|
||||
}
|
||||
category.level = levels.toLevel(inputLevel);
|
||||
if (!category.level) {
|
||||
throw new Error(
|
||||
"Level '" + inputLevel +
|
||||
"' is not valid for category '" + categoryName +
|
||||
"'. Acceptable values are: " + levels.levels.join(', ') + "."
|
||||
);
|
||||
}
|
||||
|
||||
if (!category.appenders || !category.appenders.length) {
|
||||
throw new Error("You must specify an appender for category '" + categoryName + "'.");
|
||||
}
|
||||
|
||||
category.appenders.forEach(function(appender) {
|
||||
if (!appenders[appender]) {
|
||||
throw new Error(
|
||||
"Appender '" + appender +
|
||||
"' for category '" + categoryName +
|
||||
"' does not exist. Known appenders are: " + Object.keys(appenders).join(', ') + "."
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clearAppenders () {
|
||||
debug("clearing appenders and appender makers");
|
||||
appenders = {};
|
||||
appenderMakers = {};
|
||||
}
|
||||
|
||||
function appenderByName(name) {
|
||||
if (appenders.hasOwnProperty(name)) {
|
||||
return appenders[name];
|
||||
} else {
|
||||
throw new Error("Appender '" + name + "' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
function configureAppenders(appenderMap) {
|
||||
clearAppenders();
|
||||
Object.keys(appenderMap).forEach(function(appenderName) {
|
||||
var appender, appenderConfig = appenderMap[appenderName];
|
||||
loadAppender(appenderConfig.type);
|
||||
try {
|
||||
appenders[appenderName] = appenderMakers[appenderConfig.type](
|
||||
appenderConfig,
|
||||
appenderByName
|
||||
);
|
||||
} catch(e) {
|
||||
throw new Error(
|
||||
"log4js configuration problem for appender '" + appenderName +
|
||||
"'. Error was " + e.stack
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadAppender(appender) {
|
||||
var appenderModule;
|
||||
|
||||
if (!appenderMakers[appender]) {
|
||||
debug("Loading appender ", appender);
|
||||
try {
|
||||
appenderModule = require('./appenders/' + appender);
|
||||
} catch (e) {
|
||||
try {
|
||||
debug("Appender ", appender, " is not a core log4js appender.");
|
||||
appenderModule = require(appender);
|
||||
} catch (err) {
|
||||
debug("Error loading appender %s: ", appender, err);
|
||||
throw new Error("Could not load appender of type '" + appender + "'.");
|
||||
}
|
||||
}
|
||||
appenderMakers[appender] = appenderModule(layouts, levels);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getLogger: getLogger,
|
||||
configure: configure
|
||||
};
|
||||
|
||||
//set ourselves up
|
||||
debug("Starting configuration");
|
||||
configure(defaultConfig);
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"appenders": [
|
||||
{
|
||||
"type": "console"
|
||||
}
|
||||
]
|
||||
}
|
||||
49
lib/logger.js
Normal file
49
lib/logger.js
Normal file
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
var debug = require('debug')('log4js:logger')
|
||||
, levels = require('./levels');
|
||||
|
||||
module.exports = function Logger(dispatch, category) {
|
||||
if (typeof dispatch !== 'function') {
|
||||
throw new Error("Logger must have a dispatch delegate.");
|
||||
}
|
||||
|
||||
if (!category) {
|
||||
throw new Error("Logger must have a category.");
|
||||
}
|
||||
|
||||
function log() {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
, logLevel = args.shift()
|
||||
, loggingEvent = new LoggingEvent(category, logLevel, args);
|
||||
debug("Logging event ", loggingEvent, " to dispatch = ", dispatch);
|
||||
dispatch(loggingEvent);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
['trace','debug','info','warn','error','fatal'].forEach(
|
||||
function(level) {
|
||||
self[level] = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
args.unshift(level);
|
||||
log.apply(this, args);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Models a logging event.
|
||||
* @constructor
|
||||
* @param {String} category name of category
|
||||
* @param {Log4js.Level} level level of message
|
||||
* @param {Array} data objects to log
|
||||
* @author Seth Chisamore
|
||||
*/
|
||||
function LoggingEvent (category, level, data) {
|
||||
this.startTime = new Date();
|
||||
this.category = category;
|
||||
this.data = data;
|
||||
this.level = levels.toLevel(level);
|
||||
}
|
||||
|
||||
64
package.json
64
package.json
@@ -1,24 +1,44 @@
|
||||
{
|
||||
"name": "log4js",
|
||||
"version": "0.2.0",
|
||||
"description": "Port of Log4js to work with node.",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"log",
|
||||
"log4j",
|
||||
"node"
|
||||
],
|
||||
"main": "./lib/log4js",
|
||||
"author": "Gareth Jones <gareth.jones@sensis.com.au>",
|
||||
"bugs": {
|
||||
"web": "http://github.com/csausdev/log4js-node/issues"
|
||||
},
|
||||
"engines": [ "node >=0.1.100" ],
|
||||
"scripts": {
|
||||
"test": "vows test/logging.js"
|
||||
},
|
||||
"directories": {
|
||||
"test": "test",
|
||||
"lib": "lib"
|
||||
}
|
||||
"name": "log4js",
|
||||
"version": "0.7.0",
|
||||
"description": "Port of Log4js to work with node.",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"log",
|
||||
"log4j",
|
||||
"node"
|
||||
],
|
||||
"main": "./lib/log4js",
|
||||
"author": "Gareth Jones <gareth.jones@sensis.com.au>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nomiddlename/log4js-node.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/nomiddlename/log4js-node/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --recursive"
|
||||
},
|
||||
"directories": {
|
||||
"test": "test",
|
||||
"lib": "lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "~0.7.2",
|
||||
"streamroller": "0.0.1",
|
||||
"date-format": "0.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "0.1.15",
|
||||
"sandboxed-module": "0.1.3",
|
||||
"mocha": "~1.12.0",
|
||||
"should": "~1.2.2"
|
||||
},
|
||||
"browser": {
|
||||
"os": false
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 154 B |
Binary file not shown.
|
Before Width: | Height: | Size: 321 B |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 145 B |
@@ -1,149 +0,0 @@
|
||||
body.jspec {
|
||||
margin: 45px 0;
|
||||
font: 12px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
|
||||
background: #efefef url(images/bg.png) top left repeat-x;
|
||||
text-align: center;
|
||||
}
|
||||
#jspec {
|
||||
margin: 0 auto;
|
||||
padding-top: 30px;
|
||||
width: 1008px;
|
||||
background: url(images/vr.png) top left repeat-y;
|
||||
text-align: left;
|
||||
}
|
||||
#jspec-top {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 1008px;
|
||||
height: 40px;
|
||||
background: url(images/sprites.bg.png) top left no-repeat;
|
||||
}
|
||||
#jspec-bottom {
|
||||
margin: 0 auto;
|
||||
width: 1008px;
|
||||
height: 15px;
|
||||
background: url(images/sprites.bg.png) bottom left no-repeat;
|
||||
}
|
||||
#jspec .loading {
|
||||
margin-top: -45px;
|
||||
width: 1008px;
|
||||
height: 80px;
|
||||
background: url(images/loading.gif) 50% 50% no-repeat;
|
||||
}
|
||||
#jspec-title {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 20px;
|
||||
width: 160px;
|
||||
font-size: 22px;
|
||||
font-weight: normal;
|
||||
background: url(images/sprites.png) 0 -126px no-repeat;
|
||||
text-align: center;
|
||||
}
|
||||
#jspec-title em {
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
color: #BCC8D1;
|
||||
}
|
||||
#jspec-report * {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
#jspec-report {
|
||||
padding: 15px 40px;
|
||||
font: 11px "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
|
||||
color: #7B8D9B;
|
||||
}
|
||||
#jspec-report.has-failures {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
#jspec-report .hidden {
|
||||
display: none;
|
||||
}
|
||||
#jspec-report .heading {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#jspec-report .heading span {
|
||||
padding-right: 10px;
|
||||
}
|
||||
#jspec-report .heading .passes em {
|
||||
color: #0ea0eb;
|
||||
}
|
||||
#jspec-report .heading .failures em {
|
||||
color: #FA1616;
|
||||
}
|
||||
#jspec-report table {
|
||||
font-size: 11px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#jspec-report td {
|
||||
padding: 8px;
|
||||
text-indent: 30px;
|
||||
color: #7B8D9B;
|
||||
}
|
||||
#jspec-report tr.body {
|
||||
display: none;
|
||||
}
|
||||
#jspec-report tr.body pre {
|
||||
margin: 0;
|
||||
padding: 0 0 5px 25px;
|
||||
}
|
||||
#jspec-report tr.even:hover + tr.body,
|
||||
#jspec-report tr.odd:hover + tr.body {
|
||||
display: block;
|
||||
}
|
||||
#jspec-report tr td:first-child em {
|
||||
display: block;
|
||||
clear: both;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
color: #7B8D9B;
|
||||
}
|
||||
#jspec-report tr.even:hover,
|
||||
#jspec-report tr.odd:hover {
|
||||
text-shadow: 1px 1px 1px #fff;
|
||||
background: #F2F5F7;
|
||||
}
|
||||
#jspec-report td + td {
|
||||
padding-right: 0;
|
||||
width: 15px;
|
||||
}
|
||||
#jspec-report td.pass {
|
||||
background: url(images/sprites.png) 3px -7px no-repeat;
|
||||
}
|
||||
#jspec-report td.fail {
|
||||
background: url(images/sprites.png) 3px -158px no-repeat;
|
||||
font-weight: bold;
|
||||
color: #FC0D0D;
|
||||
}
|
||||
#jspec-report td.requires-implementation {
|
||||
background: url(images/sprites.png) 3px -333px no-repeat;
|
||||
}
|
||||
#jspec-report tr.description td {
|
||||
margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-indent: 0;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
#jspec-report tr.description:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
#jspec-report .assertion {
|
||||
display: block;
|
||||
float: left;
|
||||
margin: 0 0 0 1px;
|
||||
padding: 0;
|
||||
width: 1px;
|
||||
height: 5px;
|
||||
background: #7B8D9B;
|
||||
}
|
||||
#jspec-report .assertion.failed {
|
||||
background: red;
|
||||
}
|
||||
.jspec-sandbox {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
|
||||
// JSpec - Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
;(function(){
|
||||
|
||||
Growl = {
|
||||
|
||||
// --- Version
|
||||
|
||||
version: '1.0.0',
|
||||
|
||||
/**
|
||||
* Execute the given _cmd_, returning an array of lines from stdout.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Growl.exec('growlnotify', '-m', msg)
|
||||
*
|
||||
* @param {string ...} cmd
|
||||
* @return {array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exec: function(cmd) {
|
||||
var lines = [], line
|
||||
with (JavaImporter(java.lang, java.io)) {
|
||||
var proccess = Runtime.getRuntime().exec(Array.prototype.slice.call(arguments))
|
||||
var stream = new DataInputStream(proccess.getInputStream())
|
||||
while (line = stream.readLine())
|
||||
lines.push(line + '')
|
||||
stream.close()
|
||||
}
|
||||
return lines
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the extension of the given _path_ or null.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
extname: function(path) {
|
||||
return path.lastIndexOf('.') != -1 ?
|
||||
path.slice(path.lastIndexOf('.') + 1, path.length) :
|
||||
null
|
||||
},
|
||||
|
||||
/**
|
||||
* Version of the 'growlnotify' binary.
|
||||
*
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
binVersion: function() {
|
||||
try { return this.exec('growlnotify', '-v')[0].split(' ')[1] } catch (e) {}
|
||||
},
|
||||
|
||||
/**
|
||||
* Send growl notification _msg_ with _options_.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - title Notification title
|
||||
* - sticky Make the notification stick (defaults to false)
|
||||
* - name Application name (defaults to growlnotify)
|
||||
* - image
|
||||
* - path to an icon sets --iconpath
|
||||
* - path to an image sets --image
|
||||
* - capitalized word sets --appIcon
|
||||
* - filename uses extname as --icon
|
||||
* - otherwise treated as --icon
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Growl.notify('New email')
|
||||
* Growl.notify('5 new emails', { title: 'Thunderbird' })
|
||||
*
|
||||
* @param {string} msg
|
||||
* @param {options} hash
|
||||
* @api public
|
||||
*/
|
||||
|
||||
notify: function(msg, options) {
|
||||
options = options || {}
|
||||
var args = ['growlnotify', '-m', msg]
|
||||
if (!this.binVersion()) throw new Error('growlnotify executable is required')
|
||||
if (image = options.image) {
|
||||
var flag, ext = this.extname(image)
|
||||
flag = flag || ext == 'icns' && 'iconpath'
|
||||
flag = flag || /^[A-Z]/.test(image) && 'appIcon'
|
||||
flag = flag || /^png|gif|jpe?g$/.test(ext) && 'image'
|
||||
flag = flag || ext && (image = ext) && 'icon'
|
||||
flag = flag || 'icon'
|
||||
args.push('--' + flag, image)
|
||||
}
|
||||
if (options.sticky) args.push('--sticky')
|
||||
if (options.name) args.push('--name', options.name)
|
||||
if (options.title) args.push(options.title)
|
||||
this.exec.apply(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
JSpec.include({
|
||||
name: 'Growl',
|
||||
reporting: function(options){
|
||||
var stats = JSpec.stats
|
||||
if (stats.failures) Growl.notify('failed ' + stats.failures + ' assertions', { title: 'JSpec'})
|
||||
else Growl.notify('passed ' + stats.passes + ' assertions', { title: 'JSpec' })
|
||||
}
|
||||
})
|
||||
|
||||
})()
|
||||
@@ -1,71 +0,0 @@
|
||||
|
||||
// JSpec - jQuery - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
JSpec
|
||||
.requires('jQuery', 'when using jspec.jquery.js')
|
||||
.include({
|
||||
name: 'jQuery',
|
||||
|
||||
// --- Initialize
|
||||
|
||||
init : function() {
|
||||
jQuery.ajaxSetup({ async: false })
|
||||
},
|
||||
|
||||
// --- Utilities
|
||||
|
||||
utilities : {
|
||||
element: jQuery,
|
||||
elements: jQuery,
|
||||
sandbox : function() {
|
||||
return jQuery('<div class="sandbox"></div>')
|
||||
}
|
||||
},
|
||||
|
||||
// --- Matchers
|
||||
|
||||
matchers : {
|
||||
have_tag : "jQuery(expected, actual).length == 1",
|
||||
have_one : "alias have_tag",
|
||||
have_tags : "jQuery(expected, actual).length > 1",
|
||||
have_many : "alias have_tags",
|
||||
have_child : "jQuery(actual).children(expected).length == 1",
|
||||
have_children : "jQuery(actual).children(expected).length > 1",
|
||||
have_text : "jQuery(actual).text() == expected",
|
||||
have_value : "jQuery(actual).val() == expected",
|
||||
be_enabled : "!jQuery(actual).attr('disabled')",
|
||||
have_class : "jQuery(actual).hasClass(expected)",
|
||||
|
||||
be_visible : function(actual) {
|
||||
return jQuery(actual).css('display') != 'none' &&
|
||||
jQuery(actual).css('visibility') != 'hidden' &&
|
||||
jQuery(actual).attr('type') != 'hidden'
|
||||
},
|
||||
|
||||
be_hidden : function(actual) {
|
||||
return !JSpec.does(actual, 'be_visible')
|
||||
},
|
||||
|
||||
have_classes : function(actual) {
|
||||
return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){
|
||||
return !JSpec.does(actual, 'have_class', arg)
|
||||
})
|
||||
},
|
||||
|
||||
have_attr : function(actual, attr, value) {
|
||||
return value ? jQuery(actual).attr(attr) == value:
|
||||
jQuery(actual).attr(attr)
|
||||
},
|
||||
|
||||
'be disabled selected checked' : function(attr) {
|
||||
return 'jQuery(actual).attr("' + attr + '")'
|
||||
},
|
||||
|
||||
'have type id title alt href src sel rev name target' : function(attr) {
|
||||
return function(actual, value) {
|
||||
return JSpec.does(actual, 'have_attr', attr, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
1773
spec/lib/jspec.js
1773
spec/lib/jspec.js
File diff suppressed because it is too large
Load Diff
@@ -1,39 +0,0 @@
|
||||
|
||||
// JSpec - Shell - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
;(function(){
|
||||
|
||||
var _quit = quit
|
||||
|
||||
Shell = {
|
||||
|
||||
// --- Global
|
||||
|
||||
main: this,
|
||||
|
||||
// --- Commands
|
||||
|
||||
commands: {
|
||||
quit: ['Terminate the shell', function(){ _quit() }],
|
||||
exit: ['Terminate the shell', function(){ _quit() }],
|
||||
p: ['Inspect an object', function(o){ return o.toSource() }]
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the interactive shell.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
start : function() {
|
||||
for (var name in this.commands)
|
||||
if (this.commands.hasOwnProperty(name))
|
||||
this.commands[name][1].length ?
|
||||
this.main[name] = this.commands[name][1] :
|
||||
this.main.__defineGetter__(name, this.commands[name][1])
|
||||
}
|
||||
}
|
||||
|
||||
Shell.start()
|
||||
|
||||
})()
|
||||
@@ -1,90 +0,0 @@
|
||||
|
||||
// JSpec - Mock Timers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
;(function(){
|
||||
|
||||
/**
|
||||
* Version.
|
||||
*/
|
||||
|
||||
mockTimersVersion = '1.0.2'
|
||||
|
||||
/**
|
||||
* Localized timer stack.
|
||||
*/
|
||||
|
||||
var timers = []
|
||||
|
||||
/**
|
||||
* Set mock timeout with _callback_ and timeout of _ms_.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @param {int} ms
|
||||
* @return {int}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
setTimeout = function(callback, ms) {
|
||||
var id
|
||||
return id = setInterval(function(){
|
||||
callback()
|
||||
clearInterval(id)
|
||||
}, ms)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mock interval with _callback_ and interval of _ms_.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @param {int} ms
|
||||
* @return {int}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
setInterval = function(callback, ms) {
|
||||
callback.step = ms, callback.current = callback.last = 0
|
||||
return timers[timers.length] = callback, timers.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy timer with _id_.
|
||||
*
|
||||
* @param {int} id
|
||||
* @return {bool}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
clearInterval = clearTimeout = function(id) {
|
||||
return delete timers[--id]
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset timers.
|
||||
*
|
||||
* @return {array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
resetTimers = function() {
|
||||
return timers = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment each timers internal clock by _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @api public
|
||||
*/
|
||||
|
||||
tick = function(ms) {
|
||||
for (var i = 0, len = timers.length; i < len; ++i)
|
||||
if (timers[i] && (timers[i].current += ms))
|
||||
if (timers[i].current - timers[i].last >= timers[i].step) {
|
||||
var times = Math.floor((timers[i].current - timers[i].last) / timers[i].step)
|
||||
var remainder = (timers[i].current - timers[i].last) % timers[i].step
|
||||
timers[i].last = timers[i].current - remainder
|
||||
while (times-- && timers[i]) timers[i]()
|
||||
}
|
||||
}
|
||||
|
||||
})()
|
||||
@@ -1,193 +0,0 @@
|
||||
|
||||
// JSpec - XHR - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
(function(){
|
||||
|
||||
// --- Original XMLHttpRequest
|
||||
|
||||
var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
|
||||
XMLHttpRequest :
|
||||
function(){}
|
||||
var OriginalActiveXObject = 'ActiveXObject' in this ?
|
||||
ActiveXObject :
|
||||
undefined
|
||||
|
||||
// --- MockXMLHttpRequest
|
||||
|
||||
var MockXMLHttpRequest = function() {
|
||||
this.requestHeaders = {}
|
||||
}
|
||||
|
||||
MockXMLHttpRequest.prototype = {
|
||||
status: 0,
|
||||
async: true,
|
||||
readyState: 0,
|
||||
responseText: '',
|
||||
abort: function(){},
|
||||
onreadystatechange: function(){},
|
||||
|
||||
/**
|
||||
* Return response headers hash.
|
||||
*/
|
||||
|
||||
getAllResponseHeaders : function(){
|
||||
return this.responseHeaders
|
||||
},
|
||||
|
||||
/**
|
||||
* Return case-insensitive value for header _name_.
|
||||
*/
|
||||
|
||||
getResponseHeader : function(name) {
|
||||
return this.responseHeaders[name.toLowerCase()]
|
||||
},
|
||||
|
||||
/**
|
||||
* Set case-insensitive _value_ for header _name_.
|
||||
*/
|
||||
|
||||
setRequestHeader : function(name, value) {
|
||||
this.requestHeaders[name.toLowerCase()] = value
|
||||
},
|
||||
|
||||
/**
|
||||
* Open mock request.
|
||||
*/
|
||||
|
||||
open : function(method, url, async, user, password) {
|
||||
this.user = user
|
||||
this.password = password
|
||||
this.url = url
|
||||
this.readyState = 1
|
||||
this.method = method.toUpperCase()
|
||||
if (async != undefined) this.async = async
|
||||
if (this.async) this.onreadystatechange()
|
||||
},
|
||||
|
||||
/**
|
||||
* Send request _data_.
|
||||
*/
|
||||
|
||||
send : function(data) {
|
||||
var self = this
|
||||
this.data = data
|
||||
this.readyState = 4
|
||||
if (this.method == 'HEAD') this.responseText = null
|
||||
this.responseHeaders['content-length'] = (this.responseText || '').length
|
||||
if(this.async) this.onreadystatechange()
|
||||
lastRequest = function(){
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Response status codes
|
||||
|
||||
JSpec.statusCodes = {
|
||||
100: 'Continue',
|
||||
101: 'Switching Protocols',
|
||||
200: 'OK',
|
||||
201: 'Created',
|
||||
202: 'Accepted',
|
||||
203: 'Non-Authoritative Information',
|
||||
204: 'No Content',
|
||||
205: 'Reset Content',
|
||||
206: 'Partial Content',
|
||||
300: 'Multiple Choice',
|
||||
301: 'Moved Permanently',
|
||||
302: 'Found',
|
||||
303: 'See Other',
|
||||
304: 'Not Modified',
|
||||
305: 'Use Proxy',
|
||||
307: 'Temporary Redirect',
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
406: 'Not Acceptable',
|
||||
407: 'Proxy Authentication Required',
|
||||
408: 'Request Timeout',
|
||||
409: 'Conflict',
|
||||
410: 'Gone',
|
||||
411: 'Length Required',
|
||||
412: 'Precondition Failed',
|
||||
413: 'Request Entity Too Large',
|
||||
414: 'Request-URI Too Long',
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Requested Range Not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
422: 'Unprocessable Entity',
|
||||
500: 'Internal Server Error',
|
||||
501: 'Not Implemented',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
504: 'Gateway Timeout',
|
||||
505: 'HTTP Version Not Supported'
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock XMLHttpRequest requests.
|
||||
*
|
||||
* mockRequest().and_return('some data', 'text/plain', 200, { 'X-SomeHeader' : 'somevalue' })
|
||||
*
|
||||
* @return {hash}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function mockRequest() {
|
||||
return { and_return : function(body, type, status, headers) {
|
||||
XMLHttpRequest = MockXMLHttpRequest
|
||||
ActiveXObject = false
|
||||
status = status || 200
|
||||
headers = headers || {}
|
||||
headers['content-type'] = type
|
||||
JSpec.extend(XMLHttpRequest.prototype, {
|
||||
responseText: body,
|
||||
responseHeaders: headers,
|
||||
status: status,
|
||||
statusText: JSpec.statusCodes[status]
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmock XMLHttpRequest requests.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function unmockRequest() {
|
||||
XMLHttpRequest = OriginalXMLHttpRequest
|
||||
ActiveXObject = OriginalActiveXObject
|
||||
}
|
||||
|
||||
JSpec.include({
|
||||
name: 'Mock XHR',
|
||||
|
||||
// --- Utilities
|
||||
|
||||
utilities : {
|
||||
mockRequest: mockRequest,
|
||||
unmockRequest: unmockRequest
|
||||
},
|
||||
|
||||
// --- Hooks
|
||||
|
||||
afterSpec : function() {
|
||||
unmockRequest()
|
||||
},
|
||||
|
||||
// --- DSLs
|
||||
|
||||
DSLs : {
|
||||
snake : {
|
||||
mock_request: mockRequest,
|
||||
unmock_request: unmockRequest,
|
||||
last_request: function(){ return lastRequest() }
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
})()
|
||||
@@ -1,144 +0,0 @@
|
||||
describe 'log4js'
|
||||
before
|
||||
extend(context, {
|
||||
log4js : require("log4js")()
|
||||
});
|
||||
end
|
||||
|
||||
before_each
|
||||
log4js.clearAppenders();
|
||||
event = '';
|
||||
logger = log4js.getLogger('tests');
|
||||
logger.setLevel("TRACE");
|
||||
logger.addListener("log", function (logEvent) { event = logEvent; });
|
||||
end
|
||||
|
||||
describe 'addAppender'
|
||||
before_each
|
||||
appenderEvent = undefined;
|
||||
appender = function(logEvent) { appenderEvent = logEvent; };
|
||||
end
|
||||
|
||||
describe 'without a category'
|
||||
it 'should register the function as a listener for all loggers'
|
||||
log4js.addAppender(appender);
|
||||
logger.debug("This is a test");
|
||||
appenderEvent.should.be event
|
||||
end
|
||||
|
||||
it 'should also register as an appender for loggers if an appender for that category is defined'
|
||||
var otherEvent;
|
||||
log4js.addAppender(appender);
|
||||
log4js.addAppender(function (evt) { otherEvent = evt; }, 'cheese');
|
||||
|
||||
var cheeseLogger = log4js.getLogger('cheese');
|
||||
cheeseLogger.addListener("log", function (logEvent) { event = logEvent; });
|
||||
|
||||
cheeseLogger.debug('This is a test');
|
||||
|
||||
appenderEvent.should.be event
|
||||
otherEvent.should.be event
|
||||
|
||||
otherEvent = undefined;
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('pants').debug("this should not be propagated to otherEvent");
|
||||
otherEvent.should.be undefined
|
||||
appenderEvent.should.not.be undefined
|
||||
appenderEvent.message.should.be "this should not be propagated to otherEvent"
|
||||
|
||||
cheeseLogger = null;
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with a category'
|
||||
it 'should only register the function as a listener for that category'
|
||||
log4js.addAppender(appender, 'tests');
|
||||
|
||||
logger.debug('this is a test');
|
||||
appenderEvent.should.be event
|
||||
|
||||
appenderEvent = undefined;
|
||||
log4js.getLogger('some other category').debug('Cheese');
|
||||
appenderEvent.should.be undefined
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with multiple categories'
|
||||
it 'should register the function as a listener for all the categories'
|
||||
log4js.addAppender(appender, 'tests', 'biscuits');
|
||||
|
||||
logger.debug('this is a test');
|
||||
appenderEvent.should.be event
|
||||
appenderEvent = undefined;
|
||||
|
||||
var otherLogger = log4js.getLogger('biscuits');
|
||||
otherLogger.debug("mmm... garibaldis");
|
||||
appenderEvent.should.not.be undefined
|
||||
appenderEvent.message.should.be "mmm... garibaldis"
|
||||
appenderEvent = undefined;
|
||||
|
||||
otherLogger = null;
|
||||
|
||||
log4js.getLogger("something else").debug("pants");
|
||||
appenderEvent.should.be undefined
|
||||
end
|
||||
|
||||
it 'should register the function when the list of categories is an array'
|
||||
log4js.addAppender(appender, ['tests', 'pants']);
|
||||
|
||||
logger.debug('this is a test');
|
||||
appenderEvent.should.be event
|
||||
appenderEvent = undefined;
|
||||
|
||||
var otherLogger = log4js.getLogger('pants');
|
||||
otherLogger.debug("big pants");
|
||||
appenderEvent.should.not.be undefined
|
||||
appenderEvent.message.should.be "big pants"
|
||||
appenderEvent = undefined;
|
||||
|
||||
otherLogger = null;
|
||||
|
||||
log4js.getLogger("something else").debug("pants");
|
||||
appenderEvent.should.be undefined
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'basicLayout'
|
||||
it 'should take a logevent and output a formatted string'
|
||||
logger.debug('this is a test');
|
||||
var output = log4js.basicLayout(event);
|
||||
output.should.match /\[.*?\] \[DEBUG\] tests - this is a test/
|
||||
end
|
||||
|
||||
it 'should output a stacktrace, message if the event has an error attached'
|
||||
var error = new Error("Some made-up error");
|
||||
var stack = error.stack.split(/\n/);
|
||||
|
||||
logger.debug('this is a test', error);
|
||||
|
||||
var output = log4js.basicLayout(event);
|
||||
var lines = output.split(/\n/);
|
||||
lines.length.should.be stack.length+1
|
||||
lines[0].should.match /\[.*?\] \[DEBUG\] tests - this is a test/
|
||||
lines[1].should.match /\[.*?\] \[DEBUG\] tests - Error: Some made-up error/
|
||||
for (var i = 1; i < stack.length; i++) {
|
||||
lines[i+1].should.eql stack[i]
|
||||
}
|
||||
end
|
||||
|
||||
it 'should output a name and message if the event has something that pretends to be an error'
|
||||
logger.debug('this is a test', { name: 'Cheese', message: 'Gorgonzola smells.' });
|
||||
var output = log4js.basicLayout(event);
|
||||
var lines = output.split(/\n/);
|
||||
lines.length.should.be 2
|
||||
lines[0].should.match /\[.*?\] \[DEBUG\] tests - this is a test/
|
||||
lines[1].should.match /\[.*?\] \[DEBUG\] tests - Cheese: Gorgonzola smells./
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
99
test/categoryFilter-test.js
Normal file
99
test/categoryFilter-test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async')
|
||||
, should = require('should')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, assert = require('assert');
|
||||
|
||||
function remove() {
|
||||
var files = Array.prototype.slice.call(arguments);
|
||||
return function(done) {
|
||||
async.forEach(
|
||||
files.map(function(file) { return path.join(__dirname, file); }),
|
||||
fs.unlink.bind(fs),
|
||||
function() { done(); }
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
describe('log4js', function() {
|
||||
|
||||
before(
|
||||
remove(
|
||||
'test-category-filter-web.log',
|
||||
'test-category-filter-all.log'
|
||||
)
|
||||
);
|
||||
|
||||
after(
|
||||
remove(
|
||||
'test-category-filter-web.log',
|
||||
'test-category-filter-all.log'
|
||||
)
|
||||
);
|
||||
|
||||
describe('category filtering', function() {
|
||||
before(function() {
|
||||
var log4js = require('../lib/log4js')
|
||||
, webLogger = log4js.getLogger("web")
|
||||
, appLogger = log4js.getLogger("app");
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
rest: {
|
||||
type: "file",
|
||||
layout: { type: "messagePassThrough" },
|
||||
filename: path.join(__dirname, "test-category-filter-all.log")
|
||||
},
|
||||
web: {
|
||||
type: "file",
|
||||
layout: { type: "messagePassThrough"},
|
||||
filename: path.join(__dirname, "test-category-filter-web.log")
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "rest" ] },
|
||||
web: { level: "debug", appenders: [ "web" ] }
|
||||
}
|
||||
});
|
||||
|
||||
webLogger.debug('This should get logged');
|
||||
appLogger.debug('This should not');
|
||||
webLogger.debug('Hello again');
|
||||
log4js.getLogger('db').debug('This shouldn\'t be included by the appender anyway');
|
||||
});
|
||||
|
||||
it('should only pass matching category', function(done) {
|
||||
setTimeout(function() {
|
||||
fs.readFile(
|
||||
path.join(__dirname, 'test-category-filter-web.log'),
|
||||
'utf8',
|
||||
function(err, contents) {
|
||||
var lines = contents.trim().split('\n');
|
||||
lines.should.eql(["This should get logged", "Hello again"]);
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
it('should send everything else to default appender', function(done) {
|
||||
setTimeout(function() {
|
||||
fs.readFile(
|
||||
path.join(__dirname, 'test-category-filter-all.log'),
|
||||
'utf8',
|
||||
function(err, contents) {
|
||||
var lines = contents.trim().split('\n');
|
||||
lines.should.eql([
|
||||
"This should not",
|
||||
"This shouldn't be included by the appender anyway"
|
||||
]);
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
}, 50);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
147
test/clusteredAppender-test.js
Executable file
147
test/clusteredAppender-test.js
Executable file
@@ -0,0 +1,147 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, sandbox = require('sandboxed-module');
|
||||
|
||||
|
||||
describe('log4js in a cluster', function() {
|
||||
describe('when in master mode', function() {
|
||||
|
||||
var log4js
|
||||
, clusterOnFork = false
|
||||
, workerCb
|
||||
, events = []
|
||||
, worker = {
|
||||
on: function(evt, cb) {
|
||||
evt.should.eql('message');
|
||||
this.cb = cb;
|
||||
}
|
||||
};
|
||||
|
||||
before(function() {
|
||||
log4js = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cluster': {
|
||||
isMaster: true,
|
||||
on: function(evt, cb) {
|
||||
evt.should.eql('fork');
|
||||
clusterOnFork = true;
|
||||
cb(worker);
|
||||
}
|
||||
},
|
||||
'./appenders/console': function() {
|
||||
return function() {
|
||||
return function(event) {
|
||||
events.push(event);
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should listen for fork events', function() {
|
||||
clusterOnFork.should.eql(true);
|
||||
});
|
||||
|
||||
it('should listen for messages from workers', function() {
|
||||
//workerCb was created in a different context to the test
|
||||
//(thanks to sandbox.require), so doesn't pick up the should prototype
|
||||
(typeof worker.cb).should.eql('function');
|
||||
});
|
||||
|
||||
it('should log valid ::log4js-message events', function() {
|
||||
worker.cb({
|
||||
type: '::log4js-message',
|
||||
event: JSON.stringify({
|
||||
startTime: '2010-10-10 18:54:06',
|
||||
category: 'cheese',
|
||||
level: { levelStr: 'DEBUG' },
|
||||
data: [ "blah" ]
|
||||
})
|
||||
});
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("blah");
|
||||
events[0].category.should.eql('cheese');
|
||||
//startTime was created in a different context to the test
|
||||
//(thanks to sandbox.require), so instanceof doesn't think
|
||||
//it's a Date.
|
||||
events[0].startTime.constructor.name.should.eql('Date');
|
||||
events[0].level.toString().should.eql('DEBUG');
|
||||
});
|
||||
|
||||
it('should handle invalid ::log4js-message events', function() {
|
||||
worker.cb({
|
||||
type: '::log4js-message',
|
||||
event: "biscuits"
|
||||
});
|
||||
worker.cb({
|
||||
type: '::log4js-message',
|
||||
event: JSON.stringify({
|
||||
startTime: 'whatever'
|
||||
})
|
||||
});
|
||||
|
||||
events.should.have.length(3);
|
||||
events[1].data[0].should.eql('Unable to parse log:');
|
||||
events[1].data[1].should.eql('biscuits');
|
||||
events[1].category.should.eql('log4js');
|
||||
events[1].level.toString().should.eql('ERROR');
|
||||
|
||||
events[2].data[0].should.eql('Unable to parse log:');
|
||||
events[2].data[1].should.eql(JSON.stringify({ startTime: 'whatever'}));
|
||||
|
||||
});
|
||||
|
||||
it('should ignore other events', function() {
|
||||
worker.cb({
|
||||
type: "::blah-blah",
|
||||
event: "blah"
|
||||
});
|
||||
|
||||
events.should.have.length(3);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when in worker mode', function() {
|
||||
var log4js, events = [];
|
||||
|
||||
before(function() {
|
||||
log4js = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cluster': {
|
||||
isMaster: false,
|
||||
on: function() {}
|
||||
}
|
||||
},
|
||||
globals: {
|
||||
'process': {
|
||||
'send': function(event) {
|
||||
events.push(event);
|
||||
},
|
||||
'env': {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js.getLogger('test').debug("just testing");
|
||||
});
|
||||
|
||||
it('should emit ::log4js-message events', function() {
|
||||
events.should.have.length(1);
|
||||
events[0].type.should.eql('::log4js-message');
|
||||
events[0].event.should.be.a('string');
|
||||
|
||||
var evt = JSON.parse(events[0].event);
|
||||
evt.category.should.eql('test');
|
||||
evt.level.levelStr.should.eql('DEBUG');
|
||||
evt.data[0].should.eql('just testing');
|
||||
});
|
||||
});
|
||||
});
|
||||
31
test/consoleAppender-test.js
Normal file
31
test/consoleAppender-test.js
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, sandbox = require('sandboxed-module');
|
||||
|
||||
describe('../lib/appenders/console', function() {
|
||||
var messages = [];
|
||||
|
||||
before(function() {
|
||||
var fakeConsole = {
|
||||
log: function(msg) { messages.push(msg); }
|
||||
}
|
||||
, appenderModule = sandbox.require(
|
||||
'../lib/appenders/console',
|
||||
{
|
||||
globals: {
|
||||
'console': fakeConsole
|
||||
}
|
||||
}
|
||||
)
|
||||
, appender = appenderModule(require('../lib/layouts'))(
|
||||
{ layout: { type: "messagePassThrough" } }
|
||||
);
|
||||
|
||||
appender({ data: ["blah"] });
|
||||
});
|
||||
|
||||
it('should output to console', function() {
|
||||
messages.should.eql(["blah"]);
|
||||
});
|
||||
|
||||
});
|
||||
221
test/dateFileAppender-test.js
Normal file
221
test/dateFileAppender-test.js
Normal file
@@ -0,0 +1,221 @@
|
||||
"use strict";
|
||||
/*jshint expr:true */
|
||||
var should = require('should')
|
||||
, async = require('async')
|
||||
, path = require('path')
|
||||
, fs = require('fs')
|
||||
, sandbox = require('sandboxed-module');
|
||||
|
||||
function remove(filename, cb) {
|
||||
fs.unlink(path.join(__dirname, filename), function(err) {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
describe('../lib/appenders/dateFile', function() {
|
||||
describe('adding multiple dateFileAppenders', function() {
|
||||
var files = [], initialListeners;
|
||||
|
||||
before(function() {
|
||||
var dateFileAppender = require('../lib/appenders/dateFile')({ basicLayout: function() {} }),
|
||||
count = 5,
|
||||
logfile;
|
||||
|
||||
initialListeners = process.listeners('exit').length;
|
||||
|
||||
while (count--) {
|
||||
logfile = path.join(__dirname, 'datefa-default-test' + count + '.log');
|
||||
dateFileAppender({
|
||||
filename: logfile
|
||||
});
|
||||
files.push(logfile);
|
||||
}
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
async.forEach(files, remove, done);
|
||||
});
|
||||
|
||||
it('should only add one `exit` listener', function () {
|
||||
process.listeners('exit').length.should.be.below(initialListeners + 2);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('exit listener', function() {
|
||||
var openedFiles = [];
|
||||
|
||||
before(function() {
|
||||
var exitListener
|
||||
, dateFileAppender = sandbox.require(
|
||||
'../lib/appenders/dateFile',
|
||||
{
|
||||
globals: {
|
||||
process: {
|
||||
on: function(evt, listener) {
|
||||
exitListener = listener;
|
||||
}
|
||||
}
|
||||
},
|
||||
requires: {
|
||||
'streamroller': {
|
||||
DateRollingFileStream: function(filename) {
|
||||
openedFiles.push(filename);
|
||||
|
||||
this.end = function() {
|
||||
openedFiles.shift();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)({ basicLayout: function() {} });
|
||||
|
||||
for (var i=0; i < 5; i += 1) {
|
||||
dateFileAppender({
|
||||
filename: 'test' + i
|
||||
});
|
||||
}
|
||||
|
||||
openedFiles.should.not.be.empty;
|
||||
exitListener();
|
||||
});
|
||||
|
||||
it('should close all open files', function() {
|
||||
openedFiles.should.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe('with default settings', function() {
|
||||
var contents;
|
||||
|
||||
before(function(done) {
|
||||
var testFile = path.join(__dirname, 'date-appender-default.log'),
|
||||
log4js = require('../lib/log4js'),
|
||||
logger = log4js.getLogger('default-settings');
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"date": { type: "dateFile", filename: testFile }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "debug", appenders: [ "date" ] }
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("This should be in the file.");
|
||||
|
||||
setTimeout(function() {
|
||||
fs.readFile(testFile, "utf8", function(err, data) {
|
||||
contents = data;
|
||||
done(err);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
remove('date-appender-default.log', done);
|
||||
});
|
||||
|
||||
it('should write to the file', function() {
|
||||
contents.should.include('This should be in the file');
|
||||
});
|
||||
|
||||
it('should use the basic layout', function() {
|
||||
contents.should.match(
|
||||
/\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('configure', function() {
|
||||
describe('with dateFileAppender', function() {
|
||||
var contents;
|
||||
|
||||
before(function(done) {
|
||||
var log4js = require('../lib/log4js')
|
||||
, logger = log4js.getLogger('tests');
|
||||
|
||||
//this config file defines one file appender (to ./date-file-test.log)
|
||||
//and sets the log level for "tests" to WARN
|
||||
log4js.configure('test/with-dateFile.json');
|
||||
logger.info('this should not be written to the file');
|
||||
logger.warn('this should be written to the file');
|
||||
|
||||
fs.readFile(path.join(__dirname, 'date-file-test.log'), 'utf8', function(err, data) {
|
||||
contents = data;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
remove('date-file-test.log', done);
|
||||
});
|
||||
|
||||
it('should load appender configuration from a json file', function() {
|
||||
contents.should.include('this should be written to the file' + require('os').EOL);
|
||||
contents.should.not.include('this should not be written to the file');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with options.alwaysIncludePattern', function() {
|
||||
var contents, thisTime;
|
||||
|
||||
before(function(done) {
|
||||
var log4js = require('../lib/log4js')
|
||||
, format = require('date-format')
|
||||
, logger
|
||||
, options = {
|
||||
"appenders": {
|
||||
"datefile": {
|
||||
"type": "dateFile",
|
||||
"filename": "test/date-file-test",
|
||||
"pattern": "-from-MM-dd.log",
|
||||
"alwaysIncludePattern": true,
|
||||
"layout": {
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
}
|
||||
},
|
||||
categories: { default: { level: "debug", appenders: [ "datefile" ] } }
|
||||
};
|
||||
thisTime = format.asString(options.appenders.datefile.pattern, new Date());
|
||||
|
||||
fs.writeFile(
|
||||
path.join(__dirname, 'date-file-test' + thisTime),
|
||||
"this is existing data" + require('os').EOL,
|
||||
'utf8',
|
||||
function(err) {
|
||||
log4js.configure(options);
|
||||
logger = log4js.getLogger('tests');
|
||||
logger.warn('this should be written to the file with the appended date');
|
||||
//wait for filesystem to catch up
|
||||
setTimeout(function() {
|
||||
fs.readFile(
|
||||
path.join(__dirname, 'date-file-test' + thisTime),
|
||||
'utf8',
|
||||
function(err, data) {
|
||||
contents = data;
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
}, 200);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
remove('date-file-test' + thisTime, done);
|
||||
});
|
||||
|
||||
it('should create file with the correct pattern', function() {
|
||||
contents.should.include('this should be written to the file with the appended date');
|
||||
});
|
||||
|
||||
it('should not overwrite the file on open (bug found in issue #132)', function() {
|
||||
contents.should.include('this is existing data');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
335
test/fileAppender-test.js
Normal file
335
test/fileAppender-test.js
Normal file
@@ -0,0 +1,335 @@
|
||||
"use strict";
|
||||
/*jshint expr:true */
|
||||
var fs = require('fs')
|
||||
, async = require('async')
|
||||
, path = require('path')
|
||||
, sandbox = require('sandboxed-module')
|
||||
, log4js = require('../lib/log4js')
|
||||
, should = require('should');
|
||||
|
||||
function remove(filename, cb) {
|
||||
fs.unlink(filename, function(err) { cb(); });
|
||||
}
|
||||
|
||||
describe('log4js fileAppender', function() {
|
||||
|
||||
describe('adding multiple fileAppenders', function() {
|
||||
var files = [], initialCount, listenersCount;
|
||||
|
||||
before(function() {
|
||||
var logfile
|
||||
, count = 5
|
||||
, config = {
|
||||
appenders: {},
|
||||
categories: { default: { level: "debug", appenders: ["file0"] } }
|
||||
};
|
||||
|
||||
initialCount = process.listeners('exit').length;
|
||||
|
||||
while (count--) {
|
||||
logfile = path.join(__dirname, '/fa-default-test' + count + '.log');
|
||||
config.appenders["file" + count] = { type: "file", filename: logfile };
|
||||
files.push(logfile);
|
||||
}
|
||||
|
||||
log4js.configure(config);
|
||||
|
||||
listenersCount = process.listeners('exit').length;
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
async.forEach(files, remove, done);
|
||||
});
|
||||
|
||||
it('does not add more than one `exit` listeners', function () {
|
||||
listenersCount.should.be.below(initialCount + 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exit listener', function() {
|
||||
var openedFiles = [];
|
||||
|
||||
before(function() {
|
||||
var exitListener
|
||||
, fileAppender = sandbox.require(
|
||||
'../lib/appenders/file',
|
||||
{
|
||||
globals: {
|
||||
process: {
|
||||
on: function(evt, listener) {
|
||||
exitListener = listener;
|
||||
}
|
||||
}
|
||||
},
|
||||
requires: {
|
||||
'streamroller': {
|
||||
RollingFileStream: function(filename) {
|
||||
openedFiles.push(filename);
|
||||
|
||||
this.end = function() {
|
||||
openedFiles.shift();
|
||||
};
|
||||
|
||||
this.on = function() {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)(require('../lib/layouts'));
|
||||
for (var i=0; i < 5; i += 1) {
|
||||
fileAppender({ filename: 'test' + i, maxLogSize: 100 });
|
||||
}
|
||||
openedFiles.should.not.be.empty;
|
||||
exitListener();
|
||||
});
|
||||
|
||||
it('should close all open files', function() {
|
||||
openedFiles.should.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe('with default fileAppender settings', function() {
|
||||
var fileContents
|
||||
, testFile = path.join(__dirname, '/fa-default-test.log');
|
||||
|
||||
before(function(done) {
|
||||
var logger = log4js.getLogger('default-settings');
|
||||
|
||||
remove(testFile, function() {
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: testFile }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("This should be in the file.");
|
||||
|
||||
setTimeout(function() {
|
||||
fs.readFile(testFile, "utf8", function(err, contents) {
|
||||
if (!err) {
|
||||
fileContents = contents;
|
||||
}
|
||||
done(err);
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
remove(testFile, done);
|
||||
});
|
||||
|
||||
it('should write log messages to the file', function() {
|
||||
fileContents.should.include("This should be in the file.\n");
|
||||
});
|
||||
|
||||
it('log messages should be in the basic layout format', function() {
|
||||
fileContents.should.match(
|
||||
/\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\] \[INFO\] default-settings - /
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a max file size and no backups', function() {
|
||||
var testFile = path.join(__dirname, '/fa-maxFileSize-test.log');
|
||||
|
||||
before(function(done) {
|
||||
var logger = log4js.getLogger('max-file-size');
|
||||
|
||||
async.forEach([
|
||||
testFile,
|
||||
testFile + '.1'
|
||||
], remove, function() {
|
||||
|
||||
//log file of 100 bytes maximum, no backups
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: testFile, maxLogSize: 100, backups: 0 }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
logger.info("This is the first log message.");
|
||||
logger.info("This is an intermediate log message.");
|
||||
logger.info("This is the second log message.");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
async.forEach([ testFile, testFile + '.1' ], remove, done);
|
||||
});
|
||||
|
||||
describe('log file', function() {
|
||||
it('should only contain the second message', function(done) {
|
||||
//wait for the file system to catch up
|
||||
setTimeout(function() {
|
||||
fs.readFile(testFile, "utf8", function(err, fileContents) {
|
||||
fileContents.should.include("This is the second log message.\n");
|
||||
fileContents.should.not.include("This is the first log message.");
|
||||
done(err);
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the number of files starting with the test file name', function() {
|
||||
it('should be two', function(done) {
|
||||
fs.readdir(__dirname, function(err, files) {
|
||||
//there will always be one backup if you've specified a max log size
|
||||
var logFiles = files.filter(
|
||||
function(file) { return file.indexOf('fa-maxFileSize-test.log') > -1; }
|
||||
);
|
||||
logFiles.should.have.length(2);
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a max file size and 2 backups', function() {
|
||||
var testFile = path.join(__dirname, '/fa-maxFileSize-with-backups-test.log');
|
||||
|
||||
before(function(done) {
|
||||
var logger = log4js.getLogger('max-file-size-backups');
|
||||
|
||||
async.forEach([
|
||||
testFile,
|
||||
testFile+'.1',
|
||||
testFile+'.2'
|
||||
], remove, function() {
|
||||
|
||||
//log file of 50 bytes maximum, 2 backups
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: testFile, maxLogSize: 50, backups: 2 }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("This is the first log message.");
|
||||
logger.info("This is the second log message.");
|
||||
logger.info("This is the third log message.");
|
||||
logger.info("This is the fourth log message.");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the log files', function() {
|
||||
var logFiles;
|
||||
|
||||
before(function(done) {
|
||||
setTimeout(function() {
|
||||
fs.readdir(__dirname, function(err, files) {
|
||||
if (files) {
|
||||
logFiles = files.sort().filter(
|
||||
function(file) {
|
||||
return file.indexOf('fa-maxFileSize-with-backups-test.log') > -1;
|
||||
}
|
||||
);
|
||||
done(null);
|
||||
} else {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
async.forEach(logFiles, remove, done);
|
||||
});
|
||||
|
||||
it('should be 3', function () {
|
||||
logFiles.should.have.length(3);
|
||||
});
|
||||
|
||||
it('should be named in sequence', function() {
|
||||
logFiles.should.eql([
|
||||
'fa-maxFileSize-with-backups-test.log',
|
||||
'fa-maxFileSize-with-backups-test.log.1',
|
||||
'fa-maxFileSize-with-backups-test.log.2'
|
||||
]);
|
||||
});
|
||||
|
||||
describe('and the contents of the first file', function() {
|
||||
it('should be the last log message', function(done) {
|
||||
fs.readFile(path.join(__dirname, logFiles[0]), "utf8", function(err, contents) {
|
||||
contents.should.include('This is the fourth log message.');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the contents of the second file', function() {
|
||||
it('should be the third log message', function(done) {
|
||||
fs.readFile(path.join(__dirname, logFiles[1]), "utf8", function(err, contents) {
|
||||
contents.should.include('This is the third log message.');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the contents of the third file', function() {
|
||||
it('should be the second log message', function(done) {
|
||||
fs.readFile(path.join(__dirname, logFiles[2]), "utf8", function(err, contents) {
|
||||
contents.should.include('This is the second log message.');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when underlying stream errors', function() {
|
||||
var consoleArgs;
|
||||
|
||||
before(function() {
|
||||
var errorHandler
|
||||
, fileAppender = sandbox.require(
|
||||
'../lib/appenders/file',
|
||||
{
|
||||
globals: {
|
||||
console: {
|
||||
error: function() {
|
||||
consoleArgs = Array.prototype.slice.call(arguments);
|
||||
}
|
||||
}
|
||||
},
|
||||
requires: {
|
||||
'streamroller': {
|
||||
RollingFileStream: function(filename) {
|
||||
|
||||
this.end = function() {};
|
||||
this.on = function(evt, cb) {
|
||||
if (evt === 'error') {
|
||||
errorHandler = cb;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)(require('../lib/layouts'));
|
||||
fileAppender({
|
||||
filename: 'test1.log',
|
||||
maxLogSize: 100
|
||||
});
|
||||
errorHandler({ error: 'aargh' });
|
||||
});
|
||||
|
||||
it('should log the error to console.error', function() {
|
||||
consoleArgs.should.not.be.empty;
|
||||
consoleArgs[0].should.eql('log4js.fileAppender - Writing to file %s, error happened ');
|
||||
consoleArgs[1].should.eql('test1.log');
|
||||
consoleArgs[2].error.should.eql('aargh');
|
||||
});
|
||||
});
|
||||
});
|
||||
350
test/layouts-test.js
Normal file
350
test/layouts-test.js
Normal file
@@ -0,0 +1,350 @@
|
||||
"use strict";
|
||||
var assert = require('assert');
|
||||
|
||||
//used for patternLayout tests.
|
||||
function test(layout, event, tokens, pattern, value) {
|
||||
assert.equal(layout(pattern, tokens)(event), value);
|
||||
}
|
||||
|
||||
describe('log4js layouts', function() {
|
||||
describe('colouredLayout', function() {
|
||||
var layout = require('../lib/layouts').colouredLayout;
|
||||
|
||||
it('should apply level colour codes to output', function() {
|
||||
var output = layout({
|
||||
data: ["nonsense"],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
});
|
||||
assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mnonsense');
|
||||
});
|
||||
|
||||
it('should support the console.log format for the message', function() {
|
||||
var output = layout({
|
||||
data: ["thing %d", 2],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
});
|
||||
assert.equal(output, '\x1B[31m[2010-12-05 14:18:30.045] [ERROR] cheese - \x1B[39mthing 2');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('messagePassThroughLayout', function() {
|
||||
var layout = require('../lib/layouts').messagePassThroughLayout;
|
||||
|
||||
it('should take a logevent and output only the message', function() {
|
||||
assert.equal(layout({
|
||||
data: ["nonsense"],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}), "nonsense");
|
||||
});
|
||||
|
||||
it('should support the console.log format for the message', function() {
|
||||
assert.equal(layout({
|
||||
data: ["thing %d", 1, "cheese"],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level : {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}), "thing 1 cheese");
|
||||
});
|
||||
|
||||
it('should output the first item even if it is not a string', function() {
|
||||
assert.equal(layout({
|
||||
data: [ { thing: 1} ],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}), "{ thing: 1 }");
|
||||
});
|
||||
|
||||
it('should print the stacks of a passed error objects', function() {
|
||||
assert.ok(Array.isArray(
|
||||
layout({
|
||||
data: [ new Error() ],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}).match(
|
||||
/Error\s+at Context\..*\s+\((.*)test[\\\/]layouts-test\.js\:\d+\:\d+\)\s/
|
||||
)
|
||||
), 'regexp did not return a match');
|
||||
});
|
||||
|
||||
describe('with passed augmented errors', function() {
|
||||
var layoutOutput;
|
||||
|
||||
before(function() {
|
||||
var e = new Error("My Unique Error Message");
|
||||
e.augmented = "My Unique attribute value";
|
||||
e.augObj = { at1: "at2" };
|
||||
|
||||
layoutOutput = layout({
|
||||
data: [ e ],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should print error the contained error message', function() {
|
||||
var m = layoutOutput.match(/\{ \[Error: My Unique Error Message\]/);
|
||||
assert.ok(Array.isArray(m));
|
||||
});
|
||||
|
||||
it('should print error augmented string attributes', function() {
|
||||
var m = layoutOutput.match(/augmented:\s'My Unique attribute value'/);
|
||||
assert.ok(Array.isArray(m));
|
||||
});
|
||||
|
||||
it('should print error augmented object attributes', function() {
|
||||
var m = layoutOutput.match(/augObj:\s\{ at1: 'at2' \}/);
|
||||
assert.ok(Array.isArray(m));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('basicLayout', function() {
|
||||
var layout = require('../lib/layouts').basicLayout
|
||||
, event = {
|
||||
data: ['this is a test'],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "tests",
|
||||
level: {
|
||||
toString: function() { return "DEBUG"; }
|
||||
}
|
||||
};
|
||||
|
||||
it('should take a logevent and output a formatted string', function() {
|
||||
assert.equal(layout(event), "[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test");
|
||||
});
|
||||
|
||||
it('should output a stacktrace, message if the event has an error attached', function() {
|
||||
var output
|
||||
, lines
|
||||
, error = new Error("Some made-up error")
|
||||
, stack = error.stack.split(/\n/);
|
||||
|
||||
event.data = ['this is a test', error];
|
||||
output = layout(event);
|
||||
lines = output.split(/\n/);
|
||||
|
||||
assert.equal(lines.length - 1, stack.length);
|
||||
assert.equal(
|
||||
lines[0],
|
||||
"[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test [Error: Some made-up error]"
|
||||
);
|
||||
|
||||
for (var i = 1; i < stack.length; i++) {
|
||||
assert.equal(lines[i+2], stack[i+1]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should output any extra data in the log event as util.inspect strings', function() {
|
||||
var output, lines;
|
||||
|
||||
event.data = ['this is a test', {
|
||||
name: 'Cheese',
|
||||
message: 'Gorgonzola smells.'
|
||||
}];
|
||||
output = layout(event);
|
||||
|
||||
assert.equal(
|
||||
output,
|
||||
"[2010-12-05 14:18:30.045] [DEBUG] tests - this is a test " +
|
||||
"{ name: 'Cheese', message: 'Gorgonzola smells.' }"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('patternLayout', function() {
|
||||
var event = {
|
||||
data: ['this is a test'],
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
category: "multiple.levels.of.tests",
|
||||
level: {
|
||||
toString: function() { return "DEBUG"; }
|
||||
}
|
||||
}
|
||||
, layout = require('../lib/layouts').patternLayout
|
||||
, tokens = {
|
||||
testString: 'testStringToken',
|
||||
testFunction: function() { return 'testFunctionToken'; },
|
||||
fnThatUsesLogEvent: function(logEvent) { return logEvent.level.toString(); }
|
||||
};
|
||||
|
||||
event.startTime.getTimezoneOffset = function() { return 0; };
|
||||
|
||||
it('should default to "time logLevel loggerName - message"', function() {
|
||||
test(
|
||||
layout,
|
||||
event,
|
||||
tokens,
|
||||
null,
|
||||
"14:18:30 DEBUG multiple.levels.of.tests - this is a test\n"
|
||||
);
|
||||
});
|
||||
|
||||
it('%r should output time only', function() {
|
||||
test(layout, event, tokens, '%r', '14:18:30');
|
||||
});
|
||||
|
||||
it('%p should output the log level', function() {
|
||||
test(layout, event, tokens, '%p', 'DEBUG');
|
||||
});
|
||||
|
||||
it('%c should output the log category', function() {
|
||||
test(layout, event, tokens, '%c', 'multiple.levels.of.tests');
|
||||
});
|
||||
|
||||
it('%m should output the log data', function() {
|
||||
test(layout, event, tokens, '%m', 'this is a test');
|
||||
});
|
||||
|
||||
it('%n should output a new line', function() {
|
||||
test(layout, event, tokens, '%n', '\n');
|
||||
});
|
||||
|
||||
it('%h should output hostname', function() {
|
||||
test(layout, event, tokens, '%h', require('os').hostname().toString());
|
||||
});
|
||||
|
||||
it('%c should handle category names like java-style package names', function() {
|
||||
test(layout, event, tokens, '%c{1}', 'tests');
|
||||
test(layout, event, tokens, '%c{2}', 'of.tests');
|
||||
test(layout, event, tokens, '%c{3}', 'levels.of.tests');
|
||||
test(layout, event, tokens, '%c{4}', 'multiple.levels.of.tests');
|
||||
test(layout, event, tokens, '%c{5}', 'multiple.levels.of.tests');
|
||||
test(layout, event, tokens, '%c{99}', 'multiple.levels.of.tests');
|
||||
});
|
||||
|
||||
it('%d should output the date in ISO8601 format', function() {
|
||||
test(layout, event, tokens, '%d', '2010-12-05 14:18:30.045');
|
||||
});
|
||||
|
||||
it('%d should allow for format specification', function() {
|
||||
test(layout, event, tokens, '%d{ISO8601_WITH_TZ_OFFSET}', '2010-12-05T14:18:30-0000');
|
||||
test(layout, event, tokens, '%d{ISO8601}', '2010-12-05 14:18:30.045');
|
||||
test(layout, event, tokens, '%d{ABSOLUTE}', '14:18:30.045');
|
||||
test(layout, event, tokens, '%d{DATE}', '05 12 2010 14:18:30.045');
|
||||
test(layout, event, tokens, '%d{yy MM dd hh mm ss}', '10 12 05 14 18 30');
|
||||
test(layout, event, tokens, '%d{yyyy MM dd}', '2010 12 05');
|
||||
test(layout, event, tokens, '%d{yyyy MM dd hh mm ss SSS}', '2010 12 05 14 18 30 045');
|
||||
});
|
||||
|
||||
it('%% should output %', function() {
|
||||
test(layout, event, tokens, '%%', '%');
|
||||
});
|
||||
|
||||
it('should output anything not preceded by % as literal', function() {
|
||||
test(layout, event, tokens, 'blah blah blah', 'blah blah blah');
|
||||
});
|
||||
|
||||
it('should output the original string if no replacer matches the token', function() {
|
||||
test(layout, event, tokens, '%a{3}', 'a{3}');
|
||||
});
|
||||
|
||||
it('should handle complicated patterns', function() {
|
||||
test(layout, event, tokens,
|
||||
'%m%n %c{2} at %d{ABSOLUTE} cheese %p%n',
|
||||
'this is a test\n of.tests at 14:18:30.045 cheese DEBUG\n'
|
||||
);
|
||||
});
|
||||
|
||||
it('should truncate fields if specified', function() {
|
||||
test(layout, event, tokens, '%.4m', 'this');
|
||||
test(layout, event, tokens, '%.7m', 'this is');
|
||||
test(layout, event, tokens, '%.9m', 'this is a');
|
||||
test(layout, event, tokens, '%.14m', 'this is a test');
|
||||
test(layout, event, tokens, '%.2919102m', 'this is a test');
|
||||
});
|
||||
|
||||
it('should pad fields if specified', function() {
|
||||
test(layout, event, tokens, '%10p', ' DEBUG');
|
||||
test(layout, event, tokens, '%8p', ' DEBUG');
|
||||
test(layout, event, tokens, '%6p', ' DEBUG');
|
||||
test(layout, event, tokens, '%4p', 'DEBUG');
|
||||
test(layout, event, tokens, '%-4p', 'DEBUG');
|
||||
test(layout, event, tokens, '%-6p', 'DEBUG ');
|
||||
test(layout, event, tokens, '%-8p', 'DEBUG ');
|
||||
test(layout, event, tokens, '%-10p', 'DEBUG ');
|
||||
});
|
||||
|
||||
it('%[%r%] should output colored time', function() {
|
||||
test(layout, event, tokens, '%[%r%]', '\x1B[36m14:18:30\x1B[39m');
|
||||
});
|
||||
|
||||
describe('%x{}', function() {
|
||||
it('%x{testString} should output the string stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x{testString}', 'testStringToken');
|
||||
});
|
||||
|
||||
it('%x{testFunction} should output the result of the function stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x{testFunction}', 'testFunctionToken');
|
||||
});
|
||||
|
||||
it('%x{doesNotExist} should output the string stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x{doesNotExist}', '%x{doesNotExist}');
|
||||
});
|
||||
|
||||
it('%x{fnThatUsesLogEvent} should be able to use the logEvent', function() {
|
||||
test(layout, event, tokens, '%x{fnThatUsesLogEvent}', 'DEBUG');
|
||||
});
|
||||
|
||||
it('%x should output the string stored in tokens', function() {
|
||||
test(layout, event, tokens, '%x', '%x');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('layout makers', function() {
|
||||
var layouts = require('../lib/layouts');
|
||||
|
||||
it('should have a maker for each layout', function() {
|
||||
assert.ok(layouts.layout("messagePassThrough"));
|
||||
assert.ok(layouts.layout("basic"));
|
||||
assert.ok(layouts.layout("colored"));
|
||||
assert.ok(layouts.layout("coloured"));
|
||||
assert.ok(layouts.layout("pattern"));
|
||||
});
|
||||
|
||||
it('should return falsy if a layout does not exist', function() {
|
||||
assert.ok(!layouts.layout("cheese"));
|
||||
});
|
||||
|
||||
it('should pass config to layouts that need it', function() {
|
||||
var layout = layouts.layout(
|
||||
"pattern",
|
||||
{
|
||||
pattern: "%m"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(layout({ data: [ "blah" ] }), "blah");
|
||||
});
|
||||
});
|
||||
});
|
||||
427
test/levels-test.js
Normal file
427
test/levels-test.js
Normal file
@@ -0,0 +1,427 @@
|
||||
"use strict";
|
||||
var assert = require('assert')
|
||||
, should = require('should')
|
||||
, levels = require('../lib/levels');
|
||||
|
||||
function assertThat(level) {
|
||||
function assertForEach(val, test, otherLevels) {
|
||||
otherLevels.forEach(function(other) {
|
||||
test.call(level, other).should.eql(val);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
isLessThanOrEqualTo: function(levels) {
|
||||
assertForEach(true, level.isLessThanOrEqualTo, levels);
|
||||
},
|
||||
isNotLessThanOrEqualTo: function(levels) {
|
||||
assertForEach(false, level.isLessThanOrEqualTo, levels);
|
||||
},
|
||||
isGreaterThanOrEqualTo: function(levels) {
|
||||
assertForEach(true, level.isGreaterThanOrEqualTo, levels);
|
||||
},
|
||||
isNotGreaterThanOrEqualTo: function(levels) {
|
||||
assertForEach(false, level.isGreaterThanOrEqualTo, levels);
|
||||
},
|
||||
isEqualTo: function(levels) {
|
||||
assertForEach(true, level.isEqualTo, levels);
|
||||
},
|
||||
isNotEqualTo: function(levels) {
|
||||
assertForEach(false, level.isEqualTo, levels);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('../lib/levels', function() {
|
||||
it('should define some levels', function() {
|
||||
should.exist(levels.ALL);
|
||||
should.exist(levels.TRACE);
|
||||
should.exist(levels.DEBUG);
|
||||
should.exist(levels.INFO);
|
||||
should.exist(levels.WARN);
|
||||
should.exist(levels.ERROR);
|
||||
should.exist(levels.FATAL);
|
||||
should.exist(levels.OFF);
|
||||
});
|
||||
|
||||
describe('ALL', function() {
|
||||
var all = levels.ALL;
|
||||
|
||||
it('should be less than the other levels', function() {
|
||||
assertThat(all).isLessThanOrEqualTo(
|
||||
[
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should be greater than no levels', function() {
|
||||
assertThat(all).isNotGreaterThanOrEqualTo(
|
||||
[
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should only be equal to ALL', function() {
|
||||
assertThat(all).isEqualTo([levels.toLevel("ALL")]);
|
||||
assertThat(all).isNotEqualTo(
|
||||
[
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TRACE', function() {
|
||||
var trace = levels.TRACE;
|
||||
|
||||
it('should be less than DEBUG', function() {
|
||||
assertThat(trace).isLessThanOrEqualTo(
|
||||
[
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
assertThat(trace).isNotLessThanOrEqualTo([levels.ALL]);
|
||||
});
|
||||
|
||||
it('should be greater than ALL', function() {
|
||||
assertThat(trace).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
|
||||
assertThat(trace).isNotGreaterThanOrEqualTo(
|
||||
[
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should only be equal to TRACE', function() {
|
||||
assertThat(trace).isEqualTo([levels.toLevel("TRACE")]);
|
||||
assertThat(trace).isNotEqualTo(
|
||||
[
|
||||
levels.ALL,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('DEBUG', function() {
|
||||
var debug = levels.DEBUG;
|
||||
|
||||
it('should be less than INFO', function() {
|
||||
assertThat(debug).isLessThanOrEqualTo(
|
||||
[
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
assertThat(debug).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE]);
|
||||
});
|
||||
|
||||
it('should be greater than TRACE', function() {
|
||||
assertThat(debug).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE]);
|
||||
assertThat(debug).isNotGreaterThanOrEqualTo(
|
||||
[
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('should only be equal to DEBUG', function() {
|
||||
assertThat(debug).isEqualTo([levels.toLevel("DEBUG")]);
|
||||
assertThat(debug).isNotEqualTo(
|
||||
[
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('INFO', function() {
|
||||
var info = levels.INFO;
|
||||
|
||||
it('should be less than WARN', function() {
|
||||
assertThat(info).isLessThanOrEqualTo([
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
assertThat(info).isNotLessThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
|
||||
});
|
||||
|
||||
it('should be greater than DEBUG', function() {
|
||||
assertThat(info).isGreaterThanOrEqualTo([levels.ALL, levels.TRACE, levels.DEBUG]);
|
||||
assertThat(info).isNotGreaterThanOrEqualTo([
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only be equal to INFO', function() {
|
||||
assertThat(info).isEqualTo([levels.toLevel("INFO")]);
|
||||
assertThat(info).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WARN', function() {
|
||||
var warn = levels.WARN;
|
||||
|
||||
it('should be less than ERROR', function() {
|
||||
assertThat(warn).isLessThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
|
||||
assertThat(warn).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than INFO', function() {
|
||||
assertThat(warn).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO
|
||||
]);
|
||||
assertThat(warn).isNotGreaterThanOrEqualTo([levels.ERROR, levels.FATAL, levels.OFF]);
|
||||
});
|
||||
|
||||
it('should only be equal to WARN', function() {
|
||||
assertThat(warn).isEqualTo([levels.toLevel("WARN")]);
|
||||
assertThat(warn).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.ERROR,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERROR', function() {
|
||||
var error = levels.ERROR;
|
||||
|
||||
it('should be less than FATAL', function() {
|
||||
assertThat(error).isLessThanOrEqualTo([levels.FATAL, levels.OFF]);
|
||||
assertThat(error).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than WARN', function() {
|
||||
assertThat(error).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN
|
||||
]);
|
||||
assertThat(error).isNotGreaterThanOrEqualTo([levels.FATAL, levels.OFF]);
|
||||
});
|
||||
|
||||
it('should only be equal to ERROR', function() {
|
||||
assertThat(error).isEqualTo([levels.toLevel("ERROR")]);
|
||||
assertThat(error).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.FATAL,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FATAL', function() {
|
||||
var fatal = levels.FATAL;
|
||||
|
||||
it('should be less than OFF', function() {
|
||||
assertThat(fatal).isLessThanOrEqualTo([levels.OFF]);
|
||||
assertThat(fatal).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than ERROR', function() {
|
||||
assertThat(fatal).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR
|
||||
]);
|
||||
assertThat(fatal).isNotGreaterThanOrEqualTo([levels.OFF]);
|
||||
});
|
||||
|
||||
it('should only be equal to FATAL', function() {
|
||||
assertThat(fatal).isEqualTo([levels.toLevel("FATAL")]);
|
||||
assertThat(fatal).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.OFF
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('OFF', function() {
|
||||
var off = levels.OFF;
|
||||
|
||||
it('should not be less than anything', function() {
|
||||
assertThat(off).isNotLessThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be greater than everything', function() {
|
||||
assertThat(off).isGreaterThanOrEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only be equal to OFF', function() {
|
||||
assertThat(off).isEqualTo([levels.toLevel("OFF")]);
|
||||
assertThat(off).isNotEqualTo([
|
||||
levels.ALL,
|
||||
levels.TRACE,
|
||||
levels.DEBUG,
|
||||
levels.INFO,
|
||||
levels.WARN,
|
||||
levels.ERROR,
|
||||
levels.FATAL
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isGreaterThanOrEqualTo', function() {
|
||||
var info = levels.INFO;
|
||||
it('should handle string arguments', function() {
|
||||
assertThat(info).isGreaterThanOrEqualTo(["all", "trace", "debug"]);
|
||||
assertThat(info).isNotGreaterThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLessThanOrEqualTo', function() {
|
||||
var info = levels.INFO;
|
||||
it('should handle string arguments', function() {
|
||||
assertThat(info).isNotLessThanOrEqualTo(["all", "trace", "debug"]);
|
||||
assertThat(info).isLessThanOrEqualTo(['warn', 'ERROR', 'Fatal', 'off']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEqualTo', function() {
|
||||
var info = levels.INFO;
|
||||
it('should handle string arguments', function() {
|
||||
assertThat(info).isEqualTo(["info", "INFO", "iNfO"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toLevel', function() {
|
||||
it('should ignore the case of arguments', function() {
|
||||
levels.toLevel("debug").should.eql(levels.DEBUG);
|
||||
levels.toLevel("DEBUG").should.eql(levels.DEBUG);
|
||||
levels.toLevel("DeBuG").should.eql(levels.DEBUG);
|
||||
});
|
||||
|
||||
it('should return undefined when argument is not recognised', function() {
|
||||
should.not.exist(levels.toLevel("cheese"));
|
||||
});
|
||||
|
||||
it('should return the default value if argument is not recognised', function() {
|
||||
levels.toLevel("cheese", levels.DEBUG).should.eql(levels.DEBUG);
|
||||
});
|
||||
|
||||
it('should return the default value if argument is falsy', function() {
|
||||
levels.toLevel(undefined, levels.DEBUG).should.eql(levels.DEBUG);
|
||||
levels.toLevel(null, levels.DEBUG).should.eql(levels.DEBUG);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
423
test/log4js-test.js
Normal file
423
test/log4js-test.js
Normal file
@@ -0,0 +1,423 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, fs = require('fs')
|
||||
, sandbox = require('sandboxed-module')
|
||||
, log4js = require('../lib/log4js');
|
||||
|
||||
describe('../lib/log4js', function() {
|
||||
describe('#getLogger', function() {
|
||||
it('should return a Logger', function() {
|
||||
log4js.getLogger().should.have.property('debug').be.a('function');
|
||||
log4js.getLogger().should.have.property('info').be.a('function');
|
||||
log4js.getLogger().should.have.property('error').be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#configure', function() {
|
||||
it('should require an object or a filename', function() {
|
||||
[
|
||||
undefined,
|
||||
null,
|
||||
true,
|
||||
42,
|
||||
function() {}
|
||||
].forEach(function(arg) {
|
||||
(function() { log4js.configure(arg); }).should.throw(
|
||||
"You must specify configuration as an object or a filename."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should complain if the file cannot be found', function() {
|
||||
(function() { log4js.configure("pants"); }).should.throw(
|
||||
"ENOENT, no such file or directory 'pants'"
|
||||
);
|
||||
});
|
||||
|
||||
it('should pick up the configuration filename from env.LOG4JS_CONFIG', function() {
|
||||
process.env.LOG4JS_CONFIG = 'made-up-file';
|
||||
(function() { log4js.configure(); }).should.throw(
|
||||
"ENOENT, no such file or directory 'made-up-file'"
|
||||
);
|
||||
delete process.env.LOG4JS_CONFIG;
|
||||
});
|
||||
|
||||
it('should complain if the config does not specify any appenders', function() {
|
||||
|
||||
(function() { log4js.configure({}); }).should.throw(
|
||||
"You must specify at least one appender."
|
||||
);
|
||||
|
||||
(function() { log4js.configure({ appenders: {} }); }).should.throw(
|
||||
"You must specify at least one appender."
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
it(
|
||||
'should complain if the config does not specify an appender for the default category',
|
||||
function() {
|
||||
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: {
|
||||
"console": { type: "console" }
|
||||
},
|
||||
categories: {}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"You must specify an appender for the default category"
|
||||
);
|
||||
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"console": { type: "console" }
|
||||
},
|
||||
categories: {
|
||||
"cheese": { level: "DEBUG", appenders: [ "console" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(
|
||||
"You must specify an appender for the default category"
|
||||
);
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
it('should complain if a category does not specify level or appenders', function() {
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" } },
|
||||
categories: {
|
||||
"default": { thing: "thing" }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"You must specify a level for category 'default'."
|
||||
);
|
||||
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" } },
|
||||
categories: {
|
||||
"default": { level: "DEBUG" }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"You must specify an appender for category 'default'."
|
||||
);
|
||||
});
|
||||
|
||||
it('should complain if a category specifies a level that does not exist', function() {
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" }},
|
||||
categories: {
|
||||
"default": { level: "PICKLES" }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"Level 'PICKLES' is not valid for category 'default'. " +
|
||||
"Acceptable values are: OFF, TRACE, DEBUG, INFO, WARN, ERROR, FATAL."
|
||||
);
|
||||
});
|
||||
|
||||
it('should complain if a category specifies an appender that does not exist', function() {
|
||||
(function() {
|
||||
log4js.configure(
|
||||
{
|
||||
appenders: { "console": { type: "console" }},
|
||||
categories: {
|
||||
"default": { level: "DEBUG", appenders: [ "cheese" ] }
|
||||
}
|
||||
}
|
||||
);
|
||||
}).should.throw(
|
||||
"Appender 'cheese' for category 'default' does not exist. Known appenders are: console."
|
||||
);
|
||||
});
|
||||
|
||||
before(function(done) {
|
||||
fs.unlink("test.log", function (err) { done(); });
|
||||
});
|
||||
|
||||
it('should set up the included appenders', function(done) {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"file": { type: "file", filename: "test.log" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "file" ] }
|
||||
}
|
||||
});
|
||||
log4js.getLogger('test').debug("cheese");
|
||||
|
||||
setTimeout(function() {
|
||||
fs.readFile("test.log", "utf-8", function(err, contents) {
|
||||
contents.should.include("cheese");
|
||||
done(err);
|
||||
});
|
||||
}, 50);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
fs.unlink("test.log", function (err) { done(); });
|
||||
});
|
||||
|
||||
it('should set up third-party appenders', function() {
|
||||
var events = [], log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(evt) { events.push(evt); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"thing": { type: "cheese" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "thing" ] }
|
||||
}
|
||||
});
|
||||
log4js_sandbox.getLogger().info("edam");
|
||||
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("edam");
|
||||
|
||||
});
|
||||
|
||||
it('should only load third-party appenders once', function() {
|
||||
var moduleCalled = 0
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
moduleCalled += 1;
|
||||
return function() {
|
||||
return function() {};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"thing1": { type: "cheese" },
|
||||
"thing2": { type: "cheese" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "thing1", "thing2" ] }
|
||||
}
|
||||
});
|
||||
|
||||
moduleCalled.should.eql(1);
|
||||
});
|
||||
|
||||
it('should pass layouts and levels to appender modules', function() {
|
||||
var layouts
|
||||
, levels
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function(arg1, arg2) {
|
||||
layouts = arg1;
|
||||
levels = arg2;
|
||||
return function() {
|
||||
return function() {};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"thing": { type: "cheese" }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: [ "thing" ] }
|
||||
}
|
||||
});
|
||||
|
||||
layouts.should.have.property("basicLayout");
|
||||
levels.should.have.property("toLevel");
|
||||
});
|
||||
|
||||
it('should pass config and appenderByName to appender makers', function() {
|
||||
var otherAppender = function() { /* I do nothing */ }
|
||||
, config
|
||||
, other
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'other': function() {
|
||||
return function() {
|
||||
return otherAppender;
|
||||
};
|
||||
},
|
||||
'cheese': function() {
|
||||
return function(arg1, arg2) {
|
||||
config = arg1;
|
||||
other = arg2("other");
|
||||
return function() {};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandbox.configure({
|
||||
appenders: {
|
||||
"other": { type: "other" },
|
||||
"thing": { type: "cheese", something: "something" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "debug", appenders: [ "other", "thing" ] }
|
||||
}
|
||||
});
|
||||
|
||||
other.should.equal(otherAppender);
|
||||
config.should.have.property("something", "something");
|
||||
|
||||
});
|
||||
|
||||
it('should complain about unknown appenders', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"thing": { type: "madeupappender" }
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "thing" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(
|
||||
"Could not load appender of type 'madeupappender'."
|
||||
);
|
||||
});
|
||||
|
||||
it('should read config from a file', function() {
|
||||
var events = [], log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js_sandbox.configure(__dirname + "/with-cheese.json");
|
||||
log4js_sandbox.getLogger().debug("gouda");
|
||||
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("gouda");
|
||||
});
|
||||
|
||||
it('should set up log levels for categories', function() {
|
||||
var events = []
|
||||
, noisyLogger
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js_sandbox.configure(__dirname + "/with-cheese.json");
|
||||
noisyLogger = log4js_sandbox.getLogger("noisy");
|
||||
noisyLogger.debug("pow");
|
||||
noisyLogger.info("crash");
|
||||
noisyLogger.warn("bang");
|
||||
noisyLogger.error("boom");
|
||||
noisyLogger.fatal("aargh");
|
||||
|
||||
events.should.have.length(2);
|
||||
events[0].data[0].should.eql("boom");
|
||||
events[1].data[0].should.eql("aargh");
|
||||
|
||||
});
|
||||
|
||||
it('should have a default log level for all categories', function() {
|
||||
var events = []
|
||||
, log4js_sandbox = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'cheese': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//with-cheese.json only specifies categories noisy and default
|
||||
//unspecified categories should use the default category config
|
||||
log4js_sandbox.configure(__dirname + "/with-cheese.json");
|
||||
log4js_sandbox.getLogger("surprise").trace("not seen");
|
||||
log4js_sandbox.getLogger("surprise").info("should be seen");
|
||||
|
||||
events.should.have.length(1);
|
||||
events[0].data[0].should.eql("should be seen");
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('with no configuration', function() {
|
||||
var events = []
|
||||
, log4js_sandboxed = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{
|
||||
requires: {
|
||||
'./appenders/console': function() {
|
||||
return function() {
|
||||
return function(event) { events.push(event); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
log4js_sandboxed.getLogger("blah").debug("goes to console");
|
||||
log4js_sandboxed.getLogger("yawn").trace("does not go to console");
|
||||
log4js_sandboxed.getLogger().error("also goes to console");
|
||||
|
||||
it('should log events of debug level and higher to console', function() {
|
||||
events.should.have.length(2);
|
||||
events[0].data[0].should.eql("goes to console");
|
||||
events[0].category.should.eql("blah");
|
||||
events[0].level.toString().should.eql("DEBUG");
|
||||
events[1].data[0].should.eql("also goes to console");
|
||||
events[1].category.should.eql("default");
|
||||
events[1].level.toString().should.eql("ERROR");
|
||||
});
|
||||
});
|
||||
});
|
||||
141
test/logLevelFilter-test.js
Normal file
141
test/logLevelFilter-test.js
Normal file
@@ -0,0 +1,141 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, sandbox = require('sandboxed-module')
|
||||
, log4js = require('../lib/log4js');
|
||||
|
||||
describe('log level filter', function() {
|
||||
describe('when configured correctly', function() {
|
||||
var events = [], logger;
|
||||
|
||||
before(function() {
|
||||
var log4js_sandboxed = sandbox.require(
|
||||
'../lib/log4js',
|
||||
{ requires:
|
||||
{ './appenders/console': function() {
|
||||
return function() {
|
||||
return function(evt) { events.push(evt); };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
log4js_sandboxed.configure({
|
||||
appenders: {
|
||||
"console": { type: "console", layout: { type: "messagePassThrough" } },
|
||||
"errors only": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "ERROR", "FATAL" ],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors only" ] }
|
||||
}
|
||||
});
|
||||
logger = log4js_sandboxed.getLogger("test");
|
||||
});
|
||||
|
||||
it('should pass events to an appender if they match', function() {
|
||||
logger.error("oh no");
|
||||
logger.fatal("boom");
|
||||
|
||||
events.should.have.length(2);
|
||||
events[0].data[0].should.eql("oh no");
|
||||
events[1].data[0].should.eql("boom");
|
||||
});
|
||||
|
||||
it('should not pass events to the appender if they do not match', function() {
|
||||
events.should.have.length(2);
|
||||
logger.debug("cheese");
|
||||
events.should.have.length(2);
|
||||
logger.info("yawn");
|
||||
events.should.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should complain if it has no appender', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "ERROR", "FATAL" ]
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/Missing an appender\./);
|
||||
});
|
||||
|
||||
it('should complain if it has no list of allowed levels', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"console": { type: "console" },
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/No allowed log levels specified\./);
|
||||
});
|
||||
|
||||
it('should complain if the referenced appender does not exist', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "ERROR" ],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/Appender 'console' not found\./);
|
||||
});
|
||||
|
||||
it('should complain if the list of levels is not valid', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [ "cheese", "biscuits", "ERROR" ],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "DEBUG", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/Unrecognised log level 'cheese'\./);
|
||||
});
|
||||
|
||||
it('should complain if the list of levels is empty', function() {
|
||||
(function() {
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"console": { type: "console" },
|
||||
"errors": {
|
||||
type: "logLevelFilter",
|
||||
allow: [],
|
||||
appender: "console"
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
default: { level: "debug", appenders: [ "errors" ] }
|
||||
}
|
||||
});
|
||||
}).should.throw(/No allowed log levels specified\./);
|
||||
});
|
||||
|
||||
});
|
||||
53
test/logger-test.js
Normal file
53
test/logger-test.js
Normal file
@@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
var should = require('should')
|
||||
, levels = require('../lib/levels')
|
||||
, Logger = require('../lib/logger');
|
||||
|
||||
describe('../lib/logger', function() {
|
||||
describe('Logger constructor', function() {
|
||||
it('must be passed a dispatch delegate and a category', function() {
|
||||
(function() { new Logger(); }).should.throw(
|
||||
"Logger must have a dispatch delegate."
|
||||
);
|
||||
(function() { new Logger(function() {}); }).should.throw(
|
||||
"Logger must have a category."
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Logger instance', function() {
|
||||
var event
|
||||
, logger = new Logger(
|
||||
function(evt) { event = evt; },
|
||||
"exciting category"
|
||||
);
|
||||
|
||||
beforeEach(function() {
|
||||
event = null;
|
||||
});
|
||||
|
||||
it('should be immutable', function() {
|
||||
logger.category = "rubbish";
|
||||
logger.debug("thing");
|
||||
|
||||
event.category.should.equal("exciting category");
|
||||
});
|
||||
|
||||
['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(function(level) {
|
||||
it('should have a ' + level + ' function', function() {
|
||||
logger[level].should.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
it('should send log events to the dispatch delegate', function() {
|
||||
logger.debug("interesting thing");
|
||||
event.should.have.property('category').equal('exciting category');
|
||||
event.should.have.property('level').equal(levels.DEBUG);
|
||||
event.should.have.property('data').eql(["interesting thing"]);
|
||||
event.should.have.property('startTime');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
362
test/logging.js
362
test/logging.js
@@ -1,362 +0,0 @@
|
||||
var vows = require('vows'),
|
||||
assert = require('assert');
|
||||
|
||||
vows.describe('log4js').addBatch({
|
||||
'getLogger': {
|
||||
topic: function() {
|
||||
var log4js = require('../lib/log4js')();
|
||||
log4js.clearAppenders();
|
||||
var logger = log4js.getLogger('tests');
|
||||
logger.setLevel("DEBUG");
|
||||
return logger;
|
||||
},
|
||||
|
||||
'should take a category and return a logger': function(logger) {
|
||||
assert.equal(logger.category, 'tests');
|
||||
assert.equal(logger.level.toString(), "DEBUG");
|
||||
assert.isFunction(logger.debug);
|
||||
assert.isFunction(logger.info);
|
||||
assert.isFunction(logger.warn);
|
||||
assert.isFunction(logger.error);
|
||||
assert.isFunction(logger.fatal);
|
||||
},
|
||||
|
||||
'log events' : {
|
||||
topic: function(logger) {
|
||||
var events = [];
|
||||
logger.addListener("log", function (logEvent) { events.push(logEvent); });
|
||||
logger.debug("Debug event");
|
||||
logger.trace("Trace event 1");
|
||||
logger.trace("Trace event 2");
|
||||
logger.warn("Warning event");
|
||||
return events;
|
||||
},
|
||||
|
||||
'should emit log events': function(events) {
|
||||
assert.equal(events[0].level.toString(), 'DEBUG');
|
||||
assert.equal(events[0].message, 'Debug event');
|
||||
assert.instanceOf(events[0].startTime, Date);
|
||||
},
|
||||
|
||||
'should not emit events of a lower level': function(events) {
|
||||
assert.length(events, 2);
|
||||
assert.equal(events[1].level.toString(), 'WARN');
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
'fileAppender': {
|
||||
topic: function() {
|
||||
var appender, logmessages = [], thing = "thing", fakeFS = {
|
||||
openSync: function() {
|
||||
assert.equal(arguments[0], './tmp-tests.log');
|
||||
assert.equal(arguments[1], 'a');
|
||||
assert.equal(arguments[2], 0644);
|
||||
return thing;
|
||||
},
|
||||
write: function() {
|
||||
assert.equal(arguments[0], thing);
|
||||
assert.isString(arguments[1]);
|
||||
assert.isNull(arguments[2]);
|
||||
assert.equal(arguments[3], "utf8");
|
||||
logmessages.push(arguments[1]);
|
||||
},
|
||||
watchFile: function() {
|
||||
throw new Error("watchFile should not be called if logSize is not defined");
|
||||
}
|
||||
},
|
||||
log4js = require('../lib/log4js')(fakeFS);
|
||||
log4js.clearAppenders();
|
||||
|
||||
appender = log4js.fileAppender('./tmp-tests.log', log4js.messagePassThroughLayout);
|
||||
log4js.addAppender(appender, 'file-test');
|
||||
|
||||
var logger = log4js.getLogger('file-test');
|
||||
logger.debug("this is a test");
|
||||
|
||||
return logmessages;
|
||||
},
|
||||
'should write log messages to file': function(logmessages) {
|
||||
assert.length(logmessages, 1);
|
||||
assert.equal(logmessages, "this is a test\n");
|
||||
}
|
||||
},
|
||||
|
||||
'fileAppender - with rolling based on size and number of files to keep': {
|
||||
topic: function() {
|
||||
var watchCb,
|
||||
filesOpened = [],
|
||||
filesClosed = [],
|
||||
filesRenamed = [],
|
||||
newFilenames = [],
|
||||
existingFiles = ['tests.log'],
|
||||
log4js = require('../lib/log4js')({
|
||||
watchFile: function(file, options, callback) {
|
||||
assert.equal(file, 'tests.log');
|
||||
assert.equal(options.persistent, false);
|
||||
assert.equal(options.interval, 30000);
|
||||
assert.isFunction(callback);
|
||||
watchCb = callback;
|
||||
},
|
||||
openSync: function(file) {
|
||||
assert.equal(file, 'tests.log');
|
||||
filesOpened.push(file);
|
||||
return file;
|
||||
},
|
||||
statSync: function(file) {
|
||||
if (existingFiles.indexOf(file) < 0) {
|
||||
throw new Error("this file doesn't exist");
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
renameSync: function(oldFile, newFile) {
|
||||
filesRenamed.push(oldFile);
|
||||
existingFiles.push(newFile);
|
||||
},
|
||||
closeSync: function(file) {
|
||||
//it should always be closing tests.log
|
||||
assert.equal(file, 'tests.log');
|
||||
filesClosed.push(file);
|
||||
}
|
||||
});
|
||||
var appender = log4js.fileAppender('tests.log', log4js.messagePassThroughLayout, 1024, 2, 30);
|
||||
return [watchCb, filesOpened, filesClosed, filesRenamed, existingFiles];
|
||||
},
|
||||
|
||||
'should close current log file, rename all old ones, open new one on rollover': function(args) {
|
||||
var watchCb = args[0], filesOpened = args[1], filesClosed = args[2], filesRenamed = args[3], existingFiles = args[4];
|
||||
assert.isFunction(watchCb);
|
||||
//tell the watchCb that the file is below the threshold
|
||||
watchCb({ size: 891 }, { size: 0 });
|
||||
//filesOpened should still be the first one.
|
||||
assert.length(filesOpened, 1);
|
||||
//tell the watchCb that the file is now over the threshold
|
||||
watchCb({ size: 1053 }, { size: 891 });
|
||||
//it should have closed the first log file.
|
||||
assert.length(filesClosed, 1);
|
||||
//it should have renamed the previous log file
|
||||
assert.length(filesRenamed, 1);
|
||||
//and we should have two files now
|
||||
assert.length(existingFiles, 2);
|
||||
assert.deepEqual(existingFiles, ['tests.log', 'tests.log.1']);
|
||||
//and opened a new log file.
|
||||
assert.length(filesOpened, 2);
|
||||
|
||||
//now tell the watchCb that we've flipped over the threshold again
|
||||
watchCb({ size: 1025 }, { size: 123 });
|
||||
//it should have closed the old file
|
||||
assert.length(filesClosed, 2);
|
||||
//it should have renamed both the old log file, and the previous '.1' file
|
||||
assert.length(filesRenamed, 3);
|
||||
assert.deepEqual(filesRenamed, ['tests.log', 'tests.log.1', 'tests.log' ]);
|
||||
//it should have renamed 2 more file
|
||||
assert.length(existingFiles, 4);
|
||||
assert.deepEqual(existingFiles, ['tests.log', 'tests.log.1', 'tests.log.2', 'tests.log.1']);
|
||||
//and opened a new log file
|
||||
assert.length(filesOpened, 3);
|
||||
|
||||
//tell the watchCb we've flipped again.
|
||||
watchCb({ size: 1024 }, { size: 234 });
|
||||
//close the old one again.
|
||||
assert.length(filesClosed, 3);
|
||||
//it should have renamed the old log file and the 2 backups, with the last one being overwritten.
|
||||
assert.length(filesRenamed, 5);
|
||||
assert.deepEqual(filesRenamed, ['tests.log', 'tests.log.1', 'tests.log', 'tests.log.1', 'tests.log' ]);
|
||||
//it should have renamed 2 more files
|
||||
assert.length(existingFiles, 6);
|
||||
assert.deepEqual(existingFiles, ['tests.log', 'tests.log.1', 'tests.log.2', 'tests.log.1', 'tests.log.2', 'tests.log.1']);
|
||||
//and opened a new log file
|
||||
assert.length(filesOpened, 4);
|
||||
}
|
||||
},
|
||||
|
||||
'configure' : {
|
||||
topic: function() {
|
||||
var messages = {}, fakeFS = {
|
||||
openSync: function(file) {
|
||||
return file;
|
||||
},
|
||||
write: function(file, message) {
|
||||
if (!messages.hasOwnProperty(file)) {
|
||||
messages[file] = [];
|
||||
}
|
||||
messages[file].push(message);
|
||||
},
|
||||
readFileSync: function(file, encoding) {
|
||||
return require('fs').readFileSync(file, encoding);
|
||||
},
|
||||
watchFile: function(file) {
|
||||
messages.watchedFile = file;
|
||||
}
|
||||
},
|
||||
log4js = require('../lib/log4js')(fakeFS);
|
||||
return [ log4js, messages ];
|
||||
},
|
||||
'should load appender configuration from a json file': function(args) {
|
||||
var log4js = args[0], messages = args[1];
|
||||
delete messages['tmp-tests.log'];
|
||||
log4js.clearAppenders();
|
||||
//this config file defines one file appender (to ./tmp-tests.log)
|
||||
//and sets the log level for "tests" to WARN
|
||||
log4js.configure('test/log4js.json');
|
||||
var logger = log4js.getLogger("tests");
|
||||
logger.info('this should not be written to the file');
|
||||
logger.warn('this should be written to the file');
|
||||
assert.length(messages['tmp-tests.log'], 1);
|
||||
assert.equal(messages['tmp-tests.log'][0], 'this should be written to the file\n');
|
||||
},
|
||||
'should handle logLevelFilter configuration': function(args) {
|
||||
var log4js = args[0], messages = args[1];
|
||||
delete messages['tmp-tests.log'];
|
||||
delete messages['tmp-tests-warnings.log'];
|
||||
log4js.clearAppenders();
|
||||
log4js.configure('test/with-logLevelFilter.json');
|
||||
var logger = log4js.getLogger("tests");
|
||||
logger.info('main');
|
||||
logger.error('both');
|
||||
logger.warn('both');
|
||||
logger.debug('main');
|
||||
|
||||
assert.length(messages['tmp-tests.log'], 4);
|
||||
assert.length(messages['tmp-tests-warnings.log'], 2);
|
||||
assert.deepEqual(messages['tmp-tests.log'], ['main\n','both\n','both\n','main\n']);
|
||||
assert.deepEqual(messages['tmp-tests-warnings.log'], ['both\n','both\n']);
|
||||
},
|
||||
'should handle fileAppender with log rolling' : function(args) {
|
||||
var log4js = args[0], messages = args[1];
|
||||
delete messages['tmp-test.log'];
|
||||
log4js.configure('test/with-log-rolling.json');
|
||||
assert.equal(messages.watchedFile, 'tmp-test.log');
|
||||
}
|
||||
},
|
||||
|
||||
'with no appenders defined' : {
|
||||
topic: function() {
|
||||
var logger, message, log4js = require('../lib/log4js')(null, function (msg) { message = msg; } );
|
||||
logger = log4js.getLogger("some-logger");
|
||||
logger.debug("This is a test");
|
||||
return message;
|
||||
},
|
||||
'should default to the console appender': function(message) {
|
||||
assert.isTrue(/This is a test$/.test(message));
|
||||
}
|
||||
},
|
||||
|
||||
'default setup': {
|
||||
topic: function() {
|
||||
var pathsChecked = [],
|
||||
message,
|
||||
logger,
|
||||
fakeFS = {
|
||||
readFileSync: function (file, encoding) {
|
||||
assert.equal(file, '/path/to/config/log4js.json');
|
||||
assert.equal(encoding, 'utf8');
|
||||
return '{ "appenders" : [ { "type": "console", "layout": { "type": "messagePassThrough" }} ] }';
|
||||
},
|
||||
statSync: function (path) {
|
||||
pathsChecked.push(path);
|
||||
if (path === '/path/to/config/log4js.json') {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error("no such file");
|
||||
}
|
||||
}
|
||||
},
|
||||
fakeConsoleLog = function (msg) { message = msg; },
|
||||
fakeRequirePath = [ '/a/b/c', '/some/other/path', '/path/to/config', '/some/later/directory' ],
|
||||
log4js = require('../lib/log4js')(fakeFS, fakeConsoleLog, fakeRequirePath),
|
||||
logger = log4js.getLogger('a-test');
|
||||
logger.debug("this is a test");
|
||||
|
||||
return [ pathsChecked, message ];
|
||||
},
|
||||
|
||||
'should check current directory, require paths, and finally the module dir for log4js.json': function(args) {
|
||||
var pathsChecked = args[0];
|
||||
assert.deepEqual(pathsChecked, [
|
||||
'log4js.json',
|
||||
'/a/b/c/log4js.json',
|
||||
'/some/other/path/log4js.json',
|
||||
'/path/to/config/log4js.json',
|
||||
'/some/later/directory/log4js.json',
|
||||
require('path').normalize(__dirname + '/../lib/log4js.json')
|
||||
]);
|
||||
},
|
||||
|
||||
'should configure log4js from first log4js.json found': function(args) {
|
||||
var message = args[1];
|
||||
assert.equal(message, 'this is a test');
|
||||
}
|
||||
},
|
||||
|
||||
'colouredLayout': {
|
||||
topic: function() {
|
||||
return require('../lib/log4js')().colouredLayout;
|
||||
},
|
||||
|
||||
'should apply level colour codes to output': function(layout) {
|
||||
var output = layout({
|
||||
message: "nonsense",
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
categoryName: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
});
|
||||
assert.equal(output, '\033[90m[2010-12-05 14:18:30.045] \033[39m\033[32m[ERROR] \033[39m\033[90mcheese - \033[39mnonsense');
|
||||
}
|
||||
},
|
||||
|
||||
'messagePassThroughLayout': {
|
||||
topic: function() {
|
||||
return require('../lib/log4js')().messagePassThroughLayout;
|
||||
},
|
||||
'should take a logevent and output only the message' : function(layout) {
|
||||
assert.equal(layout({
|
||||
message: "nonsense",
|
||||
startTime: new Date(2010, 11, 5, 14, 18, 30, 45),
|
||||
categoryName: "cheese",
|
||||
level: {
|
||||
colour: "green",
|
||||
toString: function() { return "ERROR"; }
|
||||
}
|
||||
}), "nonsense");
|
||||
}
|
||||
},
|
||||
|
||||
'logLevelFilter': {
|
||||
topic: function() {
|
||||
var log4js = require('../lib/log4js')(), logEvents = [], logger;
|
||||
log4js.clearAppenders();
|
||||
log4js.addAppender(log4js.logLevelFilter('ERROR', function(evt) { logEvents.push(evt); }));
|
||||
logger = log4js.getLogger();
|
||||
logger.debug('this should not trigger an event');
|
||||
logger.warn('neither should this');
|
||||
logger.error('this should, though');
|
||||
logger.fatal('so should this');
|
||||
return logEvents;
|
||||
},
|
||||
'should only pass log events greater than or equal to its own level' : function(logEvents) {
|
||||
assert.length(logEvents, 2);
|
||||
assert.equal(logEvents[0].message, 'this should, though');
|
||||
assert.equal(logEvents[1].message, 'so should this');
|
||||
}
|
||||
},
|
||||
|
||||
'Date extensions': {
|
||||
topic: function() {
|
||||
require('../lib/log4js');
|
||||
return new Date(2010, 0, 11, 14, 31, 30, 5);
|
||||
},
|
||||
'should add a toFormattedString method to Date': function(date) {
|
||||
assert.isFunction(date.toFormattedString);
|
||||
},
|
||||
'should default to a format': function(date) {
|
||||
assert.equal(date.toFormattedString(), '2010-01-11 14:31:30.005');
|
||||
}
|
||||
}
|
||||
|
||||
}).export(module);
|
||||
23
test/with-categoryFilter.json
Normal file
23
test/with-categoryFilter.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"appenders": [
|
||||
{
|
||||
"type": "categoryFilter",
|
||||
"exclude": "web",
|
||||
"appender": {
|
||||
"type": "file",
|
||||
"filename": "test/categoryFilter-noweb.log",
|
||||
"layout": {
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"category": "web",
|
||||
"type": "file",
|
||||
"filename": "test/categoryFilter-web.log",
|
||||
"layout": {
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
9
test/with-cheese.json
Normal file
9
test/with-cheese.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"appenders": {
|
||||
"thing": { "type": "cheese" }
|
||||
},
|
||||
"categories": {
|
||||
"default": { "level": "DEBUG", "appenders": [ "thing" ] },
|
||||
"noisy": { "level": "ERROR", "appenders": [ "thing" ] }
|
||||
}
|
||||
}
|
||||
16
test/with-dateFile.json
Normal file
16
test/with-dateFile.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"appenders": {
|
||||
"dateFile": {
|
||||
"type": "dateFile",
|
||||
"filename": "test/date-file-test.log",
|
||||
"pattern": "-from-MM-dd",
|
||||
"layout": {
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"categories": {
|
||||
"default": { "level": "WARN", "appenders": [ "dateFile" ] }
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,7 @@
|
||||
"type": "file",
|
||||
"filename": "tmp-test.log",
|
||||
"maxLogSize": 1024,
|
||||
"backups": 3,
|
||||
"pollInterval": 15
|
||||
"backups": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"level": "WARN",
|
||||
"appender": {
|
||||
"type": "file",
|
||||
"filename": "tmp-tests-warnings.log",
|
||||
"filename": "test/logLevelFilter-warnings.log",
|
||||
"layout": {
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
{
|
||||
"category": "tests",
|
||||
"type": "file",
|
||||
"filename": "tmp-tests.log",
|
||||
"filename": "test/logLevelFilter.log",
|
||||
"layout": {
|
||||
"type": "messagePassThrough"
|
||||
}
|
||||
|
||||
43
tests.js
43
tests.js
@@ -1,43 +0,0 @@
|
||||
require.paths.unshift("./spec/lib", "./lib");
|
||||
require("jspec");
|
||||
|
||||
var sys = require("sys"), fs = require("fs");
|
||||
|
||||
quit = process.exit
|
||||
print = sys.puts
|
||||
|
||||
readFile = function(path) {
|
||||
var result;
|
||||
try {
|
||||
result = fs.readFileSync(path, "utf8");
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var specsFound = false;
|
||||
|
||||
if (process.ARGV[2]) {
|
||||
specsFound = true;
|
||||
JSpec.exec('spec/spec.' + process.ARGV[2] + '.js');
|
||||
} else {
|
||||
var files = fs.readdirSync('spec/');
|
||||
files.filter(
|
||||
function (file) {
|
||||
return file.indexOf('spec.') === 0;
|
||||
}
|
||||
).forEach(
|
||||
function(file) {
|
||||
specsFound = true;
|
||||
JSpec.exec('spec/'+file);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (specsFound) {
|
||||
JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: false });
|
||||
JSpec.report();
|
||||
} else {
|
||||
print("No tests to run. This makes me sad.");
|
||||
}
|
||||
|
||||
81
writing-appenders.md
Normal file
81
writing-appenders.md
Normal file
@@ -0,0 +1,81 @@
|
||||
Writing Appenders For log4js
|
||||
============================
|
||||
|
||||
Loading appenders
|
||||
-----------------
|
||||
log4js supports loading appender modules from outside its own code. The [log4js-gelf](http://github.com/nomiddlename/log4js-gelf), [log4js-smtp](http://github.com/nomiddlename/log4js-smtp), and [log4js-hookio](http://github.com/nomiddlename/log4js-hookio) appenders are examples of this. In the configuration for an appender, log4js will first attempt to `require` the module from `./lib/appenders/ + type` within log4js - if that fails, it will `require` just using the type. e.g.
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"custom": { type: "log4js-gelf", hostname: "blah", port: 1234 }
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: ["custom"] }
|
||||
}
|
||||
});
|
||||
|
||||
log4js will first attempt to `require('./appenders/' + log4js-gelf)`, this will fail. It will then attempt `require('log4js-gelf')`, which (assuming you have previously run `npm install log4js-gelf`) will pick up the gelf appender.
|
||||
|
||||
Writing your own custom appender
|
||||
--------------------------------
|
||||
This is easiest to explain with an example. Let's assume you want to write a [CouchDB](http://couchdb.apache.org) appender. CouchDB is a document database that you talk to via HTTP and JSON. Our log4js configuration is going to look something like this:
|
||||
|
||||
log4js.configure({
|
||||
appenders: {
|
||||
"couch": {
|
||||
type: "log4js-couchdb",
|
||||
url: "http://mycouchhost:5984",
|
||||
db: "logs",
|
||||
layout: {
|
||||
type: "messagePassThrough"
|
||||
}
|
||||
}
|
||||
},
|
||||
categories: {
|
||||
"default": { level: "debug", appenders: ["couch"] }
|
||||
}
|
||||
});
|
||||
|
||||
When processing this configuration, the first thing log4js will do is `require('log4js-couchdb')`. It expects this module to return a function that accepts two arguments
|
||||
|
||||
module.exports = function(layouts, levels) {
|
||||
...
|
||||
};
|
||||
|
||||
log4js will then call that function, passing in the `layouts` and `levels` sub-modules in case your appender might need to use them. Layouts contains functions which will format a log event as a string in various different ways. Levels contains the definitions of the log levels used by log4js - you might need this for mapping log4js levels to external definitions (the GELF appender does this). These are passed in so that appenders do not need to include a hard dependency on log4js (see below), and so that log4js does not need to expose these modules to the public API. The module function will only be called once per call to `log4js.configure`, even if there are multiple appenders of that type defined.
|
||||
|
||||
The module function should return another function, a configuration function, which will be called for each appender of that type defined in the config. That function should return an appender instance. For our CouchDB example, the calling process is roughly like this:
|
||||
|
||||
couchDbModule = require('log4js-couchdb');
|
||||
appenderMaker = couchDbModule(layouts, levels);
|
||||
appender = appenderMaker({
|
||||
type: "log4js-couchdb",
|
||||
url: "http://mycouchhost:5984",
|
||||
db: "logs",
|
||||
layout: {
|
||||
type: "messagePassThrough"
|
||||
}
|
||||
}, appenderByName)
|
||||
|
||||
Note that in addition to our couchdb appender config, the appenderMaker function gets an extra argument: `appenderByName`, a function which returns an appender when passed its name. This is used by appenders that wrap other appenders. The `logLevelFilter` is an example of this use.
|
||||
|
||||
The `layout` portion of the config can be passed directly to `layouts.layout(config.layout)` to generate a layout function.
|
||||
|
||||
The appender function returned after processing your config should just take one argument: a log event. This function will be called for every log event that should be handled by your appender. In our case, with the config above, every log event of DEBUG level and above will be sent to our appender.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
You should declare which version of log4js your appender works with by
|
||||
including a "peerDependencies" section in your package.json. e.g.
|
||||
|
||||
{
|
||||
"name": "my-cool-appender",
|
||||
"version": "0.0.1",
|
||||
...
|
||||
"peerDependencies": {
|
||||
"log4js": "0.7.x"
|
||||
}
|
||||
}
|
||||
|
||||
For more details on peer dependencies, see
|
||||
[this blog post](http://blog.nodejs.org/2013/02/07/peer-dependencies/).
|
||||
Reference in New Issue
Block a user