npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

isotropic-logger

v0.5.0

Published

A preconfigured shared pino logger with error serialization and task timing

Readme

isotropic-logger

npm version License

A pre-configured pino logger instance with enhanced error serialization and task duration tracking.

Isotropic relies upon pino for fast, structured JSON logging. The package exports a single shared logger instance so that every module in an application imports the same logger and can start logging immediately.

Why Use This?

  • Singleton Logger: Import once, use everywhere in your application.
  • Structured Logging: JSON-formatted logs for easy parsing and analysis.
  • Enhanced Error Handling: Serialization of error objects, including isotropic-error instances, with stack traces, details, codes, and signals.
  • Task Tracking: Built-in task logging with duration measurements.
  • Pluggable Output: Redirect the log stream at runtime, or switch to human-readable output with a single call.

Installation

npm install isotropic-logger

Usage

import _Error from 'isotropic-error';
import _logger from 'isotropic-logger';

// Basic logging
_logger.info('Application started');
_logger.warn('Configuration missing, using defaults');
_logger.error('Failed to connect to database');

// Logging with additional data
_logger.info({
    action: 'login',
    userId: 123
}, 'User logged in');

// Logging errors
try {
    // Some code that might throw
    throw _Error({
        message: 'Something went wrong'
    });
} catch (error) {
    _logger.error({
        error
    }, 'Error during operation');
}

Log Levels

The logger supports the standard log levels and their numeric values:

| Method | Level | | ---------------- | ----- | | logger.trace() | 10 | | logger.debug() | 20 | | logger.info() | 30 | | logger.warn() | 40 | | logger.error() | 50 | | logger.fatal() | 60 |

Redirecting Output

The logger writes to process.stdout by default. Change outputStream to send log records somewhere else, such as a file, a socket, or an in-memory buffer for testing:

import _fs from 'node:fs';
import _logger from 'isotropic-logger';
import _process from 'node:process';

// Send all subsequent log records to a file
_logger.outputStream = _fs.createWriteStream('application.log');

_logger.info('This goes to the file');

// Reset back to process.stdout
_logger.outputStream = _process.stdout;

This is the single, explicit replacement for stream configuration. (isotropic-logger-pretty's enable() is implemented on top of it.)

Child Loggers

child() returns a logger that includes a set of bindings in every record. Child loggers preserve the parent's bindings by default, and grandchildren accumulate the bindings of the whole chain:

import _logger from 'isotropic-logger';

const requestLogger = _logger.child({
        requestId: 'abc123'
    });

requestLogger.info('Handling request');
// Record includes requestId: 'abc123'

const userLogger = requestLogger.child({
        userId: 42
    });

userLogger.info('Loaded user');
// Record includes both requestId: 'abc123' and userId: 42

Task Logging

The module provides a special task() method for logging the start and completion of operations with duration tracking:

import _logger from 'isotropic-logger';

{
    // Start a task
    const task = _logger.task('Processing files');

    // Do some work...

    // Complete the task successfully
    task.complete();
    // Logs: "Done processing files" with duration info

    // Or if the task failed
    task.error();
    // Logs: "Error processing files" with duration info
}

Task API

logger.task([data], [beginData], [description])

Begins tracking a task and optionally logs the start of the task.

  • data (Object, optional): Additional data to include in the log entry. (will also be included in the complete log entry)
  • beginData (Object, optional): Additional data to include in the log entry. (only included when the task begins)
  • description (String, optional): Description of the task.

Returns a TaskLogger object with the following methods:

  • complete([data], [description]): Logs successful completion of the task with duration.
  • error([data], [description]): Logs failure of the task with duration.

Each completion record includes a duration field (elapsed milliseconds) and a human-readable durationString (for example, "1.234 seconds").

The TaskLogger object also has the following readonly properties:

  • completed: Boolean, will be false until either complete or error are called.
  • duration: Number, null be null until either complete or error are called.

Task Examples

{
    // Basic task with start and end logging
    const task = _logger.task('Importing data');

    importData()
        .then(() => task.complete())
        .catch(error => task.error({
            error
        }));
}

{
    // Task with custom complete message
    const backupTask = _logger.task('Starting backup');

    backup()
        .then(result => backupTask.complete({
            size: result.size
        }, 'Backup completed successfully'))
        .catch(error => backupTask.error({
            error
        }, 'Backup failed'));
}

{
    // Task with additional data
    const userId = 123,
        userTask = _logger.task({
            userId
        }, 'Processing user data');

    processUser(userId)
        .then(result => userTask.complete({
            changedFields: result.changes,
            userId
        }))
        .catch(error => userTask.error({
            error,
            userId
        }));
}

{
    // Automatic task completion
    let data;

    {
        using task = _logger.task('Fetching external data');

        data = await _fetchFromExternalService();
    } // task.complete() is called automatically at the end of the block
}

Error Serialization

The logger automatically serializes objects logged under the error key for better readability:

import _Error from 'isotropic-error';
import _logger from 'isotropic-logger';

// Regular Error objects
try {
    throw _Error({
        message: 'Something failed'
    });
} catch (error) {
    _logger.error({
        error
    }, 'Operation failed');
}

// isotropic-error objects with nested errors
try {
    throw _Error({
        message: 'Network timeout'
    });
} catch (error) {
    _logger.error({
        error: _Error({
            error,
            message: 'Database connection failed'
        })
    }, 'Cannot initialize system');
}

The error serializer extracts:

  • Error message
  • Error name
  • Stack trace (formatted as an array of lines)
  • Error code (if available)
  • Signal (if available)
  • Details object (from isotropic-error)

Non-error values logged under the error key (strings, plain objects, null) are passed through unchanged.

Async/Await Example

import _logger from 'isotropic-logger';

const _processItems = async items => {
    const task = _logger.task({
        count: items.length
    }, 'Processing items');

    try {
        for (const item of items) {
            _logger.debug({
                itemId: item.id
            }, 'Processing item');

            await processItem(item);
        }

        task.complete({
            processedCount: items.length
        });

        return true;
    } catch (error) {
        task.error({
            error
        });

        return false;
    }
};

Log Output Format

The log output is JSON-formatted for easy parsing:

{
    "hostname": "server-name",
    "level": 30,
    "msg": "Application started",
    "name": "isotropic",
    "pid": 12345,
    "time": 1686832496789
}

For tasks with duration tracking:

{
    "duration": 1234,
    "durationString": "1.234 seconds",
    "hostname": "server-name",
    "level": 30,
    "msg": "Done processing files",
    "name": "isotropic",
    "pid": 12345,
    "time": 1686832496789
}

The time field is the number of milliseconds since the Unix epoch, which is the fastest representation to produce. When you need an ISO 8601 string while reading logs, convert it:

Temporal.Instant.from(record.time).toString()

Migrating from Bunyan

Earlier versions of isotropic-logger were built on Bunyan. The public surface is intentionally similar, but there are a few differences to be aware of:

  • No v field. Bunyan added a "v": 0 envelope field to every record; pino does not.
  • time is an epoch-millisecond number. Bunyan wrote an ISO 8601 string. Convert with Temporal.Instant.fromEpochMilliseconds(record.time).toString() when a string is needed. (The same numeric levels, and the msg, name, pid, and hostname fields, are unchanged.)
  • child() merges parent bindings by default. There is no need to pass a second argument to inherit the parent's data. Inheritance is the default, and chains accumulate.
  • Stream configuration is replaced by outputStream. Bunyan's streams array and addStream() are gone. Use logger.outputStream = writable to redirect output.

Human-Readable Output

See isotropic-logger-pretty.

Contributing

Please refer to CONTRIBUTING.md for contribution guidelines.

Issues

If you encounter any issues, please file them at https://github.com/ibi-group/isotropic-logger/issues