A simple, lightweight, console logger for Node.JS.
A perfect choice for Kubernetes, Docker, and other systems that collect logs directly from stdout.
$ npm install loggis
The logger can be used right out of the box, i.e. it does not require any configuration by default. The default settings are:
// /app/test_log.js
const logger = require('loggis');
const logFn = () => {
logger.info('its simple');
logger.error('this', ['will', 'be'], { serialized: { into: 'a string' } });
// won't be printed since the default loglevel is info
logger.trace('trace info');
};
logFn();
// [2022-09-03T08:17:26.549Z] [INFO] [123] [default] [/app/test_log.js||logFn:4] its simple
// [2022-09-03T08:17:26.554Z] [ERROR] [123] [default] [/app/test_log.js||logFn:5] this ["will","be"] {"serialized":{"into":"a string"}}
The default configuration can be set either through the configure
method or the environment variables.
Each logger instance can be configured individually by setting an instance properties like level
, colorize
, etc.
The default configuration can be set through the environment variables.
Name | Type | Default value | Description |
---|---|---|---|
LOG_LEVEL | String | info | The default logging level |
LOG_JSON | Boolean | false | Turns on/off JSON output |
LOG_COLORS | Boolean | false | Turns on/off the colorized output |
The default configuration can be passed to the configure
method.
It accepts the following parameters:
error
, warn
, info
, debug
, and trace
The same parameters can be used for an individual configuration of a logger.
It's safe to pass any kind of arguments to the log functions, including Express requests, Sequelize models, etc. The logger automatically detects circular structures and replaces them by string references. The reference is a path within an object.
const logger = require('loggis');
const a = { a: 1 };
a.b = a; // circular reference
a.c = [1, { d: 2, e: { f: 'abc' } }]
a.g = a.c[1].e; // circular reference
logger.info(a);
// ...{"a":1,"b":"[REF => .]","c":[1,{"d":2,"e":{"f":"abc"}}],"g":"[REF => c[1].e]"}
The formatter function accepts an object with the following properties:
It might be convenient to set the default message format for a particular application.
For example, if you want to:
You can do it like this:
// ./src/logger/index.js
const loggis = require('loggis');
const customFormatter = ({ args, level, logger }) => JSON.stringify({
date: new Date(),
module: module.parent.filename.replace(process.cwd(), ''),
category: logger.category,
level,
data: args, // be careful, it might crash if data is not compatible with JSON.stringify
});
loggis.configure({ format: customFormatter });
module.exports = loggis;
// ./src/app.js
const logger = require('./logger');
const log = logger.getLogger('MY_APP');
log.info('the configuration is =>', {
level: logger.loglevel,
color: logger.colorize,
json: logger.json,
});
// ./index.js
require('./src/app');
// {"date":"2022-09-03T08:17:26.549Z","module":"/app.js","category":"MY_APP","level":"info","data":["the configuration is =>",{"level":"info","color":false,"json":false}]}
const logger = require('loggis')
const log = logger.configure({ loglevel: 'debug' }).getLogger('MY_APP');
log.error('easy to log error')
log.debug('easy to log debug');
log.trace('will not be printed, since the log level is DEBUG');
// [2022-09-03T08:17:26.549Z] [ERROR] [123] [MY_APP] [/app/test_log.js||-:5] easy to log error
// [2022-09-03T08:17:26.554Z] [DEBUG] [123] [MY_APP] [/app/test_log.js||-:6] easy to log debug
const logger = require('loggis');
logger.configure({ json: true });
logger.info('user info =>', { id: 1, name: 'John', email: '[email protected]' });
// {"date":"2022-09-03T08:17:26.549Z","level":"info","pid":123,"category":"json","filename":"/app/test_log.js","function":"-","line":5,"data":["user info =>",{"id":1,"name":"John","email":"[email protected]"}]}
const logger = require('loggis');
logger.configure({ loglevel: 'warn', colorize: true });
const logLine = logger.getLogger('line'); // the default configuration will be applied
const logJson = logger.getLogger('json');
// configure an instance
logJson.loglevel = 'trace';
logJson.json = true;
logJson.colorize = false;
logLine.info('the configuration is =>', {
level: logLine.loglevel,
color: logLine.colorize,
json: logLine.json,
});
logJson.trace('the configuration is =>', {
level: logJson.loglevel,
color: logJson.colorize,
json: logJson.json,
});
// [2022-09-03T08:17:26.549Z] [WARN] [123] [line] [/app/test_log.js||-:13] the configuration is => {"level":"warn","color":true,"json":false}
// {"date":"2022-09-03T08:17:26.554Z","level":"trace","pid":123,"category":"json","filename":"/app/test_log.js","function":"-","line":19,"data":["the configuration is =>",{"level":"trace","color":false,"json":true}]}
import logger from 'loggis';
const log = logger.getLogger('MY_APP');
import { getLogger } from 'loggis';
const log = getLogger('MY_APP')
Data processing includes two stages:
The parsing of each argument looks like this:
This way it's possible to format any element at any level of nesting.
An element for which one or more formatting rules are specified is called a primitive
.
The formatting rules for primitives are set by an instance of the Primitives
class, the add
method.
This method takes two arguments:
The add
method returns the instance itself, so the method can be chained.
The Primitives
class is available in the formatters
property of the logger.
An instance of the Primitives
class can be passed to the configure
method of the logger as the primitives
parameter.
Example:
const logger = require('loggis');
const primitives = new logger.formatters.Primitives()
.add(
(item) => typeof item === 'number', // for any number
(item) => item.toFixed(2), // apply this function
);
logger.configure({ primitives });
logger.info(10.987654, '123.1589', { float: 1.5499, int: 1, str: '9.98765' });
// ... 10.99 123.1589 {"float":"1.55","int":"1.00","str":"9.98765"}
For convenience, the Primitives
class has two static methods - typeof
and instanceof
with the following definitions:
interface Cls<T, A extends any[] = any[]> extends Function { new(...args: A): T; }
static typeof<T = any>(type: string): ((data: T) => boolean);
static instanceof<T, V = any>(cls: Cls<T>): ((data: V) => boolean);
primitives
.add(Primitives.typeof('function'), (data) => `<Function ${data.name || 'anonymous'}>`)
.add(Primitives.instanceof(Date), (date) => date.toISOString())
.add(Primitives.instanceof(Buffer), (data) => data.toString())
.add(Primitives.instanceof(Promise), () => '<Promise>')
.add(Primitives.instanceof(Error), (error) => Object
.getOwnPropertyNames(error)
.reduce((acc, prop) => `${acc}\n${prop}: ${error[prop]}`, ''));
const logger = require('loggis');
const { Primitives } = logger.formatters;
const isObject = (obj) => typeof obj === 'object' && obj !== null && !Array.isArray(obj);
const hide = (obj, prop) => (Object.hasOwn(obj, prop) ? ({ ...obj, [prop]: '***' }) : obj);
const primitives = new Primitives()
.add(isObject, (obj) => hide(obj, 'password'))
.add(isObject, (obj) => hide(obj, 'card_number'))
.add(isObject, (obj) => hide(obj, 'card_cvv'));
logger.configure({ primitives });
logger.info({ user: { id: 1, name: 'John', password: 'secret', card: { card_cvv: 321, card_number: 4111111111111111 } } });
// ... {"user":{"id":1,"name":"John","password":"***","card":{"card_cvv":"***","card_number":"***"}}}
const { Model } = require('sequelize');
const logger = require('loggis');
const primitives = new logger.formatters.Primitives()
.add(Primitives.instanceof(Model), (model) => model.toJSON())
The elements to be printed are specified by calling the add
method of an instance of the Logline
class.
The add
method accepts a Message
instance and returns any type of data.
The Message
instance has the following properties:
json
option of the loggerThe Logline
class is available in the formatters
property of the logger.
The join
method of the logline instance defines a separator that is used while joining all the logline elements. It does not work for JSON format.
const logger = require('loggis');
const { Logline } = logger.formatters;
// --- Simplest format ---
const logline = new Logline().add(message => message.text);
logger.configure({ logline }).info(1, [2, 3], { 4: 5 }); // 1 [2,3] {"4":5}
// --- Static text ---
const logline = new Logline()
.add(() => '[static_text]')
.add(message => `[${message.text}]`);
logger.configure({ logline }).info('log message'); // [static_text] [log message]
// --- JOIN ---
const logline = new Logline()
.add(message => message.date.valueOf())
.add(message => message.level.toUpperCase())
.add(message => message.pid)
.add(message => message.text)
.join(' | ');
logger.configure({ logline }).info('log message'); // 1662193046549 | INFO | 123 | log message
// --- JSON format ---
const logline = new Logline()
.add(message => ({ date: message.date }))
.add(message => ({ message: message.text }));
logger.configure({ json: true, logline }).info('user =>', { id: 1, name: 'John' }); // {"date":"2022-09-03T08:17:26.549Z","message":["user =>",{"id":1,"name":"John"}]}
const wrap = data => `[${data}]`;
logline
.add(message => wrap(message.date.toISOString()))
.add(message => wrap(message.level.toUpperCase()))
.add(message => wrap(message.pid))
.add(message => wrap(message.category))
.add(message => wrap(`${message.fileName}||${message.functionName || '-'}:${message.lineNumber || -1}`))
.add(message => message.text);
loglineJson
.add(message => ({ date: message.date.toISOString() }))
.add(message => ({ level: message.level }))
.add(message => ({ pid: message.pid }))
.add(message => ({ category: message.category }))
.add(message => ({ filename: message.fileName }))
.add(message => ({ function: message.functionName || '-' }))
.add(message => ({ line: message.lineNumber || -1 }))
.add(message => ({ data: message.text }));