Skip to main content

Architecture

This page explains how Logpilot is structured internally – useful if you're contributing code or building a custom transport.

Overview​

Logpilot is built around three core concepts:

Your application
│
▼
Logger instance ← Creates log entries with level, message, metadata
│
▼
Formatter ← Transforms entries into the desired output format (JSON, text)
│
▼
Transport(s) ← Writes formatted entries to a destination (stdout, file, HTTP)

Each of these layers is independent and replaceable.

The logger​

The Logger class (src/logger.js) is the public-facing API. It:

  • Accepts log calls (log.info(), log.error(), etc.)
  • Applies the minimum log level filter
  • Attaches standard fields: timestamp, level, pid
  • Passes the entry to the configured formatter, then each transport
const logger = new Logger({
level: 'info', // Minimum level to log
formatter: jsonFormatter,
transports: [stdoutTransport, fileTransport],
});

Log levels​

Logpilot uses five levels, in ascending severity:

LevelValueUse for
debug10Detailed diagnostic information
info20Normal application events
warn30Unexpected situations that aren't errors
error40Errors that need attention
fatal50Application is about to crash

Entries below the configured minimum level are discarded before formatting or transport.

Formatters​

A formatter is a pure function that takes a log entry object and returns a string:

// src/formatters/json.js
function jsonFormatter(entry) {
return JSON.stringify(entry);
}

Logpilot ships two built-in formatters:

  • jsonFormatter (default) – compact single-line JSON, ideal for production and log aggregation tools
  • prettyFormatter – colourised, human-readable output for local development

Transports​

A transport is an object with a write(formattedString) method. It receives the formatted log string and sends it somewhere.

Built-in transports​

TransportDescription
StdoutTransportWrites to process.stdout (default)
FileTransportAppends to a log file with optional rotation
HttpTransportPOSTs log batches to an HTTP endpoint

Writing a custom transport​

Implement a write method and optionally a close method for cleanup:

class SlackTransport {
constructor({ webhookUrl, minLevel = 'error' }) {
this.webhookUrl = webhookUrl;
this.minLevel = minLevel;
}

async write(formattedString) {
const entry = JSON.parse(formattedString);

// Only send errors and above to Slack
if (entry.levelValue < levels[this.minLevel]) return;

await fetch(this.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: `*${entry.level.toUpperCase()}*: ${entry.message}` }),
});
}
}

Register it like any built-in transport:

const log = new Logger({
transports: [
new StdoutTransport(),
new SlackTransport({ webhookUrl: process.env.SLACK_WEBHOOK }),
],
});

Error handling​

Logpilot handles transport errors internally so a logging failure never crashes your application. If a transport's write method throws, the error is emitted on the logger's error event:

log.on('transportError', (err, transport) => {
console.error(`Transport failed: ${transport.name}`, err);
});