inkan

OpenTelemetry

A SERVER span for every request, named by its route, continued from the caller's traceparent.

Needs inkan 0.8

@inkanjs/otel needs inkan 0.8.0 or newer, below 1.0.

npm i @inkanjs/otel @opentelemetry/api
import {  } from "@inkanjs/otel";


  .(({ : () => .path === "/health" }))
  .("/teas/:id", () => {
    .span.setAttribute("tea.id", ..);
    return findTea(..);
  });

The one official package with a dependency: @opentelemetry/api (a peer, ^1.4.0), the vendor-neutral API every OpenTelemetry SDK implements. No SDK, no exporter: which SDK turns the spans into traces is up to your app, and without one every call is a no-op. Set one up before the app starts, as the OpenTelemetry docs for Node describe (for example @opentelemetry/sdk-node with an OTLP exporter, loaded with node --import ./tracing.js).

What a span holds

GET /teas/:id   SERVER
  http.request.method        GET            (_OTHER and http.request.method_original for unknown ones)
  url.path                   /teas/7
  url.scheme                 https          (ctx.protocol)
  http.route                 /teas/:id
  http.response.status_code  200
  client.address             203.0.113.9    (ctx.ip, so with trustProxy the real client)
  user_agent.original        curl/8.5
  server.address, .port      api.example.com, 8443   (the Host header)
  network.protocol.version   1.1
  • Name. The method and the route's pattern (GET /teas/:id), never the path itself, so names stay few. A request no route takes is named by its method alone.
  • Parent. The caller's trace context is read with the global propagator (W3C traceparent by default), from the headers as they arrived.
  • Errors. A 5xx sets the status to ERROR, error.type to the problem's type (internal, bad-gateway, …) and records the problem as an exception event. A 4xx is the client's mistake and stays UNSET, as the conventions ask for server spans.
  • End. In onResponse, once the answer is written: a streamed answer's span lasts until its last chunk.
  • The query string is not recorded: it may hold tokens. Add it with attributes if yours does not.

On the context

ctx.spanthis request's span: setAttribute, addEvent, recordException. Non-recording for an ignored request
ctx.traceContextthe OpenTelemetry context with the span in it, for children

The span is not made the active one for the handler's async work. Start children under it with the context:

const  = trace.getTracer("tea-shop");

.("/teas/:id", () =>
  .startActiveSpan("load tea", {}, .traceContext, async () => {
    try {
      return await db.tea(..);
    } finally {
      .end();
    }
  }),
);

A thrown error reaches the span as a problem only (internal): inkan keeps the error itself for onError. Record it there with spanOf, which takes the ctx onError gets, undefined too (an error from outside any request has no span):

import {  } from "@inkanjs/otel";

const  = ({ : (, ) => void ()?.recordException( as Error) });

Behaviour

  • A shared plugin: register it on the app for every route, or inside a plugin for its routes. Register it first, so its span covers the hooks of the plugins after it.
  • The span starts in an onRequest hook, after routing and before the body is read.
  • Jobs get spans for their routes like any other; the work that runs after the 202 does not. Start a span in it yourself if you want one.

Options

optiondefault
tracerthe global provider's @inkanjs/otel tracer
propagatetruecontinue the caller's trace from its headers
ignorenone(ctx) => boolean: requests to leave untraced, such as health checks
spanNameMETHOD /route/:pattern(ctx) => string
attributesnone(ctx) => attributes, set when the span starts, so samplers see them

OpenAPI

Unchanged.

On this page