For the complete documentation index, see llms.txt. Markdown versions are available by appending .md to documentation URLs.

Hono OpenTelemetry Instrumentation - Send Traces and Metrics

SigNoz Cloud - This page applies to SigNoz Cloud editions.
Self-Host - This page applies to self-hosted SigNoz editions.

Overview

This guide shows you how to instrument a Hono application with OpenTelemetry and send traces and metrics to SigNoz. The @hono/otel middleware creates one span for each request, named by route, and records HTTP request metrics. The OpenTelemetry Node.js SDK exports the data and traces the calls that your routes make, such as fetch requests and database queries. The main path runs Hono on Node.js. For Bun, see Instrument Hono on Bun.

Prerequisites

  • Node.js 22.15 or newer. The current OpenTelemetry SDK packages need this version.
  • An instance of SigNoz (either Cloud or Self-Hosted)
  • A Hono app on Node.js. To create one, run npm create hono@latest my-app -- --template nodejs.

Tested with Node.js v22.23.2, Hono 4.13.13, @hono/otel 1.2.0, and @opentelemetry/sdk-node 0.300.0.

Send telemetry from a Hono app

Step 1. Install the packages

Run this command in the root of your project:

npm install @hono/otel \
  @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/instrumentation

Step 2. Create the instrumentation file

Create src/instrumentation.ts. This file starts the OpenTelemetry SDK before your app loads:

src/instrumentation.ts
import { register } from 'node:module'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
 
// Lets OpenTelemetry patch modules that the ESM app imports
register('@opentelemetry/instrumentation/hook.mjs', import.meta.url)
 
const sdk = new NodeSDK({
  instrumentations: [
    getNodeAutoInstrumentations({
      // @hono/otel creates the server span, so skip the duplicate HTTP server span
      '@opentelemetry/instrumentation-http': {
        disableIncomingRequestInstrumentation: true,
      },
    }),
  ],
})
 
sdk.start()
 
process.on('SIGTERM', () => {
  sdk.shutdown().finally(() => process.exit(0))
})

The file does three things:

  • The register() call adds the OpenTelemetry loader hook. The Hono Node.js template is an ECMAScript module (ESM) app, and without the hook the SDK cannot trace outgoing node:http calls.
  • disableIncomingRequestInstrumentation stops the HTTP instrumentation from creating a second server span for each request. Outgoing HTTP calls are still traced.
  • The SIGTERM handler sends the remaining data to SigNoz before the app stops.

The SDK reads the endpoint, the ingestion key, and the service name from environment variables, which you set in Step 4.

Step 3. Add the middleware to your app

Add httpInstrumentationMiddleware() before your routes in src/index.ts:

src/index.ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { httpInstrumentationMiddleware } from '@hono/otel'
 
const app = new Hono()
 
app.use(httpInstrumentationMiddleware())
 
app.get('/', (c) => {
  return c.text('Hello Hono!')
})
 
serve({
  fetch: app.fetch,
  port: 3000
}, (info) => {
  console.log(`Server is running on http://localhost:${info.port}`)
})

Then update the dev and start scripts in package.json, so that Node.js loads the instrumentation file first:

package.json
"scripts": {
  "dev": "tsx watch --import ./src/instrumentation.ts src/index.ts",
  "build": "tsc",
  "start": "node --import ./dist/instrumentation.js dist/index.js"
}

Step 4. Set environment variables and run

Set the variables in the shell that starts your app, then build and run it:

export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=<environment>"
 
npm run build
npm start

Verify these values:

  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.
  • <service-name>: The name that SigNoz shows for your service, for example hono-app.
  • <environment>: The deployment environment, for example production.

Send a few requests to your app, for example curl http://localhost:3000/. For local development, npm run dev with the same variables also sends data.

Instrument Hono on Bun

On Bun, the same middleware creates the request spans and the HTTP metrics. Bun does not need the loader hook. Bun's built-in fetch is not traced, so calls that your routes make with fetch do not get their own spans.

Install the packages:

bun add @hono/otel @opentelemetry/api @opentelemetry/sdk-node

Create src/instrumentation.ts:

src/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node'
 
const sdk = new NodeSDK()
 
sdk.start()
 
process.on('SIGTERM', () => {
  sdk.shutdown().finally(() => process.exit(0))
})

Add the middleware to src/index.ts:

src/index.ts
import { Hono } from 'hono'
import { httpInstrumentationMiddleware } from '@hono/otel'
 
const app = new Hono()
 
app.use(httpInstrumentationMiddleware())
 
app.get('/', (c) => {
  return c.text('Hello Hono!')
})
 
export default app

Set the same environment variables as in Step 4, then start the app with the instrumentation file preloaded:

bun --preload ./src/instrumentation.ts src/index.ts

Tested with Bun v1.4.3.

Validate

After you run your instrumented application, make sure that traces and metrics reach SigNoz:

  1. Send a few requests to your app.
  2. Open the Services tab and find the service name that you set in OTEL_SERVICE_NAME. Each operation has the route name, for example GET /users/:id.
  3. Wait about a minute for the first metric export, then search for http.server.request.duration in the Metrics Explorer.
SigNoz Traces Explorer list view filtered to a Hono service, with request spans named GET / and status code 200
Hono request spans in the Traces Explorer
SigNoz Metrics Explorer summary for a Hono service listing http.server.request.duration.bucket and Node.js runtime metrics
Hono HTTP and runtime metrics in the Metrics Explorer

Limitations

  • @hono/otel creates one span for the whole request. Individual Hono middleware do not get their own spans.
  • @hono/otel sends only two HTTP metrics: http.server.request.duration and http.server.active_requests.
  • On Cloudflare Workers, the OpenTelemetry Node.js SDK does not run. To trace a Hono app on Workers, see Cloudflare Workers OpenTelemetry.

Troubleshooting

Still no data? See Debug missing traces, logs, and metrics.

Each request has two server spans

  • Likely cause: the HTTP instrumentation creates a server span named GET, without a route, and the Hono span is its child.
  • Fix: set disableIncomingRequestInstrumentation: true for @opentelemetry/instrumentation-http, as shown in Step 2. Do not use ignoreIncomingRequestHook for this. It stops tracing for the whole request, so the Hono span is not sent.
  • Verify: each request shows one span named by route, for example GET /.

Outgoing HTTP calls have no spans

  • Likely cause: the register('@opentelemetry/instrumentation/hook.mjs', import.meta.url) call is missing. In an ESM app, the SDK cannot patch node:http without the loader hook.
  • Fix: add the register() call to src/instrumentation.ts, as shown in Step 2, and restart the app.
  • Verify: a request to a route that calls another service shows a GET client span under the Hono span.

No data at all

  • Likely cause: the app does not load the instrumentation file, or the ingestion key is wrong. The app prints no error by default.
  • Fix: make sure that the start command includes --import ./dist/instrumentation.js. Then set OTEL_LOG_LEVEL=info and restart the app. An invalid key prints Export failed with non-retryable error: OTLPExporterError: Unauthorized. Correct OTEL_EXPORTER_OTLP_HEADERS.
  • Verify: the error no longer appears, and the service shows on the Services tab.

Setup OpenTelemetry Collector (Optional)

To send telemetry through an OpenTelemetry Collector instead of directly to SigNoz Cloud, set OTEL_EXPORTER_OTLP_ENDPOINT to the OTLP/HTTP endpoint of your Collector, for example http://localhost:4318, and remove the OTEL_EXPORTER_OTLP_HEADERS variable.

See Switch from direct export to Collector for the steps.

Next steps

Get Help

If you need help with the steps in this topic, please reach out to us on SigNoz Community Slack. If you are a SigNoz Cloud user, please use in product chat support located at the bottom right corner of your SigNoz instance or contact us at cloud-support@signoz.io.

Is this page helpful

Last updated—October 22, 2026

Edit on GitHub