npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@brizz/sdk

v0.1.44

Published

OpenTelemetry-based observability SDK for AI applications

Readme

Brizz SDK

npm version License: Apache-2.0 TypeScript

OpenTelemetry-based observability SDK for AI applications. Traces popular AI libraries including OpenAI, Anthropic, Vercel AI SDK, and more.

Table of Contents

Features

  • 🔍 One-Call Setup - Pass your AI libraries to Brizz.initialize() and they're traced
  • 📊 OpenTelemetry Native - Standards-compliant tracing, metrics, and logs
  • 🛡️ PII Masking - Optional masking for sensitive data
  • 🔄 Session Tracking - Group related operations and traces
  • 📦 Runs Anywhere - ESM, CommonJS, bundlers, and tsx

Installation

npm install @brizz/sdk
# or
yarn add @brizz/sdk
# or
pnpm add @brizz/sdk

Quick Start

First, set up your environment variables:

BRIZZ_API_KEY=your-api-key
BRIZZ_BASE_URL=https://telemetry.brizz.dev  # Optional
BRIZZ_APP_NAME=my-app                       # Optional
BRIZZ_LOG_LEVEL=info                        # Optional: debug, info, warn, error, none

Then initialize the SDK in your application, passing the libraries you want to trace:

import { Brizz } from '@brizz/sdk';
import OpenAI from 'openai';

Brizz.initialize({
  apiKey: process.env.BRIZZ_API_KEY,
  appName: 'my-ai-app',
  instrumentModules: { openAI: OpenAI },
});

Run your app the way you already do — node app.js, tsx app.ts, or your own start script.

Important: Call Brizz.initialize() before making any AI calls. If using dotenv, use import "dotenv/config" before importing @brizz/sdk.

Module System Support

The same setup works in ESM, CommonJS, bundlers, and tsx — import @brizz/sdk and call Brizz.initialize().

Bundlers that inline modules (Next.js, Webpack) may need a dynamic import so you can hand the resolved module to instrumentModules:

// For problematic bundlers
const { default: OpenAI } = await import('openai');

Brizz.initialize({
  apiKey: 'your-api-key',
  instrumentModules: {
    openAI: OpenAI,
  },
});

Supported Libraries

Pass the libraries you use to instrumentModules and the SDK traces them:

  • OpenAI - openai package
  • Anthropic - @anthropic-ai/sdk package
  • Vercel AI SDK - ai package (generateText, streamText, etc.)
    • Captured through experimental_telemetry: { isEnabled: true } on each call, not instrumentModules
  • LangChain - langchain and @langchain/* packages
  • LlamaIndex - llamaindex package
  • AWS Bedrock - @aws-sdk/client-bedrock-runtime
  • Vector Databases - Pinecone, Qdrant, ChromaDB

Optional Instrumentations

A few instrumentations ship as optional peer dependencies so they're only pulled in when you need them. Install the matching package(s) to enable them:

| To trace | Install | | --- | --- | | Vercel AI SDK v7+ (ai@7) — required for span capture | npm install @ai-sdk/otel | | Google GenAI / Gemini (@google/genai) — see Gemini | npm install @traceloop/instrumentation-google-generativeai | | Google Vertex AI (@google-cloud/vertexai) | npm install @traceloop/instrumentation-vertexai | | MCP (Model Context Protocol) | npm install @arizeai/openinference-instrumentation-mcp @modelcontextprotocol/sdk |

If a package isn't installed, the SDK keeps working and simply skips that instrumentation. On the automatic path the skip is a quiet debug log; when you opt in explicitly via instrumentModules, a missing package is logged at error so the misconfiguration is visible.

Vercel AI SDK Integration

On ai@7 and newer, also install @ai-sdk/otel — v7 moved AI SDK span collection into that package, so without it your generateText / streamText calls produce no spans. It isn't needed on ai@6.

For Vercel AI SDK instrumentation, you need to enable telemetry in your function calls:

import { generateText, streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

// For generateText
const result = await generateText({
  model: openai('gpt-4'),
  prompt: 'Hello, world!',
  experimental_telemetry: { isEnabled: true }, // Required for instrumentation
});

// For streamText
const stream = streamText({
  model: openai('gpt-4'),
  messages: [{ role: 'user', content: 'Hello!' }],
  experimental_telemetry: { isEnabled: true }, // Required for instrumentation
});

This enables automatic tracing of:

  • Model calls and responses
  • Token usage and costs
  • Tool calls and executions
  • Streaming data

Vercel eve

Vercel eve agents emit standard OpenTelemetry GenAI spans, so Brizz ingests their traces, message content, and cost as soon as you point eve's exporter at your Brizz endpoint. @brizz/sdk/eve gives you a one-line registerOTel config that reads BRIZZ_DSN — it wires the exporter and the session grouping that makes each agent run render as a single conversation:

// agent/instrumentation.ts
import { defineInstrumentation } from 'eve/instrumentation';
import { registerOTel } from '@vercel/otel';
import { createEveOtelConfig } from '@brizz/sdk/eve';

export default defineInstrumentation({
  setup: ({ agentName }) => registerOTel(createEveOtelConfig({ serviceName: agentName })),
});

Then set your DSN in the environment — a single pasted credential:

BRIZZ_DSN=https://<bearer>@<gateway-host>/<service-name>

createEveOtelConfig derives the bearer and endpoint from the DSN and ignores its service segment — eve's agentName stays the service name. Prefer to compose it yourself? The pieces are exported too:

import { createEveTraceExporter, createEveSessionProcessor } from '@brizz/sdk/eve';

registerOTel({
  serviceName: agentName,
  traceExporter: createEveTraceExporter(),
  // 'auto' keeps the default exporter processor.
  spanProcessors: [createEveSessionProcessor(), 'auto'],
});

@brizz/sdk/eve is a standalone, side-effect-free export: it does not initialize the full SDK, so it won't duplicate the spans eve already emits.

Google GenAI (Gemini)

@google/genai is Google's unified SDK for Gemini (both the Gemini Developer API and Vertex AI). Install the peer dependency, then build your client from the class instrumentGoogleGenAI returns:

import { Brizz } from '@brizz/sdk';
import { instrumentGoogleGenAI } from '@brizz/sdk/google-genai';
import * as genai from '@google/genai';

Brizz.initialize({ appName: 'my-app' });

// Call after Brizz.initialize(). The returned class is instrumented — build the client from it.
const GoogleGenAI = instrumentGoogleGenAI(genai);
const ai = new GoogleGenAI({ vertexai: true, project, location });

const response = await ai.models.generateContent({
  model: 'gemini-2.5-flash',
  contents: 'Hello, world!',
});

This traces generateContent / generateContentStream — messages, tool calls, token usage, and cost — with no per-call instrumentation.

Unlike the other integrations, @google/genai is not wired through instrumentModules. It's an ESM-only package, and a Node ES module namespace is read-only, so it can't be patched in place — instrumentGoogleGenAI returns an instrumented GoogleGenAI class instead. Construct your client from that class and you're done.

PII Masking

Optional masking for span attributes.

// Enable default masking
Brizz.initialize({
  apiKey: 'your-api-key',
  masking: true,
});

// Custom masking configuration
Brizz.initialize({
  apiKey: 'your-api-key',
  masking: {
    spanMasking: {
      rules: [
        {
          attributePattern: 'gen_ai\\.(prompt|completion)',
          mode: 'partial', // 'partial' or 'full'
          patterns: ['sk-[a-zA-Z0-9]{48}'],
        },
      ],
    },
  },
});

When enabled, defaults cover a curated set of common secret patterns. Add custom rules for anything else you need masked.

Session Tracking

Group related operations under a session context:

Session Context Manager (Recommended)

The startSession function creates a session span and provides a Session object:

import { startSession } from '@brizz/sdk';

// Basic usage - all LLM calls within the callback are automatically linked
const result = await startSession(
  'session-123',
  async (session) => {
    // Add custom properties (optional)
    session.updateProperties({ userId: 'user-456', model: 'gpt-4' });

    const response = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{ role: 'user', content: userQuery }],
    });

    return response;
  },
  { feature: 'chat' }, // optional: extraProperties propagated to all spans
);

Session Methods:

  • session.updateProperties({ key: value }) - Add custom properties to the session span
  • session.setInput(text, context?) - (Optional) Manually track input text; optional context bag attaches per-turn metadata rendered in the dashboard's Context panel
  • session.setOutput(text, context?) - (Optional) Manually track output text; optional context bag attaches per-turn metadata rendered in the dashboard's Context panel
  • session.setTitle(text) - Set a session title (typically used with mode: 'title')
  • session.addExternalLink(url, options?) - (Optional) Attach an external link (e.g. a Datadog trace or dashboard) to the session; it appears on the session detail panel. Also available as the top-level addExternalLink(url, options?).

Per-turn context example:

await startSession('session-123', async (session) => {
  session.setInput('Why is my bill high?', { selected_invoice: 'INV-9182' });
  const response = await openai.chat.completions.create({ ... });
  session.setOutput(response.choices[0].message.content, {
    message_id: 'msg-42',
    sources: ['doc-abc'],
  });
});

When to use manual input/output tracking:

In most cases, Brizz automatically captures inputs and outputs from your LLM calls. Use setInput/setOutput for special scenarios:

  • Multi-agent flows: Track only user-facing input/output, not intermediate agent communications
  • Structured data extraction: Track a specific field from complex JSON requests
  • Post-processing: Track transformed responses before returning to the user
await startSession('session-456', async (session) => {
  // Extract just the query from a structured request
  const requestData = { query: "What's the weather?", context: {...} };
  session.setInput(requestData.query);

  const response = await openai.chat.completions.create({...});

  // Extract answer from structured response
  const responseJson = JSON.parse(response.choices[0].message.content);
  session.setOutput(responseJson.answer);

  return responseJson;
});

External link example:

import { addExternalLink, startSession } from '@brizz/sdk';

startSession('session-123', (session) => {
  // Top-level function — resolves the active session from context.
  addExternalLink('https://app.datadoghq.com/trace/abc', { title: 'Datadog trace' });
});

// Outside a session — pass the id explicitly.
addExternalLink('https://sentry.io/issues/456', { sessionId: 'session-123', linkType: 'sentry' });

Session Title Generation

If you use an LLM call to generate session titles, wrap it so those spans don't appear as part of the conversation:

import { startSession, startSessionTitle } from '@brizz/sdk';

await startSession('session-123', async (session) => {
  const response = await openai.chat.completions.create({...});

  // Title generation — excluded from conversation view
  await startSessionTitle(async (title) => {
    const t = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{ role: 'user', content: 'Summarize this chat in 3 words' }],
    });
    title.setTitle(t.choices[0].message.content);
  });
});

// Or use mode: 'title' on startSession directly
await startSession('session-123', async (session) => {
  const t = await openai.chat.completions.create({...});
  session.setTitle(t.choices[0].message.content);
}, undefined, { mode: 'title' });

// Or use startSessionTitle outside a session (pass sessionId explicitly)
await startSessionTitle(async (title) => {
  title.setTitle('My Title');
}, { sessionId: 'session-123' });

Accessing the Active Session

Use getActiveSession() to retrieve the current session from anywhere within a startSession scope — no need to pass the session object through your call stack:

import { startSession, getActiveSession } from '@brizz/sdk';

function deepHelper() {
  const session = getActiveSession();
  session?.updateProperties({ step: 'helper' });
}

await startSession('session-123', async () => {
  deepHelper(); // accesses session without it being passed as a parameter
});

// Outside a session, returns undefined
getActiveSession(); // undefined

Function Wrapper Pattern

For simpler cases where you just need to tag traces with a session ID:

import { withSessionId, emitEvent } from '@brizz/sdk';

async function processUserWorkflow(userId: string) {
  // All traces within this function will include the session ID
  const result = await generateText({
    model: openai('gpt-4'),
    messages: [{ role: 'user', content: 'Hello' }],
    experimental_telemetry: { isEnabled: true },
  });

  return result;
}

// Create a wrapped function that always executes with session context
// withSessionId(sessionId, fn, thisArg?, extraProperties?)
const sessionedWorkflow = withSessionId('session-123', processUserWorkflow, undefined, {
  feature: 'workflow',
});

// Call multiple times, each with the same session context
await sessionedWorkflow('user-456');
await sessionedWorkflow('user-789');

Immediate Execution Pattern

import { callWithSessionId } from '@brizz/sdk';

// Execute function immediately with session context
await callWithSessionId('session-123', processUserWorkflow, null, 'user-456');

Identifying Users, Organizations & Messages

Attach the end-user, their organization, and a per-message id with typed setters. Call them inside a session — they apply to the turn's spans:

import { startSession, setUser, setOrganization, setMessageId } from '@brizz/sdk';

await startSession(sessionId, async () => {
  setUser({ id: user.id, email: user.email, role: user.role, plan: user.plan });
  setOrganization({ id: org.id, name: org.name, plan: org.plan, domain: org.domain });
  setMessageId(message.id); // your own id, to reference this message later

  return agent.run(prompt);
});

Only id is required. Each field maps to its own attribute (brizz.user.id, brizz.organization.plan, brizz.message.id, …). For anything beyond the named fields, pass a traits record — each entry becomes brizz.user.<key> / brizz.organization.<key>:

setUser({ id: user.id, traits: { department: 'sales', signup_source: 'referral' } });
setOrganization({ id: org.id, traits: { industry: 'fintech' } });

The same setters are available on the session object (session.setUser(...)) and as scoped wrappers (callWithUser / withUser).

Recording Feedback

Capture an end-user's reaction to a specific reply — a 👍/👎, a rating, a reason. Pair it with the message id you set on the turn:

import { startSession, setMessageId, recordFeedback } from '@brizz/sdk';

await startSession(sessionId, async () => {
  setMessageId(message.id); // the id you'll reference this reply by
  const reply = await agent.run(prompt);
  recordFeedback('thumbs_up'); // defaults to the current message
});

Only type is required; score, reason, comment, and source are optional, and attributes adds free-form brizz.feedback.<key> entries. Feedback is anchored by messageId and/or sessionId, so you can send it later — even minutes or days after the reply — by passing the id(s) explicitly:

recordFeedback('thumbs_down', { messageId: message.id, sessionId, reason: 'inaccurate' });

Recording Metrics

Report a number your own system already computes about an interaction — an eval score, a customer rating, a latency, a cost. It becomes a real Brizz metric you can filter and chart by, rather than an untyped bag of event attributes.

import { startSession, recordMetric } from '@brizz/sdk';

await startSession(sessionId, async () => {
  const reply = await agent.run(prompt);

  const score = await myEvaluator.score(prompt, reply);
  recordMetric({
    name: 'quality_score',
    value: score,
    unit: 'score',
    minValue: 0,
    maxValue: 1,
    polarity: 'positive',
  });
});

polarity tells Brizz which direction is good — a rising quality_score is an improvement, a rising hallucination_rate is a regression. minValue / maxValue describe the scale, so a 4 out of 5 isn't read as a 4 out of 1.

Scoring often happens after the fact. Pass sessionId to attach a metric from anywhere, and timestamp to say when the measured thing happened, so a nightly job lands the score on the turn it describes rather than on the evaluation run:

recordMetric({
  name: 'quality_score',
  value: await judge.score(session),
  sessionId: session.id,
  timestamp: session.endedAt,
  comment: 'graded by the nightly LLM judge',
  attributes: { evaluator: 'gpt-4o', rubric: 'v2' },
});

attributes are flat labels you can slice the metric by. Re-reporting the same metric supersedes the previous value, so a re-score wins over the original.

Custom Properties

Add custom properties to telemetry context:

import { withProperties, callWithProperties } from '@brizz/sdk';

// Wrapper pattern - properties applied to all calls
const taggedFn = withProperties({ env: 'prod', feature: 'chat' }, myFunction);
await taggedFn(args);

// Immediate execution - properties applied once
await callWithProperties({ userId: 'user-123' }, myFunction, null, args);

Handling Method Context

When wrapping methods that use this, you have several options:

Option 1: Arrow Function (Recommended)

class ChatService {
  async processMessage(userId: string, message: string) {
    // This method uses 'this' context
    return `Processed by ${this.serviceName}: ${message}`;
  }
}

const service = new ChatService();
// Wrap with arrow function to preserve 'this' context
const sessionedProcess = withSessionId('session-123', (userId: string, message: string) =>
  service.processMessage(userId, message),
);

Option 2: Using bind()

// Pre-bind the method to preserve 'this' context
const sessionedProcess = withSessionId('session-123', service.processMessage.bind(service));

Option 3: Explicit thisArg Parameter

// Pass 'this' context explicitly as third parameter
const sessionedProcess = withSessionId(
  'session-123',
  service.processMessage,
  service, // explicit 'this' context
);

Note: The arrow function approach (Option 1) is recommended as it's more explicit, avoids lint warnings, and is less prone to this binding issues.

Subagents

Mark work as a subagent so it shows as its own lane in the conversation instead of mixing into the session's main transcript. Nesting composes, and two runs of the same agent stay two lanes.

Three forms, same first argument — a name, { name, id, parentId }, or { token }:

import { setAgent, startAgent, withAgent } from '@brizz/sdk';

// startAgent — scoped: runs the callback now, returns its return value
await startAgent('research', () => researchAgent.generate({ prompt: task }));

// withAgent — wraps a function; every call is its own run, so its own lane
const research = withAgent('research', runResearch);
await research(topicA);
await research(topicB);

// setAgent — imperative, no scope: marks everything created after it, and
// returns the handle. The natural form at the top of a job or route handler.
export async function handleJob(job) {
  setAgent({ token: job.brizzAgent });
  await boardCreator.generate({ prompt: job.goal });
}

const { token } = setAgent('board-creator'); // grab the token to send onward

setAgent mirrors setUser / setSessionId — there is no auto-reset, so use startAgent when you want the scope to close.

Handing a subagent to code running elsewhere — a queue message, an RPC argument, a database row — is the token: send it with the work and re-enter the agent from it on the other side. It carries the session and trace context, so the detached run joins the same conversation.

await startAgent('board-creator', ({ token }) => sendToWorker({ goal, brizzAgent: token }));

Already tracking your own agent ids? Pass them instead of a bare name:

await startAgent({ name: 'research', id: runId, parentId: callerRunId }, () => run());

id is your id for this run (unique per run, or two runs share a lane) and parentId overrides the enclosing agent. Anything omitted is filled in — a fresh id per run, the enclosing agent as the parent. Don't give withAgent an id: it would put every call to the wrapped function in one lane.

Mute Messages

Keep internal or unrelated LLM calls — summarization, title generation, classification, guardrail checks — out of the captured conversation. The call still runs and its telemetry (latency, tokens, cost) is recorded; only the content is left out — the prompt, the reply, and the tool calls.

import { callWithMute, withMute } from '@brizz/sdk';

// Hide both sides of an internal call
await callWithMute({}, () => agent.run('Summarize this conversation for internal logging.'));

// Keep the assistant reply, drop the prompt
await callWithMute({ output: false }, () => agent.run('…a long internal prompt…'));

// Keep the tool calls, drop the prompt and the reply
await callWithMute({ tools: false }, () => agent.run('…a long internal prompt…'));

// Pre-wrap a function to reuse the same muting
const muteTitle = withMute({}, agent.run, agent);

tools covers tool calls and their results. Leave it out and it follows output.

Deployment Environment

Optionally specify the deployment environment for better filtering and organization:

Brizz.initialize({
  apiKey: 'your-api-key',
  appName: 'my-app',
  environment: 'production', // Optional: 'dev', 'staging', 'production', etc.
});

Multiple Services in One Process

appName names the whole process. When one process runs several agents that should show up as separate services in Brizz, scope the service name instead:

import { callWithServiceName, withServiceName, setServiceName } from '@brizz/sdk';

// Everything traced inside the callback reports as 'checkout-agent',
// including auto-instrumented LLM calls
await callWithServiceName('checkout-agent', async () => {
  return openai.chat.completions.create({ ... });
});

// Pre-wrap a handler
const handleSupport = withServiceName('support-agent', supportHandler);

// Imperative form, for when the enclosing span already exists
// (an MCP or HTTP instrumentation opens its span before your handler runs)
setServiceName('billing-agent');

Spans and events created outside any of these keep the configured appName.

Custom Events & Logging

Emit custom events and structured logs:

import { emitEvent, emitEventWithSessionId, logger } from '@brizz/sdk';

// Emit custom events
emitEvent('user.signup', {
  userId: '123',
  plan: 'pro',
  source: 'website',
});

emitEvent('ai.request.completed', {
  model: 'gpt-4',
  tokens: 150,
  latency: 1200,
});

// Emit with a known session ID, outside any session scope
emitEventWithSessionId('session-123', 'user.feedback.submitted', {
  rating: 5,
});

// Structured logging
logger.info('Processing user request', { userId: '123', requestId: 'req-456' });
logger.error('AI request failed', { error: err.message, model: 'gpt-4' });

Environment Variables

# Required
BRIZZ_API_KEY=your-api-key

# Optional Configuration
BRIZZ_BASE_URL=https://telemetry.brizz.dev    # Default telemetry endpoint
BRIZZ_APP_NAME=my-app                         # Application name
BRIZZ_APP_VERSION=1.0.0                       # Application version
BRIZZ_ENVIRONMENT=production                  # Environment (dev, staging, prod)
BRIZZ_LOG_LEVEL=info                          # SDK log level
BRIZZ_DISABLE_SPAN_EXPORTER=true              # Skip span export (see "Disable Span Export")

# OpenTelemetry Standard Variables
OTEL_SERVICE_NAME=my-service                               # Service name
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true    # Capture AI content

Disable Span Export

Keep Brizz.initialize() in your code without sending any spans — useful for dev/test environments. When enabled, the SDK skips span exporter and processor setup, so no spans are exported. Metrics and logs continue to work.

Brizz.initialize({ apiKey: 'your-api-key', disableSpanExporter: true });

Or via env var: BRIZZ_DISABLE_SPAN_EXPORTER=true.

Dropping Spans

Filter spans before export with beforeSendSpan. Return false to drop a span, true to keep it. Useful for stripping noisy paths (health checks, internal tooling) or excluding telemetry for specific end-users.

Brizz.initialize({
  apiKey: 'your-api-key',
  beforeSendSpan: (span) => {
    // OpenInference (LangChain JS, LangGraph JS) packs per-call
    // `config.metadata` into a JSON-stringified `metadata` attribute.
    const raw = span.attributes['metadata'];
    if (typeof raw !== 'string') return true;
    try {
      return JSON.parse(raw).customer_id !== 'cust_42';
    } catch {
      return true;
    }
  },
});

Tag the call so the filter has something to match on:

await llm.invoke([new HumanMessage('Hello')], {
  metadata: { customer_id: 'cust_42' },
});

Both sync and async filters are supported. Exceptions are caught and the span passes through.

Advanced Configuration

Brizz.initialize({
  apiKey: 'your-api-key',
  appName: 'my-app',
  baseUrl: 'https://telemetry.brizz.dev',

  // Custom headers for authentication
  headers: {
    'X-API-Version': '2024-01',
    'X-Environment': 'production',
  },

  // Disable batching for immediate export (testing)
  disableBatch: false,

  // Custom exporters for testing
  customSpanExporter: new InMemorySpanExporter(),
  customLogExporter: new InMemoryLogExporter(),

  // Disable internal NodeSDK (advanced usage)
  disableNodeSdk: false,

  // Log level for SDK diagnostics
  logLevel: 'info', // debug, info, warn, error, none
});

Testing & Development

For testing and development, you can use in-memory exporters:

import { InMemorySpanExporter } from '@opentelemetry/sdk-trace-base';

const spanExporter = new InMemorySpanExporter();

Brizz.initialize({
  apiKey: 'test-key',
  customSpanExporter: spanExporter,
  logLevel: 'debug',
});

// Later in tests
const spans = spanExporter.getFinishedSpans();
expect(spans).toHaveLength(1);
expect(spans[0].name).toBe('openai.chat');

Package.json Examples

The SDK is initialized from your application code, so your scripts stay as they are:

{
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js",
    "debug": "node --inspect src/index.js"
  }
}

Examples

Check out the examples directory for complete working examples:

  • Basic Usage - Simple AI application setup
  • Vercel AI SDK - Integration with Vercel's AI SDK
  • Session Tracking - Grouping related operations
  • Custom Events - Emitting business metrics
  • PII Masking - Data protection configuration

Troubleshooting

Common Issues

"Could not find declaration file"

  • Make sure to build the SDK: pnpm build
  • Check that dist/ contains .d.ts files

Instrumentation not working

  • Ensure Brizz.initialize() runs before any AI calls
  • Pass the libraries you want to trace via instrumentModules
  • Check that BRIZZ_API_KEY is set
  • For Vercel AI SDK: Add experimental_telemetry: { isEnabled: true } to function calls

CJS/ESM compatibility issues

  • Use dynamic imports in CommonJS: const ai = await import('ai')
  • For bundlers, use manual instrumentation with instrumentModules

Debug Mode

Enable debug logging to troubleshoot issues:

BRIZZ_LOG_LEVEL=debug node your-app.js

Contributing

See our Contributing Guide for development setup and guidelines.

License

Apache-2.0 - see LICENSE file.