Skip to main content
Logwiz 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 token (default: otel-logs-v0_9).

Setup

1

Install

npm install @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-proto
Node.js 18+ is required (for native fetch and top-level await).
2

Set environment variables

export OTEL_SERVICE_NAME=my-node-service
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://<your-logwiz>/api/otlp/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.
3

Minimal working example

import { logs } from '@opentelemetry/api-logs';
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';

const provider = new LoggerProvider({
    processors: [new BatchLogRecordProcessor(new OTLPLogExporter())]
});
logs.setGlobalLoggerProvider(provider);

logs.getLogger('hello').emit({ severityText: 'INFO', body: 'Hello from Node to Logwiz' });
await provider.forceFlush();
4

Verify in Logwiz

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 Logwiz.
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 provider 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 { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';

const provider = new LoggerProvider({
	processors: [new BatchLogRecordProcessor(new OTLPLogExporter())]
});
logs.setGlobalLoggerProvider(provider);
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 provider.forceFlush() before exit.
  • fetch is not defined — upgrade to Node.js 18+ or pass a polyfilled fetch to the exporter.
  • Pino transport not emittingpino-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.