agentsclimarketplace

Otel node

Skill krkrkrr/skills/skills/env/otel-node

Node.js OpenTelemetry setup — SDK init, auto-instrumentation packages, and the esbuild ESM silent-failure gotcha (instrumentation-* packages produce no spans when bundled with esbuild --format=esm). Use when adding OTel to a Node.js/Hono/Express service or debugging missing spans after bundling. See devops/opentelemetry for signal design decisions.From its SKILL.md

Install
npx -y skills add krkrkrr/skills --skill otel-node

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

5.2 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it

OpenTelemetry — Node.js

SDK Initialization

// otel.ts — import before everything else
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { Resource } from "@opentelemetry/resources";
import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";

const exporter = new OTLPTraceExporter({
  url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + "/v1/traces",
});

export const sdk = new NodeSDK({
  resource: new Resource({
    [SEMRESATTRS_SERVICE_NAME]: process.env.SERVICE_NAME ?? "my-service",
  }),
  spanProcessor: new BatchSpanProcessor(exporter),
});

sdk.start();
process.on("SIGTERM", () => sdk.shutdown());

Load before the app entry: node --import ./otel.js server.js or tsx --import ./otel.ts server.ts.

Auto-instrumentation packages

Add only what you need — each package patches a specific module:

pnpm add @opentelemetry/instrumentation-http       # node:http / node:https
pnpm add @opentelemetry/instrumentation-express    # Express routing spans
pnpm add @opentelemetry/instrumentation-pg         # postgres queries
pnpm add @opentelemetry/instrumentation-ioredis    # Redis commands

Register in SDK init:

import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";

export const sdk = new NodeSDK({
  instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
  // ...
});

Hono: no official instrumentation package. Use manual middleware (see below).

esbuild ESM — Silent Auto-Instrumentation Failure

Symptom

App starts normally, SDK init log appears, but no spans arrive at the collector. No errors. Happens when:

  • Output format is --format=esm
  • Using @opentelemetry/instrumentation-* auto-instrumentation
  • Bundler is esbuild (also Vite / SWC — same root cause)

Root cause

@opentelemetry/instrumentation-* uses require-in-the-middle to hook require() and monkey-patch target modules (node:http etc.). In an esbuild ESM bundle, import is resolved statically at bundle time — require() is never called at runtime → hook never fires → no patches → no spans. No error is raised (silent failure).

Fix A — Manual spans (recommended for Hono/esbuild)

Write a middleware that creates spans explicitly:

import { context, propagation, SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";

const tracer = trace.getTracer("app");

app.use(async (c, next) => {
  const ctx = propagation.extract(context.active(), c.req.raw.headers);
  const route = new URL(c.req.url).pathname;

  await tracer.startActiveSpan(
    `${c.req.method} ${route}`,
    { kind: SpanKind.SERVER, attributes: { "http.method": c.req.method, "http.route": route } },
    ctx,
    async (span) => {
      try {
        await next();
        span.setAttribute("http.status_code", c.res.status);
        span.setStatus({ code: SpanStatusCode.OK });
      } catch (e: any) {
        span.recordException(e);
        span.setStatus({ code: SpanStatusCode.ERROR, message: e?.message });
        throw e;
      } finally {
        span.end();
      }
    }
  );
});

Propagate context to outgoing gRPC calls via metadata:

const callUnary = (method, req) => {
  const metadata = new grpc.Metadata();
  propagation.inject(context.active(), metadata, {
    set: (carrier, key, value) => (carrier as grpc.Metadata).set(key, value),
  });
  return new Promise((resolve, reject) =>
    client[method](req, metadata, (err, res) => (err ? reject(err) : resolve(res)))
  );
};

Fix B — Switch to CJS output

Change esbuild to --format=cjs. require() hook fires normally. Works when no ESM-only dependencies are present. Hono supports CJS.

Fix C — Run unbundled

Use tsx or node --loader ts-node/esm + --import @opentelemetry/auto-instrumentations-node/register. Skip bundling for the Node process. Increases container image size; not recommended for production.

Verifying the Pipeline

Add a debug exporter to the OTel Collector temporarily:

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlphttp/tempo, debug]

Collector logs show Trace ID: ... Name: GET /api/users when spans arrive. No output = broken before the collector.

Related

  • devops/opentelemetry — signal design, span naming, sampling, W3C propagation
  • cloudflare/workers-otel-utels — Cloudflare Workers telemetry (no Node runtime, fetch-boundary approach)
  • esbuild + Vite + SWC all share the same require-in-the-middle failure mode
  • instrumentation-* packages do not support static import patching as of 2026-05

Keep looking

Skills are one crate of 326,871. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.