@zerilog/opentelemetryzink
v1.0.0
Published
A zink for shipping logs over [OTLP](https://opentelemetry.io/docs/specs/otlp/) (the OpenTelemetry Protocol) using `zerilog`.
Maintainers
Readme
📡 @zerilog/opentelemetryzink
A zink for shipping logs over OTLP (the OpenTelemetry Protocol) using zerilog.
OTLP is a vendor-neutral standard, so this zink works with any OTLP/HTTP receiver — an OpenTelemetry Collector, or a backend that ingests OTLP directly such as Berserk, Grafana, Honeycomb, and others. It sends each log event as an OTLP/HTTP log record; no Collector is required in between.
Getting Started
npm install zerilog @zerilog/opentelemetryzink
pnpm add zerilog @zerilog/opentelemetryzink
yarn add zerilog @zerilog/opentelemetryzinkUsage
import { LoggerConfiguration } from "zerilog";
import OpenTelemetryZink from "@zerilog/opentelemetryzink";
const logger = new LoggerConfiguration()
.writeTo.zink(
new OpenTelemetryZink({
endpoint: "https://otelcol:4318",
headers: { Authorization: "Bearer <token>" },
resourceAttributes: {
"service.name": "my-api",
"deployment.environment": "prod",
},
}),
)
.createLogger();
logger.information("Hello {Name}!", "World");Configuration
| Option | Required | Default | Description |
| -------------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
| endpoint | ✅ | — | Base URL of the OTLP receiver. For http the logs path (/v1/logs) is appended; for grpc the scheme selects TLS (https://) vs insecure (http://). |
| protocol | | "http" | "http" (OTLP/JSON, typically port 4318) or "grpc" (LogsService/Export, typically port 4317). |
| headers | | {} | Sent with every request: HTTP headers, or gRPC metadata. E.g. { Authorization: "Bearer <token>" }. |
| resourceAttributes | | {} | OTLP resource attributes attached to every batch (e.g. service.name, or backend routing keys). |
| batchSizeLimit | | 50 | Maximum number of log records sent in a single batch. |
| batchTimeout | | 5000 | How often (ms) the batch is flushed. |
| maxRetries | | 3 | Times a failed batch is retried before its events are dropped. |
How It Works
Events are buffered in memory and flushed either when batchSizeLimit records accumulate or every batchTimeout milliseconds — over HTTP (POST /v1/logs, port 4318) or gRPC (LogsService/Export, port 4317) depending on protocol. Each Zerilog level maps to the matching OTLP severity number (Information → 9/INFO, Error → 17/ERROR, …), the rendered message becomes the log record body, and message properties become OTLP log attributes. Failed batches are requeued and retried up to maxRetries times before being dropped.
Flushing on shutdown
Before a short-lived process exits, make sure the last buffered batch is sent. await flush() sends it and waits for delivery; await using does the same automatically when the binding goes out of scope:
await using zink = new OpenTelemetryZink({ endpoint: "…", protocol: "grpc" });
// … log … ; the batch is flushed and the transport closed at end of scope.Plain using (or [Symbol.dispose]()) is best-effort only — it can't await the final send, so over gRPC the channel may close before it completes.
Example: Berserk
Berserk ingests OTLP directly. Authenticate with an ingest token and route to a table via the bzrk.table resource attribute. Hosted Berserk ingresses commonly expose gRPC (4317) rather than HTTP (4318):
new OpenTelemetryZink({
protocol: "grpc",
endpoint: "https://ingest.bzrk.dev:4317",
headers: { Authorization: "Bearer ing_YOUR_INGEST_TOKEN" },
resourceAttributes: { "bzrk.table": "default" },
});Create an ingest token with the Berserk CLI (it's only shown once):
bzrk ingest-token create --table default my-tokenVerify data is flowing:
bzrk search "default | take 10" --since "5m ago"