Hono
Quick Start

Hono

Instrument your Hono application with OpenTelemetry and export traces and metrics to Traceway. Hono runs on multiple runtimes — this guide covers the Node.js setup. For Cloudflare Workers, see the Cloudflare guide.

Installation

npm install hono @hono/node-server @hono/otel \
  @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/exporter-metrics-otlp-http

Setup

Create an instrumentation.ts file at the root of your project:

import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
 
const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: "https://your-traceway-instance.com/api/otel/v1/traces",
    headers: { Authorization: "Bearer your-project-token" },
  }),
 
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: "https://your-traceway-instance.com/api/otel/v1/metrics",
      headers: { Authorization: "Bearer your-project-token" },
    }),
    exportIntervalMillis: 30_000,
  }),
 
  // Disable instrumentation-http — @hono/otel handles HTTP spans at the middleware level
  instrumentations: [
    getNodeAutoInstrumentations({
      "@opentelemetry/instrumentation-http": { enabled: false },
    }),
  ],
});
 
sdk.start();

Why disable instrumentation-http? Hono uses @hono/node-server, which imports Node's http module via ESM. On Node.js 22+, OTel's import-in-the-middle hook cannot intercept ESM imports of built-in modules, so instrumentation-http never patches the server — no spans are created. The @hono/otel (opens in a new tab) middleware solves this by creating spans at the Hono middleware level instead, which works on all Node.js versions and runtimes.

Application Code

The @hono/otel middleware handles everything automatically — endpoint grouping via http.route, status codes, and exception recording:

import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { otel } from "@hono/otel";
 
const app = new Hono();
 
app.use(otel());
 
app.get("/api/users", (c) => {
  return c.json({ users: [] });
});
 
app.get("/api/users/:id", (c) => {
  const id = c.req.param("id");
  return c.json({ id, name: "John" });
});
 
serve({ fetch: app.fetch, port: 3000 });

Running Your App

Load the instrumentation file before your application code:

# CommonJS
node --require ./instrumentation.js server.js
 
# ESM / TypeScript (with tsx)
node --import ./instrumentation.ts server.ts

Or set via environment variable:

export NODE_OPTIONS="--require ./instrumentation.js"
node server.js

What Gets Captured

With @hono/otel and the auto-instrumentations enabled, the following is captured automatically:

LayerWhat's Captured
@hono/otel middlewareIncoming requests with method, route, status code, duration
@hono/otel middlewarehttp.route for endpoint grouping (e.g., GET /api/users/:id)
@hono/otel middlewareExceptions recorded as Issues with stack traces
Database (pg, mysql2, mongodb)Query spans with SQL statements, connection info
Cache (redis, ioredis)Cache operation spans
Outgoing fetch()Outgoing HTTP request spans (auto via instrumentation-undici)
DNSDNS lookup spans

Root SERVER spans become Endpoints in Traceway, child spans become Spans, and exception events become Issues.

Database Auto-Instrumentation

Database libraries like pg, mysql2, mongodb, and ioredis are CommonJS packages. Even in an ESM Hono app, OTel's require-in-the-middle hook patches them successfully. Database queries automatically appear as child spans under the @hono/otel root span — no manual instrumentation needed:

import pg from "pg";
import { otel } from "@hono/otel";
 
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
const app = new Hono();
 
app.use(otel());
 
app.get("/api/users/:id", async (c) => {
  const id = c.req.param("id");
  // This query automatically creates a child span:
  //   pg.query → SELECT * FROM users WHERE id = $1
  const { rows } = await pool.query("SELECT * FROM users WHERE id = $1", [id]);
  return c.json(rows[0]);
});

The resulting trace in Traceway:

GET /api/users/:id          ← Endpoint (from @hono/otel)
  └─ pg.query               ← Span (from instrumentation-pg)
      ├─ dns.lookup          ← Span (from instrumentation-dns)
      └─ tcp.connect         ← Span (from instrumentation-net)

Note: This works because pg, mysql2, and similar packages use CommonJS internally. The ESM import issue that affects http (a Node.js built-in) does not affect third-party CJS npm packages.

Environment Variable Alternative

export OTEL_SERVICE_NAME="my-hono-app"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-traceway-instance.com/api/otel"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer your-project-token"

Test Your Integration

Add a test endpoint:

app.get("/test-error", () => {
  throw new Error("Test error from Hono");
});

Visit /test-error and check your Traceway dashboard — the error should appear under Issues with a full stack trace.

Multi-Runtime Notes

RuntimeInstrumentation
Node.jsOTel Node SDK (this guide)
Cloudflare WorkersNative OTLP export — see Cloudflare guide
DenoUse Deno OTel (opens in a new tab) with OTLP/HTTP export to Traceway
BunUse @opentelemetry/sdk-node (Bun supports most Node.js OTel packages)

Next Steps

  • Traces — manual spans, middleware instrumentation, and exception recording
  • Node.js OTel Guide — general Node.js setup details
  • OTel Overview — protocol details and authentication