Skip to content
root@hisham:~/blog/posts$ glow observability-hono-bun.md
observability-hono-bun.md
· 9 min read · bun

# Building an observability platform on Hono and Bun

Ingesting telemetry from 15+ services, enriching requests on the way in, and deciding which alerts are worth waking someone up for.

We had fifteen services and fifteen ways of finding out something was wrong, most of them starting with someone opening a dashboard because a customer complained. The goal was not a full tracing stack. It was one place that could answer which service, which endpoint, and since when.

## One middleware per service

Every service ships the same twelve-line middleware. It times the request, reads the status off the response, and posts a batch to the ingest endpoint on a timer. Nothing blocks the response, and a dead ingest endpoint costs the caller nothing but a dropped batch.

# the whole client integration, copied into every service
app.use('*', async (c, next) => {
  const started = performance.now()
  await next()
  queue.push({
    svc: Bun.env.SERVICE_NAME, path: c.req.path,
    status: c.res.status, ms: performance.now() - started,
  })
})

## What Bun changed

  • Ingest is cheap enough to be boring. A single Bun process handles the batch volume from every service without a queue in front of it, so there is one less moving part to page about.
  • The built-in SQL client removed a dependency. Batches go straight into Postgres with prepared statements, in one transaction per batch.
  • Hono runs the same code on the edge and on a box. The ingest endpoint and the dashboard share route definitions and validation, which is most of the reason the project stayed small.
An alert that fires when nothing is wrong trains everyone to ignore the one that matters.

## Alerting, after two rewrites

The first version alerted on error rate per service and was useless, because a service with two requests an hour hits 50% error rate on one bad request. The second version alerted on absolute error counts and missed a service that quietly started failing every third call. What works now is a threshold on both, evaluated over a rolling fifteen minutes, with a floor on request volume before anything fires. Slack gets the summary, and the link goes to the filtered request list rather than a dashboard home page.

services reporting
15+
client integration
12 lines
ingest processes
1
alert rewrites
2

## What I left out on purpose

No distributed tracing, no sampling strategy, no retention tiers. Fifteen services with request-level data and honest alerts answered every question we actually had. When one of those questions needs a trace id, that is the day to add tracing.

Questions or corrections: hishammedhat0@gmail.com github
root@hisham:~/blog/posts$