The universal logger with the pluggable architecture.
npm install --save-prod pipit
You can start using Pipit as a replacement for console
logging, no additional configuration is required:
import { logger } from 'pipit';
logger.log('Oh, snap!');
Logger uses channels to deliver messages. Each channel is a sequence of processors that may filter or enrich messages, print them to console or stdout, send them to a remote service, write them to a file, and do whatever you want, even send a push notification.
Let's create a new logger and configure it:
import { Logger, printToConsole } from 'pipit';
const myLogger = new Logger();
// Open a channel that prints a message to the console
myLogger.openChannel().to(printToConsole());
myLogger.log('Oh, snap!');
Messages can be logged with different severity levels:
myLogger.fatal('A very severe error events that will presumably lead the application to abort');
myLogger.error('An error event that might still allow the application to continue running');
myLogger.warn('Potentially harmful situation');
myLogger.info('Highlight the progress of the application at coarse-grained level');
// or myLogger.log( … )
myLogger.debug('Useful to debug an application');
myLogger.trace('A finer-grained informational message than debug, usually with a stack trace');
By default, Logger
sends all messages to channels, but you can set a minimum required level of the message severity:
import { Logger, LogLevel } from 'pipit';
// Log messages that have an error severity level or higher
const myLogger = new Logger(LogLevel.ERROR);
// This message is ignored
myLogger.debug('Hello there');
// This message is logged
myLogger.fatal('Damn!');
You can open as many channels on a single logger as you need:
import { levelCutoff, Logger, LogLevel, printToConsole } from 'pipit';
import { sendToSentry } from '@pipit/sentry';
const myLogger = new Logger();
myLogger
.openChannel()
.to(printToConsole());
myLogger
.openChannel()
.to(levelCutoff(LogLevel.ERROR))
.to(sendToSentry());
myLogger.log('Good job');
myLogger.fatal('A severe error has occurred!');
The first message in the example above would be printed to the console, while the second one is printed to console and sent to Sentry as well.
You can remove all channels using reset
. This is especially useful if you want to re-configure the default global
logger.
import { logger } from 'pipit';
import { sendToSentry } from '@pipit/sentry';
// Send all messages to Sentry
logger.reset().openChannel().to(sendToSentry());
You can also reset the logging level:
logger.reset(LogLevel.WARN);
Logger channels are sequences of processors. Processor is a callback that receives an array of messages and performs arbitrary operations on those messages. When processor has completed its job, it can pass messages to the next processor in the channel.
To showcase how processors work, let's create a basic processor that prepends a timestamp to each logged message:
import { Logger, printToConsole } from 'pipit';
const myLogger = new Logger();
myLogger
.openChannel()
.to((messages, next) => {
const date = new Date().toISOString();
for (const message of messages) {
message.args.unshift(date);
}
next(messages);
})
.to(printToConsole());
myLogger.log('Okay, cowboy');
// ⮕ '2022-11-25T16:59:44.286Z Okay, cowboy'
You can use a logger or a channel as a processor:
const myLogger1 = new Logger();
const myLogger2 = new Logger();
myLogger1.openChannel().to(myLogger2);
Batches messages using a timeout and/or limit strategy.
myLogger
.openChannel()
.to(batchMessages({ timeout: 1_000, limit: 2 }))
.to(printToConsole());
myLogger.log('No way');
// Does nothing, since not enough messages to dispatch
myLogger.log('Yay');
// ⮕ 'No way'
// ⮕ 'Yay'
By default, at most 50 messages are batched in the 1s timeframe. You can provide both the timeout
and
limit
options at the same time and when any constraint is hit, then batched messages are sent to the next
processor.
Excludes messages that have an insufficient severity level.
import { LogLevel } from 'pipit';
myLogger
.openChannel()
.to(levelCutoff(LogLevel.WARN))
.to(printToConsole());
myLogger.info('Something happened');
// Does nothing, since level of this message is INFO
myLogger.fatal('The base is under attack');
// Prints the message, since its level is FATAL
This processor comes handy if you have multiple channels in your logger and want some of them to be used only if message is severe enough.
Prepends a set args to each message.
myLogger
.openChannel()
.to(prepend('Hello,'))
.to(printToConsole());
myLogger.log('Boss');
// ⮕ 'Hello, Boss'
Prepends date and time in ISO format to each message.
myLogger
.openChannel()
.to(prependDateTime())
.to(printToConsole());
myLogger.log('Okay, cowboy');
// ⮕ '2022-11-25T16:59:44.286Z Okay, cowboy'
Prepends severity level label to each message.
myLogger
.openChannel()
.to(prependLevel())
.to(printToConsole());
myLogger.fatal('No way!');
// ⮕ 'FATAL No way
Prints messages to the console.
myLogger.openChannel().to(printToConsole());
myLogger.log('Okay');
// ⮕ 'Okay'
Sends a message to Sentry.
import { Logger } from 'pipit';
import { sendToSentry } from '@pipit/sentry';
import * as Sentry from '@sentry/browser';
const myLogger = new Logger();
myLogger.openChannel().to(sendToSentry(Sentry));
myLogger.log('To the moon!');
// Prints nothing, sends everything to Sentry