Skip to content

Hono

The autotel-hono package provides an otel() middleware for automatic request tracing and HTTP metrics in Hono applications.

Terminal window
npm install autotel-hono autotel
import { Hono } from 'hono';
import { init, getRequestLogger } from 'autotel';
import { otel } from 'autotel-hono';
init({ service: 'my-api' });
const app = new Hono();
app.use('*', otel());
app.get('/api/users', async (c) => {
const log = getRequestLogger();
log.set({ route: '/api/users' });
const users = await db.users.findAll();
log.emitNow();
return c.json(users);
});

Use useLogger() from autotel-adapters/hono when the otel() middleware already creates a span (avoids duplicate spans):

import { otel } from 'autotel-hono';
import { useLogger } from 'autotel-adapters/hono';
app.use('*', otel());
app.get('/orders/:id', (c) => {
const log = useLogger(c);
log.set({ route: c.req.path });
return c.json({ ok: true });
});

A handler returning an SSE, NDJSON, or AI stream returns before the work finishes. otel() ends the request span, http.server.request.duration and the active-request count when the body has been sent, so a 60-second agent run records as 60 seconds. An error part-way through the stream lands on the span; a client disconnect ends it without one. In tests, read the body (await res.text()) before you assert on the span.

For the request snapshot as well, use autotelMiddleware() from autotel-adapters/hono in place of otel(): it creates the span and the request logger, and emits the snapshot once the stream closes, errors, or gets cancelled.

import { autotelMiddleware, useLogger } from 'autotel-adapters/hono';
app.use('*', autotelMiddleware());
app.get('/chat', (c) => {
const log = useLogger(c);
return streamSSE(c, async (stream) => {
for await (const token of model.stream(prompt)) {
await stream.writeSSE({ data: token });
}
log.set({ 'chat.tokens': tokenCount }); // still lands in the snapshot
});
});

A client that disconnects mid-stream still produces a snapshot, so an abandoned generation reads as a cancelled stream rather than a request that never finished. The middleware wraps the response without locking the original body, so the client receives exactly what it would have without it.

Pass autoEmit: false when you want to call .emitNow() yourself.

The otel() middleware accepts options for customizing tracing behavior:

Option Description
tracer Custom tracer instance
tracerProvider Custom tracer provider
meter Custom meter instance
spanNameFactory Custom span name from request
captureRequestHeaders Headers to capture as span attributes
captureResponseHeaders Response headers to capture
captureActiveRequests Track active request gauge
captureRequestDuration Track request duration histogram
serviceName Override service name
serviceVersion Override service version
disableTracing Disable span creation (metrics only)

HTTP request spans include:

  • http.request.method: GET, POST, etc.
  • url.path: Request path
  • http.response.status_code: Response status
  • http.request.duration_ms: Request duration
  • example-hono: Hono with otel() middleware, useLogger() request-scoped logging, and manual trace() in handlers.