isotropic-logger
v0.5.0
Published
A preconfigured shared pino logger with error serialization and task timing
Maintainers
Readme
isotropic-logger
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-loggerUsage
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: 42Task 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 befalseuntil eithercompleteorerrorare called.duration: Number, null benulluntil eithercompleteorerrorare 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
vfield. Bunyan added a"v": 0envelope field to every record; pino does not. timeis an epoch-millisecond number. Bunyan wrote an ISO 8601 string. Convert withTemporal.Instant.fromEpochMilliseconds(record.time).toString()when a string is needed. (The same numeric levels, and themsg,name,pid, andhostnamefields, 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'sstreamsarray andaddStream()are gone. Uselogger.outputStream = writableto redirect output.
Human-Readable Output
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
