Package Exports
- @visulima/pail
- @visulima/pail/browser
- @visulima/pail/package.json
- @visulima/pail/processor/caller
- @visulima/pail/processor/message-formatter
- @visulima/pail/processor/redact
- @visulima/pail/reporter/file
- @visulima/pail/reporter/json
- @visulima/pail/reporter/pretty
- @visulima/pail/reporter/simple
- @visulima/pail/server
Readme
Visulima Pail
Highly configurable Logger for Node.js, Edge and Browser, built on top of
[@visulima/fmt][fmt], @visulima/colorize, @visulima/string, ansi-escapes, safe-stable-stringify, and terminal-size
[][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
Daniel Bannert's open source work is supported by the community on GitHub Sponsors
📋 Migration Guide
If you're upgrading from an earlier version of pail, check out our Migration Guide for breaking changes and upgrade instructions.
Why Pail?
- Easy to use
- Hackable to the core
- Integrated timers
- Custom pluggable processors and reporters
- 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
- Object and error interpolation
- Stack trace and pretty errors
- Simple and minimal syntax
- Spam prevention by throttling logs
- Browser and Server support
- Redirect console and stdout/stderr to pail and easily restore redirect.
PrettyorJSONoutput- ESM‑only with tree‑shaking support (Node.js ≥ 20.19)
- Supports circular structures
- Fast and powerful, see the benchmarks
Install
npm install @visulima/pailyarn add @visulima/pailpnpm add @visulima/pailConcepts
Most importantly,
pailadheres to the log levels defined in [RFC 5424][rfc-5424] extended withtracelevel. This means that you can use the log levels to filter out messages that are not important to you.
Log Levels
Pail supports the logging levels described by [RFC 5424][rfc-5424].
DEBUG: Detailed debug information.INFO: Interesting events. Examples: User logs in, SQL logs.NOTICE: Normal but significant events.TRACE: Very detailed and fine-grained informational events.WARNING: Exceptional occurrences that are not errors. Examples: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.ERROR: Runtime errors that do not require immediate action but should typically be logged and monitored.CRITICAL: Critical conditions. Example: Application component unavailable, unexpected exception.ALERT: Action must be taken immediately. Example: Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.EMERGENCY: Emergency: system is unusable.
Reporters
Reporters are responsible for writing the log messages to the console or a file. pail comes with a few built-in reporters:
| Browser (console.{function}) | Server (stdout or stderr) |
|---|---|
JsonReporter |
JsonReporter |
PrettyReporter |
PrettyReporter |
| x | SimpleReporter |
| x | FileReporter |
Processors
Processors are responsible for processing the log message (Meta Object) before it's written to the console or a file.
This usually means that they add some metadata to the record's context property.
A processor can be added to a logger directly (and is subsequently applied to log records before they reach any handler).
pail comes with a few built-in processors:
CallerProcessor- adds the caller information to the log message- The Meta Object is extended with a file name, line number and column number
RedactProcessor- redacts sensitive information from the log messageThe redact processor needs the "@visulima/redact" package to work. Use
npm install @visulima/redact,pnpm add @visulima/redactoryarn add @visulima/redactto install it.MessageFormatterProcessor- formats the log message (Util.format-like unescaped string formatting utility) [@visulima/fmt][fmt]ErrorProcessor- serializes the error with cause object to a std error object that can be serialized.
Usage
import { pail } from "@visulima/pail";
pail.success("Operation successful");
pail.debug("Hello", "from", "L59");
pail.pending("Write release notes for %s", "1.2.0");
pail.fatal(new Error("Unable to acquire lock"));
pail.watch("Recursively watching build directory...");
pail.complete({
prefix: "[task]",
message: "Fix issue #59",
suffix: "(@prisis)",
});
Custom Loggers
To create a custom logger define an options object yielding a types field with the logger data and pass it as argument to the createPail function.
import { createPail } from "@visulima/pail";
const custom = createPail({
types: {
remind: {
badge: "**",
color: "yellow",
label: "reminder",
logLevel: "info",
},
santa: {
badge: "🎅",
color: "red",
label: "santa",
logLevel: "info",
},
},
});
custom.remind("Improve documentation.");
custom.santa("Hoho! You have an unused variable on L45.");
Here is an example where we override the default error and success loggers.
import { pail, createPail } from "@visulima/pail";
pail.error("Default Error Log");
pail.success("Default Success Log");
const custom = createPail({
scope: "custom",
types: {
error: {
badge: "!!",
label: "fatal error",
},
success: {
badge: "++",
label: "huge success",
},
},
});
custom.error("Custom Error Log");
custom.success("Custom Success Log");
Scoped Loggers
To create a scoped logger from scratch, define the scope field inside the options object and pass it as argument to the createPail function.
import { createPail } from "@visulima/pail";
const mayAppLogger = createPail({
scope: "my-app",
});
mayAppLogger.info("Hello from my app");
To create a scoped logger based on an already existing one, use the scope() function, which will return a new pail instance, inheriting all custom loggers, timers, secrets, streams, configuration, log level, interactive mode & disabled statuses from the initial one.
import { pail } from "@visulima/pail";
const global = pail.scope("global scope");
global.success("Hello from the global scope");
function foo() {
const outer = global.scope("outer", "scope");
outer.success("Hello from the outer scope");
setTimeout(() => {
const inner = outer.scope("inner", "scope");
inner.success("Hello from the inner scope");
}, 500);
}
foo();
Interactive Loggers (Only on if stdout and stderr is a TTY)
To initialize an interactive logger, create a new pail instance with the interactive attribute set to true.
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.
import { createPail } from "@visulima/pail";
console.log("\n");
const pail = createPail();
const interactive = createPail({ interactive: true });
pail.info("This is a log message 1");
setTimeout(() => {
interactive.await("[%d/4] - Process A", 1);
setTimeout(() => {
interactive.success("[%d/4] - Process A", 2);
setTimeout(() => {
interactive.await("[%d/4] - Process B", 3);
setTimeout(() => {
interactive.error("[%d/4] - Process B", 4);
}, 1000);
}, 1000);
}, 1000);
});
pail.info("This is a log message 2");
pail.info("This is a log message 3");
pail.info("This is a log message 4");For a more complex example, use can use the getInteractiveManager function, see the following code:
import { createPail } from "@visulima/pail";
const interactive = createPail({ interactive: true });
const TICKS = 60;
const TIMEOUT = 80;
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
const messages = ["Swapping time and space...", "Have a good day.", "Don't panic...", "Updating Updater...", "42"];
let ticks = TICKS;
let i = 0;
const interactiveManager = interactive.getInteractiveManager();
interactiveManager.hook();
// eslint-disable-next-line no-console
console.log(" - log message");
// eslint-disable-next-line no-console
console.error(" - error message");
// eslint-disable-next-line no-console
console.warn(" - warn message");
const id = setInterval(() => {
if (--ticks < 0) {
clearInterval(id);
interactiveManager.update("stdout", ["✔ Success", "", "Messages:", "this line will be deleted!!!"]);
interactiveManager.erase("stdout", 1);
interactiveManager.unhook(false);
} else {
const frame = frames[(i = ++i % frames.length)];
const index = Math.round(ticks / 10) % messages.length;
const message = messages[index];
if (message) {
interactiveManager.update("stdout", [`${frame} Some process...`, message]);
}
}
}, TIMEOUT);Timers
Timer are managed by the time(), timeLog() 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.
import { pail } from "@visulima/pail";
pail.time("test");
pail.time();
pail.timeLog("default", "Hello");
setTimeout(() => {
pail.timeEnd();
pail.timeEnd("test");
}, 500);
Its also possible to change the text inside time() and timeEnd() by using the options object.
import { createPail } from "@visulima/pail";
const pail = createPail({
messages: {
timerStart: "Start Timer:",
timerEnd: "End Timer:",
},
});
pail.time("test");
pail.timeEnd("test");Progress Bars (Server Only)
Pail includes a comprehensive progress bar system inspired by cli-progress, with support for single and multi-bar modes, various styles, and interactive terminal output.
Single Progress Bar
import { createPail } from "@visulima/pail";
const logger = createPail({ interactive: true });
const bar = logger.createProgressBar({
total: 100,
format: "Downloading [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}",
});
bar.start();
bar.update(50);
bar.increment(10);
bar.stop();Styled Progress Bars
Pail supports various built-in progress bar styles:
import { createPail } from "@visulima/pail";
const logger = createPail({ interactive: true });
// Shades classic (default)
const bar1 = logger.createProgressBar({
total: 100,
style: "shades_classic",
format: "Progress [{bar}] {percentage}%",
});
// Shades grey
const bar2 = logger.createProgressBar({
total: 100,
style: "shades_grey",
format: "Progress [{bar}] {percentage}%",
});
// Rect style
const bar3 = logger.createProgressBar({
total: 100,
style: "rect",
format: "Progress [{bar}] {percentage}%",
});
// ASCII style
const bar4 = logger.createProgressBar({
total: 100,
style: "ascii",
format: "Progress [{bar}] {percentage}%",
});
// You can still override individual style settings
const bar5 = logger.createProgressBar({
total: 100,
style: "shades_classic",
barCompleteChar: "🚀", // Override the complete character
format: "Progress [{bar}] {percentage}%",
});Available styles: shades_classic, shades_grey, rect, filled, solid, ascii, custom
Multi Progress Bars
Display multiple progress bars simultaneously:
import { createPail } from "@visulima/pail";
const logger = createPail({ interactive: true });
const multiBar = logger.createMultiProgressBar({
style: "shades_classic", // Apply style to all bars
});
const bar1 = multiBar.create(100);
const bar2 = multiBar.create(200);
const bar3 = multiBar.create(150);
// Update bars as needed
bar1.update(50);
bar2.update(75);
bar3.update(25);
// Clean up when done
multiBar.stop();Custom Progress Bar
Create fully customized progress bars with your own characters and formatting:
import { createPail } from "@visulima/pail";
const logger = createPail({ interactive: true });
const bar = logger.createProgressBar({
total: 100,
format: "🚀 Downloading {filename}: [{bar}] {percentage}% | Speed: {speed} MB/s | ETA: {eta}s",
barCompleteChar: "🚀",
barIncompleteChar: "⚪",
width: 20,
});
bar.start(0, 0, {
filename: "large-file.zip",
speed: "0.0",
});
// Update with payload data
bar.update(50, { speed: "2.5" });
bar.stop();Integrations
Use with @visulima/boxen
Create nicely framed messages with @visulima/boxen and log them with Pail:
import { pail } from "@visulima/pail";
import { boxen } from "@visulima/boxen";
const output = boxen("Build completed successfully", {
headerText: "Success",
padding: 1,
borderStyle: "round",
});
pail.success(output);You can also include multi‑line content; Pail preserves newlines:
import { pail } from "@visulima/pail";
import { boxen } from "@visulima/boxen";
const details = ["Service: api", "Env: production", "Commit: a1b2c3"].join("\n");
pail.info(
boxen(details, {
headerText: "Deploy Info",
padding: { top: 0, right: 2, bottom: 0, left: 2 },
borderStyle: "classic",
}),
);Use with @visulima/tabular
Render tables with @visulima/tabular and send them through Pail:
import { pail } from "@visulima/pail";
import { createTable } from "@visulima/tabular";
const table = createTable({
showHeader: true,
style: {
// optional: customize colors/border via @visulima/colorize
},
});
table.setHeaders(["Task", "Status", "Duration"]).addRow(["build", "ok", "1.2s"]).addRow(["test", "ok", "3.4s"]).addRow(["lint", "warn", "0.8s"]);
pail.info("\n" + table.toString());For dynamic dashboards, combine tabular with Pail's interactive mode and update the rendered table as your process runs.
Disable and Enable Loggers
To disable a logger, use the disable() function, which will prevent any log messages from being written to the console or a file.
import { pail } from "@visulima/pail";
pail.disable();
pail.success("This message will not be logged");To enable a logger, use the enable() function, which will allow log messages to be written to the console or a file.
import { pail } from "@visulima/pail";
pail.disable();
pail.success("This message will not be logged");
pail.enable();
pail.success("This message will be logged");Pause and Resume Loggers
The pause() and resume() functions allow you to temporarily queue log messages and then flush them all at once. This is similar to how consola works and is useful when you need to buffer output during critical operations.
When paused, all log calls will be queued instead of being output immediately. When you call resume(), all queued messages will be processed in the order they were called.
import { pail } from "@visulima/pail";
pail.pause();
// These messages will be queued
pail.info("First message");
pail.warn("Second message");
pail.success("Third message");
// Resume logging - all queued messages are now output in order
pail.resume();
// This message is output immediately
pail.info("Fourth message");Note: Unlike disable(), which discards log messages, pause() queues them for later output. This makes it ideal for scenarios where you want to control when messages appear without losing them.
Api
Supported Node.js Versions
Libraries in this ecosystem make the best effort to track Node.js’ release schedule. Here’s a post on why we think this is important.
Contributing
If you would like to help take a look at the list of issues and check our Contributing guild.
Note: please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
Credits
About
Related Projects
- pino - 🌲 super fast, all natural json logger
- winston - A logger for just about everything.
- signale - Highly configurable logging utility
- consola - 🐨 Elegant Console Logger for Node.js and Browser
License
The visulima pail is open-sourced software licensed under the [MIT][license-url]
[typescript-url]: https://www.typescriptlang.org/ "TypeScript" "typescript" [license-image]: https://img.shields.io/npm/l/@visulima/pail?color=blueviolet&style=for-the-badge [license-url]: LICENSE.md "license" [npm-image]: https://img.shields.io/npm/v/@visulima/pail/latest.svg?style=for-the-badge&logo=npm [npm-url]: https://www.npmjs.com/package/@visulima/pail/v/latest "npm" [rfc-5424]: https://datatracker.ietf.org/doc/html/rfc5424#page-36 [fmt]: https://github.com/visulima/visulima/tree/main/packages/fmt