Signale

Highly configurable logging utility

README


Signale


Highly configurable logging utility


Build Status NPM Downloads

Description


Hackable and configurable to the core, signale can be used for logging purposes, status reporting, as well as for handling the output rendering process of other node modules and applications.

Read this document in: 简体中文 .

You can now support the development process through GitHub Sponsors .

Visit the contributing guidelines to learn more on how to translate this document into more languages.

Come over to Gitter or Twitter to share your thoughts on the project.

Highlights


19 out-of-the-box loggers
Hackable to the core
Clean and beautiful output
Integrated timers
Custom pluggable loggers
TypeScript support
Interactive and regular modes
Secrets & sensitive information filtering
Filename, date and timestamp support
Scoped loggers and timers
Scaled logging levels mechanism
String interpolation support
Multiple configurable writable streams
Simple and minimal syntax
Globally configurable through package.json
Overridable configuration per file and logger

Contents



Install


Yarn


  1. ``` shell
  2. yarn add signale
  3. ```

NPM


  1. ``` shell
  2. npm install signale
  3. ```

Usage


Default Loggers


Import signale and start using any of the default loggers.

View all of the available loggers.

  1. ``` js
  2. const signale = require('signale');

  3. signale.success('Operation successful');
  4. signale.debug('Hello', 'from', 'L59');
  5. signale.pending('Write release notes for %s', '1.2.0');
  6. signale.fatal(new Error('Unable to acquire lock'));
  7. signale.watch('Recursively watching build directory...');
  8. signale.complete({prefix: '[task]', message: 'Fix issue #59', suffix: '(@klauscfhq)'});
  9. ```

Custom Loggers


To create a custom logger define an optionsobject yielding a typesfield with the logger data and pass it as argument to a new signale instance.

  1. ``` js
  2. const {Signale} = require('signale');

  3. const options = {
  4.   disabled: false,
  5.   interactive: false,
  6.   logLevel: 'info',
  7.   scope: 'custom',
  8.   secrets: [],
  9.   stream: process.stdout,
  10.   types: {
  11.     remind: {
  12.       badge: '**',
  13.       color: 'yellow',
  14.       label: 'reminder',
  15.       logLevel: 'info'
  16.     },
  17.     santa: {
  18.       badge: '🎅',
  19.       color: 'red',
  20.       label: 'santa',
  21.       logLevel: 'info'
  22.     }
  23.   }
  24. };

  25. const custom = new Signale(options);
  26. custom.remind('Improve documentation.');
  27. custom.santa('Hoho! You have an unused variable on L45.');
  28. ```

Here is an example where we override the default errorand successloggers.

  1. ``` js
  2. const {Signale} = require('signale');

  3. const options = {
  4.   types: {
  5.     error: {
  6.       badge: '!!',
  7.       label: 'fatal error'
  8.     },
  9.     success: {
  10.       badge: '++',
  11.       label: 'huge success'
  12.     }
  13.   }
  14. };

  15. const signale = new Signale();
  16. signale.error('Default Error Log');
  17. signale.success('Default Success Log');

  18. const custom = new Signale(options);
  19. custom.error('Custom Error Log');
  20. custom.success('Custom Success Log');
  21. ```

The optionsobject can hold any of the following attributes: disabled, interactive, logLevel, secrets, stream, scopeand types.

disabled

Type: Boolean
Default: false

Disables the logging functionality of all loggers belonging to the created instance.

interactive

Type: Boolean
Default: false

Switches all loggers belonging to the created instance into the interactive mode.

logLevel

Type: String
Default: 'info'

Sets the general logging level of the created instance. Can be one of the following:

'info'- Displays all messages from all loggers.
'timer'-  Displays messages only from the time, timeEnd, debug, warn, error& fatalloggers.
'debug'- Displays messages only from the debug, warn, error& fatalloggers.
'warn'- Displays messages only from the warn, error& fatalloggers.
'error'- Displays messages only from the error& fatalloggers.

secrets

Type: (String|Number)[]
Default: []

An array holding secrets/sensitive-information to be removed from the body and metadata of to-be-logged messages and replaced with the default '[secure]'string.

stream

Type: stream.Writable|stream.Writable[]
Default: process.stdout

Destination to which the data is written, can be a single valid Writable stream or an array holding multiple valid Writable streams.

scope

Type: String|String[]

Name of the scope the logger is reporting from.

types

Type: Object

Holds the configuration of the custom and default loggers.

Additionally, the configuration object of each custom/default logger type, defined in the typesoption, can hold any of the following attributes: badge, label, color, logLevel& stream.

badge

Type: String

The icon corresponding to the logger.

label

Type: String

The label used to identify the type of the logger.

color

Type: String

The color of the label, can be any of the foreground colors supported by chalk .

logLevel

Type: String
Default: 'info'

The log level corresponding to the logger. Messages originating from the logger are displayed only if the log level is greater or equal to the above described general logging level logLevelof the Signaleinstance.

stream

Type: stream.Writable|stream.Writable[]
Default: process.stdout

Destination to which the data is written, can be a single valid Writable stream or an array holding multiple valid Writable streams.

Scoped Loggers


To create a scoped logger from scratch, define the scopefield inside the optionsobject and pass it as argument to a new signale instance.

  1. ``` js
  2. const {Signale} = require('signale');

  3. const options = {
  4.   scope: 'global scope'
  5. };

  6. const global = new Signale(options);
  7. global.success('Successful Operation');
  8. ```

To create a scoped logger based on an already existing one, use the scope()function, which will return a new signale instance, inheriting all custom loggers, timers, secrets, streams, configuration, log level, interactive mode & disabled statuses from the initial one.

  1. ``` js
  2. const signale = require('signale');

  3. const global = signale.scope('global scope');
  4. global.success('Hello from the global scope');

  5. function foo() {
  6.   const outer = global.scope('outer', 'scope');
  7.   outer.success('Hello from the outer scope');

  8.   setTimeout(() => {
  9.     const inner = outer.scope('inner', 'scope');
  10.     inner.success('Hello from the inner scope');
  11.   }, 500);
  12. }

  13. foo();
  14. ```

Interactive Loggers


To initialize an interactive logger, create a new signale instance with the interactive attribute set totrue. While into the interactive mode, previously logged messages originating from an interactive logger, will be overridden only by new ones originating from the same or a different interactive logger. Note that regular messages originating from regular loggers are not overridden by the interactive ones.

  1. ``` js
  2. const {Signale} = require('signale');

  3. const interactive = new Signale({interactive: true, scope: 'interactive'});

  4. interactive.await('[%d/4] - Process A', 1);

  5. setTimeout(() => {
  6.   interactive.success('[%d/4] - Process A', 2);
  7.   setTimeout(() => {
  8.     interactive.await('[%d/4] - Process B', 3);
  9.     setTimeout(() => {
  10.       interactive.error('[%d/4] - Process B', 4);
  11.       setTimeout(() => {}, 1000);
  12.     }, 1000);
  13.   }, 1000);
  14. }, 1000);
  15. ```

Writable Streams


By default, all signale instances log their messages to the process.stdoutstream. This can be modified, to match your own preference, through the stream property, where you can define a single or multiple valid Writable streams, which will be used by all logger types to log your data. Additionally, it is possible to define one or more Writable streams exclusively for a specific logger type, thus write data independently from the rest logger types.

  1. ``` js
  2. const {Signale} = require('signale');

  3. const options = {
  4.   stream: process.stderr, // All loggers will now write to `process.stderr`
  5.   types: {
  6.     error: {
  7.       // Only `error` will write to both `process.stdout` & `process.stderr`
  8.       stream: [process.stdout, process.stderr]
  9.     }
  10.   }
  11. };

  12. const signale = new Signale(options);
  13. signale.success('Message will appear on `process.stderr`');
  14. signale.error('Message will appear on both `process.stdout` & `process.stderr`');
  15. ```

Secrets Filtering


By utilizing the `secrets`option, secrets and other sensitive information can be filtered out from the body as well as the metadata, i.e. scope names etc, of to-be-logged messages. The option is part of the configuration object passed to a `Signale`instance on its initialization, and is of type `Array`. The array can hold multiple secrets, all of which are removed, if present, from the to-be-logged messages and are replaced with the default `'[secure]'`string. Additionally, when the unary `signale.scope(name)`function is used, the returned `Signale`instance inherits all the secrets belonging to its parent. The secrets checking process is performed in a **case-sensitive**manner. Also, the unary [signale.addSecrets()](https://github.com/klaussinani/signale#signaleaddsecretssecrets) and the nullary [signale.clearSecrets()](https://github.com/klaussinani/signale#signaleclearsecrets) functions are available through the API for adding and clearing secrets respectively.

It is criticaland highly recommendedto not type directly secrets in your code, thus the following example serves onlyas a simple & easily reproducible usage demonstration.

  1. ``` js
  2. const {Signale} = require('signale');

  3. // In reality secrets could be securely fetched/decrypted through a dedicated API
  4. const [USERNAME, TOKEN] = ['klaussinani', 'token'];

  5. const logger1 = new Signale({
  6.   secrets: [USERNAME, TOKEN]
  7. });

  8. logger1.log('$ exporting USERNAME=%s', USERNAME);
  9. logger1.log('$ exporting TOKEN=%s', TOKEN);

  10. // `logger2` inherits all secrets from its parent `logger1`
  11. const logger2 = logger1.scope('parent');

  12. logger2.log('$ exporting USERNAME=%s', USERNAME);
  13. logger2.log('$ exporting TOKEN=%s', TOKEN);
  14. ```

Timers


Timer are managed by the time()and timeEnd()functions. A unique label can be used to identify a timer on initialization, though if none is provided the timer will be assigned one automatically. In addition, calling the timeEnd()function without a specified label will have as effect the termination of the most recently initialized timer, that was created without providing a label.

  1. ``` js
  2. const signale = require('signale');

  3. signale.time('test');
  4. signale.time();
  5. signale.time();

  6. setTimeout(() => {
  7.   signale.timeEnd();
  8.   signale.timeEnd();
  9.   signale.timeEnd('test');
  10. }, 500);
  11. ```

Configuration


Global


To enable global configuration define the options under the signalenamespace in your package.json.

The following illustrates all the available options with their respective default values.

  1. ``` json
  2. {
  3.   "signale": {
  4.     "displayScope": true,
  5.     "displayBadge": true,
  6.     "displayDate": false,
  7.     "displayFilename": false,
  8.     "displayLabel": true,
  9.     "displayTimestamp": false,
  10.     "underlineLabel": true,
  11.     "underlineMessage": false,
  12.     "underlinePrefix": false,
  13.     "underlineSuffix": false,
  14.     "uppercaseLabel": false
  15.   }
  16. }
  17. ```

View all of the available options in detail.

Type:
Default:

Display the scope name of the logger.


Type:
Default:

Display the badge of the logger.


Type:
Default:

Display the current local date in format.


Type:
Default:

Display the name of the file that the logger is reporting from.


Type:
Default:

Display the label of the logger.


Type:
Default:

Display the current local time in format.


Type:
Default:

Underline the logger label.


Type:
Default:

Underline the logger message.


Type:
Default:

Underline the logger prefix.


Type:
Default:

Underline the logger suffix.


Type:
Default:

Display the label of the logger in uppercase.

Local


To enable local configuration call the config()function on your signale instance. Local configurations will always override any pre-existing configuration inherited from package.json.

In the following example, loggers in the foo.jsfile will run under their own configuration, overriding the package.jsonone.

  1. ``` js
  2. // foo.js
  3. const signale = require('signale');

  4. // Overrides any existing `package.json` config
  5. signale.config({
  6.   displayFilename: true,
  7.   displayTimestamp: true,
  8.   displayDate: false
  9. });

  10. signale.success('Hello from the Global scope');
  11. ```

  1. ``` js
  2. // foo.js
  3. const signale = require('signale');

  4. signale.config({
  5.   displayFilename: true,
  6.   displayTimestamp: true,
  7.   displayDate: false
  8. });

  9. signale.success('Hello from the Global scope');

  10. function foo() {
  11.   // `fooLogger` inherits the config of `signale`
  12.   const fooLogger = signale.scope('foo scope');

  13.   // Overrides both `signale` and `package.json` configs
  14.   fooLogger.config({
  15.     displayFilename: true,
  16.     displayTimestamp: false,
  17.     displayDate: true
  18.   });

  19.   fooLogger.success('Hello from the Local scope');
  20. }

  21. foo();
  22. ```

API


#### signale.`(message[, message]|messageObj|errorObj)`

logger

Type: Function

Can be any default or custom logger.

message

Type: String

Can be one or more comma delimited strings.

  1. ``` js
  2. const signale = require('signale');

  3. signale.success('Successful operation');
  4. //=> ✔  success  Successful operation

  5. signale.success('Successful', 'operation');
  6. //=> ✔  success  Successful operation

  7. signale.success('Successful %s', 'operation');
  8. //=> ✔  success  Successful operation
  9. ```

errorObj

Type: Error Object

Can be any error object.

  1. ``` js
  2. const signale = require('signale');

  3. signale.error(new Error('Unsuccessful operation'));
  4. //=> ✖  error  Error: Unsuccessful operation
  5. //        at Module._compile (module.js:660:30)
  6. //        at Object.Module._extensions..js (module.js:671:10)
  7. //        ...
  8. ```

messageObj

Type: Object

Can be an object holding the prefix, messageand suffixattributes, with prefixand suffixalways prepended and appended respectively to the logged message.

  1. ``` js
  2. const signale = require('signale');

  3. signale.complete({prefix: '[task]', message: 'Fix issue #59', suffix: '(@klaussinani)'});
  4. //=> [task] ☒  complete  Fix issue #59 (@klaussinani)

  5. signale.complete({prefix: '[task]', message: ['Fix issue #%d', 59], suffix: '(@klaussinani)'});
  6. //=> [task] ☒  complete  Fix issue #59 (@klaussinani)
  7. ```

signale.scope(name[, name])


Defines the scope name of the logger.

name

Type: String

Can be one or more comma delimited strings.

  1. ``` js
  2. const signale = require('signale');

  3. const foo = signale.scope('foo');
  4. const fooBar = signale.scope('foo', 'bar');

  5. foo.success('foo');
  6. //=> [foo] › ✔  success  foo

  7. fooBar.success('foo bar');
  8. //=> [foo] [bar] › ✔  success  foo bar
  9. ```

signale.unscope()


Clears the scope name of the logger.

  1. ``` js
  2. const signale = require('signale');

  3. const foo = signale.scope('foo');

  4. foo.success('foo');
  5. //=> [foo] › ✔  success  foo

  6. foo.unscope();

  7. foo.success('foo');
  8. //=> ✔  success  foo
  9. ```

signale.config(settingsObj)


Sets the configuration of an instance overriding any existing global or local configuration.

settingsObj

Type: Object

Can hold any of the documented options .

  1. ``` js
  2. // foo.js
  3. const signale = require('signale');

  4. signale.config({
  5.   displayFilename: true,
  6.   displayTimestamp: true,
  7.   displayDate: true
  8. });

  9. signale.success('Successful operations');
  10. //=> [2018-5-15] [11:12:38] [foo.js] › ✔  success  Successful operations
  11. ```

signale.time([, label])


Return Type: String

Sets a timers and accepts an optional label. If none provided the timer will receive a unique label automatically.

Returns a string corresponding to the timer label.

label

Type: String

Label corresponding to the timer. Each timer must have its own unique label.

  1. ``` js
  2. const signale = require('signale');

  3. signale.time();
  4. //=> ▶  timer_0  Initialized timer...

  5. signale.time();
  6. //=> ▶  timer_1  Initialized timer...

  7. signale.time('label');
  8. //=> ▶  label    Initialized timer...
  9. ```

signale.timeEnd([, label])


Return Type: Object

Deactivates the timer to which the given label corresponds. If no label is provided the most recent timer, that was created without providing a label, will be deactivated.

Returns an object {label, span}holding the timer label and the total running time.

label

Type: String

Label corresponding to the timer, each timer has its own unique label.

  1. ``` js
  2. const signale = require('signale');

  3. signale.time();
  4. //=> ▶  timer_0  Initialized timer...

  5. signale.time();
  6. //=> ▶  timer_1  Initialized timer...

  7. signale.time('label');
  8. //=> ▶  label    Initialized timer...

  9. signale.timeEnd();
  10. //=> ◼  timer_1  Timer run for: 2ms

  11. signale.timeEnd();
  12. //=> ◼  timer_0  Timer run for: 2ms

  13. signale.timeEnd('label');
  14. //=> ◼  label    Timer run for: 2ms
  15. ```

signale.disable()


Disables the logging functionality of all loggers belonging to a specific instance.

  1. ``` js
  2. const signale = require('signale');

  3. signale.success('foo');
  4. //=> ✔  success  foo

  5. signale.disable();

  6. signale.success('foo');
  7. //=>
  8. ```

signale.enable()


Enables the logging functionality of all loggers belonging to a specific instance.

  1. ``` js
  2. const signale = require('signale');

  3. signale.disable();

  4. signale.success('foo');
  5. //=>

  6. signale.enable();

  7. signale.success('foo');
  8. //=> ✔  success  foo
  9. ```

signale.isEnabled()


Checks whether the logging functionality of a specific instance is enabled.

  1. ``` js
  2. const signale = require('signale');

  3. signale.success('foo');
  4. //=> ✔  success  foo

  5. signale.isEnabled();
  6. // => true

  7. signale.disable();

  8. signale.success('foo');
  9. //=>

  10. signale.isEnabled();
  11. // => false
  12. ```

signale.addSecrets(secrets)


Adds new secrets/sensitive-information to the targeted Signale instance.

secrets

Type: (String|Number)[]

Array holding the secrets/sensitive-information to be filtered out.

  1. ``` js
  2. const signale = require('signale');

  3. signale.log('$ exporting USERNAME=%s', 'klaussinani');
  4. //=> $ exporting USERNAME=klaussinani

  5. signale.addSecrets(['klaussinani']);

  6. signale.log('$ exporting USERNAME=%s', 'klaussinani');
  7. //=> $ exporting USERNAME=[secure]
  8. ```

signale.clearSecrets()


Removes all secrets/sensitive-information from the targeted Signale instance.

  1. ``` js
  2. const signale = require('signale');

  3. signale.addSecrets(['klaussinani']);

  4. signale.log('$ exporting USERNAME=%s', 'klaussinani');
  5. //=> $ exporting USERNAME=[secure]

  6. signale.clearSecrets();

  7. signale.log('$ exporting USERNAME=%s', 'klaussinani');
  8. //=> $ exporting USERNAME=klaussinani
  9. ```

Development


For more info on how to contribute to the project, please read the contributing guidelines .

Fork the repository and clone it to your machine
Navigate to your local fork: cd signale
Install the project dependencies: npm installor yarn install
Lint code for errors: npm testor yarn test

Related


qoa - Minimal interactive command-line prompts
taskbook - Tasks, boards & notes for the command-line habitat
hyperocean - Deep oceanic blue Hyper terminal theme

Who's Using It?



View in detail all the packages and repositories that are using Signale here .

Team


Klaus Sinani (@klaussinani)
Mario Sinani (@mariosinani)

License