├── .github
└── workflows
│ └── nodejs.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── examples
├── colored-message
│ └── index.js
├── custom-console
│ └── index.js
├── custom-date-token
│ └── index.js
├── custom-method
│ └── index.js
├── index.js
└── workers
│ ├── index.js
│ └── worker.js
├── gfx
├── console.png
└── console2.png
├── index.d.ts
├── index.js
├── lib
├── defaults.js
└── utils.js
├── package-lock.json
├── package.json
├── test
├── index.test.js
├── tokens.test.js
├── tools
│ └── SpyStream.js
└── utils.test.js
└── tokens
├── date.js
├── label.js
└── msg.js
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | node-version: [16.x]
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Use Node.js ${{ matrix.node-version }}
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 | - run: npm ci
22 | - run: npm test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .nyc_output/
3 | .tap/
4 | node_modules/
5 | npm-debug.log
6 |
7 | stdout.log
8 | stderr.log
9 | stdout_stderr.log
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | gfx/
2 | test/
3 | .circleci/
4 | .idea/
5 | .nyc_output/
6 | examples/
7 | package-lock.json
8 | test*
9 | *.log
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Ståle Raknes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Console-stamp 3
2 |
3 | [![npm][npm-image]][npm-url]
4 | [![Downloads][downloads-image]][npm-url]
5 | [![Build Status][build-img]][build-url]
6 |
7 | [npm-url]: https://npmjs.org/package/console-stamp
8 | [build-url]: https://github.com/starak/node-console-stamp/actions
9 | [npm-image]: https://img.shields.io/npm/v/console-stamp.svg?style=flat-square
10 | [build-img]: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fstarak%2Fnode-console-stamp%2Fbadge%3Fref%3Dmain&style=flat-square
11 | [downloads-image]: https://img.shields.io/npm/dm/console-stamp.svg?style=flat-square
12 |
13 | This module lets you take control over the output from `console` logging methods in Node.js. Such as prefixing the log statement with timestamp information, log levels, add coloured output and much more.
14 |
15 | ## Usage ##
16 |
17 | ### Install
18 | ```console
19 | npm install console-stamp
20 | ```
21 |
22 | ### Patching the console
23 |
24 | You need to provide the console object to `console-stamp` in order to patch the builtin console.
25 |
26 | ```js
27 | require( 'console-stamp' )( console );
28 |
29 | console.log('Hello, World!');
30 | ```
31 | The default behaviour is to add a prefix to each log statement with timestamp information and log level.
32 | ```terminal
33 | [10.02.2019 15:37:43.452] [LOG] Hello, World!
34 | ```
35 |
36 | You can change this by provinding an [options](#options) object as the second parameter.
37 |
38 | ```js
39 | require('console-stamp')(console, {
40 | format: ':date(yyyy/mm/dd HH:MM:ss.l)'
41 | } );
42 |
43 | console.log('Hello, World!');
44 | ```
45 |
46 | ```terminal
47 | [2020/01/19 13:56:49.383] Hello, World!
48 | ```
49 |
50 | Notice how the log level is suddenly missing. You need to add it specifically to the format string.
51 |
52 | ```js
53 | require('console-stamp')(console, {
54 | format: ':date(yyyy/mm/dd HH:MM:ss.l) :label'
55 | } );
56 |
57 | console.log('Hello, World!');
58 | ```
59 |
60 | ```terminal
61 | [2020/01/19 23:20:30.371] [LOG] Hello, World!
62 | ```
63 |
64 | [Read more](#configuration) about how to customize the formatting of the log statement below.
65 |
66 |
67 | ### Patch your own console
68 |
69 | You can also provide a custom console with its own `stdout` and `stderr` like this:
70 |
71 | ```js
72 | const fs = require('fs');
73 | const output = fs.createWriteStream('./stdout.log');
74 | const errorOutput = fs.createWriteStream('./stderr.log');
75 | const logger = new console.Console(output, errorOutput);
76 |
77 | require('console-stamp')(logger, {
78 | stdout: output,
79 | stderr: errorOutput
80 | });
81 | ```
82 |
83 | Everything is then written to the files.
84 |
85 | **NOTE:** If `stderr` isn't passed, warning and error output will be sent to the given `stdout`.
86 |
87 | ### Backwards incompatibility with 2.x versions
88 |
89 | `console-stamp` v3 has been rewritten adding [tokens](#tokens) as a new and easier way to customize and extend your logging output.
90 |
91 | With that in mind, some consessions has been made and you will probably need to update your `console-stamp` integration.
92 |
93 | #### `options.pattern` is replaced by `options.format`
94 |
95 | `options.format` is now the place where you provide the format of the logging prefix using [tokens](#tokens).
96 |
97 | For example, `{ pattern: 'dd.mm.yyyy HH:MM:ss.l'}` is replaced by `{ format: ':date(dd.mm.yyyy HH:MM:ss.l)' }`.
98 |
99 | PS: Providing a string with a date format based on [dateformat](https://www.npmjs.com/package/dateformat) as a second parameter is still supported.
100 |
101 | #### `options.label` is gone
102 |
103 | The log level label (INFO, DEBUG etc.) is now only shown if the token `:label` is part of the format string in `options.format`. It is part of the default format.
104 |
105 | `options.labelSuffix` and `options.labelPrefix` are also gone as now you can provide these values directly in the `options.format` string.
106 |
107 |
108 | ### Configuration
109 |
110 | Here are some examples on how to customize your log statements with `console-stamp`.
111 |
112 | #### Only update timestamp format
113 |
114 | Without any other customizations you can provide the timestamp format directly.
115 |
116 | ```js
117 | require('console-stamp')( console, 'yyyy/mm/dd HH:MM:ss.l' );
118 | ```
119 | To set the timestamp format using the [options](#options) object you can use the `date` token.
120 |
121 | ```js
122 | require('console-stamp')(console, {
123 | format: ':date(yyyy/mm/dd HH:MM:ss.l)'
124 | } );
125 |
126 | console.log('Hello, World!');
127 | ```
128 |
129 | ```
130 | [2020/01/19 23:08:39.202] Hello, World!
131 | ```
132 |
133 | #### Add coloured output
134 |
135 | `console-stamp` uses the excellent [chalk](https://www.npmjs.com/package/chalk) library to provide coloured output and other styling.
136 |
137 | ```js
138 | require( 'console-stamp' )( console, {
139 | format: ':date().blue.bgWhite.underline :label(7)'
140 | } );
141 | ```
142 | You can also simply place some text in parenthesis, and then add your styling to that.
143 |
144 | ```js
145 | require( 'console-stamp' )( console, {
146 | format: '(->).yellow :date().blue.bgWhite.underline :label(7)'
147 | } );
148 | ```
149 |
150 | **Note** that by sending the parameter `--no-color` when you start your node app, will prevent any colors from console.
151 | ```console
152 | $ node my-app.js --no-color
153 | ```
154 | For more examples on styling, check out the [chalk](https://www.npmjs.com/package/chalk) documentation.
155 |
156 |
157 |
158 | ### Tokens
159 |
160 | There are only three predefined tokens registered by default. These are:
161 |
162 | :date([format][,utc])[.color]
163 | :label([padding])[.color]
164 | :msg[.color]
165 |
166 | **:date([format][,utc])**
167 | * **format** {String}
168 | Containing the date format based on [dateformat](https://www.npmjs.com/package/dateformat)
169 | **Default**: 'dd.mm.yyyy HH:MM:ss.l'
170 | * **utc** {Boolean}
171 | Set to `true` will return UTC-time
172 | **Default**: false
173 |
174 | **:label([padding])**
175 | * **padding** {Number}
176 | The total length of the label, including the brackets and padding
177 | **Default:** 7
178 |
179 | **:msg**
180 | * If the `:msg` token is provided in `format`, the output from the console will be returned in its place, otherwise the console output will be added as the last output, with no formatting.
181 |
182 | #### Create a custom token
183 | To define your own token, simply add a callback function with the token name to the tokens option. This callback function is expected to return a string. The value returned is then available as ":foo()" in this case:
184 |
185 | ```javascript
186 | require( 'console-stamp' )( console, {
187 | format: ':foo() :label(7)',
188 | tokens:{
189 | foo: () => {
190 | return '[my prefix]';
191 | }
192 | }
193 | } );
194 |
195 | console.log("Bar");
196 | ```
197 | ```terminal
198 | [my prefix] [LOG] Bar
199 | ```
200 |
201 | The token callback function is called with one argument, representing an Object with the following properties:
202 | * `method` {String}
203 | The invoked method
204 | * `msg` {String}
205 | The console output as a string
206 | * `params` {Array}
207 | The token parameters (ex: The token call `:label(7)` will have params `[7]`)
208 | * `tokens` {Object}
209 | All the defined tokens, incl. the defaults
210 | * `defaultTokens` {Object}
211 | Only the default tokens, even if it's been redefined in options
212 |
213 | ##### Example
214 | Here we are making a custom date token called `mydate` using moment.js to format the date
215 | ```js
216 | const moment = require('moment');
217 | moment.locale('ja');
218 |
219 | require( 'console-stamp' )( console, {
220 | format: ':mydate() :label(7)',
221 | tokens:{
222 | mydate: () => {
223 | return `[${moment().format('LLLL')}]`;
224 | }
225 | }
226 | } );
227 |
228 | console.log('This is a console.log message');
229 | console.info('This is a console.info message');
230 | console.debug('This is a console.debug message');
231 | console.warn('This is a console.warn message');
232 | console.error('This is a console.error message');
233 | ```
234 |
235 | Result:
236 | ```terminal
237 | [2016年5月12日午前11時10分 木曜日] [LOG] This is a console.log message
238 | [2016年5月12日午前11時10分 木曜日] [INFO] This is a console.info message
239 | [2016年5月12日午前11時10分 木曜日] [DEBUG] This is a console.debug message
240 | [2016年5月12日午前11時10分 木曜日] [WARN] This is a console.warn message
241 | [2016年5月12日午前11時10分 木曜日] [ERROR] This is a console.error message
242 | ```
243 |
244 |
245 |
246 | ### Custom Methods
247 |
248 | The **option.extend** option enables the extension or modification of the logging methods and their associated log levels:
249 |
250 | The default logging methods and their log levels are as follows:
251 |
252 | ```js
253 | levels = {
254 | error: 1,
255 | warn: 2,
256 | info: 3,
257 | log: 4,
258 | debug: 4
259 | };
260 | ```
261 |
262 | The **extend** option enables the usage of custom console logging methods to be
263 | used with this module, for example:
264 |
265 | ```js
266 | // Extending the console with a custom method
267 | console.fatal = function(msg) {
268 | console.org.error(msg);
269 | process.exit(1);
270 | }
271 |
272 | // Initialising the output formatter
273 | require( 'console-stamp' )( console, {
274 | extend: {
275 | fatal: 1
276 | }
277 | } );
278 | ```
279 |
280 | **Note** how the `console.org.error` method used in the custom method. This is to prevent circular calls to `console.error`
281 |
282 | -------------
283 |
284 | ### API
285 | ```js
286 | require( 'console-stamp' )( console, [options] );
287 | ```
288 |
289 | #### console
290 | The global console or [custom console](#customconsole).
291 |
292 |
293 | #### options {Object|String}
294 |
295 | The second parameter is an object with several options. As a feature this parameter can be a string containing the date-format.
296 |
297 | * **options.format** {String}
A string with date format based on [dateformat](https://www.npmjs.com/package/dateformat)
298 | **Default**: ':date(dd.mm.yyyy HH:MM:ss.l) :label'
299 |
300 | * **options.tokens** {Object}
Containing token-functions. See example [here](#tokens).
301 |
302 | * **options.include** {Array}
An array containing the methods to include in the patch
303 | **Default**: ["debug", "log", "info", "warn", "error"]
304 |
305 | * **options.level** {String}
A string choosing the most verbose logging function to allow.
306 | **Default**: `log`
307 |
308 | * **options.extend** {Object}
An object describing methods and their associated log level,
309 | to extend the existing `method <-> log level` pairs.
310 | For an example see [Custom methods](#custommethods).
311 |
312 | * **options.stdout** {WritableStream}
A custom `stdout` to use with [custom console](#customconsole).
313 | **Default:** `process.stdout`
314 |
315 | * **options.stderr** {WritableStream}
A custom `stderr` to use with [custom console](#customconsole).
316 | **Default:** `options.stdout` or `process.stderr`
317 |
318 | * **options.preventDefaultMessage** {Boolean}
If set to `true` Console-stamp will not print out the standard output from the console. This can be used in combination with a custom message token.
**Default:** `false`
319 |
320 |
--------------------------------------------------------------------------------
/examples/colored-message/index.js:
--------------------------------------------------------------------------------
1 | const console_stamp = require( '../../index' );
2 |
3 | const chalk = require( "chalk" );
4 |
5 | const map = {
6 | log: 'cyanBright',
7 | error: 'red',
8 | info: 'blue',
9 | warn: 'yellow'
10 | };
11 |
12 | console_stamp( console, {
13 | format: '(->).yellow :date().gray :label :msg',
14 | tokens: {
15 | label: ( obj ) => {
16 | return chalk`{${map[obj.method] || 'reset'} ${obj.defaultTokens.label( obj )}}`;
17 | },
18 | msg: ( { method, msg } ) => {
19 | return chalk`{${map[method] || 'reset'} ${msg}}`;
20 | }
21 | }
22 | } );
23 |
24 | console.debug( 'This is a console.debug message' );
25 | console.log( 'This is a console.log message Test (1).csv' );
26 | console.info( 'This is a console.info message' );
27 | console.warn( 'This is a console.warn message' );
28 | console.error( 'This is a console.error message' );
29 | console.reset();
30 |
--------------------------------------------------------------------------------
/examples/custom-console/index.js:
--------------------------------------------------------------------------------
1 | const console_stamp = require( '../../index' );
2 | const logger = new console.Console( process.stdout, process.stderr );
3 |
4 | console_stamp( logger );
5 |
6 | logger.debug( 'This is a console.debug message' );
7 | logger.log( 'This is a console.log message' );
8 | logger.info( 'This is a console.info message' );
9 | logger.warn( 'This is a console.warn message' );
10 | logger.error( 'This is a console.error message' );
11 |
--------------------------------------------------------------------------------
/examples/custom-date-token/index.js:
--------------------------------------------------------------------------------
1 | const console_stamp = require( '../../index' );
2 |
3 | console_stamp( console, {
4 | format: ':mydate() :label(7)',
5 | tokens:{
6 | mydate: ({params}) => {
7 | // noinspection JSCheckFunctionSignatures
8 | return `[${new Intl.DateTimeFormat('no-NB', { dateStyle: 'long', timeStyle: 'medium' }).format(new Date())}]`;
9 | }
10 | }
11 | } );
12 |
13 | console.debug('This is a console.debug message');
14 | console.log('This is a console.log message');
15 | console.info('This is a console.info message');
16 | console.warn('This is a console.warn message');
17 | console.error('This is a console.error message');
18 | console.reset();
19 |
--------------------------------------------------------------------------------
/examples/custom-method/index.js:
--------------------------------------------------------------------------------
1 | const console_stamp = require( '../../index' );
2 | // Extending the console with a custom method
3 | console.fatal = function( msg ) {
4 | console.org.error( msg );
5 | // process.exit( 1 );
6 | }
7 |
8 | console_stamp( console, {
9 | extend: {
10 | fatal: 1
11 | }
12 | } );
13 |
14 | console.debug( 'This is a console.debug message' );
15 | console.log( 'This is a console.log message' );
16 | console.info( 'This is a console.info message' );
17 | console.warn( 'This is a console.warn message' );
18 | console.error( 'This is a console.error message' );
19 | console.fatal( 'This is a custom console.fatal message' );
20 | console.reset();
21 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | const { lstatSync, readdirSync } = require( 'fs' );
2 | const { join } = require( 'path' );
3 |
4 | const isDirectory = source => lstatSync( source ).isDirectory()
5 | const getDirectories = source =>
6 | readdirSync( source ).map( name => join( source, name ) ).filter( isDirectory );
7 |
8 | getDirectories( __dirname ).map( d => {
9 | process.stdout.write( `\nLoading ${d.replace( __dirname, '.' )}\n` );
10 | require( d )
11 | } );
--------------------------------------------------------------------------------
/examples/workers/index.js:
--------------------------------------------------------------------------------
1 | const { Worker } = require('worker_threads');
2 | const worker = new Worker(__dirname + '/worker.js');
3 | console.log('Log 1 from parent');
4 |
5 | setTimeout(()=>{
6 | console.log('Log 2 from parent');
7 | },50)
8 |
--------------------------------------------------------------------------------
/examples/workers/worker.js:
--------------------------------------------------------------------------------
1 | require('../../index')(console, { format: ':date(yyyy/mm/dd HH:MM:ss.l) :label' });
2 | console.log('Log 1 from worker');
3 | console.error('Error 1 from worker');
4 | console.log('Log 2 from worker');
5 | console.error('Error 2 from worker');
6 |
--------------------------------------------------------------------------------
/gfx/console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starak/node-console-stamp/eadc3836781e29937085acd5aa87f9514653a0a1/gfx/console.png
--------------------------------------------------------------------------------
/gfx/console2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/starak/node-console-stamp/eadc3836781e29937085acd5aa87f9514653a0a1/gfx/console2.png
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export type Token = ( payload: TokenPayload ) => string | number;
2 |
3 | export interface TokenPayload {
4 | method: string
5 | params: (string | number)[]
6 | tokens: Record
7 | defaultTokens: Record
8 | msg: string
9 | }
10 |
11 | export interface SpyStream extends NodeJS.WriteStream{
12 | length: number
13 | last: string
14 | flush: ()=>void
15 | asArray: string[]
16 | }
17 |
18 | declare global {
19 | interface Console {
20 | reset: () => void;
21 | org: Console
22 | }
23 | }
24 |
25 | declare function consoleStamp(console: Console, options?: {
26 | format?: string
27 | tokens?: Record
28 | include?: string[]
29 | level?: string
30 | extend?: Record
31 | stdout?: NodeJS.WriteStream | SpyStream
32 | stderr?: NodeJS.WriteStream | SpyStream
33 | preventDefaultMessage?: boolean
34 | }): void;
35 |
36 | export default consoleStamp;
37 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const { checkLogLevel, generateConfig, generatePrefix, selectOutputStream, FakeStream } = require( './lib/utils.js' );
2 | let consoleStamp = ( con, options = {} ) => {
3 |
4 | if ( con.__patched ) {
5 | con.reset();
6 | }
7 |
8 | const helperConsoleStream = new FakeStream();
9 | const helperConsole = new console.Console( helperConsoleStream, helperConsoleStream );
10 |
11 | const config = generateConfig( options );
12 | const include = config.include.filter( m => typeof con[m] === 'function' );
13 |
14 | const org = {};
15 | Object.keys( con ).forEach( m => org[m] = con[m] );
16 | con.org = org;
17 |
18 | include.forEach( method => {
19 | const stream = selectOutputStream( method, config );
20 | const trg = con[method];
21 |
22 | con[method] = new Proxy( trg, {
23 | apply: ( target, context, args ) => {
24 | if ( checkLogLevel( config, method ) ) {
25 | helperConsole.log.apply( context, args );
26 | // TODO: custom msg vs table will not work
27 | let outputMessage = `${generatePrefix( method, config, helperConsoleStream.last_msg )} `;
28 | if(method === 'table'){
29 | outputMessage += '\n';
30 | helperConsole.table.apply( context, args);
31 | outputMessage += helperConsoleStream.last_msg;
32 | }else if(!( config.preventDefaultMessage || /:msg\b/.test( config.format ) )){
33 | outputMessage += `${helperConsoleStream.last_msg}`;
34 | }
35 | outputMessage += '\n';
36 | stream.write( outputMessage );
37 | }
38 | }
39 | } );
40 |
41 | con.__patched = true
42 | } );
43 |
44 | if(!include.includes('table')) {
45 | // Normaly table calls log to write to stream, we need to prevent prefix when table is not included
46 | const tableConsole = new console.Console( config.stdout, config.stderr );
47 | con.table = tableConsole.table;
48 | }
49 |
50 | con.reset = () => {
51 | Object.keys( con.org ).forEach( m => {
52 | con[m] = con.org[m];
53 | delete con.org[m];
54 | } );
55 | delete con.org;
56 | delete con.__patched;
57 | delete con.reset;
58 | helperConsoleStream.end();
59 | };
60 |
61 | };
62 |
63 | module.exports = consoleStamp;
64 | module.exports.default = consoleStamp;
65 |
66 |
--------------------------------------------------------------------------------
/lib/defaults.js:
--------------------------------------------------------------------------------
1 | const date = require( '../tokens/date' );
2 | const label = require( '../tokens/label' );
3 | const msg = require( '../tokens/msg' );
4 |
5 | module.exports = {
6 | format: '',
7 | dfFormat: ':date($$) :label(7)',
8 | dfDateFormat: 'dd.mm.yyyy HH:MM.ss.l',
9 | include: ['debug', 'log', 'info', 'warn', 'error'],
10 | tokens: {
11 | date,
12 | label,
13 | msg
14 | },
15 | level: 'log',
16 | levels: {
17 | error: 1,
18 | warn: 2,
19 | info: 3,
20 | log: 4,
21 | debug: 4,
22 | },
23 | extend: {},
24 | groupCount: 0,
25 | preventDefaultMessage: false,
26 | };
27 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | const df = require( './defaults' );
2 | const chalk = require( 'chalk' );
3 | const { Writable } = require( 'stream' );
4 |
5 | function checkLogLevel( { levels, level }, method ) {
6 | return levels[level] >= levels[method];
7 | }
8 |
9 | function parseParams( str = '' ) {
10 | return str
11 | .replace( /[()"']*/g, '' )
12 | .split( ',' )
13 | .map( s => s.trim() )
14 | .filter( s => s !== '' )
15 | .map( s => isNaN( +s ) ? s : +s );
16 | }
17 |
18 | function generateConfig( options ) {
19 |
20 | if ( typeof options === 'string' ) {
21 | options = {
22 | format: df.dfFormat.replace( '$$', options )
23 | }
24 | }
25 |
26 | // Using Object.assign and not Object spread properties to support Node v6.4
27 | const config = Object.assign( {}, df, options || {}, {
28 | format: typeof options.format === 'undefined' ? df.dfFormat.replace( '$$', df.dfDateFormat ) : options.format,
29 | include: [...( new Set( [...( options.include || df.include ), ...Object.keys( options.extend || {} )] ) )],
30 | tokens: Object.assign( {}, df.tokens, options.tokens || {} ),
31 | levels: Object.assign( {}, df.levels, options.levels || {}, options.extend || {} ),
32 | stdout: options.stdout || process.stdout,
33 | stderr: options.stderr || options.stdout || process.stderr
34 | } );
35 |
36 | config.tokensKeys = Object.keys( config.tokens );
37 | config.defaultTokens = df.tokens;
38 |
39 | return config;
40 | }
41 |
42 | function generatePrefix( method, { tokens, defaultTokens, format: prefix, tokensKeys }, msg ) {
43 | tokensKeys
44 | .sort( ( a, b ) => b.length - a.length )
45 | .forEach( key => {
46 | const token = tokens[key];
47 | const re = new RegExp( `:${key}(\\([^)]*\\))?(\\.\\w+)*`, 'g' );
48 | prefix = prefix.replace( re, ( match, params ) => {
49 | try {
50 | let ret = token({method, defaultTokens, params: parseParams(params), tokens, msg});
51 | match.replace(params, '').split('.').slice(1).forEach(decorator => {
52 | ret = chalk`{${decorator} ${ret}}`;
53 | });
54 | return ret;
55 | }catch (e){
56 | return match;
57 | }
58 | } );
59 | } );
60 |
61 | // Color groups
62 | const rec = /(\([^)]*\))(\.\w+)+/g;
63 |
64 | if(/(\([^)]*\))(\.\w+)/.test(prefix.replace(msg,''))){ // If there is a color group in the prefix
65 | prefix = prefix.replace(rec, (match, text) => {
66 | try {
67 | let ret = text.replace(/[()]/g, '');
68 | match.replace(text, '').split('.').slice(1).forEach(decorator => {
69 | ret = chalk`{${decorator} ${ret}}`;
70 | });
71 | return ret;
72 | } catch (e) {
73 | return match;
74 | }
75 | });
76 | }
77 |
78 | return prefix;
79 | }
80 |
81 | function selectOutputStream( method, { levels, stdout, stderr } ) {
82 | return levels[method] <= 2 ? stderr : stdout;
83 | }
84 |
85 | class FakeStream extends Writable {
86 | constructor() {
87 | super();
88 | this._last_message = '';
89 | }
90 |
91 | get last_msg() {
92 | return this._last_message.replace( /\n$/, '' );
93 | }
94 |
95 | _write( chunk, enc, cb ) {
96 | this._last_message = chunk.toString();
97 | cb();
98 | }
99 | }
100 |
101 | module.exports = {
102 | checkLogLevel,
103 | parseParams,
104 | generateConfig,
105 | generatePrefix,
106 | selectOutputStream,
107 | FakeStream
108 | };
109 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "console-stamp",
3 | "main": "index.js",
4 | "version": "3.1.2",
5 | "scripts": {
6 | "test": "jest",
7 | "test:watch": "nodemon --exec npm test",
8 | "t:w": "npm run test:watch",
9 | "preversion": "npm test",
10 | "examples": "node examples/index || true"
11 | },
12 | "engines": {
13 | "node": ">=12"
14 | },
15 | "author": {
16 | "name": "Ståle Raknes",
17 | "email": "stale@raknes.net",
18 | "url": "https://github.com/starak"
19 | },
20 | "contributors": [
21 | {
22 | "name": "Ståle Raknes",
23 | "url": "https://github.com/starak"
24 | },
25 | {
26 | "name": "Jotham Read",
27 | "url": "https://github.com/jotham"
28 | },
29 | {
30 | "name": "Christiaan Westerbeek",
31 | "url": "https://github.com/devotis"
32 | },
33 | {
34 | "name": "Leon Lucardie",
35 | "url": "https://github.com/Gameleon12"
36 | },
37 | {
38 | "name": "Steffan Donal",
39 | "url": "https://github.com/SteffanDonal"
40 | },
41 | {
42 | "name": "Sören Schwert",
43 | "url": "https://github.com/sisou"
44 | },
45 | {
46 | "name": "Fkscorpion",
47 | "url": "https://github.com/Fkscorpion"
48 | },
49 | {
50 | "name": "Alexis Tyler",
51 | "url": "https://github.com/OmgImAlexis"
52 | },
53 | {
54 | "name": "Jan Åge Lavik",
55 | "url": "https://github.com/jalavik"
56 | },
57 | {
58 | "name": "Alexey Zakhlestin",
59 | "url": "https://github.com/indeyets"
60 | }
61 | ],
62 | "description": "Patch NodeJS console methods in order to add timestamp information by pattern",
63 | "keywords": [
64 | "console",
65 | "timestamp",
66 | "log",
67 | "jslog",
68 | "debug"
69 | ],
70 | "repository": {
71 | "type": "git",
72 | "url": "https://github.com/starak/node-console-stamp.git"
73 | },
74 | "license": "MIT",
75 | "dependencies": {
76 | "chalk": "^4.1.2",
77 | "dateformat": "^4.6.3"
78 | },
79 | "devDependencies": {
80 | "@types/node": "^17.0.31",
81 | "jest": "^29.7.0",
82 | "nodemon": "^3.0.1"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | // noinspection JSUnresolvedReference
2 |
3 | const consoleStamp = require('../');
4 | const chalk = require('chalk');
5 | const SpyStream = require('./tools/SpyStream');
6 |
7 | const message = "foo";
8 |
9 | function logAll(logger) {
10 | logger.log(message);
11 | logger.info(message);
12 | logger.debug(message);
13 | logger.warn(message);
14 | logger.error(message);
15 | }
16 |
17 | test('general test', () => {
18 | const stderr = new SpyStream();
19 | const stdout = new SpyStream();
20 |
21 | consoleStamp(console, {
22 | format: ':label(8)',
23 | stdout: stdout,
24 | stderr: stderr,
25 | level: 'debug',
26 | });
27 |
28 | logAll(console);
29 |
30 | expect(stdout.length).toEqual(3);
31 | expect(stdout.asArray[0]).toEqual('[LOG] ' + message + '\n');
32 | expect(stdout.asArray[1]).toEqual('[INFO] ' + message + '\n');
33 | expect(stdout.asArray[2]).toEqual('[DEBUG] ' + message + '\n');
34 | expect(stderr.length).toEqual(2);
35 | expect(stderr.asArray[0]).toEqual('[WARN] ' + message + '\n');
36 | expect(stderr.asArray[1]).toEqual('[ERROR] ' + message + '\n');
37 |
38 | console.reset();
39 | stdout.flush();
40 | stderr.flush();
41 |
42 | const pid = process.pid;
43 |
44 | consoleStamp(console, {
45 | format: `:pid :foo(bar)`,
46 | stdout,
47 | stderr,
48 | level: 'debug',
49 | tokens: {
50 | foo: ({ params: [bar] }) => bar,
51 | pid: () => pid,
52 | },
53 | });
54 |
55 | logAll(console);
56 |
57 | expect(stdout.length).toEqual(3);
58 | let expected = `${pid} bar ${message}\n`;
59 | expect(stdout.asArray[0]).toEqual(expected);
60 | expect(stdout.asArray[1]).toEqual(expected);
61 | expect(stdout.asArray[2]).toEqual(expected);
62 | expect(stderr.asArray.length).toEqual(2);
63 | expect(stderr.asArray[0]).toEqual(expected);
64 | expect(stderr.asArray[1]).toEqual(expected);
65 |
66 | console.reset();
67 | stdout.flush();
68 | stderr.flush();
69 |
70 | consoleStamp(console, {
71 | format: `:foo(bar).blue.bgRed`,
72 | stdout,
73 | stderr,
74 | tokens: {
75 | foo: ({ params: [bar] }) => bar,
76 | },
77 | });
78 |
79 | logAll(console);
80 | expected = chalk`{bgRed.blue bar} ${message}\n`;
81 | expect(stdout.asArray[0]).toEqual(expected);
82 |
83 | console.reset();
84 | stdout.flush();
85 | stderr.flush();
86 |
87 | consoleStamp(console, {
88 | format: `(bar).blue.bgRed`,
89 | stdout,
90 | stderr,
91 | });
92 |
93 | logAll(console);
94 | expected = chalk`{bgRed.blue bar} ${message}\n`;
95 | expect(stdout.asArray[0]).toEqual(expected);
96 | });
--------------------------------------------------------------------------------
/test/tokens.test.js:
--------------------------------------------------------------------------------
1 | // noinspection JSUnresolvedReference
2 |
3 | const label = require('../tokens/label.js');
4 | const dformat = require('dateformat');
5 | const date = require('../tokens/date.js');
6 |
7 | describe('Tokens', () => {
8 | describe('label.js', () => {
9 | it('label', () => {
10 | expect(label).toBeTruthy();
11 | expect(label({ method: 'log', params: [7] })).toBe('[LOG] ');
12 | expect(label({ method: 'info', params: [7] })).toBe('[INFO] ');
13 | expect(label({ method: 'debug', params: [7] })).toBe('[DEBUG]');
14 | expect(label({ method: 'error', params: [-5] })).toBe('[ERROR]');
15 | expect(label({ method: 'error', params: [] })).toBe('[ERROR]');
16 | });
17 | });
18 |
19 | describe('date.js', () => {
20 | it('date', () => {
21 | const now = new Date();
22 | const format = 'dd.mm.yyyy HH:MM:ss.l';
23 | expect(date({ params: [format, false, now] })).toBe(`[${dformat(now, format)}]`);
24 | expect(date({ params: [format, true, now] })).toBe(`[${dformat(now, format, true)}]`);
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/tools/SpyStream.js:
--------------------------------------------------------------------------------
1 | const { PassThrough } = require('stream');
2 |
3 | class SpyStream extends PassThrough{
4 | constructor() {
5 | super();
6 | this._stream = [];
7 | this.on('data', d => this._stream.push(d.toString()));
8 | }
9 |
10 | get length(){
11 | return this._stream.length;
12 | }
13 |
14 | get last(){
15 | return this._stream[this._stream.length - 1];
16 | }
17 |
18 | flush(){
19 | this._stream = [];
20 | }
21 |
22 | get asArray(){
23 | return this._stream;
24 | }
25 | }
26 |
27 | module.exports = SpyStream;
28 |
--------------------------------------------------------------------------------
/test/utils.test.js:
--------------------------------------------------------------------------------
1 | // noinspection JSUnresolvedReference
2 |
3 | const df = require('../lib/defaults.js');
4 | const fs = require('fs');
5 | const {
6 | selectOutputStream,
7 | parseParams,
8 | checkLogLevel,
9 | generateConfig,
10 | generatePrefix,
11 | FakeStream
12 | } = require('../lib/utils.js');
13 |
14 | describe('utils', () => {
15 | describe('generateConfig default', () => {
16 | it('should exist', () => {
17 | expect(generateConfig).toBeTruthy();
18 | const config = generateConfig({});
19 | expect(config.format).toBe(df.dfFormat.replace('$$', df.dfDateFormat));
20 | expect(df.level).toBe('log');
21 | expect(df.include).toEqual(config.include);
22 | expect(df.tokens).toEqual(config.tokens);
23 | expect(config.stdout).toBe(process.stdout);
24 | expect(config.stderr).toBe(process.stderr);
25 | });
26 | });
27 |
28 | describe('generateConfig override', () => {
29 | it('should override properties', () => {
30 | const stdout = fs.createWriteStream('/dev/null');
31 | const stderr = fs.createWriteStream('/dev/null');
32 | const config = generateConfig({
33 | level: 'log',
34 | format: ':foo(bar)',
35 | include: ['log'],
36 | stdout: stdout,
37 | stderr: stderr,
38 | });
39 | expect(config.format).toBe(':foo(bar)');
40 | expect(config.level).toBe('log');
41 | expect(config.include.length).toBe(1);
42 | expect(config.stdout).toBe(stdout);
43 | expect(config.stderr).toBe(stderr);
44 | });
45 | });
46 |
47 | describe('generateConfig extend', () => {
48 | it('should extend properties', () => {
49 | const config = generateConfig({
50 | format: ':foo(bar)',
51 | tokens: {
52 | foo: ({ params: [bar] }) => bar
53 | },
54 | extend: {
55 | foo: 1
56 | }
57 | });
58 | expect(config.include.length).toBe(df.include.length + 1);
59 | expect(typeof config.tokens.foo).toBe('function');
60 | });
61 | });
62 |
63 | describe('generateConfig with string input', () => {
64 | it('should use string as format', () => {
65 | const str = 'HH:MM.ss.l';
66 | const config = generateConfig(str);
67 | expect(config.format).toBe(df.dfFormat.replace('$$', str));
68 | });
69 | });
70 |
71 | describe('generatePrefix plain', () => {
72 | it('should generate prefix correctly', () => {
73 | const config = generateConfig({
74 | format: ':foo(bar)',
75 | tokens: {
76 | foo: ({ params: [bar] }) => bar
77 | },
78 | extend: {
79 | foo: 1
80 | }
81 | }, console);
82 | expect(generatePrefix('log', config)).toBe('bar');
83 | });
84 | });
85 |
86 | describe('selectOutputStream default', () => {
87 | it('should select output stream', () => {
88 | const config = generateConfig({}, console);
89 | expect(selectOutputStream('log', config)).toBe(process.stdout);
90 | expect(selectOutputStream('info', config)).toBe(process.stdout);
91 | expect(selectOutputStream('warn', config)).toBe(process.stderr);
92 | expect(selectOutputStream('error', config)).toBe(process.stderr);
93 | });
94 | });
95 |
96 | describe('selectOutputStream override', () => {
97 | it('should override output stream', () => {
98 | const stdout = fs.createWriteStream('/dev/null');
99 | const stderr = fs.createWriteStream('/dev/null');
100 | const config = generateConfig({
101 | stdout: stdout,
102 | stderr: stderr,
103 | }, console);
104 | expect(selectOutputStream('debug', config)).toBe(stdout);
105 | expect(selectOutputStream('log', config)).toBe(stdout);
106 | expect(selectOutputStream('info', config)).toBe(stdout);
107 | expect(selectOutputStream('warn', config)).toBe(stderr);
108 | expect(selectOutputStream('error', config)).toBe(stderr);
109 | });
110 | });
111 |
112 | describe('checkLogLevel', () => {
113 | it('should check log levels', () => {
114 | const config = generateConfig({}, console);
115 | expect(config.level).toBe('log');
116 | expect(checkLogLevel(config, 'log')).toBeTruthy();
117 | expect(checkLogLevel(config, 'info')).toBeTruthy();
118 | expect(checkLogLevel(config, 'debug')).toBeTruthy();
119 | });
120 | });
121 |
122 | describe('checkLogLevel override info', () => {
123 | it('should override log level to info', () => {
124 | const config = generateConfig({ level: 'info' }, console);
125 | expect(config.level).toBe('info');
126 | expect(checkLogLevel(config, 'log')).toBeFalsy();
127 | expect(checkLogLevel(config, 'debug')).toBeFalsy();
128 | expect(checkLogLevel(config, 'info')).toBeTruthy();
129 | expect(checkLogLevel(config, 'error')).toBeTruthy();
130 | });
131 | });
132 |
133 | describe('checkLogLevel override error', () => {
134 | it('should override log level to error', () => {
135 | const config = generateConfig({ level: 'error' }, console);
136 | expect(config.level).toBe('error');
137 | expect(checkLogLevel(config, 'log')).toBeFalsy();
138 | expect(checkLogLevel(config, 'info')).toBeFalsy();
139 | expect(checkLogLevel(config, 'warn')).toBeFalsy();
140 | expect(checkLogLevel(config, 'error')).toBeTruthy();
141 | });
142 | });
143 |
144 | describe('parseParams', () => {
145 | it('should parse parameters', () => {
146 | const [a, b, c, d] = parseParams('( 1,2,3,foo )');
147 | expect(a).toBe(1);
148 | expect(b).toBe(2);
149 | expect(c).toBe(3);
150 | expect(d).toBe('foo');
151 | });
152 | });
153 |
154 | describe('FakeStream', () => {
155 | it('should work as a fake stream', () => {
156 | const fakeStream = new FakeStream();
157 | const msg = 'Msg from console';
158 | expect(fakeStream).toBeTruthy();
159 | fakeStream.write(msg);
160 | expect(fakeStream.last_msg).toBe(msg);
161 | fakeStream.end();
162 | });
163 | });
164 | });
165 |
--------------------------------------------------------------------------------
/tokens/date.js:
--------------------------------------------------------------------------------
1 | const dateformat = require( 'dateformat' );
2 | module.exports = ( { params: [format, utc = false, date = new Date] } ) => {
3 | return `[${dateformat( date, format, utc )}]`;
4 | }
--------------------------------------------------------------------------------
/tokens/label.js:
--------------------------------------------------------------------------------
1 | module.exports = ( { method, params: [len] } ) => {
2 | return `[${method.toUpperCase()}]`.padEnd(len);
3 | }
--------------------------------------------------------------------------------
/tokens/msg.js:
--------------------------------------------------------------------------------
1 | module.exports = ( { msg } ) => msg;
--------------------------------------------------------------------------------