[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build an Observability Stack with OpenTelemetry

Build an Observability Stack with OpenTelemetry

Build a full observability stack with OpenTelemetry — distributed tracing, metrics, and structured logging across microservices with automatic instrumentation and Grafana dashboards.

DevOps#observability#opentelemetry#grafana#tracing#monitoring
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 96/100·All SAFE
>

observability-setup

v1.1.0

Set up end-to-end observability for microservices. Use when someone asks to "add tracing", "set up monitoring", "configure OpenTelemetry", "build Grafana dashboards", "distributed tracing", "structured logging", "metrics collection", or "debug production issues". Covers OpenTelemetry instrumentation, collector configuration, Grafana LGTM stack deployment, dashboard provisioning, and alert rules.

93/100 quality
1.52× impact
SAFE· stale score
View skill
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
$

The Problem

Piotr leads SRE at a 45-person company with 15 microservices. When a request is slow, nobody knows which service is the bottleneck. Logs are scattered across services with no correlation. Metrics exist for each service individually, but there's no way to trace a single user request across services. Last week a p99 latency spike took 4 hours to debug because the team checked each service manually. They need distributed tracing that follows requests across services, correlated logs, and unified metrics — all with a single instrumentation standard.

Step 1: Build the OpenTelemetry Instrumentation

typescript
// src/telemetry/setup.ts — OpenTelemetry auto-instrumentation for Node.js
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 { Resource } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { W3CTraceContextPropagator } from "@opentelemetry/core";

const SERVICE_NAME = process.env.SERVICE_NAME || "unknown-service";
const OTEL_ENDPOINT = process.env.OTEL_ENDPOINT || "http://localhost:4318";

export function initTelemetry(): NodeSDK {
  const sdk = new NodeSDK({
    resource: new Resource({
      [ATTR_SERVICE_NAME]: SERVICE_NAME,
      [ATTR_SERVICE_VERSION]: process.env.SERVICE_VERSION || "0.0.0",
      "deployment.environment": process.env.NODE_ENV || "development",
    }),

    // Distributed tracing
    traceExporter: new OTLPTraceExporter({
      url: `${OTEL_ENDPOINT}/v1/traces`,
    }),

    // Metrics
    metricReader: new PeriodicExportingMetricReader({
      exporter: new OTLPMetricExporter({
        url: `${OTEL_ENDPOINT}/v1/metrics`,
      }),
      exportIntervalMillis: 15000, // export every 15s
    }),

    // Auto-instrument everything: HTTP, pg, Redis, Express/Hono
    instrumentations: [
      getNodeAutoInstrumentations({
        "@opentelemetry/instrumentation-http": {
          requestHook: (span, request) => {
            span.setAttribute("http.request_id", request.headers?.["x-request-id"] || "");
          },
        },
        "@opentelemetry/instrumentation-pg": {
          enhancedDatabaseReporting: true,
        },
        "@opentelemetry/instrumentation-redis": {
          dbStatementSerializer: (cmdName, cmdArgs) => `${cmdName} ${cmdArgs[0] || ""}`,
        },
      }),
    ],

    textMapPropagator: new W3CTraceContextPropagator(),
  });

  sdk.start();
  console.log(`[Telemetry] OpenTelemetry initialized for ${SERVICE_NAME}`);

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

  return sdk;
}
typescript
// src/telemetry/custom-spans.ts — Custom spans for business logic
import { trace, SpanStatusCode, metrics } from "@opentelemetry/api";

const tracer = trace.getTracer("app");
const meter = metrics.getMeter("app");

// Custom metrics
const orderCounter = meter.createCounter("orders.created", {
  description: "Number of orders created",
});

const orderDuration = meter.createHistogram("orders.processing_duration_ms", {
  description: "Order processing time in milliseconds",
  unit: "ms",
});

const activeUsers = meter.createUpDownCounter("users.active", {
  description: "Currently active users",
});

// Wrap business logic in custom spans
export async function processOrder(orderId: string, userId: string): Promise<any> {
  return tracer.startActiveSpan("process_order", async (span) => {
    span.setAttribute("order.id", orderId);
    span.setAttribute("user.id", userId);

    const startTime = Date.now();

    try {
      // Step 1: Validate inventory
      const inventory = await tracer.startActiveSpan("validate_inventory", async (inventorySpan) => {
        const result = await checkInventory(orderId);
        inventorySpan.setAttribute("inventory.available", result.available);
        inventorySpan.setAttribute("inventory.quantity", result.quantity);
        inventorySpan.end();
        return result;
      });

      if (!inventory.available) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: "Out of stock" });
        span.setAttribute("order.status", "rejected");
        span.end();
        throw new Error("Out of stock");
      }

      // Step 2: Charge payment
      const payment = await tracer.startActiveSpan("charge_payment", async (paymentSpan) => {
        paymentSpan.setAttribute("payment.amount", inventory.total);
        paymentSpan.setAttribute("payment.currency", "USD");
        const result = await chargePayment(userId, inventory.total);
        paymentSpan.setAttribute("payment.id", result.paymentId);
        paymentSpan.end();
        return result;
      });

      // Step 3: Create order record
      await tracer.startActiveSpan("create_order_record", async (dbSpan) => {
        await saveOrder(orderId, userId, payment.paymentId);
        dbSpan.end();
      });

      // Metrics
      orderCounter.add(1, { status: "success", payment_method: "card" });
      orderDuration.record(Date.now() - startTime, { status: "success" });

      span.setAttribute("order.status", "completed");
      span.setStatus({ code: SpanStatusCode.OK });
      span.end();

      return { orderId, status: "completed", paymentId: payment.paymentId };
    } catch (err: any) {
      span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
      span.recordException(err);
      orderCounter.add(1, { status: "failed" });
      orderDuration.record(Date.now() - startTime, { status: "failed" });
      span.end();
      throw err;
    }
  });
}

// Structured logging with trace correlation
export function log(level: string, message: string, attributes: Record<string, any> = {}): void {
  const activeSpan = trace.getActiveSpan();
  const traceId = activeSpan?.spanContext().traceId || "";
  const spanId = activeSpan?.spanContext().spanId || "";

  const logEntry = {
    timestamp: new Date().toISOString(),
    level,
    message,
    traceId,
    spanId,
    service: process.env.SERVICE_NAME,
    ...attributes,
  };

  console.log(JSON.stringify(logEntry));
}

async function checkInventory(orderId: string) { return { available: true, quantity: 1, total: 99.99 }; }
async function chargePayment(userId: string, amount: number) { return { paymentId: `pay_${Date.now()}` }; }
async function saveOrder(orderId: string, userId: string, paymentId: string) { /* db insert */ }

Step 2: Middleware for Automatic Request Tracing

typescript
// src/middleware/tracing.ts — Request tracing middleware
import { Context, Next } from "hono";
import { trace, SpanStatusCode } from "@opentelemetry/api";

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

export function tracingMiddleware() {
  return async (c: Context, next: Next) => {
    const span = trace.getActiveSpan();

    if (span) {
      // Add request attributes
      span.setAttribute("http.user_id", c.get("userId") || "anonymous");
      span.setAttribute("http.route", c.req.routePath || c.req.path);

      // Add trace ID to response headers (for debugging)
      c.header("X-Trace-ID", span.spanContext().traceId);
    }

    try {
      await next();

      if (span && c.res.status >= 400) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: `HTTP ${c.res.status}` });
      }
    } catch (err: any) {
      if (span) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
        span.recordException(err);
      }
      throw err;
    }
  };
}

Results

  • p99 debugging time: 4 hours → 10 minutes — distributed traces show exactly which service and which database query is the bottleneck; one click from slow request to root cause
  • Logs correlated across 15 services — every log entry includes traceId; searching for one request ID shows logs from all services it touched, in order
  • Custom business metrics — order processing rate, payment success rate, and duration histograms visible in Grafana; product team monitors without asking engineering
  • Auto-instrumentation covers 90% of spans — HTTP, PostgreSQL, Redis, and inter-service calls traced automatically; only business logic needs manual spans
  • Vendor-neutral — OpenTelemetry standard means switching from Jaeger to Tempo or from Prometheus to Datadog requires changing only the exporter config, not instrumentation code

Related use cases