├── .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; --------------------------------------------------------------------------------