Skip to main content

Global Instrumentation

The recommended approach is to use global instrumentation with instrument(). This patches SDK prototypes at startup, automatically tracking all client instances.
import { instrument } from "aden";
import OpenAI from "openai";

// Call ONCE at application startup, BEFORE creating clients
await instrument({
  emitMetric: myEmitter,
  sdks: { OpenAI },
});

// All OpenAI clients are now automatically instrumented
const client1 = new OpenAI();
const client2 = new OpenAI({ apiKey: "different-key" });

instrument() Options

interface MeterOptions {
  // Required: where to send metrics
  emitMetric: MetricEmitter;

  // SDKs to instrument (pass the imported classes)
  sdks?: {
    OpenAI?: typeof OpenAI;
    Anthropic?: typeof Anthropic;
    GoogleGenerativeAI?: typeof GoogleGenerativeAI;
  };

  // Control server connection
  apiKey?: string;
  serverUrl?: string;
  failOpen?: boolean; // Allow requests if server unreachable (default: true)

  // Context tracking
  getContextId?: () => string | undefined; // For per-user budgets
  trackCallRelationships?: boolean; // Enable trace/span linking (default: true)
  trackToolCalls?: boolean; // Track tool/function calls (default: true)

  // Custom ID generators
  generateTraceId?: () => string;
  generateSpanId?: () => string;

  // Request hooks
  beforeRequest?: BeforeRequestHook;
  requestMetadata?: Record<string, unknown>;

  // Alert handling
  onAlert?: (alert: AlertInfo) => void | Promise<void>;

  // Pre-configured control agent
  controlAgent?: IControlAgent;
}

Return Value

const result = await instrument({ ... });

console.log(result);
// {
//   openai: true,      // SDK was found and instrumented
//   anthropic: false,  // SDK not provided
//   gemini: false      // SDK not provided
// }

Per-Instance Instrumentation

For more control, use makeMeteredOpenAI() to wrap individual client instances:
import { makeMeteredOpenAI, createConsoleEmitter } from "aden";
import OpenAI from "openai";

const openai = new OpenAI();

const meteredClient = makeMeteredOpenAI(openai, {
  emitMetric: createConsoleEmitter(),
});

// Use the metered client
await meteredClient.chat.completions.create({ ... });

// Check if a client is metered
import { isMetered } from "aden";
console.log(isMetered(meteredClient)); // true
console.log(isMetered(openai));        // false

Checking Instrumentation Status

import {
  isInstrumented,
  getInstrumentedSDKs,
  isOpenAIInstrumented,
  isAnthropicInstrumented,
  isGeminiInstrumented,
} from "aden";

// Check if any SDK is instrumented
if (isInstrumented()) {
  console.log("Active SDKs:", getInstrumentedSDKs());
  // ["openai", "anthropic"]
}

// Check individual providers
console.log({
  openai: isOpenAIInstrumented(),
  anthropic: isAnthropicInstrumented(),
  gemini: isGeminiInstrumented(),
});

Removing Instrumentation

import { uninstrument } from "aden";

// Remove all instrumentation
await uninstrument();

// Remove specific provider
import { uninstrumentOpenAI, uninstrumentAnthropic, uninstrumentGemini } from "aden";

uninstrumentOpenAI();
uninstrumentAnthropic();
uninstrumentGemini();

Provider-Specific Instrumentation

Instrument providers individually for fine-grained control:
import {
  instrumentOpenAI,
  instrumentAnthropic,
  instrumentGemini,
} from "aden";
import OpenAI from "openai";
import Anthropic from "@anthropic-ai/sdk";

// Instrument only OpenAI with specific options
instrumentOpenAI({
  emitMetric: openaiEmitter,
  sdks: { OpenAI },
});

// Instrument Anthropic with different options
instrumentAnthropic({
  emitMetric: anthropicEmitter,
  sdks: { Anthropic },
});

Fetch Instrumentation

For frameworks like Vercel AI SDK that use fetch directly:
import { instrumentFetch, uninstrumentFetch } from "aden";

instrumentFetch({
  emitMetric: myEmitter,
  urlPatterns: [
    /api\.openai\.com/,
    /api\.anthropic\.com/,
    /generativelanguage\.googleapis\.com/,
  ],
});

// Use Vercel AI SDK
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

await generateText({
  model: openai("gpt-4o"),
  prompt: "Hello!",
});

// Clean up
uninstrumentFetch();

Lifecycle Management

1

Startup

Call instrument() before creating any SDK clients:
// app.ts or index.ts
import { instrument } from "aden";

await instrument({ ... });

// Now import your app modules that create clients
import "./routes";
2

Runtime

Instrumentation persists for the application lifetime. New client instances are automatically tracked.
3

Shutdown

Clean up in your shutdown handler:
process.on("SIGTERM", async () => {
  await uninstrument();
  process.exit(0);
});

Framework Integration

Express.js

import express from "express";
import { instrument, enterMeterContext } from "aden";

await instrument({ ... });

const app = express();

// Add context middleware
app.use((req, res, next) => {
  enterMeterContext({
    sessionId: req.headers["x-request-id"] as string,
    metadata: {
      userId: req.userId,
      endpoint: req.path,
    },
  });
  next();
});

Next.js

// instrumentation.ts (Next.js 13+)
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { instrument } = await import("aden");
    const OpenAI = (await import("openai")).default;

    await instrument({
      emitMetric: createConsoleEmitter(),
      sdks: { OpenAI },
    });
  }
}

Best Practices

Do:
  • Call instrument() once at application startup
  • Pass SDK classes directly: sdks: { OpenAI }
  • Clean up with uninstrument() on shutdown
  • Use getContextId for per-user tracking
Don’t:
  • Call instrument() multiple times
  • Create SDK clients before calling instrument()
  • Import SDK clients in the same file as instrument() (use dynamic imports)

Next Steps