---
title: "evlog and OpenTelemetry: connect logs to traces"
description: "Send evlog events to an OpenTelemetry Collector over OTLP HTTP and attach the active trace context without replacing tracing."
canonical_url: "https://www.evlog.dev/compare/opentelemetry"
---
# evlog and OpenTelemetry: connect logs to traces

> Send evlog events to an OpenTelemetry Collector over OTLP HTTP and attach the active trace context without replacing tracing.

You already export traces to an OpenTelemetry Collector and want application logs beside them. evlog can build those log events and send them through OTLP. Keep the tracing instrumentation: evlog does not create spans or metrics.

## Assign each layer a job

```text [Data flow]
Application fields -> evlog event -> OTLP HTTP /v1/logs -> Collector -> backend
Active OTel span  -> traceId + spanId on the event
OTel SDK         -> spans and metrics                -> Collector -> backend
```

The SDK instruments operations, the Collector receives and processes telemetry, and the backend provides storage, queries, and alerts. [OpenTelemetry's documentation](https://opentelemetry.io/docs/) describes those responsibilities. Semantic conventions standardize shared attributes, while custom application attributes remain valid.

## Export an event with the active span context

This example assumes a Node.js application with its OpenTelemetry SDK and context manager already initialized. The function must run inside an active span to attach trace context. Follow the [JavaScript instrumentation guide](https://opentelemetry.io/docs/languages/js/instrumentation/) if tracing is not configured yet.

Install the logging package and OTel API in that application:

```bash [Terminal]
npm install evlog @opentelemetry/api
```

The function writes a completed checkout event as local JSON and exports it through OTLP. It awaits delivery explicitly: a failed export is logged with the drain name and does not throw, so the request path never breaks. Use `drain.raw({ event })` when the caller must observe failures. Do not also register this drain globally, which would export the event twice.

```typescript [checkout-log.ts]
import { isSpanContextValid, trace } from '@opentelemetry/api'
import { createLogger, initLogger } from 'evlog'
import { createOTLPDrain } from 'evlog/otlp'

initLogger({ env: { service: 'checkout' }, pretty: false })
const drain = createOTLPDrain({ recordShape: 'compact' })

export async function recordCheckout(orderId: string, outcome: 'paid' | 'declined') {
  const log = createLogger({ orderId, outcome })
  const spanContext = trace.getActiveSpan()?.spanContext()
  if (spanContext && isSpanContextValid(spanContext)) {
    log.set({ traceId: spanContext.traceId, spanId: spanContext.spanId })
  }
  if (outcome === 'declined') log.error(new Error('Payment declined'))
  const event = log.emit()
  if (event) await drain({ event })
}
```

Configure the OTLP HTTP base endpoint in the application's environment. If it is unset, the drain logs a missing-endpoint warning and skips delivery:

```bash [.env]
OTLP_ENDPOINT=http://localhost:4318
```

Load that file through your application or Node's `--env-file` option. A file named `.env` alone does not configure an arbitrary Node.js process.

For a local Collector, enable a logs pipeline. This minimal configuration prints received records through the debug exporter:

```yaml [otel-collector.yaml]
receivers:
  otlp:
    protocols:
      http:
        endpoint: 127.0.0.1:4318
exporters:
  debug:
    verbosity: detailed
service:
  pipelines:
    logs:
      receivers: [otlp]
      exporters: [debug]
```

Run the Collector with this configuration and call `recordCheckout('order-123', 'paid')` inside an instrumented operation. A Collector in a separate container needs a reachable receiver address and port mapping. Use the loopback configuration above for processes on the same host.

## Verify correlation before shipping

Find the record by `orderId`. Its resource should contain `service.name: checkout`, and its `traceId` and `spanId` should match the active span from the same operation. Without a valid active span the event still exports, but those identifiers are absent.

The drain maps level and timestamp to OTLP severity and time fields. With `recordShape: 'compact'`, nested application fields become dotted attributes. The default JSON shape uses a serialized event body and sends nested attribute values as OTLP key-value lists. Set `semanticConventions: true` to add OpenTelemetry attribute names such as `http.request.method` and `gen_ai.usage.input_tokens` next to the evlog ones. The [OTLP adapter reference](https://www.evlog.dev/integrate/adapters/hybrid/otlp) describes the mapping and delivery options.

The TraceContext enricher reads an incoming `traceparent` and records its span identifier as `parentSpanId`, because that span belongs to the caller. It never sets `spanId`. Use the active SDK context as above to link the event to the server span.

## Decide what to sample and how to deliver

Event sampling in evlog and [trace sampling in OpenTelemetry](https://opentelemetry.io/docs/concepts/sampling/) operate on different signals. A retained log can reference a trace that your tracing policy discarded. Coordinate the policies before relying on navigation from every error log to a stored trace.

This example waits for the network request. For production handler integration, configure the drain pipeline and the framework's background-delivery lifecycle deliberately. The adapter supports OTLP HTTP logs, not a gRPC-only endpoint. Serialization, batching, retries, and export add costs that event-construction microbenchmarks do not measure. See [Performance](https://www.evlog.dev/reference/performance) for those boundaries.

If you use Nitro, the integration can invoke `createOTLPDrain()` through `evlog:drain` after evlog is enabled. Keep your existing OTel instrumentation in either setup. For a library-to-library decision, continue with [evlog vs other loggers](https://www.evlog.dev/reference/vs-other-loggers).

---

- [OTLP Adapter](https://www.evlog.dev/integrate/adapters/hybrid/otlp)
- [Performance Benchmarks](https://www.evlog.dev/reference/performance)


## Sitemap

See the full [sitemap](https://www.evlog.dev/sitemap.md) for all pages.
