Pino HTTP Log Transport
pino-http-send is a basic handler for Pino logs that facilitates sending batches of structured log data to a desired HTTP or HTTPS endpoint. Currently at version 0.4.2, it is pre-v1, meaning minor version changes may introduce breaking changes. The library supports configurable HTTP methods (POST, PUT, PATCH, GET), two body types (JSON array wrapped in a 'logs' object or newline-delimited JSON), and includes basic authentication and retry mechanisms for failed sends. It can be used either as a command-line interface tool, piping `pino` output directly, or programmatically via its `createWriteStream` API, which acts as a Pino destination. Its key differentiators include its simplicity in setting up a direct HTTP log sink, batching capabilities to optimize network requests, and built-in retry logic, making it a robust, low-overhead option for forwarding Pino logs.
Common errors
-
Error: Missing required argument: url
cause Attempting to run `pino-http-send` via the CLI without providing the `--url` argument, which is mandatory.fixSpecify the target URL using `--url` or `-u` flag. Example: `pino-http-send --url=http://your-log-endpoint.com/logs`. -
TypeError: createWriteStream is not a function
cause Incorrectly importing `createWriteStream` using a default import or a CommonJS `require` statement that expects a default export.fixEnsure `createWriteStream` is imported as a named export. For ESM: `import { createWriteStream } from 'pino-http-send';`. For CommonJS: `const { createWriteStream } = require('pino-http-send');`. -
Received logs are not in expected format (e.g., missing 'logs' array or not NDJSON)
cause Mismatch between the `bodyType` configured in `pino-http-send` (e.g., 'json') and what the receiving server expects (e.g., 'ndjson' or a raw array).fixVerify that the `bodyType` option (`json` or `ndjson`) passed to `createWriteStream` or the CLI `--bodyType` flag matches the format your log ingestion endpoint is designed to consume. Adjust either the client configuration or the server's parsing logic accordingly.
Warnings
- breaking As a pre-v1 package (current version 0.4.2), `pino-http-send` explicitly states that it is 'subject to breaking changes on minor version change'. Users should expect potential API shifts and review changelogs when updating.
- gotcha The CLI usage for `pino-http-send` only supports basic authentication via `--username` and `--password` flags. Custom HTTP headers for authentication (e.g., Bearer tokens) or other purposes are not supported via the CLI and must be configured when using the programmatic `createWriteStream` API.
- gotcha When using `bodyType: 'json'`, `pino-http-send` wraps the batch of logs in an object with a `logs` key (e.g., `{ logs: [...] }`). If your receiving endpoint expects a direct JSON array of logs, this will cause parsing issues. Conversely, `ndjson` sends new-line delimited JSON objects.
Install
-
npm install pino-http-send -
yarn add pino-http-send -
pnpm add pino-http-send
Imports
- createWriteStream
const createWriteStream = require('pino-http-send').createWriteStream;import { createWriteStream } from 'pino-http-send'; - PinoHttpSendOptions
import type { PinoHttpSendOptions } from 'pino-http-send';
Quickstart
import { createWriteStream } from 'pino-http-send';
import pino from 'pino';
import http from 'http';
// 1. Setup a dummy HTTP server to receive logs (your actual log ingestion endpoint)
const server = http.createServer((req, res) => {
if (req.url === '/logs' && req.method === 'POST') {
let body = '';
req.on('data', (chunk) => { body += chunk.toString(); });
req.on('end', () => {
try {
console.log(`\n--- Received Batch (${req.headers['content-type']}) ---\n`);
if (req.headers['content-type']?.includes('application/json')) {
console.log(JSON.parse(body).logs); // 'json' bodyType wraps logs in { logs: [...] }
} else {
console.log(body); // 'ndjson' is raw new-line delimited JSON
}
console.log(`--- End Batch ---\n`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', receivedLogsCount: body.split('\n').filter(Boolean).length }));
} catch (e) {
console.error('Error parsing received logs:', e);
res.writeHead(400).end('Bad Request');
}
});
} else {
res.writeHead(404).end('Not Found');
}
});
server.listen(3000, () => {
console.log('Dummy log server listening on http://localhost:3000');
console.log('Sending logs via pino-http-send...');
// 2. Configure pino-http-send as a Pino destination
const pinoSendStream = createWriteStream({
url: 'http://localhost:3000/logs',
method: 'POST',
bodyType: 'json', // or 'ndjson'
batchSize: 2, // Send every 2 logs
timeout: 1000, // or flush after 1 second if batch not full
log: true, // Enable internal logging for pino-http-send itself
headers: { 'X-Custom-Header': 'pino-test' }
});
// 3. Create a Pino logger instance using the pino-http-send stream
const logger = pino(pinoSendStream);
let count = 0;
const interval = setInterval(() => {
logger.info({ id: ++count, service: 'my-app', event: 'data_processed', timestamp: new Date().toISOString() });
if (count >= 5) {
clearInterval(interval);
logger.flush(); // Ensure any buffered logs are sent
setTimeout(() => {
server.close(() => console.log('Dummy server closed. Exiting.'));
}, 2000); // Give time for the last batch to be sent
}
}, 500);
});