hono icon indicating copy to clipboard operation
hono copied to clipboard

Feature Req: Otel middleware

Open kevbook opened this issue 2 years ago • 4 comments

Since Otel Telemetry is becoming the standard, it would be good to have a middleware that also plays well with Hono logger

kevbook avatar Jun 13 '23 14:06 kevbook

any updates on this @yusukebe ?

DawnImpulse avatar Jul 10 '24 12:07 DawnImpulse

resemble? https://github.com/honojs/hono/pull/3025

EdamAme-x avatar Jul 11 '24 02:07 EdamAme-x

Hi @DawnImpulse

There is no update. We (at least I) don't plan to create it right now, so I hope someone works on it.

yusukebe avatar Jul 11 '24 08:07 yusukebe

Here is a simple solution. It needs a test as well. Do you have any suggestion about coding?

@kevbook @DawnImpulse @yusukebe

import otelapi, { type Tracer } from "@opentelemetry/api";
import {
  context,
  propagation,
  SpanKind,
  SpanStatusCode,
  trace,
} from "@opentelemetry/api";
import type { MiddlewareHandler } from "hono";

import type { Env } from "../../env.ts";
import type { PortLogger } from "../logger/logger.ts";

let otel: typeof otelapi | undefined;
let rawTracer: Tracer | undefined;

export const opentelemetryMiddleware =
  (logger: PortLogger): MiddlewareHandler<Env> =>
  async (ctx, next) => {
    if (!otel) {
      try {
        await next();
        if (ctx.error) {
          logger.error({ error: ctx.error.message });
        }
      } catch (error) {
        logger.error({
          error: error instanceof Error ? error.message : "unknown error",
        });
        throw error;
      }
      return;
    }

    if (!rawTracer) {
      rawTracer = otel.trace.getTracer("hono-poc", "0.0.0");
    }

    const span = rawTracer.startSpan(
      "opentelemetry.infrastructure.middleware",
      {
        attributes: {
          "http.method": ctx.req.method,
          "http.url": ctx.req.url,
        },
        kind: SpanKind.SERVER,
      },
      propagation.extract(context.active(), ctx.req.raw.headers),
    );

    try {
      await context.with(trace.setSpan(context.active(), span), async () => {
        await next();
      });
      if (ctx.error) {
        logger.error({ error: ctx.error.message });
        span.recordException(ctx.error);
        span.setStatus({
          code: SpanStatusCode.ERROR,
          message: ctx.error.message,
        });
      } else {
        span.setStatus({ code: SpanStatusCode.OK });
      }
    } catch (error) {
      logger.error({
        error: error instanceof Error ? error.message : "unknown error",
      });
      span.recordException(error as Error);
      span.setStatus({
        code: SpanStatusCode.ERROR,
        message: error instanceof Error ? error.message : "unknown error",
      });
      throw error;
    }
    span.end();
  };

smhmayboudi avatar Sep 11 '24 04:09 smhmayboudi

Hi mates! How is this coming along? Actively worked on?

simonblund-metodika avatar Nov 29 '24 10:11 simonblund-metodika

Is there anything that needs to be done for this aside from

  1. Turning @smhmayboudi code snippet into PR-ready code
  2. Adding tests

For the latter, afaik the 2 main options for automated tests are to do the usually kind of mocking/spying on the @opentelemetry/api, but there's also a way to setup an in-memory exporter to capture the telemetry output and assert about it there (https://github.com/open-telemetry/opentelemetry-js/issues/4969 has more discussion around this and which to use)

For manual tests, there's a ConsoleSpanExporter you can use in the SDK that will write the telemetry data to stdout. There are also several local GUI options (e.g. the Aspire Dashboard docker image, OTEL Desktop Viewer binary, Seq docker image, Grafana all-in-one, Jaeger all-in-one) and 1 TUI option (OTEL-TUI) afaik.

New to Hono so apologies if I'm missing a lot.

Grunet avatar Dec 13 '24 05:12 Grunet