Logs
Ship your application logs to TracePath over OTLP/HTTP. A log emitted inside a request handler is stamped with the active trace and span id automatically, so it shows up on the trace detail page next to the request that produced it.
Install
npm install @opentelemetry/api-logs \
@opentelemetry/sdk-logs \
@opentelemetry/exporter-logs-otlp-httpIf you followed the Quick Start, these are already installed.
Setup
Add a logRecordProcessors entry to the NodeSDK you created in the Quick Start:
// instrumentation.ts
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
const sdk = new NodeSDK({
// ...resource, traceExporter, metricReaders, instrumentations
logRecordProcessors: [
new BatchLogRecordProcessor({
exporter: new OTLPLogExporter({
url: "https://ingest.tracepath.dev/api/otel/v1/logs",
headers: { Authorization: "Bearer your-project-token" },
}),
scheduledDelayMillis: 2000,
}),
],
});
sdk.start();
BatchLogRecordProcessortakes one options object. Theexportergoes on anexporterkey. Passing it positionally,new BatchLogRecordProcessor(new OTLPLogExporter({...})), fails to compile in TypeScript witherror TS2345: Property 'exporter' is missing. In plain JavaScript it runs and exports nothing: the processor readsoptions.exporter, getsundefined, and throws inside its own export loop where you never see it. Traces and metrics keep flowing, so it looks like TracePath is dropping your logs.
Emitting Logs
import { logs, SeverityNumber } from "@opentelemetry/api-logs";
const logger = logs.getLogger("app");
app.get("/api/users/:id", (req, res) => {
logger.emit({
severityNumber: SeverityNumber.INFO,
severityText: "INFO",
body: `fetching user ${req.params.id}`,
attributes: { "user.id": req.params.id },
});
res.json({ id: req.params.id });
});Called inside a request handler, the log picks up the active span on its own. You do not pass a context. In the dashboard, open the endpoint's trace and switch to the Logs tab to see it attached to that request.
Use dotted attribute keys (user.id, order.id) to match OTel semantic conventions. Attribute values are stored as strings.
Bridging Pino or Winston
If you already log with Pino or Winston, keep doing that. Once the pipeline above is configured, the auto-instrumentation forwards those records to the same OTLP exporter and stamps the active trace_id and span_id on each one. Pino works as is, Winston needs one extra package.
Pino works with no extra packages. @opentelemetry/instrumentation-pino ships in @opentelemetry/auto-instrumentations-node and is enabled by default, so every logger.info(...) is both printed locally and sent to TracePath.
import pino from "pino";
const logger = pino();
app.get("/api/orders/:id", (req, res) => {
logger.info({ "order.id": req.params.id }, "order lookup");
res.json({ ok: true });
});Winston needs one more package. Without it, @opentelemetry/instrumentation-winston only injects trace_id and span_id into your local output. The records are never sent to TracePath, and the reason is only printed with OTel diagnostics on:
npm install @opentelemetry/winston-transportThe instrumentation adds its own transport when your logger is created, so you do not register anything yourself. With the package installed, logger.info("...") reaches TracePath with the trace id attached.
Severity Levels
SeverityNumber values map to TracePath's severity labels as follows:
| SeverityNumber | TracePath Severity |
|---|---|
| 1-4 | TRACE |
| 5-8 | DEBUG |
| 9-12 | INFO |
| 13-16 | WARN |
| 17-20 | ERROR |
| 21+ | FATAL |
If you set severityText yourself it is used as-is (uppercased). See Log Record Mapping for every field TracePath reads.
Verify It Worked
- Hit a route that logs, then wait a couple of seconds for the batch, or stop the app so the shutdown handler flushes.
- Open Logs in the dashboard. The record should be listed with your
service.nameas the service. - Open the same request on Endpoints, go to its trace, and switch to the Logs tab. The log is there because it carries the request's trace id. If the Logs tab is empty but the Logs page has the record, the log was emitted outside the request's span.
Troubleshooting
Log export failures are reported only through OTel's diagnostic channel. If nothing arrives, turn it on temporarily at the top of instrumentation.ts:
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);Common causes, in the order worth checking:
BatchLogRecordProcessorcalled with a positional exporter (see the warning above).- The URL is missing the
/v1/logssuffix. The full path ishttps://ingest.tracepath.dev/api/otel/v1/logs. - The process exited before the batch was sent. Add the
SIGTERMhandler from the Quick Start. - Winston records missing while Pino records arrive: install
@opentelemetry/winston-transport.
Next Steps
- Traces: manual spans, background jobs, exception recording
- Metrics: custom counters, histograms, and gauges
- Log Record Mapping: how OTLP log fields map to TracePath fields