Browse resources

Send data

Framework guides

Copy-paste tracing setups per framework. NestJS and Hono today, more over time.

Anything that speaks OpenTelemetry works with knotel. This page collects ready-made setups, one section per framework. The general rules behind all of them are on the OpenTelemetry page: OTLP over HTTP in either encoding, JSON or protobuf, and the ingest key in the x-knotel-key header.

Python services send protobuf, and that is fine
The Python SDK ships only opentelemetry-exporter-otlp-proto-http, which posts application/x-protobuf — there is no JSON encoding to switch to, whatever an error message suggests. Ingest takes both, so a FastAPI or Django service needs nothing special. A backend that accepts JSON only answers 415 and the SDK drops that batch: tracing looks configured, the logs look healthy, and no span ever arrives.
Already running OpenTelemetry?
Most Node services that had another tracing backend already have the SDK wired up. Adding knotel is then one more exporter, not a new setup: point an OTLPTraceExporter at https://YOUR-INSTANCE/v1/traces with the x-knotel-key header and add it alongside the existing one. Both backends receive the same spans, and you can drop the old one whenever you like.

NestJS

NestJS runs on Node, so the Node SDK does the work. The auto- instrumentations include @opentelemetry/instrumentation-nestjs-core, which traces app bootstrap, each request's controller context and each handler, alongside HTTP, database and Redis clients.

npm install @opentelemetry/sdk-node @opentelemetry/api \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-http
instrumentation.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";

const sdk = new NodeSDK({
  serviceName: "orders-api",
  traceExporter: new OTLPTraceExporter({
    url: "https://YOUR-INSTANCE/v1/traces",
    headers: { "x-knotel-key": process.env.KNOTEL_KEY! },
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();
process.once("beforeExit", async () => {
  await sdk.shutdown();
});

Load it before Nest boots, so the instrumentations can patch modules as they're required:

# package.json
"start": "node --import ./dist/instrumentation.js dist/main.js"
Load order matters
If spans for HTTP or your database never appear, the file is being imported too late. It must run before NestFactory.create, which is why it goes in --import rather than in main.ts.

Naming the service

serviceName is what the dashboard groups by, so give each deployable its own (orders-api, admin-api). Add deployment.environment.name as a resource attribute if staging and production share one project.

Hono

Where Hono runs decides the setup, because the OpenTelemetry Node SDK doesn't run on Cloudflare Workers.

Hono on Cloudflare Workers

Use the knotel Workers SDK. It traces every request, cron and queue batch, and outgoing fetch() calls, with no OpenTelemetry dependencies. Wrap the object you export, not the Hono app itself:

import { Hono } from "hono";
import { instrument, trace } from "./knotel/worker";

const app = new Hono<{ Bindings: Env }>();

app.get("/orders/:id", async (c) => {
  const order = await trace(
    "postgresql SELECT orders",
    { "db.system.name": "postgresql", "db.operation.name": "SELECT", "db.collection.name": "orders" },
    () => db.getOrder(c.req.param("id")),
  );
  return c.json(order);
});

export default instrument(
  { fetch: app.fetch },
  (env) => ({
    endpoint: "https://YOUR-INSTANCE",
    key: env.KNOTEL_KEY,
    service: "api",
  }),
);

Root spans are named GET /orders/123. To group by route instead, add Hono middleware that sets the matched route on the current span:

import { currentSpan } from "./knotel/worker";

app.use(async (c, next) => {
  await next();
  const span = currentSpan();
  if (span && c.req.routePath) {
    span.name = `${c.req.method} ${c.req.routePath}`;
    span.setAttribute("http.route", c.req.routePath);
  }
});

See Cloudflare Workers for the full SDK, including the ignore option for health checks.

Hono on Node or Bun

Use the Node SDK exactly as in the NestJS example above, and optionally add @hono/otel for Hono-aware request spans:

import { Hono } from "hono";
import { httpInstrumentationMiddleware } from "@hono/otel";

const app = new Hono();
app.use(
  httpInstrumentationMiddleware({
    serviceName: "api",
    captureRequestHeaders: ["user-agent"],
  }),
);
Hono with OpenTelemetry on Workers
@hono/otel works on Workers too, but it needs @microlabs/otel-cf-workers as the tracer provider, since the Node SDK isn't available there. The knotel Workers SDK is the smaller path unless you already have an OpenTelemetry setup you want to keep.

Adding the browser side

Backend spans join up with the page that caused them when the browser script propagates a traceparent header to your API's host. Put the API host in data-propagate, and allow the traceparent header in the API's CORS configuration:

NestJS
app.enableCors({
  origin: "https://example.com",
  allowedHeaders: ["content-type", "traceparent"],
});
Hono
import { cors } from "hono/cors";

app.use(
  "/api/*",
  cors({ origin: "https://example.com", allowHeaders: ["content-type", "traceparent"] }),
);

Checklist

  • The exporter posts OTLP over HTTP, in either encoding, to a URL ending in /v1/traces.
  • The ingest key is in x-knotel-key and comes from an environment variable, not the repository.
  • Each service has its own serviceName.
  • Spans appear within a few seconds. If not, see Troubleshooting.