Documentation Index
Fetch the complete documentation index at: https://docs.rootprint.io/llms.txt
Use this file to discover all available pages before exploring further.
Rootprint accepts OpenTelemetry logs over OTLP HTTP. Node apps can use the plain OTEL SDK, or plug OTLP into Pino or Winston — all three routes ship records through the same endpoint and land in the OTEL logs index pinned by your ingest API key (default: otel-logs-v0_9).
Setup
Install
npm install @opentelemetry/api-logs @opentelemetry/sdk-node @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-proto
npm install pino pino-opentelemetry-transport
npm install winston @opentelemetry/api-logs @opentelemetry/sdk-node @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-proto @opentelemetry/winston-transport
Node.js 18+ is required (for native fetch and top-level await).Set environment variables
export OTEL_SERVICE_NAME=my-node-service
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://<your-rootprint>/v1/logs
export OTEL_EXPORTER_OTLP_LOGS_HEADERS=Authorization=Bearer%20<your-ingest-token>
The %20 after Bearer is required — OTEL expects URL-encoded header values.
Minimal working example
import { logs } from '@opentelemetry/api-logs';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
const sdk = new NodeSDK({
logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())]
});
sdk.start();
logs.getLogger('hello').emit({ severityText: 'INFO', body: 'Hello from Node to Rootprint' });
await sdk.shutdown();
import pino from 'pino';
const transport = pino.transport({
target: 'pino-opentelemetry-transport'
});
const log = pino(transport);
log.info('Hello from Pino to Rootprint');
// Short-lived scripts must wait for the worker transport to flush,
// otherwise the record never leaves the process.
await new Promise((resolve) => transport.on('close', resolve));
transport.end();
import { logs } from '@opentelemetry/api-logs';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
import { OpenTelemetryTransportV3 } from '@opentelemetry/winston-transport';
import winston from 'winston';
const sdk = new NodeSDK({
logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())]
});
sdk.start();
const logger = winston.createLogger({
level: 'info',
transports: [new OpenTelemetryTransportV3()]
});
logger.info('Hello from Winston to Rootprint');
// Short-lived scripts should shut down the SDK before exit so the
// batch processor can flush buffered records.
await sdk.shutdown();
Verify in Rootprint
Open Search, filter on service_name:my-node-service, and your record should appear within ~2 seconds.
Structured logging
Attributes become log-record attributes on the OTEL side — searchable and filterable in Rootprint.
logs.getLogger('app').emit({
severityText: 'INFO',
body: 'user signed up',
attributes: { user_id: 'alice', plan: 'pro' }
});
With Pino, pass an object as the first argument:
log.info({ user_id: 'alice', plan: 'pro' }, 'user signed up');
Framework recipe: Express
Initialise the SDK once at process start, then log from anywhere. Keep the setup in a separate module (e.g. otel.js) and import it before your Express app.
// otel.js
import { logs } from '@opentelemetry/api-logs';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
const sdk = new NodeSDK({
logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())]
});
sdk.start();
export const logger = logs.getLogger('express-app');
// server.js
import './otel.js';
import express from 'express';
import { logger } from './otel.js';
const app = express();
app.use((req, _res, next) => {
logger.emit({
severityText: 'INFO',
body: 'request',
attributes: { method: req.method, path: req.path }
});
next();
});
app.get('/', (_req, res) => res.json({ ok: true }));
app.listen(3000);
Troubleshooting
- 401 / 403 — the Bearer token is missing, malformed, or the
%20 separator is not URL-encoded. Re-check OTEL_EXPORTER_OTLP_LOGS_HEADERS.
- Nothing in Search — the batch processor buffers records. For short-lived scripts call
await sdk.shutdown() before exit.
fetch is not defined — upgrade to Node.js 18+ or pass a polyfilled fetch to the exporter.
- Pino transport not emitting —
pino-opentelemetry-transport reads the OTEL_* env vars itself; make sure they are set in the process that spawns the transport worker.
- TLS errors against a self-signed endpoint — set
NODE_EXTRA_CA_CERTS=/path/to/ca.pem.