├── .npmignore
├── .gitignore
├── .travis.yml
├── test
├── integration
│ └── run-examples.sh
├── fixtures
│ └── logger-config.json
├── mocks
│ ├── MockAppender.js
│ └── MockLogger.js
├── ConsoleAppenderTests.js
├── FileAppenderTests.js
├── AbstractAppenderTests.js
├── RollingFileAppenderTests.js
├── LoggerTests.js
└── SimpleLoggerTests.js
├── Makefile
├── index.js
├── examples
├── logger-config.json
├── custom-timestamp.js
├── log-manager.js
├── simple-file-logger.js
├── simple-logger.js
├── rolling-logger.js
├── domain-logger.js
├── json-file-logger.js
├── hourly-logger.js
├── category-logger.js
├── dynamic-rolling-logger.js
└── json-appender.js
├── todo.md
├── .eslintrc.js
├── package.json
├── watcher.js
├── logo.asc
├── lib
├── ConsoleAppender.js
├── FileAppender.js
├── AbstractAppender.js
├── RollingFileAppender.js
├── Logger.js
└── SimpleLogger.js
├── index.d.ts
├── README.md
└── LICENSE
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | file-test.log
3 | logs/
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __MAC*
2 | .DS_Store
3 | *.gz
4 | dist
5 | .idea
6 | *.bak
7 | *.log
8 | *.swp
9 | node_modules
10 | jsdoc
11 | query-db
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "10"
5 | - "12"
6 |
7 | before_script:
8 | - npm install
9 |
10 | script: make test
11 |
12 |
--------------------------------------------------------------------------------
/test/integration/run-examples.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # dpw@seattle.local
3 | # 2017.05.03
4 | #
5 |
6 | pwd=`pwd`
7 | examples="category-logger.js custom-timestamp.js domain-logger.js dynamic-rolling-logger.js hourly-logger.js json-appender.js json-file-logger.js log-manager.js rolling-logger.js simple-file-logger.js simple-logger.js"
8 |
9 | for ex in $examples
10 | do
11 | fn="${pwd}/examples/${ex}"
12 | ls $fn
13 | $fn
14 | done
15 |
16 |
--------------------------------------------------------------------------------
/test/fixtures/logger-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "appenders":[
3 | {
4 | "typeName":"RollingFileAppender",
5 | "level":"debug"
6 | },
7 | {
8 | "typeName":"ConsoleAppender",
9 | "level":"fatal"
10 | }
11 | ],
12 | "loggers":[
13 | {
14 | "category":"ApplicationFactory",
15 | "level":"warn"
16 | },
17 | {
18 | "category":"CalculationDelegate",
19 | "level":"debug"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | JSFILES=index.js lib/*.js test/*.js test/mocks/*.js examples/*.js
2 | TESTFILES=test/*.js
3 | ESLINT=node_modules/.bin/eslint
4 | MOCHA=node_modules/.bin/mocha
5 |
6 | all:
7 | @make npm
8 | @make test
9 |
10 | npm:
11 | @npm install
12 |
13 | lint:
14 | @( $(ESLINT) $(JSFILES) && echo "lint tests passed..." )
15 |
16 | test:
17 | @( [ -d node_modules ] || make npm )
18 | @( $(MOCHA) $(TESTFILES) && make lint )
19 |
20 | integration:
21 | @( ./test/integration/run-examples.sh )
22 |
23 | watch:
24 | @( ./watcher.js )
25 |
26 | .PHONY: npm
27 | .PHONY: test
28 | .PHONY: jshint
29 | .PHONY: watch
30 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | /** darryl.west@raincitysoftware.com **/
3 |
4 | module.exports = require('./lib/SimpleLogger');
5 | module.exports.AbstractAppender = require('./lib/AbstractAppender');
6 | module.exports.Logger = require('./lib/Logger');
7 |
8 | module.exports.appenders = {
9 | ConsoleAppender:require('./lib/ConsoleAppender'),
10 | FileAppender:require('./lib/FileAppender'),
11 | RollingFileAppender:require('./lib/RollingFileAppender')
12 | };
13 |
14 | module.exports.mocks = {
15 | MockAppender:require('./test/mocks/MockAppender'),
16 | MockLogger:require('./test/mocks/MockLogger')
17 | };
18 |
19 |
--------------------------------------------------------------------------------
/examples/logger-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "appenders":[
3 | {
4 | "typeName":"RollingFileAppender",
5 | "level":"debug"
6 | },
7 | {
8 | "typeName":"ConsoleAppender",
9 | "level":"fatal"
10 | }
11 | ],
12 | "loggers":[
13 | {
14 | "category":"all",
15 | "level":"debug"
16 | },
17 | {
18 | "category":"ApplicationFactory",
19 | "level":"warn"
20 | },
21 | {
22 | "category":"CalculationDelegate",
23 | "level":"debug"
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | # Simple Node Logger To Do List
2 | - - -
3 |
4 | ## Before next release
5 |
6 | * create functional tests to exersize all static methods
7 | * insure 100% code coverage
8 |
9 | ## General
10 |
11 | * add file purger for rolling file logger
12 | * add logic to archive logs to S3
13 | * bump version to 1.0
14 | * modify abstract appender to output dates and objects with minimal formatting
15 | * add dynamic config capability; invoke conf.readLoggerConfig each 2 minutes
16 | * modify SimpleLogger to act as a log manager capable of managing multiple loggers
17 |
18 | ## Documentation
19 |
20 |
21 | - - -
22 |
last updated March 26, 2017
23 |
--------------------------------------------------------------------------------
/examples/custom-timestamp.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const opts = {
4 | timestampFormat: 'mm:ss.SSS'
5 | };
6 | const log = require('../lib/SimpleLogger').createSimpleLogger(opts);
7 |
8 | log.trace('this is a simple trace log statement (should not show)');
9 | log.debug('this is a simple debug log statement (should not show)');
10 | log.info('this is a simple info log statement/entry');
11 | log.warn('this is a simple warn log statement/entry');
12 | log.error('this is a simple error log statement/entry');
13 | log.fatal('this is a simple fatal log statement/entry');
14 |
15 | log.info('set the level to all');
16 | log.setLevel('all');
17 | log.trace('this is a simple trace log statement (should show)');
18 | log.debug('this is a simple debug log statement (should show)');
19 |
20 |
--------------------------------------------------------------------------------
/examples/log-manager.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const manager = require('../lib/SimpleLogger').createLogManager();
4 | const log = manager.createLogger('MyCategory');
5 |
6 | // write some stuff...
7 | log.trace('this is a simple trace log statement (should not show)');
8 | log.debug('this is a simple debug log statement (should not show)');
9 | log.info('this is a simple info log statement/entry');
10 | log.warn('this is a simple warn log statement/entry');
11 | log.error('this is a simple error log statement/entry');
12 | log.fatal('this is a simple fatal log statement/entry');
13 |
14 | log.info('set the level to all');
15 | log.setLevel('all');
16 | log.trace('this is a simple trace log statement (should show)');
17 | log.debug('this is a simple debug log statement (should show)');
18 |
19 |
--------------------------------------------------------------------------------
/examples/simple-file-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const filename = './logs/file-test.log';
4 | console.log('opening log file: ', filename);
5 |
6 | const log = require('../lib/SimpleLogger').createSimpleFileLogger( filename );
7 |
8 | log.info('this is a simple info log statement/entry');
9 | log.warn('this is a simple warn log statement/entry');
10 | log.error('this is a simple error log statement/entry');
11 | log.trace('this is a simple trace log statement (should not show)');
12 | log.debug('this is a simple debug log statement (should not show)');
13 | log.fatal('this is a simple fatal log statement/entry');
14 |
15 | log.info('set the level to all');
16 | log.setLevel('all');
17 | log.trace('this is a simple trace log statement (should show)');
18 | log.debug('this is a simple debug log statement (should show)');
19 |
20 |
--------------------------------------------------------------------------------
/test/mocks/MockAppender.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class MockAppender
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/6/14 8:41 AM
6 | */
7 | const MockAppender = function() {
8 | 'use strict';
9 | const Logger = require('../../lib/Logger' );
10 |
11 | let level = Logger.DEFAULT_LEVEL;
12 | let levels = Logger.STANDARD_LEVELS;
13 | let currentLevel = levels.indexOf( level );
14 |
15 | let appender = this;
16 |
17 | this.entries = [];
18 |
19 | this.setLevel = function(level) {
20 | let idx = levels.indexOf( level );
21 | if (idx >= 0) {
22 | currentLevel = idx;
23 | }
24 | };
25 |
26 | this.getCurrentLevel = function() {
27 | return currentLevel;
28 | };
29 |
30 | this.write = function(entry) {
31 | appender.entries.push( entry );
32 | };
33 | };
34 |
35 | module.exports = MockAppender;
36 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "es6": true,
4 | "node": true
5 | },
6 | "extends": "eslint:recommended",
7 | "globals": {
8 | "describe": true,
9 | "it": true,
10 | "before": true
11 | },
12 | "rules": {
13 | "eqeqeq": "error",
14 | "curly": "error",
15 | "complexity": [ "error", 10 ],
16 | "brace-style": [ "error", "1tbs" ],
17 | "indent": [ "error", 4 ],
18 | "linebreak-style": [ "error", "unix" ],
19 | "no-param-reassign": [ "error", { props: false } ],
20 | "quotes": [ "error", "single" ],
21 | "semi": [ "error", "always" ],
22 | "one-var": ["error", "never"],
23 | "space-before-function-paren": ["error", {
24 | "anonymous": "never",
25 | "named": "never",
26 | "asyncArrow": "always"
27 | }],
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-node-logger",
3 | "version": "21.8.12",
4 | "description": "A node console and file logger suitable for small, medium and large production projects.",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git://github.com/darrylwest/simple-node-logger.git"
9 | },
10 | "scripts": {
11 | "test": "make test"
12 | },
13 | "keywords": [
14 | "log",
15 | "logger",
16 | "multi-appender",
17 | "file logger",
18 | "rolling file logger",
19 | "console logger"
20 | ],
21 | "dependencies": {
22 | "lodash": "^4.17.12",
23 | "moment": "^2.20.1"
24 | },
25 | "devDependencies": {
26 | "chai": "^4.1.2",
27 | "eslint": "^6.0.1",
28 | "mocha": "^9.0.3",
29 | "random-fixture-data": "^2.0.17"
30 | },
31 | "files": [
32 | "index.js",
33 | "lib/",
34 | "test/mocks",
35 | "index.d.ts"
36 | ],
37 | "author": "darryl.west@raincitysoftware.com",
38 | "license": "Apache-2.0",
39 | "homepage": "https://github.com/darrylwest/simple-node-logger",
40 | "typings": "./index.d.ts"
41 | }
42 |
--------------------------------------------------------------------------------
/examples/simple-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const log = require('../lib/SimpleLogger').createSimpleLogger({level: 'info'});
4 |
5 | log.trace('this is a simple trace log statement (should not show)');
6 | log.debug('this is a simple debug log statement (should not show)');
7 | log.info('this is a simple info log statement/entry');
8 | log.warn('this is a simple warn log statement/entry');
9 | log.error('this is a simple error log statement/entry');
10 | log.fatal('this is a simple fatal log statement/entry');
11 |
12 | // test logging a null
13 | log.error();
14 |
15 | log.info('set the level to all');
16 | log.setLevel('all');
17 | log.trace('this is a simple trace log statement (should show)');
18 | log.debug('this is a simple debug log statement (should show)');
19 |
20 | // example of an override
21 | log.warn = function() {
22 | const args = Array.prototype.slice.call(arguments);
23 | const entry = log.log('warn', args);
24 |
25 | process.nextTick(function() {
26 | console.log('custom:', JSON.stringify(entry));
27 | });
28 | };
29 |
30 | log.warn('this is an override test...');
31 |
32 |
--------------------------------------------------------------------------------
/examples/rolling-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const opts = {
4 | logDirectory: __dirname + '/../logs',
5 | fileNamePattern: 'apptest-.log',
6 | dateFormat:'YYYY.MM.DD-HHa'
7 | };
8 |
9 | const log = require('../lib/SimpleLogger').createRollingFileLogger( opts );
10 |
11 | // write some stuff...
12 | log.trace('this is a simple trace log statement (should not show)');
13 | log.debug('this is a simple debug log statement (should not show)');
14 | log.info('this is a simple info log statement/entry');
15 | log.warn('this is a simple warn log statement/entry');
16 | log.error('this is a simple error log statement/entry');
17 | log.fatal('this is a simple fatal log statement/entry');
18 |
19 | log.info('set the level to all');
20 | log.setLevel('all');
21 | log.trace('this is a simple trace log statement (should show)');
22 | log.debug('this is a simple debug log statement (should show)');
23 |
24 | const appender = log.getAppenders()[0];
25 | console.log('write to file: ', appender.__protected().currentFile );
26 |
27 | // rolling file writer uses interval, so we need to exit
28 | setTimeout(function() {
29 | console.log('exiting...');
30 | process.exit( 0 );
31 | }, 1000);
32 |
33 |
--------------------------------------------------------------------------------
/examples/domain-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const filename = '/tmp/file-test.log';
4 | console.log('a category logger with console and file appenders...\nlog file: ', filename);
5 |
6 | const SimpleLogger = require('../lib/SimpleLogger');
7 | const opts = {
8 | domain: 'MyDomain',
9 | dfltLevel: 'debug'
10 | };
11 | const manager = new SimpleLogger(opts);
12 |
13 | let log1;
14 | let log2;
15 |
16 | manager.createConsoleAppender();
17 | manager.createFileAppender({logFilePath: filename});
18 |
19 | log1 = manager.createLogger('CategoryOne');
20 | log2 = manager.createLogger('CategoryTwo');
21 |
22 | log1.trace('this is a simple info log statement/entry: ', opts);
23 | log1.debug('this is a simple debug log statement (should not show)');
24 | log1.info('this is a simple trace log statement: ', {thedate: new Date()}, ', now: ', new Date(), 1, 2, 3);
25 | log2.trace('this is a simple warn log statement/entry');
26 | log1.error('this is a simple error log statement/entry: ', manager, 4, 5);
27 | log2.fatal('this is a simple fatal log statement/entry');
28 |
29 | log2.trace('this is a simple trace log statement (should show)');
30 | log1.debug('this is a simple debug log statement (should show)');
31 |
32 |
--------------------------------------------------------------------------------
/watcher.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // dpw@alameda.local
4 | // 2015.03.04
5 | 'use strict';
6 |
7 | const fs = require('fs');
8 | const spawn = require('child_process').spawn;
9 | const clearScreen = '[H[2J';
10 |
11 | let files = new Set();
12 | let tid;
13 |
14 | const run = function() {
15 | process.stdout.write( clearScreen );
16 | console.log('Changed files: ', files);
17 |
18 | let runner = spawn( 'make', [ 'test' ] );
19 |
20 | runner.stdout.on('data', function( data ) {
21 | process.stdout.write( data );
22 | });
23 |
24 | runner.stderr.on('data', function( data ) {
25 | process.stdout.write( data );
26 | });
27 |
28 | runner.on('close', function(code) {
29 | tid = null;
30 | files.clear();
31 | });
32 | };
33 |
34 | const changeHandler = function(event, filename) {
35 | if ( filename.endsWith('.js') ) {
36 | files.add( filename );
37 |
38 | if (!tid) {
39 | tid = setTimeout(function() {
40 | run();
41 | }, 250);
42 | }
43 | }
44 | };
45 |
46 | // run();
47 | fs.watch( './lib', { recursive:true }, changeHandler );
48 | fs.watch( './test', { recursive:true }, changeHandler );
49 |
50 |
--------------------------------------------------------------------------------
/examples/json-file-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const filename = './logs/json-test.log';
4 | console.log('opening log file: ', filename);
5 |
6 | const SimpleLogger = require('../lib/SimpleLogger');
7 | const manager = new SimpleLogger();
8 | const opts = {
9 | logFilePath: filename
10 | };
11 | const appender = manager.createFileAppender(opts);
12 |
13 | appender.formatter = function(entry) {
14 | let fields = appender.formatEntry(entry);
15 |
16 | fields[1] = entry.level;
17 |
18 | return JSON.stringify(fields) + '\n';
19 | };
20 |
21 | manager.addAppender(appender);
22 | const log = manager.createLogger('JSON');
23 |
24 | log.trace('this is a simple trace log statement (should not show)');
25 | log.debug('this is a simple debug log statement (should not show)');
26 | log.info('this is a simple info log statement/entry');
27 | log.warn('this is a simple warn log statement/entry');
28 | log.error('this is a simple error log statement/entry');
29 | log.fatal('this is a simple fatal log statement/entry');
30 |
31 | log.info('set the level to all');
32 | log.setLevel('all');
33 | log.trace('this is a simple trace log statement (should show)');
34 | log.debug('this is a simple debug log statement (should show)');
35 |
36 |
--------------------------------------------------------------------------------
/examples/hourly-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const opts = {
4 | domain:'Domain-A',
5 | logDirectory: __dirname + '/../logs',
6 | fileNamePattern: 'hourly-test-.log',
7 | dateFormat:'YYYY.MM.DD-HH'
8 | };
9 |
10 | const log = require('../lib/SimpleLogger').createRollingFileLogger( opts );
11 |
12 | setInterval(function() {
13 | // write some stuff...
14 | log.trace('this is a simple trace log statement (should not show)');
15 | log.debug('this is a simple debug log statement (should not show)');
16 | log.info('this is a simple info log statement/entry');
17 | log.warn('this is a simple warn log statement/entry');
18 | log.error('this is a simple error log statement/entry');
19 | log.fatal('this is a simple fatal log statement/entry');
20 |
21 | log.info('set the level to all');
22 | log.setLevel('all');
23 | log.trace('this is a simple trace log statement (should show)');
24 | log.debug('this is a simple debug log statement (should show)');
25 |
26 | }, 800);
27 |
28 | const appender = log.getAppenders()[0];
29 | console.log('write to file: ', appender.__protected().currentFile );
30 |
31 | setTimeout(function() {
32 | console.log('exiting...');
33 | process.exit( 0 );
34 | }, 2000);
35 |
--------------------------------------------------------------------------------
/test/mocks/MockLogger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class MockLogger
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/8/14 5:16 PM
6 | */
7 | const dash = require('lodash');
8 | const Logger = require('../../lib/Logger');
9 | const MockAppender = require('./MockAppender');
10 |
11 | const MockLogger = function(options) {
12 | 'use strict';
13 |
14 | const opts = Object.assign({}, options);
15 |
16 | // const mock = this;
17 | const appender = new MockAppender();
18 |
19 | // set these if not passed in
20 | if (!opts.pid) {
21 | opts.pid = 'test12345';
22 | }
23 | if (!opts.appenders) {
24 | opts.appenders = [appender];
25 | }
26 | if (!opts.level) {
27 | opts.level = 'trace';
28 | }
29 |
30 | dash.extend(this, new Logger(opts));
31 |
32 | this.getLogEntries = function() {
33 | return appender.entries;
34 | };
35 | };
36 |
37 | MockLogger.createLogger = function(category, level) {
38 | 'use strict';
39 |
40 | const opts = {};
41 |
42 | if (category) {
43 | opts.category = category;
44 | }
45 | if (level) {
46 | opts.level = level;
47 | }
48 |
49 | return new MockLogger(opts);
50 | };
51 |
52 | module.exports = MockLogger;
53 |
--------------------------------------------------------------------------------
/examples/category-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const filename = '/tmp/file-test.log';
4 | console.log('a category logger with console and file appenders...\nlog file: ', filename);
5 |
6 | const SimpleLogger = require('../lib/SimpleLogger');
7 | const manager = new SimpleLogger({errorEventName: 'error'});
8 |
9 | let log1;
10 | let log2;
11 |
12 | process.on('error', (msg) => {
13 | console.log('Error event caught: ', JSON.stringify(msg));
14 | });
15 |
16 | manager.createConsoleAppender();
17 | manager.createFileAppender({logFilePath: filename});
18 |
19 | log1 = manager.createLogger('CategoryOne', 'trace');
20 | log2 = manager.createLogger('CategoryTwo', 'trace');
21 |
22 | log1.trace('this is a simple trace log statement (should not show)');
23 | log1.debug('this is a simple debug log statement (should not show)');
24 | log1.info('this is a simple info log statement/entry');
25 | log2.warn('this is a simple warn log statement/entry');
26 | log1.error('this is a simple error log statement/entry');
27 | log1.error();
28 | log2.fatal('this is a simple fatal log statement/entry');
29 |
30 | log2.trace('this is a simple trace log statement (should show)');
31 | log1.debug('this is a simple debug log statement (should show)');
32 |
33 | const loggers = manager.getLoggers();
34 | loggers.forEach(logger => {
35 | console.log('stats: ', logger.getCategory(), logger.getStats());
36 | });
37 |
--------------------------------------------------------------------------------
/logo.asc:
--------------------------------------------------------------------------------
1 | _____ _ _ _____ _ __
2 | | __|_|_____ ___| |___ | | |___ _| |___ | | ___ ___ ___ ___ ___
3 | |__ | | | . | | -_| | | | | . | . | -_| | |__| . | . | . | -_| _|
4 | |_____|_|_|_|_| _|_|___| |_|___|___|___|___| |_____|___|_ |_ |___|_|
5 | |_| |___|___|
6 |
7 | _____ __ _ __ __ __
8 | / __(___ _ ___ / ___ / |/ ___ ___/ ___ / / ___ ___ ____ ____ ____
9 | _\ \/ / ' \/ _ \/ / -_) / / _ / _ / -_) / /_/ _ / _ `/ _ `/ -_/ __/
10 | /___/_/_/_/_/ .__/_/\__/ /_/|_/\___\_,_/\__/ /____\___\_, /\_, /\__/_/
11 | /_/ /___//___/
12 |
13 | ___ _ _ _ _ _ _
14 | / __(_)_ __ _ __| |___ | \| |___ __| |___ | | ___ __ _ __ _ ___ _ _
15 | \__ | | ' \| '_ | / -_) | .` / _ / _` / -_) | |__/ _ / _` / _` / -_| '_|
16 | |___|_|_|_|_| .__|_\___| |_|\_\___\__,_\___| |____\___\__, \__, \___|_|
17 | |_| |___/|___/
18 |
19 | ___ _ _ _ _ _ _
20 | / __<_._ _ _ ___| |___ | \ |___ _| |___ | | ___ ___ ___ ___ _ _
21 | \__ | | ' ' | . | / ._> | / . / . / ._> | |_/ . / . / . / ._| '_>
22 | <___|_|_|_|_| _|_\___. |_\_\___\___\___. |___\___\_. \_. \___|_|
23 | |_| <___<___'
24 |
25 |
--------------------------------------------------------------------------------
/examples/dynamic-rolling-logger.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const port = 18034;
4 |
5 | // configure to read the config file each 2 minutes...
6 |
7 | const opts = {
8 | logDirectory: __dirname + '/../logs',
9 | fileNamePattern: 'dynamic-.log',
10 | dateFormat: 'YYYY.MM.DD-a',
11 | domain: 'MyApplication-' + port,
12 | level: 'info',
13 | loggerConfigFile: __dirname + '/logger-config.json',
14 | refresh: 10 * 1000 // read/refresh each 60 seconds
15 | };
16 |
17 | console.log('config file: ', opts.loggerConfigFile);
18 | const log = require('../lib/SimpleLogger').createRollingFileLogger(opts);
19 |
20 | log.setLevel('trace');
21 |
22 | // write some stuff...
23 | log.trace('this is a simple trace log statement (should not show)');
24 | log.debug('this is a simple debug log statement (should not show)');
25 | log.info('this is a simple info log statement/entry');
26 | log.warn('this is a simple warn log statement/entry');
27 |
28 | const appender = log.getAppenders()[0];
29 | console.log('write to file: ', appender.__protected().currentFile);
30 |
31 | // rolling file writer uses interval, so we need to exit
32 | let count = 5;
33 | setInterval(function() {
34 | log.trace('trace time: ', new Date().toJSON());
35 | log.debug('debug time: ', new Date().toJSON());
36 | log.info('info time: ', new Date().toJSON());
37 | log.warn('warn mark tm');
38 | log.error('error mark tm');
39 |
40 | console.log(count);
41 | count--;
42 | if (count < 0) {
43 | process.exit(0);
44 | }
45 | }, 1000);
46 |
47 |
--------------------------------------------------------------------------------
/examples/json-appender.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const SimpleLogger = require('../index.js');
4 | const Logger = require('../lib/Logger');
5 | const AbstractAppender = SimpleLogger.AbstractAppender;
6 | const manager = new SimpleLogger();
7 |
8 | const JSONAppender = function(options = {}) {
9 | const appender = this;
10 | const opts = {
11 | typeName: 'JSONAppender'
12 | };
13 |
14 | // eslint-disable-next-line no-unused-vars
15 | let level = options.level || Logger.STANDARD_LEVELS[1];
16 | let levels = options.levels || Logger.STANDARD_LEVELS;
17 |
18 | AbstractAppender.extend(this, opts);
19 |
20 | this.write = function(entry) {
21 | const fields = appender.formatEntry(entry);
22 | process.stdout.write(JSON.stringify(fields) + '\n');
23 | };
24 |
25 | this.setLevel = function(level) {
26 | const idx = levels.indexOf(level);
27 | if (idx >= 0) {
28 | // eslint-disable-next-line no-param-reassign
29 | level = idx;
30 | }
31 | };
32 | };
33 |
34 | manager.addAppender(new JSONAppender());
35 | const log = manager.createLogger('JsonTest');
36 |
37 | log.trace('this is a simple trace log statement (should not show)');
38 | log.debug('this is a simple debug log statement (should not show)');
39 | log.info('this is a simple info log statement/entry');
40 | log.warn('this is a simple warn log statement/entry');
41 | log.error('this is a simple error log statement/entry');
42 | log.fatal('this is a simple fatal log statement/entry');
43 |
44 | log.info('set the level to all');
45 | log.setLevel('all');
46 | log.trace('this is a simple trace log statement (should show)');
47 | log.debug('this is a simple debug log statement (should show)');
48 |
49 |
--------------------------------------------------------------------------------
/lib/ConsoleAppender.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class ConsoleAppender
3 | * @classdesc ConsoleAppender writes to the console all entries at or above the specified level.
4 | *
5 | * @author: darryl.west@raincitysoftware.com
6 | * @created: 7/6/14 12:02 PM
7 | */
8 | const Logger = require('./Logger' );
9 | const AbstractAppender = require('./AbstractAppender' );
10 |
11 | /*eslint no-console: "off"*/
12 | const ConsoleAppender = function(opts) {
13 | 'use strict';
14 |
15 | // get a copy of the opts
16 | const options = Object.assign({}, opts);
17 |
18 | const appender = this;
19 | const typeName = options.typeName || 'ConsoleAppender';
20 | const writer = options.writer || console.log;
21 |
22 | let level = options.level || Logger.STANDARD_LEVELS[0];
23 | let levels = options.levels || Logger.STANDARD_LEVELS;
24 | let currentLevel = levels.indexOf( level );
25 |
26 | options.typeName = typeName;
27 | AbstractAppender.extend( this, options );
28 |
29 | /**
30 | * default formatter for this appender;
31 | * @param entry
32 | */
33 | this.formatter = function(entry) {
34 | const fields = appender.formatEntry( entry );
35 |
36 | return fields.join( appender.separator );
37 | };
38 |
39 | /**
40 | * call formatter then write the entry to the console output
41 | * @param entry - the log entry
42 | */
43 | this.write = function(entry) {
44 | if (levels.indexOf( entry.level ) >= currentLevel) {
45 | writer( appender.formatter( entry ));
46 | }
47 | };
48 |
49 | this.setLevel = function(level) {
50 | const idx = levels.indexOf( level );
51 | if (idx >= 0) {
52 | currentLevel = idx;
53 | }
54 | };
55 | };
56 |
57 | module.exports = ConsoleAppender;
58 |
--------------------------------------------------------------------------------
/lib/FileAppender.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class FileAppender
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/7/14 5:15 PM
6 | */
7 | const Logger = require('./Logger' );
8 | const AbstractAppender = require('./AbstractAppender' );
9 | const dash = require( 'lodash' );
10 | const path = require( 'path' );
11 |
12 | const FileAppender = function(options) {
13 | 'use strict';
14 |
15 | const appender = this;
16 | const fs = options.fs || require( 'fs' );
17 | const newline = /^win/.test(process.platform) ? '\r\n' : '\n';
18 | const typeName = options.typeName || 'FileAppender';
19 | const autoOpen = dash.isBoolean( options.autoOpen ) ? options.autoOpen : true;
20 | const levels = options.levels || Logger.STANDARD_LEVELS;
21 |
22 | let level = options.level || Logger.DEFAULT_LEVEL;
23 | let currentLevel = levels.indexOf( level );
24 | let logFilePath = options.logFilePath;
25 | let writer = options.writer;
26 |
27 | options.typeName = typeName;
28 | AbstractAppender.extend( this, options );
29 |
30 | /**
31 | * default formatter for this appender;
32 | * @param entry
33 | */
34 | this.formatter = function(entry) {
35 | const fields = appender.formatEntry( entry );
36 |
37 | // add new line (for linux and windows)
38 | fields.push( newline );
39 |
40 | return fields.join( appender.separator );
41 | };
42 |
43 | /**
44 | * call formatter then write the entry to the console output
45 | * @param entry - the log entry
46 | */
47 | this.write = function(entry) {
48 | if (levels.indexOf( entry.level ) >= currentLevel) {
49 | writer.write( appender.formatter( entry ) );
50 | }
51 | };
52 |
53 | this.setLevel = function(level) {
54 | const idx = levels.indexOf( level );
55 | if (idx >= 0) {
56 | currentLevel = idx;
57 | }
58 | };
59 |
60 | // writer is opened on construction
61 | const openWriter = function() {
62 | if (!writer) {
63 | const file = path.normalize( logFilePath );
64 | const opts = {
65 | flags:'a',
66 | encoding:'utf8'
67 | };
68 |
69 | writer = fs.createWriteStream( file, opts );
70 | }
71 | };
72 |
73 | this.closeWriter = function() {
74 | if (writer) {
75 | writer.end('\n');
76 | }
77 | };
78 |
79 | // constructor tests
80 | (function() {
81 | if (!logFilePath) {
82 | throw new Error('appender must be constructed with a log file path');
83 | }
84 | }());
85 |
86 | if (autoOpen) {
87 | openWriter();
88 | }
89 | };
90 |
91 | module.exports = FileAppender;
92 |
--------------------------------------------------------------------------------
/test/ConsoleAppenderTests.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class ConsoleAppender
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/6/14 12:18 PM
6 | */
7 | const should = require('chai').should();
8 | const dash = require('lodash');
9 | const Logger = require('../lib/Logger');
10 | const ConsoleAppender = require('../lib/ConsoleAppender');
11 |
12 | describe('ConsoleAppender', function() {
13 | 'use strict';
14 |
15 | const createLogger = function(options) {
16 | const opts = Object.assign({}, options);
17 |
18 | opts.domain = 'MyDomain';
19 | opts.category = 'MyCategory';
20 | opts.level = 'debug';
21 |
22 | return new Logger(opts);
23 | };
24 |
25 | const createOptions = function(options) {
26 | const opts = Object.assign({}, options);
27 |
28 | opts.level = 'debug';
29 |
30 | return opts;
31 | };
32 |
33 | describe('#instance', function() {
34 | const appender = new ConsoleAppender(createOptions());
35 | const methods = [
36 | 'formatter',
37 | 'write',
38 | 'setLevel',
39 | 'getTypeName',
40 | 'formatEntry',
41 | 'formatLevel',
42 | 'formatTimestamp',
43 | 'formatMessage',
44 | 'formatDate',
45 | 'formatObject'
46 | ];
47 |
48 | it('should create an instance of ConsoleAppender', function() {
49 | should.exist(appender);
50 | appender.should.be.instanceof(ConsoleAppender);
51 | appender.getTypeName().should.equal('ConsoleAppender');
52 | });
53 |
54 | it('should have all expected methods by size and type', function() {
55 | dash.functionsIn(appender).length.should.equal(methods.length);
56 | methods.forEach(function(method) {
57 | appender[method].should.be.a('function');
58 | });
59 | });
60 | });
61 |
62 | describe('write/format', function() {
63 | const opts = createOptions();
64 | const logger = createLogger();
65 |
66 | it('should write a formatted entry', function(done) {
67 | opts.writer = function(str) {
68 | should.exist(str);
69 |
70 | // console.log( str );
71 |
72 | str.should.contain('INFO');
73 | str.should.contain(':');
74 |
75 | done();
76 | };
77 |
78 | const appender = new ConsoleAppender(opts);
79 | const entry = logger.createEntry('info', ['this is a test, time: ', new Date()]);
80 | appender.write(entry);
81 |
82 | });
83 |
84 | it('should skip log entries less than the specified level', function(done) {
85 | opts.writer = function(str) {
86 | should.not.exist(str);
87 | };
88 |
89 | opts.level = 'fatal';
90 |
91 | const appender = new ConsoleAppender(opts);
92 | const entry = logger.createEntry('info', ['this is a test, time: ', new Date()]);
93 | appender.write(entry);
94 |
95 | process.nextTick(function() {
96 | done();
97 | });
98 | });
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/test/FileAppenderTests.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/7/14 5:15 PM
6 | */
7 | const should = require('chai').should();
8 | const dash = require('lodash');
9 | const Logger = require('../lib/Logger');
10 | const FileAppender = require('../lib/FileAppender');
11 |
12 | describe('FileAppender', function() {
13 | 'use strict';
14 |
15 | const createLogger = function(options) {
16 | const opts = Object.assign({}, options);
17 |
18 | opts.domain = 'MyDomain';
19 | opts.category = 'MyCategory';
20 | opts.level = 'debug';
21 |
22 | return new Logger(opts);
23 | };
24 |
25 | const createOptions = function(options) {
26 | const opts = Object.assign({}, options);
27 |
28 | opts.level = 'debug';
29 | opts.logFilePath = '/tmp/log-test.log';
30 | opts.autoOpen = false;
31 |
32 | return opts;
33 | };
34 |
35 | describe('#instance', function() {
36 | const appender = new FileAppender(createOptions());
37 | const methods = [
38 | 'formatter',
39 | 'write',
40 | 'setLevel',
41 | 'closeWriter',
42 | 'getTypeName',
43 | 'formatEntry',
44 | 'formatLevel',
45 | 'formatTimestamp',
46 | 'formatMessage',
47 | 'formatDate',
48 | 'formatObject'
49 | ];
50 |
51 | it('should create an instance of FileAppender', function() {
52 | should.exist(appender);
53 | appender.should.be.instanceof(FileAppender);
54 | appender.getTypeName().should.equal('FileAppender');
55 | });
56 |
57 | it('should have all expected methods by size and type', function() {
58 | dash.functionsIn(appender).length.should.equal(methods.length);
59 | methods.forEach(function(method) {
60 | appender[method].should.be.a('function');
61 | });
62 | });
63 | });
64 |
65 | describe('write/format', function() {
66 | const opts = createOptions();
67 | const logger = createLogger();
68 |
69 | it('should write a formatted entry', function(done) {
70 | opts.writer = {};
71 | opts.writer.write = function(str) {
72 | should.exist(str);
73 |
74 | str.should.contain('INFO');
75 | str.should.contain(':');
76 |
77 | done();
78 | };
79 |
80 | const appender = new FileAppender(opts);
81 | const entry = logger.createEntry('info', ['this is a test, time: ', new Date()]);
82 | appender.write(entry);
83 |
84 | });
85 |
86 | it('should skip log entries less than the specified level', function(done) {
87 | opts.writer = function(str) {
88 | should.not.exist(str);
89 | };
90 |
91 | opts.level = 'fatal';
92 |
93 | const appender = new FileAppender(opts);
94 | const entry = logger.createEntry('info', ['this is a test, time: ', new Date()]);
95 | appender.write(entry);
96 |
97 | process.nextTick(function() {
98 | done();
99 | });
100 | });
101 | });
102 | });
103 |
--------------------------------------------------------------------------------
/lib/AbstractAppender.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class AbstractAppender
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/7/14 5:58 PM
6 | */
7 | const util = require( 'util' );
8 | const moment = require( 'moment' );
9 | const dash = require( 'lodash' );
10 |
11 | const AbstractAppender = function(options) {
12 | 'use strict';
13 |
14 | const appender = this;
15 | const typeName = options.typeName;
16 | const timestampFormat = options.timestampFormat || 'HH:mm:ss.SSS';
17 | const prettyPrint = options.prettyPrint;
18 |
19 | this.separator = options.separator || ' ';
20 |
21 | /**
22 | * format the entry and return the field list
23 | *
24 | * @param entry the log entry
25 | * @param thisArg - use this to override the base object
26 | *
27 | * @returns field array
28 | */
29 | this.formatEntry = function(entry, thisArg) {
30 | const apdr = thisArg || appender;
31 |
32 | const fields = [];
33 |
34 | if (entry.domain) {
35 | fields.push( entry.domain );
36 | }
37 |
38 | fields.push( apdr.formatTimestamp( entry.ts ) );
39 | fields.push( apdr.formatLevel( entry.level ) );
40 |
41 | if (entry.category) {
42 | fields.push( entry.category );
43 | }
44 |
45 | fields.push( apdr.formatMessage( entry.msg ) );
46 |
47 | return fields;
48 | };
49 |
50 | /**
51 | * format the message
52 | *
53 | * @param msg the log message
54 | * @param thisArg - use this to override the base object
55 | *
56 | * @returns field array
57 | */
58 | this.formatMessage = function(msg, thisArg) {
59 | const apdr = thisArg || appender;
60 |
61 | if (!msg) {
62 | return '';
63 | }
64 |
65 | if (util.isArray( msg )) {
66 | const list = msg.map(function(item) {
67 | if (util.isDate( item )) {
68 | return apdr.formatDate( item );
69 | } else {
70 | return apdr.formatObject( item );
71 | }
72 | });
73 |
74 | return list.join('');
75 | } else {
76 | return msg;
77 | }
78 | };
79 |
80 | this.formatDate = function(value) {
81 | return value.toJSON();
82 | };
83 |
84 | this.formatObject = function(value) {
85 | if (!value) {
86 | return '';
87 | }
88 |
89 | if (dash.isObject( value )) {
90 | try {
91 | if (value instanceof Error) {
92 | return [
93 | value.message,
94 | (prettyPrint) ? JSON.stringify( value, null, 2) : JSON.stringify( value ),
95 | value.stack
96 | ].join('\n');
97 | }
98 |
99 | return (prettyPrint) ? JSON.stringify( value, null, 2) : JSON.stringify( value );
100 | } catch (ignore) {
101 | return 'json error: ' + value.toString();
102 | }
103 | } else {
104 | var s = value.toString();
105 | if (s === '[object Object]') {
106 | return util.inspect( value );
107 | } else {
108 | return s;
109 | }
110 | }
111 | };
112 |
113 | /**
114 | * format the level string by forcing to upper case and padding to 5 chars
115 | *
116 | * @param level
117 | * @returns {string}
118 | */
119 | this.formatLevel = function(level) {
120 | let str = level.toUpperCase();
121 | if (str.length < 5) {
122 | str += ' ';
123 | }
124 |
125 | return str;
126 | };
127 |
128 | /**
129 | * format the timestamp to HH:mm:ss.SSS
130 | *
131 | * @param ts the unix milliseconds
132 | * @returns formatted string
133 | */
134 | this.formatTimestamp = function(ts) {
135 | return moment( ts ).format( timestampFormat );
136 | };
137 |
138 | /**
139 | * return the type name of this appender (ConsoleAppender)
140 | */
141 | this.getTypeName = function() {
142 | return typeName;
143 | };
144 |
145 | // constructor tests
146 | if (!typeName) {
147 | throw new Error('appender must be constructed with a type name');
148 | }
149 | };
150 |
151 | module.exports = AbstractAppender;
152 |
153 | AbstractAppender.extend = function(child, options) {
154 | 'use strict';
155 |
156 | const parent = new AbstractAppender( options );
157 |
158 | dash.extend( child, parent );
159 |
160 | return parent;
161 | };
162 |
--------------------------------------------------------------------------------
/lib/RollingFileAppender.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class RollingFileAppender
3 | *
4 | * roll on size and/or date/time;
5 | *
6 | * @author: darryl.west@raincitysoftware.com
7 | * @created: 7/27/14 9:52 AM
8 | */
9 | const Logger = require('./Logger');
10 | const AbstractAppender = require('./AbstractAppender');
11 | const dash = require('lodash');
12 | const moment = require('moment');
13 | const path = require('path');
14 |
15 | const RollingFileAppender = function(options) {
16 | 'use strict';
17 |
18 | const appender = this;
19 | const fs = options.fs || require('fs');
20 | const newline = /^win/.test(process.platform) ? '\r\n' : '\n';
21 |
22 | let typeName = options.typeName;
23 | let autoOpen = dash.isBoolean(options.autoOpen) ? options.autoOpen : true;
24 | let logDirectory = options.logDirectory;
25 | let fileNamePattern = options.fileNamePattern;
26 | let dateFormat = options.dateFormat || 'YYYY.MM.DD';
27 | let level = options.level || Logger.DEFAULT_LEVEL;
28 | let levels = options.levels || Logger.STANDARD_LEVELS;
29 | let currentLevel = levels.indexOf(level);
30 | let currentFile = options.currentFile;
31 | let rollTimer;
32 | let createInterval = options.createInterval || setInterval;
33 | let writers = [];
34 |
35 | if (!typeName) {
36 | typeName = options.typeName = 'RollingFileAppender';
37 | }
38 |
39 | AbstractAppender.extend(this, options);
40 |
41 | const getWriter = function() {
42 | return writers[0];
43 | };
44 |
45 | const openWriter = function(fname) {
46 | const filename = fname || appender.createFileName();
47 | const file = path.join(logDirectory, filename);
48 | const opts = {
49 | flags: 'a',
50 | encoding: 'utf8'
51 | };
52 |
53 | let writer = fs.createWriteStream(file, opts);
54 |
55 | // make this the current writer...
56 | writers.unshift(writer);
57 | currentFile = file;
58 |
59 | // now close the current logger and remove from the writers list
60 | while (writers.length > 1) {
61 | // close the old writer
62 | writer = writers.pop();
63 | writer.removeAllListeners();
64 | writer.end('\n');
65 | }
66 | };
67 |
68 | // check once per minute to see if we need to roll
69 | const startRollTimer = function() {
70 | rollTimer = createInterval(function() {
71 | if (appender.checkForRoll()) {
72 | openWriter();
73 | }
74 | }, 60 * 1000);
75 | };
76 |
77 | /**
78 | * default formatter for this appender;
79 | * @param entry
80 | */
81 | this.formatter = function(entry) {
82 | const fields = appender.formatEntry(entry);
83 |
84 | fields.push(newline);
85 |
86 | return fields.join(appender.separator);
87 | };
88 |
89 | /**
90 | * call formatter then write the entry to the console output
91 | * @param entry - the log entry
92 | */
93 | this.write = function(entry) {
94 | if (levels.indexOf(entry.level) >= currentLevel) {
95 | const writer = getWriter();
96 | if (writer) {
97 | writer.write(appender.formatter(entry));
98 | } else {
99 | /*eslint no-console: "off"*/
100 | console.log('no writer...');
101 | }
102 | }
103 | };
104 |
105 | this.checkForRoll = function(now) {
106 | // check to see if the
107 | const fn = appender.createFileName(now);
108 | const current = path.basename(currentFile);
109 |
110 | return fn !== current;
111 | };
112 |
113 | this.createFileName = function(now) {
114 | let dt;
115 |
116 | if (now || now instanceof moment) {
117 | dt = now.format(dateFormat);
118 | } else {
119 | dt = moment().format(dateFormat);
120 | }
121 |
122 | return fileNamePattern.replace(//i, dt);
123 | };
124 |
125 | this.setLevel = function(level) {
126 | const idx = levels.indexOf(level);
127 | if (idx >= 0) {
128 | currentLevel = idx;
129 | }
130 | };
131 |
132 | this.__protected = function() {
133 | return {
134 | openWriter: openWriter,
135 | currentFile: currentFile,
136 | rollTimer: rollTimer,
137 | writers: writers
138 | };
139 | };
140 |
141 | // constructor tests
142 | (function() {
143 | if (!logDirectory) {
144 | throw new Error('appender must be constructed with a log directory');
145 | }
146 | if (!fileNamePattern) {
147 | throw new Error('appender must be constructed with a file name pattern');
148 | }
149 | }());
150 |
151 |
152 | // now validate the date pattern and file format
153 | // date may only contain YMDHAa-.
154 |
155 | if (autoOpen) {
156 | openWriter();
157 | startRollTimer();
158 | }
159 | };
160 |
161 | module.exports = RollingFileAppender;
162 |
--------------------------------------------------------------------------------
/lib/Logger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class Logger
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/5/14 6:28 PM
6 | */
7 |
8 | const Logger = function(options) {
9 | 'use strict';
10 |
11 | const logger = this;
12 | const pid = options.pid || process.pid;
13 | const errorEventName = options.errorEventName;
14 | const stats = new Map();
15 |
16 | let domain = options.domain;
17 | let category = options.category;
18 | let level = options.level || Logger.DEFAULT_LEVEL;
19 | let levels = options.levels || Logger.STANDARD_LEVELS;
20 | let currentLevel = levels.indexOf(level);
21 | let appenders = options.appenders || [];
22 |
23 | // helper method
24 | const isLevelAt = function(lvl) {
25 | const idx = levels.indexOf(lvl);
26 |
27 | return idx >= currentLevel;
28 | };
29 |
30 | /**
31 | * log the statement message
32 | *
33 | * @param level the level of this message (label, i.e, info, warn, etc)
34 | * @param msg
35 | */
36 | this.log = function(level, msg) {
37 | const entry = logger.createEntry(level, msg);
38 |
39 | process.nextTick(function() {
40 | // write the message to the appenders...
41 | appenders.forEach(function(appender) {
42 | appender.write(entry);
43 | });
44 |
45 | if (level === 'error' && typeof (errorEventName) === 'string' || typeof (errorEventName) === String) {
46 | process.emit(errorEventName, entry);
47 | }
48 | });
49 |
50 | return entry;
51 | };
52 |
53 | /**
54 | * create the entry object used to log messages
55 | *
56 | * @param level - info, debug, etc.
57 | * @param messageList - a list of message objects
58 | * @returns then entry object
59 | */
60 | this.createEntry = function(level, messageList) {
61 | const entry = {};
62 |
63 | entry.ts = Date.now();
64 |
65 | entry.pid = pid;
66 | if (domain) {
67 | entry.domain = domain;
68 | }
69 | if (category) {
70 | entry.category = category;
71 | }
72 |
73 | entry.level = level;
74 | entry.msg = messageList;
75 |
76 | return entry;
77 | };
78 |
79 | /**
80 | * set the level
81 | *
82 | * @param lvl one of the recognized logger levels
83 | */
84 | this.setLevel = function(lvl) {
85 | currentLevel = levels.indexOf(lvl);
86 | level = lvl;
87 | appenders.forEach(app => {
88 | app.setLevel(lvl);
89 | });
90 | };
91 |
92 | /**
93 | * return the current level string
94 | */
95 | this.getLevel = function() {
96 | return level;
97 | };
98 |
99 | /**
100 | * set the list of appenders
101 | * @param appenderList
102 | */
103 | this.setAppenders = function(appenderList) {
104 | appenders = appenderList;
105 | };
106 |
107 | /**
108 | * add an appender to the list
109 | *
110 | * @param appender - implements write method
111 | */
112 | this.addAppender = function(appender) {
113 | appenders.push(appender);
114 | };
115 |
116 | /**
117 | * remove the appender using the type name
118 | */
119 | this.removeAppender = function(typeName) {
120 | throw new Error(`remove appender ${typeName} is not implemented yet...`);
121 | };
122 |
123 | this.getAppenders = function() {
124 | return appenders;
125 | };
126 |
127 | this.isDebug = function() {
128 | return isLevelAt('debug');
129 | };
130 |
131 | this.isInfo = function() {
132 | return isLevelAt('info');
133 | };
134 |
135 | /**
136 | * return the status map with log counts for each level
137 | */
138 | this.getStats = function() {
139 | return stats;
140 | };
141 |
142 | /**
143 | * return the category name
144 | */
145 | this.getCategory = function() {
146 | return category;
147 | };
148 |
149 | /**
150 | * return the domain name
151 | */
152 | this.getDomain = function() {
153 | return domain;
154 | };
155 |
156 | // now initialize the methods for the standard levels
157 | const init = function() {
158 | levels.forEach(function(lvl) {
159 | stats.set(lvl, 0);
160 | logger[lvl] = function() {
161 | stats.set(lvl, stats.get(lvl) + 1);
162 | if (levels.indexOf(lvl) >= currentLevel) {
163 | const args = Array.prototype.slice.call(arguments);
164 | logger.log(lvl, args);
165 | }
166 | };
167 | });
168 | };
169 |
170 | this.__protected = function() {
171 | return {
172 | pid: pid,
173 | domain: domain,
174 | category: category
175 | };
176 | };
177 |
178 | init();
179 | };
180 |
181 | Logger.STANDARD_LEVELS = ['all', 'trace', 'debug', 'info', 'warn', 'error', 'fatal'];
182 | Logger.DEFAULT_LEVEL = 'info';
183 |
184 | module.exports = Logger;
185 |
--------------------------------------------------------------------------------
/test/AbstractAppenderTests.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class AbstractAppenderTests
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/7/14 6:27 PM
6 | */
7 | const should = require('chai').should();
8 | const dash = require('lodash');
9 | const Logger = require('../lib/Logger');
10 | const AbstractAppender = require('../lib/AbstractAppender');
11 |
12 | describe('AbstractAppender', function() {
13 | 'use strict';
14 |
15 | const createLogger = function(options) {
16 | const opts = Object.assign({}, options);
17 |
18 | opts.domain = 'MyDomain';
19 | opts.category = 'MyCategory';
20 | opts.level = 'debug';
21 |
22 | return new Logger(opts);
23 | };
24 |
25 | const createOptions = function(options) {
26 | const opts = Object.assign({}, options);
27 |
28 | opts.typeName = 'FooAppender';
29 |
30 | return opts;
31 | };
32 |
33 | describe('#instance', function() {
34 | const appender = new AbstractAppender(createOptions());
35 |
36 | const methods = [
37 | 'getTypeName',
38 | 'formatEntry',
39 | 'formatLevel',
40 | 'formatTimestamp',
41 | 'formatMessage',
42 | 'formatDate',
43 | 'formatObject'
44 | ];
45 |
46 | it('should create an instance of AbstractAppender', function() {
47 | should.exist(appender);
48 | appender.should.be.instanceof(AbstractAppender);
49 | appender.getTypeName().should.equal('FooAppender');
50 | });
51 |
52 | it('should have all expected methods by size and type', function() {
53 | dash.functionsIn(appender).length.should.equal(methods.length);
54 | methods.forEach(function(method) {
55 | appender[method].should.be.a('function');
56 | });
57 | });
58 | });
59 |
60 | describe('formatEntry', function() {
61 | const appender = new AbstractAppender(createOptions());
62 | const logger = createLogger();
63 |
64 | it('should create and format fields for a specified log entry', function() {
65 | const entry = logger.createEntry('info', ['this is a test, time: ', new Date()]);
66 | const fields = appender.formatEntry(entry);
67 |
68 | should.exist(fields);
69 | fields.length.should.equal(5);
70 | });
71 | });
72 |
73 | describe('formatObject', function() {
74 | const appender = new AbstractAppender(createOptions());
75 |
76 | it('should format a complex object into human readable output', function() {
77 | const list = [
78 | {
79 | name: 'flarb',
80 | date: new Date()
81 | },
82 | appender
83 | ];
84 |
85 | list.forEach(function(obj) {
86 | const formatted = appender.formatObject(obj);
87 |
88 | // console.log( formatted );
89 | formatted.should.be.a('string');
90 | });
91 | });
92 |
93 | it('should format an error object with message and stack trace', function() {
94 | const err = new Error('this is my error');
95 | const fmt = appender.formatObject(err);
96 | fmt.should.be.a('string');
97 | fmt.should.contain('this is my error');
98 | fmt.should.contain('at ');
99 | });
100 | });
101 |
102 | describe('formatMessage', function() {
103 | const appender = new AbstractAppender(createOptions());
104 |
105 | it('should format a list of log messages', function() {
106 | const list = ['this is a test, time: ', new Date(), ' ', {name: 'flarb', date: new Date()}, ' ', appender];
107 |
108 | const formatted = appender.formatMessage(list);
109 |
110 | // console.log( formatted );
111 | should.exist(formatted);
112 | formatted.should.be.a('string');
113 | });
114 | });
115 |
116 | describe('#timestampFormat', function() {
117 | const ts = 1428516587697; // 2015-04-08T18:09:47.697Z
118 |
119 | it('should have the default format', function() {
120 | const opts = createOptions();
121 | const appender = new AbstractAppender(opts);
122 | const sdt = appender.formatTimestamp(ts);
123 | const parts = sdt.split('.');
124 |
125 | // get this to pass for all timezones
126 | parts[0].split(':')[1].should.equal('09');
127 | parts[0].split(':')[2].should.equal('47');
128 | parts[1].should.equal('697');
129 |
130 | // sdt.should.equal( '18:09:47.697');
131 | });
132 |
133 | it('should have a custom format from options', function() {
134 | const opts = {
135 | typeName: 'customerTSAppender',
136 | timestampFormat: 'x' // unix timestamp
137 | };
138 | const appender = new AbstractAppender(opts);
139 | const sdt = appender.formatTimestamp(ts);
140 |
141 | sdt.should.equal(ts.toString());
142 | });
143 | });
144 | });
145 |
--------------------------------------------------------------------------------
/test/RollingFileAppenderTests.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/27/14 9:53 AM
6 | */
7 | const should = require('chai').should();
8 | const dash = require('lodash');
9 | const path = require('path');
10 | const moment = require('moment');
11 | const RollingFileAppender = require('../lib/RollingFileAppender');
12 |
13 | describe('RollingFileAppender', function() {
14 | 'use strict';
15 |
16 | const createOptions = function() {
17 | const opts = {};
18 |
19 | opts.level = 'debug';
20 | opts.logDirectory = '/tmp/';
21 | opts.fileNamePattern = 'app-.log';
22 | opts.autoOpen = false;
23 |
24 | return opts;
25 | };
26 |
27 | describe('#instance', function() {
28 | const appender = new RollingFileAppender(createOptions());
29 | const methods = [
30 | 'formatter',
31 | 'write',
32 | 'setLevel',
33 | 'checkForRoll',
34 | 'createFileName',
35 | '__protected',
36 | 'getTypeName',
37 | 'formatEntry',
38 | 'formatLevel',
39 | 'formatTimestamp',
40 | 'formatMessage',
41 | 'formatDate',
42 | 'formatObject'
43 | ];
44 |
45 | it('should create an instance of RollingFileAppender', function() {
46 | should.exist(appender);
47 | appender.should.be.instanceof(RollingFileAppender);
48 | appender.getTypeName().should.equal('RollingFileAppender');
49 |
50 | const p = appender.__protected();
51 | should.exist(p);
52 | p.writers.length.should.equal(0);
53 | p.openWriter.should.be.a('function');
54 | });
55 |
56 | it('should have all expected methods by size and type', function() {
57 | dash.functionsIn(appender).length.should.equal(methods.length);
58 | methods.forEach(function(method) {
59 | appender[method].should.be.a('function');
60 | });
61 | });
62 |
63 | it('should check openWriter can open a new file with default createFileName', function() {
64 | const p = appender.__protected();
65 | p.openWriter.should.be.a('function');
66 | p.writers.length.should.equal(0);
67 | const openWriter = p.openWriter;
68 | openWriter.should.be.a('function');
69 | openWriter();
70 | p.writers.length.should.equal(1);
71 | });
72 |
73 | it('should check openWriter can open a new file with filename passed in');
74 | });
75 |
76 | describe('checkForRoll', function() {
77 | const opts = createOptions();
78 |
79 | opts.dateFormat = 'YYYY.MM.DD';
80 |
81 | it('should return false when the date stays within the same day', function() {
82 | let now = moment('2014-01-01T00:00:00');
83 | let appender;
84 | const fn = opts.fileNamePattern.replace(//i, now.format(opts.dateFormat));
85 |
86 | opts.currentFile = path.join(process.env.HOME, fn);
87 | appender = new RollingFileAppender(opts);
88 | const p = appender.__protected();
89 |
90 | should.exist(p);
91 |
92 | appender.checkForRoll(now).should.equal(false);
93 |
94 | // now add a second
95 | now = now.add(1, 's');
96 | appender.checkForRoll(now).should.equal(false);
97 |
98 | // now add a few hours
99 | now = now.add(4, 'h');
100 | appender.checkForRoll(now).should.equal(false);
101 | });
102 |
103 | it('should return true when the day changes', function() {
104 | let now = moment();
105 | let appender;
106 | const fn = opts.fileNamePattern.replace(//i, now.format(opts.dateFormat));
107 |
108 | opts.currentFile = path.join(process.env.HOME, fn);
109 | appender = new RollingFileAppender(opts);
110 | const p = appender.__protected();
111 |
112 | should.exist(p);
113 |
114 | // now add a few hours
115 | now = now.add(1, 'day');
116 | appender.checkForRoll(now).should.equal(true);
117 | });
118 |
119 | });
120 |
121 | describe('createFileName', function() {
122 | const opts = createOptions();
123 | const now = moment('2014-02-06T18:00Z').utc();
124 | const patterns = [
125 | 'YY.MM.DD',
126 | 'YYYY.MM.DD.HH',
127 | 'YYYY.MM.DD-a',
128 | 'YYYYMMDD',
129 | 'MMM-DD'
130 | ];
131 | const expected = [
132 | 'app-14.02.06.log',
133 | 'app-2014.02.06.18.log',
134 | 'app-2014.02.06-pm.log',
135 | 'app-20140206.log',
136 | 'app-Feb-06.log'
137 | ];
138 |
139 | it('should create a filename based on known pattern and date', function() {
140 | patterns.forEach((pattern, idx) => {
141 | opts.dateFormat = pattern;
142 | const appender = new RollingFileAppender(opts);
143 | const fn = appender.createFileName(now);
144 | fn.should.equal(expected[idx]);
145 | });
146 | });
147 | });
148 |
149 | });
150 |
--------------------------------------------------------------------------------
/test/LoggerTests.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class LoggerTests
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/5/14 6:28 PM
6 | */
7 | const should = require('chai').should();
8 | const dash = require('lodash');
9 | const randomData = require('random-fixture-data');
10 | const Logger = require('../lib/Logger');
11 | const MockAppender = require('./mocks/MockAppender');
12 |
13 | describe('Logger', function() {
14 | 'use strict';
15 |
16 | const createOptions = function(options) {
17 | const opts = Object.assign({}, options);
18 |
19 | opts.category = 'MyCat';
20 | opts.appenders = [new MockAppender()];
21 |
22 | return opts;
23 | };
24 |
25 | describe('#instance', function() {
26 | const logger = new Logger(createOptions());
27 | const methods = [
28 | 'log',
29 | 'createEntry',
30 | 'setLevel',
31 | 'getLevel',
32 | 'setAppenders',
33 | 'addAppender',
34 | 'removeAppender',
35 | 'getAppenders',
36 | 'isDebug',
37 | 'isInfo',
38 | 'getCategory',
39 | 'getDomain',
40 | 'getStats',
41 | '__protected'
42 | ];
43 |
44 | it('should create an instance of Logger', function() {
45 | should.exist(logger);
46 | logger.should.be.instanceof(Logger);
47 | });
48 |
49 | it('should have all expected methods by size and type', function() {
50 | const allMethods = methods.concat(Logger.STANDARD_LEVELS);
51 | dash.functionsIn(logger).length.should.equal(allMethods.length);
52 | allMethods.forEach(function(method) {
53 | logger[method].should.be.a('function');
54 | });
55 | });
56 |
57 | it('should have one appender', function() {
58 | const appenders = logger.getAppenders();
59 | should.exist(appenders);
60 | appenders.length.should.equal(1);
61 | });
62 |
63 | it('should have a category', function() {
64 | logger.getCategory().should.equal('MyCat');
65 | });
66 | });
67 |
68 | describe('log', function() {
69 | const opts = createOptions();
70 |
71 | opts.level = Logger.STANDARD_LEVELS[0]; // all
72 | opts.domain = 'MyApp';
73 | opts.pid = 999;
74 |
75 | it('should respond to all log statements', function(done) {
76 | const logger = new Logger(opts);
77 | const appender = new MockAppender();
78 |
79 | logger.setAppenders([appender]);
80 |
81 | logger.trace('this is a fallopia japonica test', {n: 'one'});
82 | logger.debug(randomData.words(3));
83 | logger.info(randomData.words(3));
84 | logger.warn(randomData.words(3));
85 | logger.error(randomData.words(3));
86 | logger.fatal(randomData.words(3));
87 |
88 | process.nextTick(function() {
89 | appender.entries.length.should.equal(6);
90 |
91 | done();
92 | });
93 |
94 | logger.getDomain().should.equal(opts.domain);
95 | });
96 |
97 | it('should contain all entry attributes', function(done) {
98 | const logger = new Logger(opts);
99 | const appender = new MockAppender();
100 | const text = randomData.sentence;
101 |
102 | logger.setAppenders([appender]);
103 |
104 | logger.info(text);
105 |
106 | process.nextTick(function() {
107 | appender.entries.length.should.equal(1);
108 | const entry = appender.entries[0];
109 |
110 | // console.log( entry );
111 |
112 | entry.ts.should.be.above(Date.now() - 1000);
113 | entry.pid.should.equal(opts.pid);
114 | entry.domain.should.equal(opts.domain);
115 | entry.category.should.equal(opts.category);
116 | entry.level.should.equal('info');
117 | entry.msg.length.should.equal(1);
118 | entry.msg[0].should.equal(text);
119 |
120 | done();
121 | });
122 | });
123 | });
124 |
125 | describe('#isLevel', function() {
126 | it('should report isDebug as true if at or below debug', function() {
127 | const log = new Logger(createOptions());
128 |
129 | log.setLevel('debug');
130 | log.getLevel().should.equal('debug');
131 | log.isDebug().should.equal(true);
132 | log.setLevel('trace');
133 | log.isDebug().should.equal(true);
134 | log.setLevel('all');
135 | log.isDebug().should.equal(true);
136 |
137 | });
138 | it('should report isDebug as false if above debug', function() {
139 | const log = new Logger(createOptions());
140 |
141 | log.setLevel('info');
142 | log.getLevel().should.equal('info');
143 | log.isDebug().should.equal(false);
144 |
145 | });
146 |
147 | it('should report isInfo as true if at or below info', function() {
148 | const log = new Logger(createOptions());
149 |
150 | log.setLevel('info');
151 | log.getLevel().should.equal('info');
152 | log.isInfo().should.equal(true);
153 | log.setLevel('trace');
154 | log.isDebug().should.equal(true);
155 | log.setLevel('all');
156 | log.isDebug().should.equal(true);
157 | });
158 |
159 | it('should report isInfo as false if above info', function() {
160 | const log = new Logger(createOptions());
161 |
162 | log.setLevel('warn');
163 | log.getLevel().should.equal('warn');
164 | log.isInfo().should.equal(false);
165 | });
166 |
167 | });
168 |
169 | describe('errorEventHandler', function() {
170 | it('should emit a process error event when configured for events', function(done) {
171 | const opts = createOptions();
172 | opts.errorEventName = 'myerrortrap';
173 | const log = new Logger(opts);
174 | process.on(opts.errorEventName, (msg) => {
175 | should.exist(msg);
176 | msg.category.should.equal('MyCat');
177 | msg.level.should.equal('error');
178 | msg.msg.pop().should.equal('my error trap thing');
179 | done();
180 | });
181 |
182 | log.info('this is a test');
183 | log.warn('anhter');
184 | log.error('my error trap thing');
185 | });
186 |
187 | it('should not emit a process error event when not configured for events', function(done) {
188 | const log = new Logger(createOptions());
189 | process.on('error', (msg) => {
190 | should.not.exist(msg);
191 | });
192 |
193 | log.info('this is a test');
194 | log.warn('anhter');
195 | log.error('my error trap thing');
196 |
197 | setTimeout(() => {
198 | done();
199 | }, 30);
200 | });
201 | });
202 |
203 | describe('stats', function() {
204 | it('should report stats with counts for each level', function() {
205 | const log = new Logger(createOptions());
206 | let stats = log.getStats();
207 |
208 | should.exist(stats);
209 | Logger.STANDARD_LEVELS.forEach(lvl => {
210 | stats.get(lvl).should.equal(0);
211 | });
212 |
213 | log.debug('this is one');
214 | log.getStats().get('debug').should.equal(1);
215 | log.info('this is one');
216 | log.getStats().get('info').should.equal(1);
217 | log.warn('this is one');
218 | log.getStats().get('warn').should.equal(1);
219 | log.error('this is one');
220 | log.getStats().get('error').should.equal(1);
221 | });
222 | });
223 | });
224 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | import moment = require("moment");
5 |
6 | type STANDARD_LEVELS = SimpleLogger.STANDARD_LEVELS;
7 | type Logger = SimpleLogger.Logger;
8 |
9 | type IConsoleAppenderOptions = SimpleLogger.IConsoleAppenderOptions;
10 | type ConsoleAppender = SimpleLogger.appenders.ConsoleAppender;
11 | type IFileAppenderOptions = SimpleLogger.IFileAppenderOptions;
12 | type FileAppender = SimpleLogger.appenders.FileAppender;
13 | type IRollingFileAppenderOptions = SimpleLogger.IRollingFileAppenderOptions;
14 | type RollingFileAppender = SimpleLogger.appenders.RollingFileAppender;
15 | type AbstractAppender = SimpleLogger.AbstractAppender;
16 | type ISimpleLoggerOptions = SimpleLogger.ISimpleLoggerOptions;
17 | declare class SimpleLogger
18 | {
19 | constructor(opts?: ISimpleLoggerOptions);
20 | createLogger(options?: SimpleLogger.ILoggerOptions): Logger;
21 | createLogger(category?: string, level?: STANDARD_LEVELS): Logger;
22 | createConsoleAppender(opts?: IConsoleAppenderOptions): ConsoleAppender;
23 | createFileAppender(opts?: IFileAppenderOptions): FileAppender;
24 | createRollingFileAppender(opts?: IRollingFileAppenderOptions): RollingFileAppender;
25 | addAppender(appender: AbstractAppender): T;
26 | getAppenders(): AbstractAppender[];
27 | getLoggers(): Logger[];
28 | startRefreshThread(): void;
29 | setAllLoggerLevels(level: STANDARD_LEVELS): void;
30 | readConfig(completeCallback?: (err?: any) => void): void;
31 | }
32 |
33 |
34 |
35 | declare namespace SimpleLogger
36 | {
37 | export type STANDARD_LEVELS = 'all' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
38 | interface IEntry
39 | {
40 | ts: moment.MomentInput;
41 | pid: number;
42 | domain?: string;
43 | category?: string;
44 | level: STANDARD_LEVELS;
45 | msg: any | any[];
46 | }
47 | interface ISimpleLoggerOptions
48 | {
49 | domain?: string;
50 | loggers?: Logger[];
51 | level?: STANDARD_LEVELS;
52 | loggerConfigFile?: string;
53 | refresh?: number;
54 | fs?: any;
55 | createInterval?: typeof setInterval;
56 | minRefresh?: number;
57 | errorEventName?: string;
58 | }
59 | export interface ILoggerOptions
60 | {
61 | pid?: number;
62 | errorEventName?: string;
63 | domain?: string;
64 | category?: string;
65 | level?: STANDARD_LEVELS;
66 | levels?: STANDARD_LEVELS[];
67 | appenders?: AbstractAppender[]
68 | }
69 | export namespace Logger
70 | {
71 | var STANDARD_LEVELS: STANDARD_LEVELS[];
72 | var DEFAULT_LEVEL: STANDARD_LEVELS;
73 | }
74 | export class Logger
75 | {
76 |
77 | constructor(options?: ILoggerOptions);
78 | /**
79 | * log the statement message
80 | *
81 | * @param level the level of this message (label, i.e, info, warn, etc)
82 | * @param msg
83 | */
84 | log(level: STANDARD_LEVELS, msg: any[] | any): void;
85 |
86 | /**
87 | * set the level
88 | *
89 | * @param lvl one of the recognized logger levels
90 | */
91 | setLevel(lvl: STANDARD_LEVELS): void;
92 |
93 | /**
94 | * return the current level string
95 | */
96 | getLevel(): STANDARD_LEVELS;
97 |
98 | /**
99 | * set the list of appenders
100 | * @param appenderList
101 | */
102 | setAppenders(appenderList: AbstractAppender[]): void;
103 |
104 | /**
105 | * add an appender to the list
106 | *
107 | * @param appender - implements write method
108 | */
109 | addAppender(appender: T): T;
110 |
111 | /**
112 | * remove the appender using the type name
113 | */
114 | removeAppender(typeName: string): void;
115 |
116 |
117 | getAppenders(): AbstractAppender[];
118 | isDebug(): boolean;
119 | isInfo(): boolean;
120 |
121 | /**
122 | * return the status map with log counts for each level
123 | */
124 | getStats(): Map;
125 |
126 | getCategory(): string | undefined;
127 |
128 | getDomain(): string | undefined;
129 |
130 | all(...arr: any[]): void;
131 | trace(...arr: any[]): void;
132 | debug(...arr: any[]): void;
133 | info(...arr: any[]): void;
134 | warn(...arr: any[]): void;
135 | error(...arr: any[]): void;
136 | fatal(...arr: any[]): void;
137 | }
138 |
139 | export interface IAbstractAppenderOptions
140 | {
141 | typeName?: string;
142 | timestampFormat?: string;
143 | prettyPrint?: boolean;
144 | separator?: string;
145 | level?: STANDARD_LEVELS;
146 | levels?: STANDARD_LEVELS[];
147 | }
148 | export interface IConsoleAppenderOptions extends IAbstractAppenderOptions
149 | {
150 | typeName?: string;
151 | writer?: Function;
152 | }
153 | export interface IFileAppenderOptions extends IAbstractAppenderOptions
154 | {
155 | typeName?: string;
156 | fs?: any;
157 | autoOpen?: boolean;
158 | logFilePath: string;
159 | writer?: any;
160 | }
161 | export interface IRollingFileAppenderOptions extends IAbstractAppenderOptions
162 | {
163 | typeName?: string;
164 | fs?: any;
165 | autoOpen?: boolean;
166 | logDirectory: string;
167 | fileNamePattern: string;
168 | dateFormat?: string;
169 | currentFile?: string;
170 | createInterval?: typeof setInterval;
171 | }
172 |
173 | export abstract class AbstractAppender
174 | {
175 | constructor(options?: IAbstractAppenderOptions);
176 | /**
177 | * format the entry and return the field list
178 | *
179 | * @param entry the log entry
180 | * @param thisArg - use this to override the base object
181 | *
182 | * @returns field array
183 | */
184 | formatEntry(entry: any, thisArg?: AbstractAppender): string[];
185 |
186 | /**
187 | * format the message
188 | *
189 | * @param msg the log message
190 | * @param thisArg - use this to override the base object
191 | *
192 | * @returns field array
193 | */
194 | formatMessage(msg: any | any[], thisArg?: AbstractAppender): string;
195 |
196 | formatDate(value: Date): string;
197 | formatObject(value: any): string;
198 | formatLevel(level: STANDARD_LEVELS): string;
199 | formatTimestamp(ts: moment.MomentInput): string;
200 | getTypeName(): string;
201 |
202 | //formatter(entry: any): string;
203 | abstract write(entry: IEntry): void;
204 | abstract setLevel(level: STANDARD_LEVELS): void;
205 |
206 | __protected(): any;
207 | }
208 |
209 | //export type IConsoleAppenderOptions = appenders.IConsoleAppenderOptions;
210 | //export type IFileAppenderOptions = appenders.IFileAppenderOptions;
211 | //export type IRollingFileAppenderOptions = appenders.IRollingFileAppenderOptions;
212 |
213 | export namespace appenders
214 | {
215 |
216 |
217 | export class ConsoleAppender extends AbstractAppender
218 | {
219 | constructor(options: IAbstractAppenderOptions & IConsoleAppenderOptions);
220 | formatter(entry: IEntry): string;
221 | write(entry: IEntry): void;
222 | setLevel(level: STANDARD_LEVELS): void;
223 | }
224 |
225 |
226 | export class FileAppender extends AbstractAppender
227 | {
228 | constructor(options?: IFileAppenderOptions);
229 | formatter(entry: IEntry): string;
230 | write(entry: IEntry): void;
231 | setLevel(level: STANDARD_LEVELS): void;
232 | }
233 |
234 |
235 | export class RollingFileAppender extends AbstractAppender
236 | {
237 | constructor(options?: IRollingFileAppenderOptions);
238 | formatter(entry: IEntry): string;
239 | write(entry: IEntry): void;
240 | setLevel(level: STANDARD_LEVELS): void;
241 |
242 | checkForRoll(now?: moment.Moment): boolean;
243 | createFileName(now?: moment.Moment): string;
244 | }
245 | }
246 |
247 |
248 | /**
249 | * static convenience method to create a file logger (no console logging);
250 | *
251 | * @param options - if string then it's the logFilePath, else options with the logFilePath
252 | * @returns Logger
253 | */
254 | export function createSimpleLogger(): Logger;
255 | export function createSimpleLogger(logFilePath: string): Logger;
256 | export function createSimpleLogger(options: ISimpleLoggerOptions &
257 | Partial &
258 | Partial): Logger;
259 |
260 | /**
261 | * create a rolling file logger by passing options to SimpleLogger and Logger. this enables setting
262 | * of domain, category, etc.
263 | *
264 | * @param options
265 | * @returns rolling logger
266 | */
267 | export function createSimpleFileLogger(logFilePath: string): Logger;
268 | export function createSimpleFileLogger(options: ISimpleLoggerOptions &
269 | IFileAppenderOptions): Logger;
270 |
271 | /**
272 | * create a rolling file appender and add it to the appender list
273 | *
274 | * @param options
275 | * @returns the appender
276 | */
277 | export function createRollingFileLogger(options: ISimpleLoggerOptions &
278 | IRollingFileAppenderOptions &
279 | { readLoggerConfig?: Function }): Logger;
280 |
281 | /**
282 | * create a log manager
283 | *
284 | * @param options - file or rolling file specs;
285 | */
286 | export function createLogManager(options?: ISimpleLoggerOptions &
287 | Partial &
288 | Partial &
289 | { readLoggerConfig?: Function }): SimpleLogger;
290 | }
291 |
292 | export = SimpleLogger;
293 |
294 |
295 |
296 |
297 |
--------------------------------------------------------------------------------
/test/SimpleLoggerTests.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class SimpleLoggerTests
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 7/7/14 9:44 AM
6 | */
7 | const should = require('chai').should();
8 | const dash = require('lodash');
9 | const fs = require('fs');
10 | const Logger = require('../lib/Logger');
11 | const SimpleLogger = require('../lib/SimpleLogger');
12 | const MockAppender = require('./mocks/MockAppender');
13 |
14 | describe('SimpleLogger', function() {
15 | 'use strict';
16 |
17 | const createOptions = function() {
18 | return {};
19 | };
20 |
21 | describe('#instance', function() {
22 | const manager = new SimpleLogger(createOptions());
23 | const methods = [
24 | 'createLogger',
25 | 'createConsoleAppender',
26 | 'createFileAppender',
27 | 'createRollingFileAppender',
28 | 'addAppender',
29 | 'getAppenders',
30 | 'getLoggers',
31 | 'setAllLoggerLevels',
32 | 'startRefreshThread',
33 | 'readConfig',
34 | '__protected'
35 | ];
36 |
37 | it('should create an instance of SimpleLogger', function() {
38 | should.exist(manager);
39 | manager.should.be.instanceof(SimpleLogger);
40 |
41 | manager.getAppenders().length.should.equal(0);
42 | manager.getLoggers().length.should.equal(0);
43 |
44 | const p = manager.__protected();
45 |
46 | should.exist(p);
47 | p.dfltLevel.should.equal('info');
48 | });
49 |
50 | it('should have all expected methods by size and type', function() {
51 | dash.functionsIn(manager).length.should.equal(methods.length);
52 | methods.forEach(function(method) {
53 | manager[method].should.be.a('function');
54 | });
55 | });
56 | });
57 |
58 | describe('createLogger', function() {
59 | const manager = new SimpleLogger(createOptions());
60 |
61 | it('should create a basic logger with console appender', function() {
62 | const log = manager.createLogger('MyCategory', 'warn');
63 |
64 | should.exist(log);
65 | log.__protected().category.should.equal('MyCategory');
66 | log.getLevel().should.equal('warn');
67 |
68 | log.should.be.instanceof(Logger);
69 | });
70 | });
71 |
72 | describe('#domain', function() {
73 | const opts = createOptions();
74 |
75 | opts.domain = 'MyDomain';
76 | opts.level = 'error';
77 |
78 | const manager = new SimpleLogger(opts);
79 |
80 | it('should create a simple logger with a domain', function() {
81 | const p = manager.__protected();
82 | p.domain.should.equal(opts.domain);
83 | p.dfltLevel.should.equal(opts.level);
84 | });
85 |
86 | it('should create a log with specified domain, category and level', function() {
87 | const log = manager.createLogger('MyCat');
88 |
89 | log.getLevel().should.equal(opts.level);
90 | log.__protected().domain.should.equal(opts.domain);
91 |
92 | // default to a single console appender
93 | log.getAppenders().length.should.equal(0);
94 | });
95 | });
96 |
97 | describe('addAppender', function() {
98 | const manager = new SimpleLogger(createOptions());
99 |
100 | it('should add a new appender to the list', function() {
101 | manager.getAppenders().length.should.equal(0);
102 |
103 | const appender = manager.addAppender(new MockAppender());
104 |
105 | should.exist(appender);
106 | appender.should.be.instanceof(MockAppender);
107 | manager.getAppenders().length.should.equal(1);
108 | });
109 | });
110 |
111 | describe('createConsoleAppender', function() {
112 | const manager = new SimpleLogger(createOptions());
113 |
114 | it('should create a new console appender and add it to the appenders list', function() {
115 | const appender = manager.createConsoleAppender();
116 | should.exist(appender);
117 | manager.getAppenders().length.should.equal(1);
118 | });
119 | });
120 |
121 | describe('createFileAppender', function() {
122 | const manager = new SimpleLogger(createOptions());
123 |
124 | it('should create a new file appender and add it to the appenders list', function() {
125 | const appender = manager.createFileAppender({logFilePath: '/dev/null'});
126 | should.exist(appender);
127 | manager.getAppenders().length.should.equal(1);
128 | });
129 | });
130 |
131 | describe('createRollingFileAppender', function() {
132 | const manager = new SimpleLogger(createOptions());
133 |
134 | it('should create a new rolling file appender and add it to the appenders list', function() {
135 | const opts = {};
136 |
137 | opts.level = 'debug';
138 | opts.logDirectory = process.env.HOME + '/logs';
139 | opts.fileNamePattern = 'app-.log';
140 | opts.autoOpen = false;
141 |
142 | const appender = manager.createRollingFileAppender(opts);
143 | should.exist(appender);
144 | manager.getAppenders().length.should.equal(1);
145 | });
146 | });
147 |
148 | describe('startRefreshThread', function() {
149 | const opts = createOptions();
150 |
151 | opts.loggerConfigFile = __dirname + '/fixtures/logger-config.json';
152 | opts.refresh = 2000;
153 |
154 | const manager = new SimpleLogger(opts);
155 |
156 | it('should start refresh thread if config file and refresh are set', function(done) {
157 | manager.startRefreshThread = function() {
158 | if (fs.existsSync(opts.loggerConfigFile) && dash.isNumber(opts.refresh)) {
159 | // console.log('file: ', opts.loggerConfigFile );
160 | const obj = JSON.parse(fs.readFileSync(opts.loggerConfigFile));
161 | should.exist(obj);
162 |
163 | done();
164 | } else {
165 | /*eslint no-console: "off"*/
166 | console.log(opts.refresh);
167 | console.log('file: ', opts.loggerConfigFile, ' does not exist?');
168 | }
169 | };
170 |
171 | process.nextTick(manager.startRefreshThread);
172 | });
173 | });
174 |
175 | describe('readConfig', function() {
176 | const opts = createOptions();
177 |
178 | opts.loggerConfigFile = __dirname + '/fixtures/logger-config.json';
179 | opts.refresh = 2000;
180 |
181 | const manager = new SimpleLogger(opts);
182 |
183 | it('should read and parse a valid configuration file', function(done) {
184 | const callback = function(err) {
185 | should.not.exist(err);
186 |
187 | // TODO test the appenders to see if at the correct level
188 |
189 | // TODO test the loggers to see if at the correct level
190 |
191 | done();
192 | };
193 |
194 | manager.readConfig(callback);
195 | });
196 | });
197 |
198 | describe('createSimpleLogger', function() {
199 | it('should create a simple logger with a single console adapter when invoked with null options', function() {
200 | const log = SimpleLogger.createSimpleLogger();
201 |
202 | should.exist(log);
203 | log.getLevel().should.equal('info');
204 | const appenders = log.getAppenders();
205 | appenders.length.should.equal(1);
206 | appenders[0].getTypeName().should.equal('ConsoleAppender');
207 | });
208 |
209 | it('should create a simple logger with a single console adapter when invoked with format options', function() {
210 | const opts = {
211 | timestampFormat: 'x' // unix timestamp
212 | };
213 | const log = SimpleLogger.createSimpleLogger(opts);
214 |
215 | should.exist(log);
216 | log.getLevel().should.equal('info');
217 | const appenders = log.getAppenders();
218 | appenders.length.should.equal(1);
219 | const appender = appenders[0];
220 | appender.getTypeName().should.equal('ConsoleAppender');
221 | const dt = new Date('2017-05-01T01:01:01Z');
222 | appender.formatTimestamp(dt).should.equal('1493600461000');
223 | });
224 |
225 | it('should create a simple logger with console and file appenders when invoked with a filename', function() {
226 | const log = SimpleLogger.createSimpleLogger('/tmp/mytmpfile.log');
227 |
228 | should.exist(log);
229 | log.getLevel().should.equal('info');
230 | const appenders = log.getAppenders();
231 | appenders.length.should.equal(2);
232 | appenders[0].getTypeName().should.equal('ConsoleAppender');
233 | appenders[1].getTypeName().should.equal('FileAppender');
234 | });
235 | });
236 | });
237 |
--------------------------------------------------------------------------------
/lib/SimpleLogger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class SimpleLogger
3 | *
4 | * @author: darryl.west@raincitysoftware.com
5 | * @created: 2014-07-06
6 | */
7 | const dash = require('lodash');
8 | const Logger = require('./Logger');
9 | const ConsoleAppender = require('./ConsoleAppender');
10 | const FileAppender = require('./FileAppender');
11 | const RollingFileAppender = require('./RollingFileAppender');
12 |
13 | const SimpleLogger = function(opts) {
14 | 'use strict';
15 |
16 | const options = Object.assign({}, opts);
17 |
18 | const manager = this;
19 | const domain = options.domain;
20 | const appenders = options.appenders || [];
21 | const loggers = options.loggers || [];
22 |
23 | let dfltLevel = options.level || Logger.DEFAULT_LEVEL;
24 | let loggerConfigFile = options.loggerConfigFile;
25 | let refresh = options.refresh;
26 | let fs = options.fs || require('fs');
27 | let createInterval = options.createInterval || setInterval;
28 | let minRefresh = options.minRefresh || 10 * 1000;
29 | let errorEventName = options.errorEventName;
30 |
31 | /**
32 | * create a logger with optional category and level
33 | *
34 | * @param category
35 | * @param level
36 | * @returns Logger
37 | */
38 | this.createLogger = function(category, level) {
39 | const opts = Object.prototype.toString.call(category) === '[object String]' ? options : dash.merge({}, options, category);
40 |
41 | opts.category = dash.isString(category) ? category : opts.category;
42 | opts.level = level ? level : opts.level || dfltLevel;
43 | opts.appenders = appenders;
44 |
45 | if (errorEventName) {
46 | opts.errorEventName = errorEventName;
47 | }
48 |
49 | const logger = new Logger(opts);
50 | loggers.push(logger);
51 |
52 | return logger;
53 | };
54 |
55 | /**
56 | * create the console appender and add it to the appenders list
57 | *
58 | * @param opts - appender settings
59 | * @returns ConsoleAppender -
60 | */
61 | this.createConsoleAppender = function(opts) {
62 | return manager.addAppender(new ConsoleAppender(Object.assign({}, opts)));
63 | };
64 |
65 | /**
66 | * create a file appender and add it to the appenders list
67 | *
68 | * @param opts
69 | * @returns a FileAppender object
70 | */
71 | this.createFileAppender = function(opts) {
72 | if (!opts) {
73 | throw new Error('file appender must be created with log file path set in options');
74 | }
75 |
76 | return manager.addAppender(new FileAppender(opts));
77 | };
78 |
79 | /**
80 | * create a rolling file appender and add it to the appender list
81 | *
82 | * @param opts
83 | * @returns the appender
84 | */
85 | this.createRollingFileAppender = function(opts) {
86 | return manager.addAppender(new RollingFileAppender(opts));
87 | };
88 |
89 | /**
90 | * add the appender to list
91 | *
92 | * @param appender
93 | * @returns the new appender
94 | */
95 | this.addAppender = function(appender) {
96 | appenders.push(appender);
97 |
98 | return appender;
99 | };
100 |
101 | this.getAppenders = function() {
102 | return appenders;
103 | };
104 |
105 | this.getLoggers = function() {
106 | return loggers;
107 | };
108 |
109 | /**
110 | * start the refresh thread; minimum cycle time = 10 seconds...
111 | */
112 | this.startRefreshThread = function() {
113 | // TODO replace with watcher thread
114 | if (fs.existsSync(loggerConfigFile) && dash.isNumber(refresh)) {
115 | const t = Math.max(minRefresh, refresh);
116 | createInterval(manager.readConfig, t);
117 | }
118 | };
119 |
120 | /**
121 | * set the level of all loggers to the specified level
122 | *
123 | * @param level - one of the know levels
124 | */
125 | this.setAllLoggerLevels = function(level) {
126 | loggers.forEach(function(logger) {
127 | logger.setLevel(level);
128 | });
129 | };
130 |
131 | /**
132 | * read and parse the config file; change settings if required
133 | */
134 | this.readConfig = function(completeCallback) {
135 | // TODO refactor into configuration delegate to read stats and then process file only if stats change
136 | const callback = (err, buf) => {
137 | if (err) {
138 | /*eslint no-console: "off"*/
139 | console.log(err);
140 | } else {
141 | const conf = JSON.parse(buf.toString());
142 | if (conf.appenders && conf.appenders.length > 0) {
143 | // find each appender and set the level
144 | conf.appenders.forEach(function(app) {
145 | const level = app.level;
146 |
147 | const appender = dash.find(appenders, (item) => {
148 | if (item.getTypeName() === app.typeName && app.level) {
149 | return item;
150 | }
151 | });
152 |
153 | if (appender && typeof appender.setLevel === 'function') {
154 | appender.setLevel(level);
155 | }
156 | });
157 | }
158 |
159 | if (conf.loggers && conf.loggers.length > 0) {
160 | conf.loggers.forEach(item => {
161 | if (item.category === 'all') {
162 | manager.setAllLoggerLevels(item.level);
163 | }
164 | });
165 | }
166 | }
167 |
168 | if (completeCallback) {
169 | return completeCallback(err);
170 | }
171 | };
172 |
173 | fs.readFile(loggerConfigFile, callback);
174 | };
175 |
176 | this.__protected = function() {
177 | return {
178 | domain: domain,
179 | dfltLevel: dfltLevel,
180 | refresh: refresh,
181 | loggerConfigFile: loggerConfigFile
182 | };
183 | };
184 | };
185 |
186 | module.exports = SimpleLogger;
187 |
188 | /**
189 | * static convenience method to create a simple console logger; see options for details
190 | *
191 | * @param options - optional, if present then it could be 1) a string or 2) and object. if it's a string it's assumed
192 | * to be the logFilePath; if it's a string or an object with logFilePath property, then a file appender is created.
193 | *
194 | * Valid options:
195 | * - logFilePath : a path to the file appender
196 | * - domain : the logger domain, e.g., machine or site id
197 | * - dfltLevel : the default log level (overrides info level)
198 | * - timestampFormat : the format used for log entries (see moment date formats for all possibilities)
199 | *
200 | * @returns Logger
201 | */
202 | SimpleLogger.createSimpleLogger = function(options) {
203 | 'use strict';
204 |
205 | let opts;
206 |
207 | // if options is a string then it must be the
208 | if (typeof options === 'string') {
209 | opts = {
210 | logFilePath: options
211 | };
212 | } else {
213 | opts = Object.assign({}, options);
214 | }
215 |
216 | const manager = new SimpleLogger(opts);
217 |
218 | // pass options in to change date formats, etc
219 | manager.createConsoleAppender(opts);
220 |
221 | if (opts.logFilePath) {
222 | manager.createFileAppender(opts);
223 | }
224 |
225 | return manager.createLogger();
226 | };
227 |
228 | /**
229 | * static convenience method to create a file logger (no console logging);
230 | *
231 | * @param options - if string then it's the logFilePath, else options with the logFilePath
232 | * @returns Logger
233 | */
234 | SimpleLogger.createSimpleFileLogger = function(options) {
235 | 'use strict';
236 |
237 | if (!options) {
238 | throw new Error('must create file logger with a logFilePath');
239 | }
240 |
241 | let opts;
242 |
243 | // if options is a string then it must be the
244 | if (typeof options === 'string') {
245 | opts = {
246 | logFilePath: options
247 | };
248 | } else {
249 | opts = Object.assign({}, options);
250 | }
251 |
252 | const manager = new SimpleLogger(opts);
253 |
254 | manager.createFileAppender(opts);
255 |
256 | return manager.createLogger();
257 | };
258 |
259 | /**
260 | * create a rolling file logger by passing options to SimpleLogger and Logger. this enables setting
261 | * of domain, category, etc.
262 | *
263 | * @param options
264 | * @returns rolling logger
265 | */
266 | SimpleLogger.createRollingFileLogger = function(options) {
267 | 'use strict';
268 |
269 | if (!options) {
270 | throw new Error('createRollingFileLogger requires configuration options for this constructor');
271 | }
272 |
273 | let opts;
274 |
275 | // read a dynamic config file if available
276 | if (typeof options.readLoggerConfig === 'function') {
277 | opts = options.readLoggerConfig();
278 |
279 | opts.readLoggerConfig = options.readLoggerConfig;
280 | } else {
281 | opts = options;
282 | }
283 |
284 | const manager = new SimpleLogger(opts);
285 |
286 | manager.createRollingFileAppender(opts);
287 |
288 | if (opts.refresh && opts.loggerConfigFile) {
289 | process.nextTick(manager.startRefreshThread);
290 | }
291 |
292 | return manager.createLogger(opts);
293 | };
294 |
295 | /**
296 | * create a log manager
297 | *
298 | * @param options - file or rolling file specs;
299 | */
300 | SimpleLogger.createLogManager = function(options) {
301 | 'use strict';
302 |
303 | let opts;
304 |
305 | // read a dynamic config file if available
306 | if (options && typeof options.readLoggerConfig === 'function') {
307 | opts = options.readLoggerConfig();
308 |
309 | opts.readLoggerConfig = options.readLoggerConfig;
310 | } else {
311 | opts = Object.assign({}, options);
312 | }
313 |
314 | const manager = new SimpleLogger(opts);
315 |
316 | if (opts.logDirectory && opts.fileNamePattern) {
317 | manager.createRollingFileAppender(opts);
318 | }
319 |
320 | // create at least one appender
321 | if (manager.getAppenders().length === 0) {
322 | manager.createConsoleAppender(opts);
323 | }
324 |
325 | return manager;
326 | };
327 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple Node Logger
2 | ```
3 | __ _ _ __ _ __
4 | / _(_)_ __ ___ _ __ | | ___ /\ \ \___ __| | ___ / / ___ __ _ __ _ ___ _ __
5 | \ \| | '_ ` _ \| '_ \| |/ _ \ / \/ / _ \ / _` |/ _ \ / / / _ \ / _` |/ _` |/ _ \ '__|
6 | _\ \ | | | | | | |_) | | __/ / /\ / (_) | (_| | __/ / /__| (_) | (_| | (_| | __/ |
7 | \__/_|_| |_| |_| .__/|_|\___| \_\ \/ \___/ \__,_|\___| \____/\___/ \__, |\__, |\___|_|
8 | |_| |___/ |___/
9 | ```
10 |
11 | [](http://badge.fury.io/js/simple-node-logger) [](https://travis-ci.org/darrylwest/simple-node-logger) [](https://david-dm.org/darrylwest/simple-node-logger)
12 |
13 | A simple multi-level logger for console, file, and rolling file appenders. Features include:
14 |
15 | - levels: trace, debug, info, warn, error and fatal levels (plus all and off)
16 | - flexible appender/formatters with default to HH:mm:ss.SSS LEVEL message
17 | - add appenders to send output to console, file, rolling file, etc
18 | - change log levels on the fly
19 | - domain and category columns
20 | - overridable format methods in base appender
21 | - stats that track counts of all log statements including warn, error, etc
22 | - ability to configure to emit process error event for central trapping
23 |
24 | ## Installation
25 |
26 | `npm install simple-node-logger --save`
27 |
28 |
29 | ## How to use
30 | ```javascript
31 | // create a stdout console logger
32 | const log = require('simple-node-logger').createSimpleLogger();
33 | ```
34 |
35 | or
36 |
37 | ```javascript
38 | // create a stdout and file logger
39 | const log = require('simple-node-logger').createSimpleLogger('project.log');
40 | ```
41 |
42 | or
43 |
44 | ```javascript
45 | // create a custom timestamp format for log statements
46 | const SimpleNodeLogger = require('simple-node-logger'),
47 | opts = {
48 | logFilePath:'mylogfile.log',
49 | timestampFormat:'YYYY-MM-DD HH:mm:ss.SSS'
50 | },
51 | log = SimpleNodeLogger.createSimpleLogger( opts );
52 | ```
53 |
54 | or
55 |
56 | ```javascript
57 | // create a file only file logger
58 | const log = require('simple-node-logger').createSimpleFileLogger('project.log');
59 | ```
60 |
61 | or
62 |
63 | ```javascript
64 | // create a rolling file logger based on date/time that fires process events
65 | const opts = {
66 | errorEventName:'error',
67 | logDirectory:'/mylogfiles', // NOTE: folder must exist and be writable...
68 | fileNamePattern:'roll-.log',
69 | dateFormat:'YYYY.MM.DD'
70 | };
71 | const log = require('simple-node-logger').createRollingFileLogger( opts );
72 | ```
73 |
74 | or
75 |
76 | ```javascript
77 | // create a log manager
78 | const manager = require('simple-node-logger').createLogManager();
79 |
80 | manager.createConsoleAppender();
81 |
82 | const log = manager.createLogger('MyClass');
83 | // create other logs and appenders...
84 | ```
85 |
86 | The first use simply logs to the console. The second logs to the console and to the project.log file. The third create a console logger with a custom timestamp format. The fourth logs to the file only. The fifth creates a rolling file log system in the target log folder. The fifth creates a log manager to enable you to add various appenders with multiple levels and create logs for each module or class.
87 |
88 | *See the examples folder for in depth samples...*
89 |
90 | ## Log Levels
91 |
92 | The log levels include the standard set: trace, debug, info, warn, error and fatal. The default level is info. The log level can be set at run-time by doing this:
93 |
94 | ```javascript
95 | log.setLevel('warn');
96 | ```
97 |
98 | This sets the log level to warn and suppresses debug and info messages.
99 |
100 | ## Log Statement Formats
101 |
102 | ### Simple Logger
103 |
104 | The default format is HH:mm:ss.SSS LEVEL message. For example, the log message:
105 |
106 | ```javascript
107 | log.info('subscription to ', channel, ' accepted at ', new Date().toJSON());
108 | ```
109 |
110 | Yields:
111 |
112 | `14:14:21.363 INFO subscription to /devchannel accepted at 2014-04-10T14:20:52.938Z`
113 |
114 | ### Category Logger
115 |
116 | If you create a logger with a category name, all log statements will include this category. Typically a category is a class or module name. If you create a logger with the category name 'MyCategory', the log statement would format like this:
117 |
118 | `14:14:21.363 INFO MyCategory subscription to /devchannel accepted at 2014-04-10T14:20:52.938Z`
119 |
120 | ## Appenders
121 |
122 | You can create a single logger / log manager and add multiple appenders with different log levels. For example, you can add a console appender that has a log level of warn and a file appender to debug.
123 |
124 | _See examples/category-logger.js for an example_.
125 |
126 | ### Console
127 |
128 | Writes to the console. This is the simplest appender typically used for command line applications or for development.
129 |
130 | ### File
131 |
132 | Writes to the specified file. This appender is typically used for services that periodically start and stop or that have a limited number of log statements. An example would be to log just error & fatal messages separate from other logs.
133 |
134 | ### Rolling File Appender
135 |
136 | The rolling file appender offers a full production logger where files roll based on date and time. The minimum roll time is a single hour. A typical application would be a production environment where log files are rolled throughout the day then archived to a separate location.
137 |
138 | The rolling file appender requires a valid date format and file name pattern. The filename must contain the key word that will be replaced with the formatted date. The configuration must also include a target log directory where the files will be written.
139 |
140 | #### Valid Filename Patterns
141 |
142 | ```
143 | mylog-.log
144 | ApplicationName.log.
145 | .log
146 |
147 | ```
148 |
149 | #### Valid Date Formats
150 |
151 | Date formats must map to acceptable file names so have more restrictions than typical dates. If you use delimiters, you are restricted to a dash or dot delimiter to separate year, month, day and hour. Valid examples include:
152 |
153 | ```
154 | MMDD // simple month day that rolls at midnight (no delimiters)
155 | YYYY.MM.DD-HH // year month day and hour that can roll up to once per hour
156 | YYYY-MM-DD.a // year month day and am/pm that rolls twice per day
157 | YYYY-MMM-DD // year month day where month is the short name (Mar, Apr, etc)
158 | ```
159 |
160 | The default format YYYY.MM.DD is used if the format is not supplied.
161 |
162 | ## Dynamic Configuration
163 |
164 | Create a javascript configuration that implements 'readConfig' to return configuration details.
165 |
166 | ## Examples
167 |
168 | The examples folder includes a handful of simple to not so simple cases for console, file, multi-appender, category, etc.
169 |
170 | ## Customizations
171 |
172 | ### Appenders
173 |
174 | Adding a new appender is as easy as implementing write( logEntry ). The easiest way to implement is by extending the base class AbstractAppender. You may also easily override the formatting, order, etc by overriding or providing your own abstract or concrete appender.
175 |
176 | For example, you can extend the AbstractAppender to create a JSON appender by doing this:
177 |
178 | ```javascript
179 | const AbstractAppender = require('simple-node-logger').AbstractAppender;
180 |
181 | const JSONAppender = function() {
182 | 'use strict';
183 | var appender = this;
184 |
185 | var opts = {
186 | typeName:'JSONAppender'
187 | };
188 |
189 | AbstractAppender.extend( this, opts );
190 |
191 | // format and write all entry/statements
192 | this.write = function(entry) {
193 | var fields = appender.formatEntry( entry );
194 |
195 | process.stdout.write( JSON.stringify( entry ) + '\n' );
196 | };
197 | };
198 | ```
199 |
200 | ### Overrides
201 |
202 | #### Appenders
203 |
204 | The appenders have formatting messages that can be overridden at the abstract or concrete level. The format methods include:
205 |
206 | - formatEntry(entry) - to override all formatting
207 | - formatMessage(msgList) - to override a list of messages
208 | - formatDate(value) - custom date, defaults to ISO8601
209 | - formatObject(value) - custom object, defaults to json for regular objects
210 |
211 | #### Logger
212 |
213 | It's easy to extend any one of the log methods at the instance level. Here is an example of overriding the error log to send a socket message:
214 |
215 | ```javascript
216 | const log = new require('simple-node-logger').createSimpleLogger();
217 | const socket = openWebSocket();
218 |
219 | // override the standard error method to send a socket message
220 | log.error = function() {
221 | var args = Array.prototype.slice.call( arguments ),
222 | entry = log.log('error', args);
223 |
224 | // now do something special with the log entry...
225 | process.nextTick(function() {
226 | socket.send( JSON.stringify( entry ));
227 | });
228 | };
229 | ```
230 |
231 |
232 | ## Tests
233 |
234 | All unit tests are written in mocha/chai/should and can be run from the command line by doing this:
235 |
236 | `make test`
237 |
238 | There is also a file watcher that can be invoked with this:
239 |
240 | `make watch`
241 |
242 |
243 | ## Mocks
244 |
245 | Mocks used for testing include MockLogger and MockAppender. Typically you would use MockLogger for unit tests like this:
246 |
247 | ```javascript
248 | const MockLogger = require('simple-node-logger').mocks.MockLogger;
249 |
250 | const log = MockLogger.createLogger('MyCategory');
251 |
252 | log.info('this is a log statement');
253 | log.getLogEntries().length.should.equal( 1 );
254 | ```
255 |
256 | MockLogger extends Logger and uses MockAppender to capture log entries.
257 |
258 | ## License
259 |
260 | Apache 2.0
261 |
262 | ## Recent updates...
263 |
264 | * 0.93.29: when an Error object is logged, the message and stack trace are sent to log targets
265 | * 0.93.30: fixed example/category-logger.js and examples/domain-logger.js to not double-log
266 | * 0.93.31: added thisArg to methods in AbstractAppender to enable proper binding and full override when extending
267 |
268 | - - -
269 | Copyright © 2014-2019, rain city software | Version 18.12.24
270 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------