Skip to content

Repository files navigation

@superlog/otel-helpers

npm npm downloads CI License: MIT Node >=18

Tiny TypeScript helpers for native OpenTelemetry.

  • Add spans with minimal diff impact
  • Record LLM costs
npm i @superlog/otel-helpers @opentelemetry/api

withSpan

withSpan allows you to create a span around a function in a way that is easy to review. Here is the same handleCheckout instrumented two ways.

Original:

async function handleCheckout(orderId: string) {
  const order = await loadOrder(orderId);
  await chargeCard(order);
  return order;
}

Vanilla OpenTelemetry SDK:

--- original.ts
+++ manual.ts
@@ -1,5 +1,20 @@
+import { trace, SpanStatusCode } from "@opentelemetry/api";
+
+const tracer = trace.getTracer("checkout");
+
 async function handleCheckout(orderId: string) {
-  const order = await loadOrder(orderId);
-  await chargeCard(order);
-  return order;
+  return tracer.startActiveSpan("api.checkout", async (span) => {
+    try {
+      span.setAttribute("order.id", orderId);
+      const order = await loadOrder(orderId);
+      await chargeCard(order);
+      return order;
+    } catch (err) {
+      span.recordException(err as Error);
+      span.setStatus({ code: SpanStatusCode.ERROR });
+      throw err;
+    } finally {
+      span.end();
+    }
+  });
 }

Notice that:

  • The entire function is indented, creating a long diff hunk.
    • The reviewer needs to visually compare versions to analyze changes.
  • The try/catch block creates code changes around the key logic of the function.

Here's the diff produced by withSpan:

--- original.ts
+++ helper.ts
@@ -1,5 +1,8 @@
-async function handleCheckout(orderId: string) {
+import { withSpan } from "@superlog/otel-helpers";
+
+const handleCheckout = withSpan("api.checkout", async (span, orderId: string) => {
+  span.setAttribute("order.id", orderId);
   const order = await loadOrder(orderId);
   await chargeCard(order);
   return order;
-}
+});
  • The diff is now purely additive
  • It is easy to identify the scope of the changes and their potential impact.

GenAI spans

  • Creates spans and metrics for LLM calls
  • Respects OTel Semantic conventions
import { withGenAiSpan, recordGenAiUsage, anthropicUsage } from "@superlog/otel-helpers";

const response = await withGenAiSpan(
  {
    operation: "chat",
    provider: "anthropic",
    requestModel: MODEL,
    useCase: "stylist",
    callSite: "initial",
  },
  async (span) => {
    const res = await client.messages.create({
      model: MODEL,
      max_tokens: 2048,
      system: SYSTEM_PROMPT,
      tools,
      messages,
    });

    recordGenAiUsage(span, anthropicUsage(res));
    return res;
  },
);

The package emits current OpenTelemetry GenAI semantic convention attributes:

  • gen_ai.operation.name
  • gen_ai.provider.name
  • gen_ai.request.model
  • gen_ai.response.model
  • gen_ai.usage.input_tokens
  • gen_ai.usage.output_tokens
  • gen_ai.response.finish_reasons

The GenAI semantic conventions are currently marked Development by OpenTelemetry. This package follows the current names, and keeps product-specific dimensions under app.gen_ai.*.

Publishing a new version

Tag a release. GitHub Actions publishes to npm with provenance:

pnpm version patch   # or minor / major
git push origin main --tags

License

MIT

About

Tiny TypeScript helpers for native OpenTelemetry spans and GenAI semantic conventions.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages