# Next.js Setup
Source: https://docs.kubiks.ai/nextjs
Send traces and logs from Next.js apps using OpenTelemetry HTTP exporter
## Overview
Connect your Next.js application to Kubiks using the Vercel OpenTelemetry SDK with HTTP exporters. This setup works for any deployment platform (not just Vercel) and provides complete observability for your app.
## Installation
Install the required OpenTelemetry packages:
```bash npm theme={null}
npm install @vercel/otel @opentelemetry/api @opentelemetry/api-logs
```
```bash pnpm theme={null}
pnpm add @vercel/otel @opentelemetry/api @opentelemetry/api-logs
```
```bash yarn theme={null}
yarn add @vercel/otel @opentelemetry/api @opentelemetry/api-logs
```
## Environment Variables
Add these environment variables to your deployment configuration:
```bash .env.local theme={null}
# Required: Your Kubiks API key (get this from kubiks.app/connect)
KUBIKS_API_KEY=your_api_key_here
# Required: OTLP endpoint
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.kubiks.app
# Optional: Set service name (defaults to your app name)
OTEL_SERVICE_NAME=my-nextjs-app
# Optional: Environment (production, staging, development)
OTEL_ENVIRONMENT=production
```
## Configuration
Create an `instrumentation.ts` file in your **project root** (same level as `app/` or `pages/`):
```typescript instrumentation.ts theme={null}
import { OTLPHttpProtoTraceExporter, registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
traceExporter: new OTLPHttpProtoTraceExporter({
url: "https://ingest.kubiks.app/v1/traces",
headers: {
"X-Kubiks-Key": process.env.KUBIKS_API_KEY!,
},
}),
});
}
```
Next.js automatically loads `instrumentation.ts` when the application starts. No additional imports needed!
## Environment-Specific Configuration
Set environment variables based on your deployment platform:
Add environment variables in your Vercel project settings:
1. Go to **Settings** → **Environment Variables**
2. Add `KUBIKS_API_KEY` with your API key
3. Add `OTEL_EXPORTER_OTLP_ENDPOINT` with value `https://ingest.kubiks.app`
4. Add `OTEL_SERVICE_NAME` with your app name
5. Deploy your application
Pass environment variables when running your container:
```bash theme={null}
docker run \
-e KUBIKS_API_KEY=your_api_key \
-e OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.kubiks.app \
-e OTEL_SERVICE_NAME=my-nextjs-app \
-p 3000:3000 \
your-image
```
Or use a `.env` file with docker-compose:
```yaml docker-compose.yml theme={null}
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env
```
Add environment variables in your platform's dashboard:
**Railway:**
1. Go to your project → **Variables**
2. Add `KUBIKS_API_KEY`, `OTEL_EXPORTER_OTLP_ENDPOINT`, and `OTEL_SERVICE_NAME`
**Render:**
1. Go to your web service → **Environment**
2. Add the same environment variables
Export environment variables before starting your app:
```bash theme={null}
export KUBIKS_API_KEY=your_api_key
export OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.kubiks.app
export OTEL_SERVICE_NAME=my-nextjs-app
npm run start
```
Or add them to your process manager config (PM2, systemd, etc.).
## Get Your API Key
Go to [kubiks.app/connect](https://kubiks.app/connect) in your dashboard
Click **Create API Key** in the OpenTelemetry Ingestion section
Copy the generated API key and add it to your environment variables
## Verify Setup
Once configured, your Next.js app will automatically send traces and logs to Kubiks. To verify:
1. Start your development server or deploy your app
2. Make some requests to your application
3. Check your [Kubiks dashboard](https://kubiks.app) for traces and logs
Traces appear in real-time. If you don't see data after a few seconds, check your API key and endpoint configuration.
## What Gets Tracked
The `@vercel/otel` package automatically instruments:
* All incoming HTTP requests
* Request/response headers
* Status codes and response times
* URL paths and query parameters
* Server-side rendering (SSR)
* API routes
* Server components
* Middleware
* Static generation
* `fetch()` calls to external APIs
* Database queries (when instrumented)
* Third-party service calls
* Request duration
* Time to first byte (TTFB)
* Server-side rendering time
* API response times
## Instrument Your Dependencies
Enhance observability by adding Kubiks OpenTelemetry SDKs for popular frameworks:
Trace all database queries and transactions
Monitor authentication flows and sessions
Track email delivery and operations
Monitor message queue operations
Trace billing and payment flows
Explore all available SDKs
## Troubleshooting
**Check these common issues:**
1. Verify `KUBIKS_API_KEY` is set correctly
2. Confirm `OTEL_EXPORTER_OTLP_ENDPOINT` is `https://ingest.kubiks.app`
3. Ensure `instrumentationHook: true` is in `next.config.ts`
4. Check that `instrumentation.ts` is in your project root
5. Restart your development server after config changes
Make sure:
1. File is named exactly `instrumentation.ts` (or `.js`)
2. File is in project root (same level as `app/` or `pages/`)
3. `experimental.instrumentationHook` is enabled in Next.js config
4. You're using Next.js 13.2 or later
1. Double-check your API key from [kubiks.app/connect](https://kubiks.app/connect)
2. Ensure no extra spaces or quotes in your `.env.local`
3. Verify environment variables are loaded (check `process.env.KUBIKS_API_KEY`)
4. For production, confirm variables are set in your deployment platform
The SDK uses sampling by default. To see all traces in development:
```typescript instrumentation.ts theme={null}
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: process.env.OTEL_SERVICE_NAME || 'nextjs-app',
// Sample all traces in development
...(process.env.NODE_ENV === 'development' && {
tracesSampleRate: 1.0,
}),
});
}
```
## Next Steps
Check your dashboard for real-time traces
Instrument your database queries
Using Vercel? Try our native integration
Explore all integrations
# Autumn Billing
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-autumn
OpenTelemetry instrumentation for Autumn billing operations
## Overview
`@kubiks/otel-autumn` provides comprehensive OpenTelemetry instrumentation for the [Autumn](https://useautumn.com) billing SDK. Capture spans for every billing operation including feature checks, usage tracking, checkout flows, product attachments, and cancellations with detailed metadata.
Visualize your billing operations with detailed span information including operation type, customer IDs, feature IDs, and billing metadata.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-autumn
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-autumn
```
```bash yarn theme={null}
yarn add @kubiks/otel-autumn
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `autumn-js` >= 0.1.0
## Quick Start
```typescript theme={null}
import { Autumn } from "autumn-js";
import { instrumentAutumn } from "@kubiks/otel-autumn";
const autumn = instrumentAutumn(
new Autumn({
secretKey: process.env.AUTUMN_SECRET_KEY!,
}),
);
// All operations are now automatically traced
const checkResult = await autumn.check({
customer_id: "user_123",
feature_id: "messages",
});
await autumn.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
});
```
`instrumentAutumn` wraps your Autumn client instance—no configuration changes needed. Every SDK call creates a client span with detailed billing attributes.
## Traced Operations
This instrumentation wraps the core Autumn billing methods:
Feature access and product status checks
Usage event tracking
Checkout session creation
Product attachment to customers
Product cancellation
Each operation creates a dedicated span with operation-specific attributes.
## Span Attributes
### Common Attributes (All Operations)
| Attribute | Description | Example |
| -------------------- | ------------------------- | ---------------------- |
| `billing.system` | Constant value `autumn` | `autumn` |
| `billing.operation` | Operation type | `check`, `track` |
| `autumn.resource` | Resource being accessed | `features`, `products` |
| `autumn.target` | Full operation target | `features.check` |
| `autumn.customer_id` | Customer ID | `user_123` |
| `autumn.entity_id` | Entity ID (if applicable) | `org_456` |
### Check Operation
| Attribute | Description | Example |
| ------------------------- | ------------------------------ | ---------- |
| `autumn.feature_id` | Feature being checked | `messages` |
| `autumn.allowed` | Whether access is allowed | `true` |
| `autumn.balance` | Current balance/remaining uses | `42` |
| `autumn.usage` | Current usage | `8` |
| `autumn.unlimited` | Whether usage is unlimited | `false` |
| `autumn.required_balance` | Required balance for operation | `1` |
| Attribute | Description | Example |
| ----------------------- | ---------------------- | ------- |
| `autumn.product_id` | Product being checked | `pro` |
| `autumn.included_usage` | Included usage in plan | `50` |
### Track Operation
| Attribute | Description | Example |
| ------------------------ | ------------------------- | -------------- |
| `autumn.feature_id` | Feature being tracked | `messages` |
| `autumn.event_name` | Custom event name | `message_sent` |
| `autumn.value` | Usage value tracked | `1` |
| `autumn.event_id` | Generated event ID | `evt_123` |
| `autumn.idempotency_key` | Idempotency key for dedup | `msg_456` |
### Checkout Operation
| Attribute | Description | Example |
| ----------------------- | ----------------------------------- | --------------------------------- |
| `autumn.product_id` | Product being purchased | `pro` |
| `autumn.product_ids` | Multiple products (comma-separated) | `pro, addon_analytics` |
| `autumn.checkout_url` | Stripe checkout URL | `https://checkout.stripe.com/...` |
| `autumn.has_prorations` | Whether prorations apply | `true` |
| `autumn.total_amount` | Total checkout amount | `2000` (cents) |
| `autumn.currency` | Currency code | `usd` |
| `autumn.force_checkout` | Whether to force Stripe checkout | `false` |
| `autumn.invoice` | Whether to create invoice | `true` |
### Attach Operation
| Attribute | Description | Example |
| --------------------- | ------------------------------ | --------------------------------- |
| `autumn.product_id` | Product being attached | `pro` |
| `autumn.success` | Whether attachment succeeded | `true` |
| `autumn.checkout_url` | Checkout URL if payment needed | `https://checkout.stripe.com/...` |
### Cancel Operation
| Attribute | Description | Example |
| ------------------- | ------------------------------ | ------- |
| `autumn.product_id` | Product being cancelled | `pro` |
| `autumn.success` | Whether cancellation succeeded | `true` |
## Configuration
You can optionally configure the instrumentation:
```typescript theme={null}
import { instrumentAutumn } from "@kubiks/otel-autumn";
const autumn = instrumentAutumn(client, {
// Capture customer data in spans (default: false)
captureCustomerData: true,
// Capture product options/configuration (default: false)
captureOptions: true,
});
```
By default, sensitive customer data is not captured. Enable `captureCustomerData` only if your observability platform is secure and compliant.
## Usage Examples
### Feature Access Control
```typescript Feature Check theme={null}
const autumn = instrumentAutumn(
new Autumn({ secretKey: process.env.AUTUMN_SECRET_KEY! }),
);
// Check if user can access a feature
const result = await autumn.check({
customer_id: "user_123",
feature_id: "messages",
required_balance: 1,
});
if (result.data?.allowed) {
// User has access
console.log(`Remaining: ${result.data.balance}`);
}
```
```typescript Product Check theme={null}
// Check product status
const result = await autumn.check({
customer_id: "user_123",
product_id: "pro",
});
if (result.data?.allowed) {
console.log("User has Pro subscription");
}
```
### Usage Tracking
```typescript Simple Tracking theme={null}
// Track feature usage
await autumn.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
});
```
```typescript With Idempotency theme={null}
// Track with idempotency key to prevent double-counting
await autumn.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
idempotency_key: `msg_${messageId}`,
});
```
```typescript Custom Event theme={null}
// Track custom event
await autumn.track({
customer_id: "user_123",
event_name: "video_upload",
value: 1,
});
```
### Checkout Flow
```typescript Single Product theme={null}
// Create a checkout session for a product
const result = await autumn.checkout({
customer_id: "user_123",
product_id: "pro",
force_checkout: false, // Use billing portal if payment method exists
});
if (result.data?.url) {
// Redirect to Stripe checkout
console.log(`Checkout URL: ${result.data.url}`);
}
```
```typescript Multiple Products theme={null}
// Checkout with multiple products
const result = await autumn.checkout({
customer_id: "user_123",
product_ids: ["pro", "addon_analytics"],
});
```
```typescript Force Checkout theme={null}
// Always use Stripe checkout (even if payment method exists)
const result = await autumn.checkout({
customer_id: "user_123",
product_id: "enterprise",
force_checkout: true,
});
```
### Product Management
```typescript Attach Product theme={null}
// Attach a free product
const attachResult = await autumn.attach({
customer_id: "user_123",
product_id: "free",
});
if (attachResult.data?.success) {
console.log("Product attached successfully");
}
```
```typescript Cancel Product theme={null}
// Cancel a subscription
const cancelResult = await autumn.cancel({
customer_id: "user_123",
product_id: "pro",
});
if (cancelResult.data?.success) {
console.log("Subscription cancelled");
}
```
## Complete Integration Example
Here's a complete example integrating Autumn with a Next.js application:
```typescript lib/autumn.ts theme={null}
import { Autumn } from "autumn-js";
import { instrumentAutumn } from "@kubiks/otel-autumn";
export const autumn = instrumentAutumn(
new Autumn({
secretKey: process.env.AUTUMN_SECRET_KEY!,
}),
{
captureCustomerData: true,
captureOptions: true,
}
);
```
```typescript app/api/features/check/route.ts theme={null}
import { autumn } from "@/lib/autumn";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const { customerId, featureId } = await request.json();
const result = await autumn.check({
customer_id: customerId,
feature_id: featureId,
required_balance: 1,
});
return NextResponse.json(result.data);
}
```
```typescript app/api/usage/track/route.ts theme={null}
import { autumn } from "@/lib/autumn";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
const { customerId, featureId, value, idempotencyKey } = await request.json();
await autumn.track({
customer_id: customerId,
feature_id: featureId,
value,
idempotency_key: idempotencyKey,
});
return NextResponse.json({ success: true });
}
```
## Best Practices
Always use idempotency keys when tracking usage to prevent double-counting:
```typescript theme={null}
await autumn.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
idempotency_key: `msg_${messageId}`,
});
```
Check feature access before performing operations:
```typescript theme={null}
const check = await autumn.check({
customer_id: "user_123",
feature_id: "messages",
required_balance: 1,
});
if (!check.data?.allowed) {
throw new Error("Insufficient balance");
}
// Proceed with operation
await sendMessage();
// Track usage
await autumn.track({
customer_id: "user_123",
feature_id: "messages",
value: 1,
});
```
Handle both checkout URLs and direct product attachments:
```typescript theme={null}
const result = await autumn.checkout({
customer_id: "user_123",
product_id: "pro",
});
if (result.data?.url) {
// Redirect to Stripe checkout
return redirect(result.data.url);
} else {
// Product attached directly (e.g., free plan)
return redirect("/dashboard");
}
```
Only enable additional data capture in secure environments:
```typescript theme={null}
const autumn = instrumentAutumn(client, {
captureCustomerData: process.env.NODE_ENV === "development",
captureOptions: true,
});
```
## Troubleshooting
Make sure OpenTelemetry is properly configured in your application:
```typescript theme={null}
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
const sdk = new NodeSDK({
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
```
Ensure you're using the latest version of the package:
```bash theme={null}
npm update @kubiks/otel-autumn
```
The instrumentation adds minimal overhead. If you experience issues:
* Verify your OpenTelemetry exporter is configured correctly
* Check if you're using sampling to reduce data volume
* Consider using batch span processors
## Resources
Learn more about Autumn billing
View source code and examples
View package on npm
Found a bug? Let us know!
## License
MIT
# Better Auth
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-better-auth
OpenTelemetry instrumentation for Better Auth authentication flows
## Overview
`@kubiks/otel-better-auth` provides comprehensive OpenTelemetry instrumentation for [Better Auth](https://better-auth.com/). Get complete authentication observability across all auth flows with a single line of code—OAuth, email/password, sessions, account management, and more.
Visualize your authentication flows with detailed span information including operation type, user IDs, session IDs, auth methods, and success/failure status.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-better-auth
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-better-auth
```
```bash yarn theme={null}
yarn add @kubiks/otel-better-auth
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `better-auth` >= 1.0.0
## Quick Start
```typescript theme={null}
import { betterAuth } from "better-auth";
import { instrumentBetterAuth } from "@kubiks/otel-better-auth";
export const auth = instrumentBetterAuth(
betterAuth({
database: db,
// ... your Better Auth config
}),
);
```
Instrumenting Better Auth is just a single call—wrap the instance you already create and every API method invocation is traced automatically. Keep the rest of your configuration unchanged.
## Traced Operations
**Sign In & Sign Up:**
* `auth.http.oauth.callback.{provider}` - OAuth callback **with user ID** ✅
* `auth.http.signin.email` - Email signin **with user ID**
* `auth.http.signup.email` - Email signup **with user ID**
* `auth.http.oauth.initiate.{provider}` - OAuth initiation
* `auth.http.signout` - User signout
* `auth.http.get_session` - Get session
**Session Operations:**
* `auth.api.get_session` - Get current session with user ID and session ID
* `auth.api.list_sessions` - List all sessions
* `auth.api.revoke_session` - Revoke a session
* `auth.api.revoke_sessions` - Revoke multiple sessions
* `auth.api.revoke_other_sessions` - Revoke all other sessions
**Account Operations:**
* `auth.api.link_social_account` - Link social account
* `auth.api.unlink_account` - Unlink account
* `auth.api.list_user_accounts` - List user accounts
* `auth.api.update_user` - Update user profile
* `auth.api.delete_user` - Delete user account
**Password Management:**
* `auth.api.change_password` - Change password
* `auth.api.set_password` - Set password
* `auth.api.forget_password` - Forgot password request
* `auth.api.reset_password` - Reset password
**Email Management:**
* `auth.api.change_email` - Change email
* `auth.api.verify_email` - Verify email **with user ID**
* `auth.api.send_verification_email` - Send verification email
## Span Attributes
Each span includes rich context about the authentication operation:
| Attribute | Description | Example |
| ---------------- | -------------------------------- | -------------------------------------------- |
| `auth.operation` | Type of operation | `signin`, `signup`, `get_session`, `signout` |
| `auth.method` | Auth method | `email`, `oauth` |
| `auth.provider` | OAuth provider (when applicable) | `google`, `github` |
| `auth.success` | Operation success | `true`, `false` |
| `auth.error` | Error message (when failed) | `Invalid credentials` |
| `user.id` | User ID (when available) | `user_123456` |
| `user.email` | User email (when available) | `user@example.com` |
| `session.id` | Session ID (when available) | `session_abcdef` |
User IDs and session IDs are captured where applicable to help with debugging and monitoring authentication flows.
## Configuration
You can optionally customize the instrumentation:
```typescript theme={null}
instrumentBetterAuth(authClient, {
tracerName: "my-app", // Custom tracer name
tracer: customTracer, // Custom tracer instance
});
```
## Usage Examples
### Basic Setup (Next.js App Router)
```typescript lib/auth.ts theme={null}
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { instrumentBetterAuth } from "@kubiks/otel-better-auth";
import { db } from "./db";
export const auth = instrumentBetterAuth(
betterAuth({
baseURL: process.env.BETTER_AUTH_URL,
database: drizzleAdapter(db, { provider: "pg" }),
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
},
}),
);
```
```typescript app/api/auth/[...all]/route.ts theme={null}
import { auth } from "@/lib/auth";
export const { GET, POST } = auth.handler;
```
All authentication operations are now automatically traced!
```typescript theme={null}
// Sign in
await auth.api.signInEmail({
email: "user@example.com",
password: "password",
});
// Get session
const session = await auth.api.getSession();
```
### OAuth Authentication
```typescript GitHub OAuth theme={null}
export const auth = instrumentBetterAuth(
betterAuth({
database: db,
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
},
}),
);
// OAuth callback is automatically traced with provider name
// Span: auth.http.oauth.callback.github
```
```typescript Google OAuth theme={null}
export const auth = instrumentBetterAuth(
betterAuth({
database: db,
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
scopes: ["email", "profile"],
},
},
}),
);
// OAuth initiation is traced
// Span: auth.http.oauth.initiate.google
```
```typescript Multiple Providers theme={null}
export const auth = instrumentBetterAuth(
betterAuth({
database: db,
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
discord: {
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
},
},
}),
);
```
### Email/Password Authentication
```typescript Sign Up theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function signUp(email: string, password: string, name: string) {
const result = await auth.api.signUpEmail({
email,
password,
name,
});
// Traced as: auth.http.signup.email
// Includes: user.id, user.email, auth.success
return result;
}
```
```typescript Sign In theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function signIn(email: string, password: string) {
const result = await auth.api.signInEmail({
email,
password,
});
// Traced as: auth.http.signin.email
// Includes: user.id, user.email, auth.success, session.id
return result;
}
```
```typescript Sign Out theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function signOut() {
await auth.api.signOut();
// Traced as: auth.http.signout
// Includes: session.id, auth.success
}
```
### Session Management
```typescript Get Session theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function getCurrentSession() {
const session = await auth.api.getSession();
// Traced as: auth.api.get_session
// Includes: user.id, session.id, auth.success
return session;
}
```
```typescript List Sessions theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function listUserSessions() {
const sessions = await auth.api.listSessions();
// Traced as: auth.api.list_sessions
// Includes: user.id, auth.success
return sessions;
}
```
```typescript Revoke Session theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function revokeSession(sessionId: string) {
await auth.api.revokeSession({ sessionId });
// Traced as: auth.api.revoke_session
// Includes: session.id, auth.success
}
```
```typescript Revoke Other Sessions theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function revokeOtherSessions() {
await auth.api.revokeOtherSessions();
// Traced as: auth.api.revoke_other_sessions
// Includes: user.id, session.id (current), auth.success
}
```
### Account Management
```typescript Update Profile theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function updateProfile(name: string, image?: string) {
const result = await auth.api.updateUser({
name,
image,
});
// Traced as: auth.api.update_user
// Includes: user.id, auth.success
return result;
}
```
```typescript Link Social Account theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function linkGithubAccount() {
const result = await auth.api.linkSocialAccount({
provider: "github",
});
// Traced as: auth.api.link_social_account
// Includes: user.id, auth.provider (github), auth.success
return result;
}
```
```typescript Delete Account theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function deleteAccount() {
await auth.api.deleteUser();
// Traced as: auth.api.delete_user
// Includes: user.id, auth.success
}
```
### Password Management
```typescript Change Password theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function changePassword(
currentPassword: string,
newPassword: string
) {
const result = await auth.api.changePassword({
currentPassword,
newPassword,
});
// Traced as: auth.api.change_password
// Includes: user.id, auth.success
return result;
}
```
```typescript Reset Password theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function requestPasswordReset(email: string) {
await auth.api.forgetPassword({ email });
// Traced as: auth.api.forget_password
// Includes: user.email, auth.success
}
export async function resetPassword(token: string, newPassword: string) {
const result = await auth.api.resetPassword({
token,
newPassword,
});
// Traced as: auth.api.reset_password
// Includes: user.id, auth.success
return result;
}
```
### Email Management
```typescript Verify Email theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function verifyEmail(token: string) {
const result = await auth.api.verifyEmail({ token });
// Traced as: auth.api.verify_email
// Includes: user.id, user.email, auth.success
return result;
}
```
```typescript Change Email theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function changeEmail(newEmail: string) {
const result = await auth.api.changeEmail({
newEmail,
});
// Traced as: auth.api.change_email
// Includes: user.id, user.email (new), auth.success
return result;
}
```
## Complete Integration Example
Here's a full example of Better Auth with OpenTelemetry in a Next.js application:
```typescript lib/auth.ts theme={null}
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { instrumentBetterAuth } from "@kubiks/otel-better-auth";
import { db } from "./db";
export const auth = instrumentBetterAuth(
betterAuth({
baseURL: process.env.BETTER_AUTH_URL!,
database: drizzleAdapter(db, { provider: "pg" }),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 1 week
updateAge: 60 * 60 * 24, // 1 day
},
}),
{
tracerName: "my-app-auth",
}
);
```
```typescript app/api/auth/[...all]/route.ts theme={null}
import { auth } from "@/lib/auth";
export const { GET, POST } = auth.handler;
```
```typescript app/actions/auth.ts theme={null}
"use server";
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
export async function signInWithEmail(email: string, password: string) {
const result = await auth.api.signInEmail({
email,
password,
});
if (!result.error) {
redirect("/dashboard");
}
return result;
}
export async function signUpWithEmail(
email: string,
password: string,
name: string
) {
const result = await auth.api.signUpEmail({
email,
password,
name,
});
if (!result.error) {
redirect("/verify-email");
}
return result;
}
export async function signOut() {
await auth.api.signOut();
redirect("/");
}
```
## Best Practices
Always use Server Actions for authentication operations in Next.js:
```typescript theme={null}
"use server";
import { auth } from "@/lib/auth";
export async function signIn(email: string, password: string) {
return await auth.api.signInEmail({ email, password });
}
```
Check for errors and provide appropriate feedback:
```typescript theme={null}
const result = await auth.api.signInEmail({ email, password });
if (result.error) {
return { error: result.error.message };
}
return { success: true };
```
Always enable email verification for production:
```typescript theme={null}
betterAuth({
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
})
```
Use descriptive tracer names for easier debugging:
```typescript theme={null}
instrumentBetterAuth(auth, {
tracerName: "my-app-auth",
});
```
## Troubleshooting
Ensure OpenTelemetry is properly initialized before creating the auth instance:
```typescript theme={null}
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({
// ... configuration
});
sdk.start();
// Then create auth instance
export const auth = instrumentBetterAuth(betterAuth({ ... }));
```
User IDs are only captured for operations where the user is authenticated. For sign-in and sign-up, the user ID is captured after successful authentication.
Make sure you're using the correct provider name in your Better Auth configuration. The provider name must match exactly (e.g., `github`, not `GitHub`).
## Resources
Learn more about Better Auth
View source code and examples
View package on npm
Found a bug? Let us know!
## License
MIT
# ClickHouse
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-clickhouse
OpenTelemetry instrumentation for ClickHouse database operations
## Overview
`@kubiks/otel-clickhouse` provides comprehensive OpenTelemetry instrumentation for [ClickHouse](https://clickhouse.com/). Add distributed tracing to your ClickHouse database queries with a single line of code—perfect for analytics workloads and OLAP queries.
Visualize your ClickHouse queries with detailed span information including query text, execution time, and performance metrics.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-clickhouse
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-clickhouse
```
```bash yarn theme={null}
yarn add @kubiks/otel-clickhouse
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `@clickhouse/client` >= 0.2.0
## Supported Frameworks
Works with any TypeScript framework and Node.js runtime:
App Router & Pages Router
High-performance server
Enterprise framework
Classic Node.js server
Full-stack framework
Modern web framework
## Supported Platforms
Works with any observability platform that supports OpenTelemetry:
* [Kubiks](https://kubiks.ai)
* [Sentry](https://sentry.io)
* [Axiom](https://axiom.co)
* [Datadog](https://www.datadoghq.com)
* [New Relic](https://newrelic.com)
* [SigNoz](https://signoz.io)
* And many more...
## Quick Start
Use `ClickHouseInstrumentation` to add tracing to your ClickHouse client:
```typescript theme={null}
import { createClient } from '@clickhouse/client';
import { ClickHouseInstrumentation } from '@kubiks/otel-clickhouse';
import { registerOTel } from '@vercel/otel';
// Register OpenTelemetry with ClickHouse instrumentation
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [
new ClickHouseInstrumentation(),
],
});
}
// Create your ClickHouse client as usual
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
username: process.env.CLICKHOUSE_USER,
password: process.env.CLICKHOUSE_PASSWORD,
database: process.env.CLICKHOUSE_DB,
});
// All queries are now automatically traced
const result = await client.query({
query: 'SELECT * FROM events WHERE timestamp > now() - INTERVAL 1 HOUR',
});
```
This is the simplest approach—just add the instrumentation and all ClickHouse queries are automatically traced!
## Configuration Options
```typescript theme={null}
new ClickHouseInstrumentation({
captureQueryText: true, // Include SQL in traces (default: true)
maxQueryTextLength: 1000, // Max SQL length (default: 1000)
captureParameters: false, // Include query parameters (default: false)
})
```
By default, SQL queries are captured in spans. You can disable this by setting `captureQueryText: false` for sensitive environments.
## What You Get
Each ClickHouse query automatically creates a span with rich telemetry data:
* **Span name**: `clickhouse.query`, `clickhouse.insert`, etc.
* **Operation type**: `db.operation` attribute (SELECT, INSERT, CREATE TABLE, etc.)
* **SQL query text**: Full query statement captured in `db.statement` (configurable)
* **Database system**: `db.system` attribute (clickhouse)
* **Database name**: `db.name` attribute
* **Server address**: `server.address` and `server.port` attributes
* Query execution time
* Number of rows read
* Number of bytes processed
* Network latency
* Exceptions are recorded with stack traces
* Proper span status (OK, ERROR)
* Error messages and ClickHouse error codes
## Span Attributes
The instrumentation adds the following attributes to each span following [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/database/):
| Attribute | Description | Example |
| ---------------- | ------------------ | ------------------------- |
| `db.operation` | SQL operation type | `SELECT` |
| `db.statement` | Full SQL query | `SELECT * FROM events...` |
| `db.system` | Database system | `clickhouse` |
| `db.name` | Database name | `analytics` |
| `server.address` | Server hostname | `clickhouse.example.com` |
| `server.port` | Server port | `8123` |
## Usage Examples
### Basic Queries
```typescript Select theme={null}
import { createClient } from '@clickhouse/client';
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
});
// Traced as: clickhouse.query
const result = await client.query({
query: 'SELECT count(*) FROM events WHERE date = today()',
format: 'JSONEachRow',
});
const data = await result.json();
```
```typescript Insert theme={null}
import { createClient } from '@clickhouse/client';
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
});
// Traced as: clickhouse.insert
await client.insert({
table: 'events',
values: [
{ user_id: 123, event_name: 'page_view', timestamp: new Date() },
{ user_id: 456, event_name: 'button_click', timestamp: new Date() },
],
format: 'JSONEachRow',
});
```
```typescript Aggregations theme={null}
import { createClient } from '@clickhouse/client';
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
});
// Complex aggregation query - fully traced
const result = await client.query({
query: `
SELECT
user_id,
count(*) as event_count,
uniq(session_id) as session_count
FROM events
WHERE date >= today() - 7
GROUP BY user_id
ORDER BY event_count DESC
LIMIT 100
`,
format: 'JSONEachRow',
});
```
### Streaming Queries
```typescript theme={null}
import { createClient } from '@clickhouse/client';
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
});
// Stream large result sets - traced from start to finish
const stream = await client.query({
query: 'SELECT * FROM large_table',
format: 'JSONEachRow',
});
const reader = stream.stream();
for await (const rows of reader) {
// Process rows in chunks
console.log(rows);
}
```
### Parameterized Queries
```typescript theme={null}
import { createClient } from '@clickhouse/client';
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
});
// Use query parameters for safety
const result = await client.query({
query: 'SELECT * FROM events WHERE user_id = {userId:UInt64} AND date >= {startDate:Date}',
query_params: {
userId: 12345,
startDate: '2024-01-01',
},
format: 'JSONEachRow',
});
```
## Complete Integration Example
Here's a complete example of ClickHouse with OpenTelemetry in a Next.js application:
```typescript lib/clickhouse.ts theme={null}
import { createClient } from '@clickhouse/client';
export const clickhouse = createClient({
host: process.env.CLICKHOUSE_HOST,
username: process.env.CLICKHOUSE_USER,
password: process.env.CLICKHOUSE_PASSWORD,
database: process.env.CLICKHOUSE_DB,
});
```
```typescript instrumentation.ts theme={null}
import { registerOTel } from '@vercel/otel';
import { ClickHouseInstrumentation } from '@kubiks/otel-clickhouse';
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [
new ClickHouseInstrumentation({
captureQueryText: true,
maxQueryTextLength: 2000,
}),
],
});
}
```
```typescript app/api/analytics/route.ts theme={null}
import { NextRequest, NextResponse } from 'next/server';
import { clickhouse } from '@/lib/clickhouse';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const days = searchParams.get('days') || '7';
// Automatically traced
const result = await clickhouse.query({
query: `
SELECT
toDate(timestamp) as date,
count(*) as events
FROM analytics_events
WHERE timestamp >= now() - INTERVAL {days:UInt32} DAY
GROUP BY date
ORDER BY date
`,
query_params: { days: parseInt(days) },
format: 'JSONEachRow',
});
const data = await result.json();
return NextResponse.json(data);
}
```
## Best Practices
ClickHouse client handles connection pooling automatically:
```typescript theme={null}
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
max_open_connections: 10,
});
```
Set appropriate timeouts for your queries:
```typescript theme={null}
const result = await client.query({
query: 'SELECT * FROM large_table',
query_params: {
max_execution_time: 30, // seconds
},
});
```
For high-throughput scenarios, batch your inserts:
```typescript theme={null}
await client.insert({
table: 'events',
values: largeArrayOfEvents, // Insert in batches
format: 'JSONEachRow',
});
```
Use traces to identify slow queries and optimize them with appropriate indexes and table engines.
## Performance Considerations
The instrumentation adds minimal overhead (\~1-2ms per query) for tracing operations.
Use OpenTelemetry sampling to reduce data volume in high-traffic applications:
```typescript theme={null}
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
registerOTel({
serviceName: 'your-app',
sampler: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces
});
```
## Troubleshooting
Ensure OpenTelemetry is initialized before making ClickHouse queries:
```typescript theme={null}
// In instrumentation.ts or instrumentation.node.ts
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [new ClickHouseInstrumentation()],
});
}
```
Check that `captureQueryText` is enabled:
```typescript theme={null}
new ClickHouseInstrumentation({
captureQueryText: true,
maxQueryTextLength: 2000,
})
```
Verify your ClickHouse connection settings:
```typescript theme={null}
const client = createClient({
host: process.env.CLICKHOUSE_HOST,
username: process.env.CLICKHOUSE_USER,
password: process.env.CLICKHOUSE_PASSWORD,
// Test the connection
});
await client.ping(); // Should return true
```
## Resources
Learn more about ClickHouse
View source code and examples
View package on npm
Found a bug? Let us know!
## License
MIT
# Drizzle ORM
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-drizzle
OpenTelemetry instrumentation for Drizzle ORM database queries
## Overview
`@kubiks/otel-drizzle` provides comprehensive OpenTelemetry instrumentation for [Drizzle ORM](https://orm.drizzle.team/). Add distributed tracing to your database queries with a single line of code—supports PostgreSQL, MySQL, and SQLite.
Visualize your database queries with detailed span information including operation type, SQL statements, and performance metrics.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-drizzle
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-drizzle
```
```bash yarn theme={null}
yarn add @kubiks/otel-drizzle
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `drizzle-orm` >= 0.28.0
## Supported Frameworks
Works with any TypeScript framework and Node.js runtime that Drizzle supports:
App Router & Pages Router
High-performance server
Enterprise framework
Classic Node.js server
Full-stack framework
Modern web framework
## Supported Platforms
Works with any observability platform that supports OpenTelemetry:
* [Kubiks](https://kubiks.ai)
* [Sentry](https://sentry.io)
* [Axiom](https://axiom.co)
* [Datadog](https://www.datadoghq.com)
* [New Relic](https://newrelic.com)
* [SigNoz](https://signoz.io)
* And many more...
## Quick Start
Use `instrumentDrizzleClient()` to add tracing to your Drizzle database instance:
```typescript theme={null}
import { drizzle } from "drizzle-orm/postgres-js";
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
// Create your Drizzle database instance as usual
const db = drizzle(process.env.DATABASE_URL!);
// Add instrumentation with a single line
instrumentDrizzleClient(db);
// That's it! All queries are now traced automatically
const users = await db.select().from(usersTable);
```
This is the simplest and most straightforward approach—just wrap your existing Drizzle instance!
## Database-Specific Setup
```typescript theme={null}
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
// Using connection string directly
const db = drizzle(process.env.DATABASE_URL!);
instrumentDrizzleClient(db, { dbSystem: "postgresql" });
// Or with a client instance
const queryClient = postgres(process.env.DATABASE_URL!);
const db = drizzle({ client: queryClient });
instrumentDrizzleClient(db, {
dbSystem: "postgresql",
dbName: "myapp",
peerName: "db.example.com",
peerPort: 5432,
});
```
```typescript theme={null}
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
// Using connection string directly
const db = drizzle(process.env.DATABASE_URL!);
instrumentDrizzleClient(db, { dbSystem: "postgresql" });
// Or with a pool instance
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
const db = drizzle({ client: pool });
instrumentDrizzleClient(db, { dbSystem: "postgresql" });
```
```typescript theme={null}
import { drizzle } from "drizzle-orm/mysql2";
import mysql from "mysql2/promise";
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
// Using connection string directly
const db = drizzle(process.env.DATABASE_URL!);
instrumentDrizzleClient(db, { dbSystem: "mysql" });
// Or with a connection instance
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
database: "mydb",
// ... other connection options
});
const db = drizzle({ client: connection });
instrumentDrizzleClient(db, {
dbSystem: "mysql",
dbName: "mydb",
peerName: "localhost",
peerPort: 3306,
});
```
```typescript theme={null}
import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
// Using file path directly
const db = drizzle("sqlite.db");
instrumentDrizzleClient(db, { dbSystem: "sqlite" });
// Or with a Database instance
const sqlite = new Database("sqlite.db");
const db = drizzle({ client: sqlite });
instrumentDrizzleClient(db, { dbSystem: "sqlite" });
```
```typescript theme={null}
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
// Using connection config directly
const db = drizzle({
connection: {
url: process.env.DATABASE_URL!,
authToken: process.env.DATABASE_AUTH_TOKEN,
}
});
instrumentDrizzleClient(db, { dbSystem: "sqlite" });
// Or with a client instance
const client = createClient({
url: process.env.DATABASE_URL!,
authToken: process.env.DATABASE_AUTH_TOKEN,
});
const db = drizzle({ client });
instrumentDrizzleClient(db, { dbSystem: "sqlite" });
```
## Configuration Options
```typescript theme={null}
instrumentDrizzleClient(db, {
dbSystem: "postgresql", // Database type: 'postgresql' | 'mysql' | 'sqlite'
dbName: "myapp", // Database name for spans
captureQueryText: true, // Include SQL in traces (default: true)
maxQueryTextLength: 1000, // Max SQL length (default: 1000)
peerName: "db.example.com", // Database server hostname
peerPort: 5432, // Database server port
});
```
By default, SQL queries are captured in spans. You can disable this by setting `captureQueryText: false` for sensitive environments.
## What You Get
Each database query automatically creates a span with rich telemetry data:
* **Span name**: `drizzle.select`, `drizzle.insert`, `drizzle.update`, etc.
* **Operation type**: `db.operation` attribute (SELECT, INSERT, UPDATE, DELETE, SET)
* **SQL query text**: Full query statement captured in `db.statement` (configurable)
* **Database system**: `db.system` attribute (postgresql, mysql, sqlite, etc.)
All queries within transactions are automatically traced, including:
* RLS (Row Level Security) queries like `SET LOCAL role` and `set_config()`
* All nested transaction queries
* Transaction rollbacks and commits
* Exceptions are recorded with stack traces
* Proper span status (OK, ERROR)
* Error messages and types
* Duration and timing information for every query
* Query execution time
* Database connection time
## Span Attributes
The instrumentation adds the following attributes to each span following [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/database/):
| Attribute | Description | Example |
| ---------------- | --------------------- | ------------------------------------- |
| `db.operation` | SQL operation type | `SELECT` |
| `db.statement` | Full SQL query | `select "id", "name" from "users"...` |
| `db.system` | Database system | `postgresql` |
| `db.name` | Database name | `myapp` |
| `operation.name` | Client operation name | `kubiks_otel-drizzle.client` |
## Usage Examples
### Basic Queries
```typescript Select theme={null}
import { db } from "@/lib/db";
import { users } from "@/db/schema";
// Traced as: drizzle.select
const allUsers = await db.select().from(users);
// With conditions
const activeUsers = await db
.select()
.from(users)
.where(eq(users.active, true));
```
```typescript Insert theme={null}
import { db } from "@/lib/db";
import { users } from "@/db/schema";
// Traced as: drizzle.insert
await db.insert(users).values({
name: "John Doe",
email: "john@example.com",
});
// Multiple rows
await db.insert(users).values([
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" },
]);
```
```typescript Update theme={null}
import { db } from "@/lib/db";
import { users } from "@/db/schema";
// Traced as: drizzle.update
await db
.update(users)
.set({ active: false })
.where(eq(users.id, userId));
```
```typescript Delete theme={null}
import { db } from "@/lib/db";
import { users } from "@/db/schema";
// Traced as: drizzle.delete
await db
.delete(users)
.where(eq(users.id, userId));
```
### Transactions
```typescript theme={null}
import { db } from "@/lib/db";
import { users, posts } from "@/db/schema";
// All queries within the transaction are traced
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({ name: "John" })
.returning();
await tx.insert(posts).values({
userId: user.id,
title: "First Post",
});
});
```
Transaction queries are automatically marked with `db.transaction` attribute.
### Complex Queries
```typescript Joins theme={null}
import { db } from "@/lib/db";
import { users, posts } from "@/db/schema";
// Traced with full SQL statement
const usersWithPosts = await db
.select({
user: users,
post: posts,
})
.from(users)
.leftJoin(posts, eq(users.id, posts.userId));
```
```typescript Aggregations theme={null}
import { db } from "@/lib/db";
import { posts } from "@/db/schema";
import { count } from "drizzle-orm";
// Traced with aggregation query
const postCounts = await db
.select({
userId: posts.userId,
count: count(),
})
.from(posts)
.groupBy(posts.userId);
```
```typescript Subqueries theme={null}
import { db } from "@/lib/db";
import { users, posts } from "@/db/schema";
// Complex queries are fully traced
const activeUsersSubquery = db
.select({ id: users.id })
.from(users)
.where(eq(users.active, true));
const postsFromActiveUsers = await db
.select()
.from(posts)
.where(inArray(posts.userId, activeUsersSubquery));
```
### Row Level Security (PostgreSQL)
```typescript theme={null}
import { db } from "@/lib/db";
import { users } from "@/db/schema";
// RLS queries are automatically traced
await db.transaction(async (tx) => {
// SET LOCAL role is traced
await tx.execute(sql`SET LOCAL role = 'authenticated'`);
await tx.execute(sql`SELECT set_config('request.jwt.claim.sub', '${userId}', true)`);
// Regular queries with RLS applied
const userPosts = await tx
.select()
.from(posts)
.where(eq(posts.userId, userId));
});
```
## Complete Integration Example
Here's a complete example of Drizzle ORM with OpenTelemetry in a Next.js application:
```typescript lib/db.ts theme={null}
import { drizzle } from "drizzle-orm/postgres-js";
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
import * as schema from "@/db/schema";
// Create Drizzle instance
export const db = drizzle(process.env.DATABASE_URL!, { schema });
// Instrument for tracing
instrumentDrizzleClient(db, {
dbSystem: "postgresql",
dbName: "myapp",
captureQueryText: true,
maxQueryTextLength: 2000,
});
```
```typescript db/schema.ts theme={null}
import { pgTable, serial, text, boolean, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
active: boolean("active").default(true),
createdAt: timestamp("created_at").defaultNow(),
});
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
userId: serial("user_id").references(() => users.id),
title: text("title").notNull(),
content: text("content"),
createdAt: timestamp("created_at").defaultNow(),
});
```
```typescript app/api/users/route.ts theme={null}
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";
import { users } from "@/db/schema";
import { eq } from "drizzle-orm";
export async function GET() {
// Automatically traced
const allUsers = await db.select().from(users);
return NextResponse.json(allUsers);
}
export async function POST(request: NextRequest) {
const body = await request.json();
// Automatically traced
const [newUser] = await db
.insert(users)
.values(body)
.returning();
return NextResponse.json(newUser);
}
```
## Best Practices
Always use connection pooling in production:
```typescript theme={null}
import { Pool } from "pg";
import { drizzle } from "drizzle-orm/node-postgres";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Maximum pool size
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
export const db = drizzle({ client: pool });
instrumentDrizzleClient(db);
```
Disable SQL capture in production if queries contain sensitive data:
```typescript theme={null}
instrumentDrizzleClient(db, {
captureQueryText: process.env.NODE_ENV !== "production",
maxQueryTextLength: 500,
});
```
Always use transactions for multi-step operations:
```typescript theme={null}
await db.transaction(async (tx) => {
const [user] = await tx.insert(users).values(userData).returning();
await tx.insert(posts).values({ userId: user.id, ...postData });
});
```
Monitor slow queries in your traces and add indexes:
```sql theme={null}
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
```
## Performance Considerations
The instrumentation adds minimal overhead (\~1-2ms per query) for tracing operations.
Use OpenTelemetry sampling to reduce data volume in high-traffic applications:
```typescript theme={null}
import { TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
const sdk = new NodeSDK({
sampler: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces
});
```
Use batch span processors for better performance:
```typescript theme={null}
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
const sdk = new NodeSDK({
spanProcessor: new BatchSpanProcessor(exporter),
});
```
## Troubleshooting
Ensure OpenTelemetry is initialized before instrumenting Drizzle:
```typescript theme={null}
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({
// ... configuration
});
sdk.start();
// Then instrument Drizzle
import { db } from "@/lib/db";
instrumentDrizzleClient(db);
```
Check that `captureQueryText` is enabled:
```typescript theme={null}
instrumentDrizzleClient(db, {
captureQueryText: true,
maxQueryTextLength: 2000,
});
```
Make sure you're using Drizzle's transaction API correctly:
```typescript theme={null}
// Correct: All queries in callback are grouped
await db.transaction(async (tx) => {
await tx.insert(users).values(data);
});
// Incorrect: Manual transaction control not supported
await db.execute(sql`BEGIN`);
await db.insert(users).values(data);
await db.execute(sql`COMMIT`);
```
## Resources
Learn more about Drizzle ORM
View source code and examples
View package on npm
Found a bug? Let us know!
## License
MIT
# E2B
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-e2b
OpenTelemetry instrumentation for E2B code execution
## Overview
`@kubiks/otel-e2b` provides comprehensive OpenTelemetry instrumentation for [E2B (Code Interpreter)](https://e2b.dev/). Add distributed tracing to your AI-powered code execution, sandbox operations, and code interpreter workflows with automatic instrumentation.
Visualize your E2B operations with detailed span information including code execution, sandbox lifecycle, and performance metrics.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-e2b
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-e2b
```
```bash yarn theme={null}
yarn add @kubiks/otel-e2b
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `@e2b/code-interpreter` >= 0.1.0
## Supported Frameworks
Works with any TypeScript framework and Node.js runtime:
App Router & Pages Router
High-performance server
Enterprise framework
Classic Node.js server
Full-stack framework
Modern web framework
## Supported Platforms
Works with any observability platform that supports OpenTelemetry:
* [Kubiks](https://kubiks.ai)
* [Sentry](https://sentry.io)
* [Axiom](https://axiom.co)
* [Datadog](https://www.datadoghq.com)
* [New Relic](https://newrelic.com)
* [SigNoz](https://signoz.io)
* And many more...
## Quick Start
Use `E2BInstrumentation` to add tracing to your E2B code interpreter:
```typescript theme={null}
import { CodeInterpreter } from '@e2b/code-interpreter';
import { E2BInstrumentation } from '@kubiks/otel-e2b';
import { registerOTel } from '@vercel/otel';
// Register OpenTelemetry with E2B instrumentation
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [
new E2BInstrumentation(),
],
});
}
// Create and use code interpreter - all operations are automatically traced
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
// Execute Python code - fully traced
const execution = await sandbox.notebook.execCell('print("Hello, World!")');
console.log(execution.text);
await sandbox.close();
```
This is the simplest approach—just add the instrumentation and all E2B operations are automatically traced!
## Configuration Options
```typescript theme={null}
new E2BInstrumentation({
captureCodeContent: true, // Include code in traces (default: true)
maxCodeLength: 1000, // Max code length (default: 1000)
captureOutput: true, // Include execution output (default: true)
maxOutputLength: 1000, // Max output length (default: 1000)
})
```
By default, code and output are captured in spans. You can disable this by setting the respective options to `false` for sensitive environments.
## What You Get
Each E2B operation automatically creates a span with rich telemetry data:
* **Span name**: `e2b.sandbox.create`, `e2b.notebook.execCell`, `e2b.filesystem.write`, etc.
* **Operation type**: Type of E2B operation (create, execute, read, write, etc.)
* **Code content**: The code being executed (configurable)
* **Execution output**: Results from code execution (configurable)
* **Sandbox ID**: Unique identifier for the sandbox
* **Execution time**: Duration of operations
* Sandbox creation and initialization
* Sandbox status changes
* Sandbox termination
* Resource allocation and usage
* Cell execution start and completion
* Code content and language
* Execution results (stdout, stderr, return values)
* Execution errors and stack traces
* File reads and writes
* File paths and sizes
* File system operations
* Exceptions are recorded with stack traces
* Proper span status (OK, ERROR)
* Error messages and codes
## Span Attributes
The instrumentation adds the following attributes to each span:
| Attribute | Description | Example |
| ---------------------- | -------------------- | ------------------- |
| `e2b.operation` | Type of operation | `notebook.execCell` |
| `e2b.sandbox.id` | Sandbox identifier | `sandbox-abc123` |
| `e2b.code` | Code being executed | `print("Hello")` |
| `e2b.language` | Programming language | `python` |
| `e2b.output` | Execution output | `Hello\n` |
| `e2b.execution.status` | Execution status | `success` |
## Usage Examples
### Basic Code Execution
```typescript Simple Execution theme={null}
import { CodeInterpreter } from '@e2b/code-interpreter';
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
// Traced as: e2b.notebook.execCell
const result = await sandbox.notebook.execCell(`
import numpy as np
data = np.array([1, 2, 3, 4, 5])
print(f"Mean: {data.mean()}")
`);
console.log(result.text); // Mean: 3.0
await sandbox.close();
```
```typescript Multiple Cells theme={null}
import { CodeInterpreter } from '@e2b/code-interpreter';
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
// Each cell execution is traced separately
await sandbox.notebook.execCell('import pandas as pd');
await sandbox.notebook.execCell('df = pd.DataFrame({"a": [1, 2, 3]})');
const result = await sandbox.notebook.execCell('print(df.describe())');
console.log(result.text);
await sandbox.close();
```
```typescript With Error Handling theme={null}
import { CodeInterpreter } from '@e2b/code-interpreter';
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
try {
// Errors are captured in traces
const result = await sandbox.notebook.execCell('1 / 0');
} catch (error) {
console.error('Execution failed:', error);
}
await sandbox.close();
```
### File Operations
```typescript theme={null}
import { CodeInterpreter } from '@e2b/code-interpreter';
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
// Traced as: e2b.filesystem.write
await sandbox.filesystem.write('/data/input.csv', 'name,age\nAlice,30\nBob,25');
// Execute code that uses the file
const result = await sandbox.notebook.execCell(`
import pandas as pd
df = pd.read_csv('/data/input.csv')
print(df.head())
`);
// Traced as: e2b.filesystem.read
const output = await sandbox.filesystem.read('/data/output.csv');
await sandbox.close();
```
### Streaming Execution
```typescript theme={null}
import { CodeInterpreter } from '@e2b/code-interpreter';
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
// Stream execution results - traced from start to finish
const execution = sandbox.notebook.execCell(`
for i in range(10):
print(f"Processing {i}")
time.sleep(0.1)
`);
// Process streaming output
execution.onStdout((output) => {
console.log('stdout:', output);
});
execution.onStderr((error) => {
console.error('stderr:', error);
});
await execution;
await sandbox.close();
```
## Complete Integration Example
Here's a complete example of E2B with OpenTelemetry in a Next.js application:
```typescript lib/e2b.ts theme={null}
import { CodeInterpreter } from '@e2b/code-interpreter';
export async function executeCode(code: string) {
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
try {
const result = await sandbox.notebook.execCell(code);
return {
success: true,
output: result.text,
error: result.error,
};
} finally {
await sandbox.close();
}
}
```
```typescript instrumentation.ts theme={null}
import { registerOTel } from '@vercel/otel';
import { E2BInstrumentation } from '@kubiks/otel-e2b';
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [
new E2BInstrumentation({
captureCodeContent: true,
captureOutput: true,
}),
],
});
}
```
```typescript app/api/execute/route.ts theme={null}
import { NextRequest, NextResponse } from 'next/server';
import { executeCode } from '@/lib/e2b';
export async function POST(request: NextRequest) {
const { code } = await request.json();
// Automatically traced
const result = await executeCode(code);
return NextResponse.json(result);
}
```
## Best Practices
Creating sandboxes is expensive. Reuse them for multiple operations:
```typescript theme={null}
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
});
// Execute multiple cells in the same sandbox
await sandbox.notebook.execCell(code1);
await sandbox.notebook.execCell(code2);
await sandbox.notebook.execCell(code3);
await sandbox.close();
```
Set appropriate timeouts for long-running code:
```typescript theme={null}
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY,
timeout: 60_000, // 60 seconds
});
```
Always close sandboxes to avoid resource leaks:
```typescript theme={null}
try {
const result = await sandbox.notebook.execCell(code);
return result;
} finally {
await sandbox.close();
}
```
Use traces to understand sandbox usage patterns and optimize costs.
## Performance Considerations
The instrumentation adds minimal overhead for tracing operations.
Use OpenTelemetry sampling for high-volume applications:
```typescript theme={null}
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
registerOTel({
serviceName: 'your-app',
sampler: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces
});
```
## Troubleshooting
Ensure OpenTelemetry is initialized before creating E2B sandboxes:
```typescript theme={null}
// In instrumentation.ts or instrumentation.node.ts
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [new E2BInstrumentation()],
});
}
```
Check that `captureCodeContent` is enabled:
```typescript theme={null}
new E2BInstrumentation({
captureCodeContent: true,
maxCodeLength: 2000,
})
```
Verify your E2B API key is set correctly:
```typescript theme={null}
const sandbox = await CodeInterpreter.create({
apiKey: process.env.E2B_API_KEY, // Make sure this is set
});
```
## Resources
Learn more about E2B
View source code and examples
View package on npm
Found a bug? Let us know!
## License
MIT
# Inbound
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-inbound
OpenTelemetry instrumentation for inbound HTTP requests
## Overview
`@kubiks/otel-inbound` provides comprehensive OpenTelemetry instrumentation for inbound HTTP requests. Automatically trace all incoming requests to your application with detailed metadata about request/response cycles, headers, status codes, and performance metrics.
Visualize every inbound HTTP request with detailed span information including URL, method, headers, response status, and timing.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-inbound
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-inbound
```
```bash yarn theme={null}
yarn add @kubiks/otel-inbound
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0
## Supported Frameworks
Works with any TypeScript framework and Node.js runtime:
App Router & Pages Router
High-performance server
Enterprise framework
Classic Node.js server
Full-stack framework
Modern web framework
## Supported Platforms
Works with any observability platform that supports OpenTelemetry:
* [Kubiks](https://kubiks.ai)
* [Sentry](https://sentry.io)
* [Axiom](https://axiom.co)
* [Datadog](https://www.datadoghq.com)
* [New Relic](https://newrelic.com)
* [SigNoz](https://signoz.io)
* And many more...
## Quick Start
Use `InboundInstrumentation` to automatically trace all incoming HTTP requests:
```typescript theme={null}
import { InboundInstrumentation } from '@kubiks/otel-inbound';
import { registerOTel } from '@vercel/otel';
// Register OpenTelemetry with Inbound instrumentation
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [
new InboundInstrumentation(),
],
});
}
// That's it! All inbound HTTP requests are now automatically traced
```
This is zero-config—just add the instrumentation and all inbound requests are automatically traced with no code changes required!
## Configuration Options
```typescript theme={null}
new InboundInstrumentation({
captureHeaders: true, // Capture request/response headers (default: true)
captureBody: false, // Capture request/response body (default: false)
maxBodyLength: 1000, // Max body length to capture (default: 1000)
ignorePaths: ['/health'], // Paths to ignore (default: [])
ignoreUserAgents: [], // User agents to ignore (default: [])
captureQueryString: true, // Include query strings (default: true)
})
```
By default, headers and query strings are captured but not request/response bodies. Enable `captureBody` carefully as it can expose sensitive data.
## What You Get
Each inbound HTTP request automatically creates a span with rich telemetry data:
* **Span name**: HTTP method + route (e.g., `GET /api/users`)
* **HTTP method**: GET, POST, PUT, DELETE, etc.
* **URL**: Full request URL including query parameters
* **Route**: Matched route pattern
* **Status code**: Response status code (200, 404, 500, etc.)
* **Duration**: Total request/response time
* Request headers (configurable)
* Query parameters
* Request body (optional)
* User agent
* Client IP address
* Content type and length
* Response status code
* Response headers (configurable)
* Response body (optional)
* Content type and length
* Total request duration
* Time to first byte
* Response size
* Exceptions are recorded with stack traces
* Proper span status (OK, ERROR)
* Error messages and HTTP status codes
* Failed request details
## Span Attributes
The instrumentation adds the following attributes to each span following [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/http/):
| Attribute | Description | Example |
| ------------------------------ | --------------------- | -------------------------------------- |
| `http.method` | HTTP method | `GET` |
| `http.url` | Full URL | `https://api.example.com/users?page=1` |
| `http.route` | Route pattern | `/api/users` |
| `http.status_code` | Response status | `200` |
| `http.user_agent` | Client user agent | `Mozilla/5.0...` |
| `http.client_ip` | Client IP | `192.168.1.1` |
| `http.request_content_length` | Request size (bytes) | `1024` |
| `http.response_content_length` | Response size (bytes) | `2048` |
## Usage Examples
### Basic HTTP Tracing
```typescript Next.js App Router theme={null}
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
// All requests are automatically traced
export async function GET(request: NextRequest) {
const users = await fetchUsers();
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const user = await createUser(body);
return NextResponse.json(user, { status: 201 });
}
```
```typescript Next.js Pages Router theme={null}
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';
// All requests are automatically traced
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'GET') {
const users = await fetchUsers();
return res.status(200).json(users);
}
if (req.method === 'POST') {
const user = await createUser(req.body);
return res.status(201).json(user);
}
return res.status(405).json({ error: 'Method not allowed' });
}
```
### Custom Configuration
```typescript theme={null}
import { InboundInstrumentation } from '@kubiks/otel-inbound';
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [
new InboundInstrumentation({
// Capture headers except sensitive ones
captureHeaders: true,
// Don't trace health checks
ignorePaths: ['/health', '/ping', '/metrics'],
// Don't trace monitoring bots
ignoreUserAgents: ['UptimeRobot', 'Pingdom'],
// Capture query strings for analytics
captureQueryString: true,
// Don't capture request bodies (may contain sensitive data)
captureBody: false,
}),
],
});
}
```
### Error Handling
```typescript theme={null}
// app/api/error-test/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
try {
// This error will be captured in the trace
throw new Error('Something went wrong');
} catch (error) {
// Error details are automatically added to the span
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
```
## Complete Integration Example
Here's a complete example with Inbound instrumentation in a Next.js application:
```typescript instrumentation.ts theme={null}
import { registerOTel } from '@vercel/otel';
import { InboundInstrumentation } from '@kubiks/otel-inbound';
export function register() {
registerOTel({
serviceName: 'my-next-app',
instrumentations: [
new InboundInstrumentation({
captureHeaders: true,
ignorePaths: ['/health', '/_next'],
captureQueryString: true,
}),
],
});
}
```
```typescript app/api/users/[id]/route.ts theme={null}
import { NextRequest, NextResponse } from 'next/server';
// GET /api/users/123 - automatically traced
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const user = await fetchUser(params.id);
if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}
return NextResponse.json(user);
}
// PUT /api/users/123 - automatically traced
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const body = await request.json();
const user = await updateUser(params.id, body);
return NextResponse.json(user);
}
// DELETE /api/users/123 - automatically traced
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
await deleteUser(params.id);
return NextResponse.json({ success: true });
}
```
## Best Practices
Be careful with capturing headers and bodies:
```typescript theme={null}
new InboundInstrumentation({
captureHeaders: true,
captureBody: false, // Bodies may contain sensitive data
// Filter sensitive headers in your collector/exporter
})
```
Exclude monitoring endpoints to reduce noise:
```typescript theme={null}
new InboundInstrumentation({
ignorePaths: [
'/health',
'/ping',
'/metrics',
'/_next/static',
],
})
```
Use traces to identify slow endpoints and optimize them:
* Look for high-duration spans
* Identify N+1 query problems
* Optimize database queries
* Add caching where appropriate
Configure alerts for high error rates or slow responses based on span data.
## Performance Considerations
The instrumentation adds minimal overhead (\~1ms per request) for tracing operations.
Use sampling for high-traffic applications:
```typescript theme={null}
import { TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
registerOTel({
serviceName: 'your-app',
sampler: new TraceIdRatioBasedSampler(0.1), // Sample 10% of traces
});
```
Don't trace static assets to reduce volume:
```typescript theme={null}
new InboundInstrumentation({
ignorePaths: [
'/_next/static',
'/static',
'/favicon.ico',
'/*.png',
],
})
```
## Troubleshooting
Ensure OpenTelemetry is initialized before the server starts:
```typescript theme={null}
// In instrumentation.ts or instrumentation.node.ts
export function register() {
registerOTel({
serviceName: 'your-app',
instrumentations: [new InboundInstrumentation()],
});
}
```
Check that `captureHeaders` is enabled:
```typescript theme={null}
new InboundInstrumentation({
captureHeaders: true,
})
```
Check if they're in the ignore list:
```typescript theme={null}
new InboundInstrumentation({
ignorePaths: ['/health'], // These paths won't be traced
})
```
Consider sampling or ignoring more paths:
```typescript theme={null}
new InboundInstrumentation({
ignorePaths: [
'/health',
'/_next',
'/static',
],
})
```
## Resources
Learn about HTTP semantic conventions
View source code and examples
View package on npm
Found a bug? Let us know!
## License
MIT
# MongoDB
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-mongodb
OpenTelemetry instrumentation for MongoDB database operations
## Overview
`@kubiks/otel-mongodb` provides comprehensive OpenTelemetry instrumentation for the [MongoDB Node.js driver](https://www.mongodb.com/docs/drivers/node/). Capture spans for all database operations with detailed metadata about collections, queries, and execution metrics.
Visualize your MongoDB operations with detailed span information including collection names, operation types, and execution metrics.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-mongodb
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-mongodb
```
```bash yarn theme={null}
yarn add @kubiks/otel-mongodb
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `mongodb` >= 5.0.0
## Quick Start
```typescript theme={null}
import { MongoClient } from "mongodb";
import { instrumentMongoClient } from "@kubiks/otel-mongodb";
const client = new MongoClient(process.env.MONGODB_URI!);
await client.connect();
instrumentMongoClient(client, {
captureFilters: true,
peerName: "mongodb.example.com",
peerPort: 27017,
});
const db = client.db("myapp");
const users = db.collection("users");
const user = await users.findOne({ email: "user@example.com" });
```
`instrumentMongoClient` wraps the client you already use—no configuration changes needed. Every database operation creates a client span with useful attributes.
## What Gets Traced
This instrumentation automatically traces all major MongoDB operations including:
`find`, `findOne`
`insertOne`, `insertMany`
`updateOne`, `updateMany`, `findOneAndUpdate`
`deleteOne`, `deleteMany`, `findOneAndDelete`
`aggregate`
`countDocuments`
## Configuration
### With Filter Capture
```typescript theme={null}
instrumentMongoClient(client, {
captureFilters: true, // Capture query filters (default: false)
peerName: "mongodb.example.com",
peerPort: 27017,
});
```
Filter capture is **disabled by default** to protect sensitive data. Only enable in secure, development environments or ensure filters don't contain sensitive information.
## Span Attributes
Each span includes rich metadata about the database operation following OpenTelemetry semantic conventions:
| Attribute | Description | Example |
| --------------------------- | ------------------------------------- | ----------------------- |
| `db.system` | Constant value `mongodb` | `mongodb` |
| `db.operation` | MongoDB operation type | `findOne`, `insertMany` |
| `db.mongodb.collection` | Collection name | `users` |
| `db.name` | Database name | `myapp` |
| `net.peer.name` | MongoDB server hostname | `mongodb.example.com` |
| `net.peer.port` | MongoDB server port | `27017` |
| `mongodb.filter` | Query filter (when enabled) | `{"status":"active"}` |
| `mongodb.result_count` | Number of documents returned | `42` |
| `mongodb.inserted_count` | Number of documents inserted | `5` |
| `mongodb.matched_count` | Number of documents matched (updates) | `10` |
| `mongodb.modified_count` | Number of documents modified | `8` |
| `mongodb.deleted_count` | Number of documents deleted | `15` |
| `mongodb.execution_time_ms` | Query execution time (when enabled) | `42.5` |
| `mongodb.pipeline` | Aggregation pipeline | `[{"$match":...}]` |
The instrumentation captures query metadata to help with debugging and monitoring, while optionally capturing filters based on your security requirements.
## Usage Examples
### Basic Find Operations
```typescript Find One theme={null}
import { users } from "@/lib/mongodb";
const user = await users.findOne({ email: "user@example.com" });
// Traced with:
// - db.operation: "findOne"
// - db.mongodb.collection: "users"
// - mongodb.result_count: 1
```
```typescript Find Many theme={null}
const activeUsers = await users.find({ status: "active" }).toArray();
// Traced with:
// - db.operation: "find"
// - db.mongodb.collection: "users"
// - mongodb.result_count: 42
// - mongodb.filter: {"status":"active"} (if captureFilters: true)
```
```typescript With Projection theme={null}
const users = await collection.find(
{ status: "active" },
{ projection: { name: 1, email: 1 } }
).toArray();
// Traced with projection metadata
```
### Insert Operations
```typescript Insert One theme={null}
const result = await users.insertOne({
name: "John Doe",
email: "john@example.com",
status: "active",
});
// Traced with:
// - db.operation: "insertOne"
// - mongodb.inserted_count: 1
```
```typescript Insert Many theme={null}
const result = await users.insertMany([
{ name: "John", email: "john@example.com" },
{ name: "Jane", email: "jane@example.com" },
{ name: "Bob", email: "bob@example.com" },
]);
// Traced with:
// - db.operation: "insertMany"
// - mongodb.inserted_count: 3
```
### Update Operations
```typescript Update One theme={null}
const result = await users.updateOne(
{ email: "user@example.com" },
{ $set: { status: "inactive" } }
);
// Traced with:
// - db.operation: "updateOne"
// - mongodb.matched_count: 1
// - mongodb.modified_count: 1
```
```typescript Update Many theme={null}
const result = await users.updateMany(
{ status: "pending" },
{ $set: { status: "active" } }
);
// Traced with:
// - db.operation: "updateMany"
// - mongodb.matched_count: 10
// - mongodb.modified_count: 10
```
```typescript Find and Update theme={null}
const user = await users.findOneAndUpdate(
{ email: "user@example.com" },
{ $set: { lastLogin: new Date() } },
{ returnDocument: "after" }
);
// Traced with:
// - db.operation: "findOneAndUpdate"
// - mongodb.matched_count: 1
// - mongodb.modified_count: 1
```
### Delete Operations
```typescript Delete One theme={null}
const result = await users.deleteOne({ email: "user@example.com" });
// Traced with:
// - db.operation: "deleteOne"
// - mongodb.deleted_count: 1
```
```typescript Delete Many theme={null}
const result = await users.deleteMany({ status: "inactive" });
// Traced with:
// - db.operation: "deleteMany"
// - mongodb.deleted_count: 15
```
### Aggregation Pipeline
```typescript theme={null}
const pipeline = [
{ $match: { status: "active" } },
{ $group: { _id: "$country", count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 10 },
];
const results = await users.aggregate(pipeline).toArray();
// Traced with:
// - db.operation: "aggregate"
// - mongodb.pipeline: [{"$match":...},{"$group":...}]
// - mongodb.result_count: 10
```
### Count Operations
```typescript Count Documents theme={null}
const count = await users.countDocuments({ status: "active" });
// Traced with:
// - db.operation: "countDocuments"
// - mongodb.result_count: 42
```
```typescript Estimated Count theme={null}
const count = await users.estimatedDocumentCount();
// Traced with:
// - db.operation: "estimatedDocumentCount"
```
## Complete Integration Example
Here's a complete example of MongoDB with OpenTelemetry in a Next.js application:
### Setup
```typescript lib/mongodb.ts theme={null}
import { MongoClient } from "mongodb";
import { instrumentMongoClient } from "@kubiks/otel-mongodb";
if (!process.env.MONGODB_URI) {
throw new Error("MONGODB_URI environment variable is not set");
}
const client = new MongoClient(process.env.MONGODB_URI);
let clientPromise: Promise;
if (process.env.NODE_ENV === "development") {
// In development, use a global variable to preserve the connection
let globalWithMongo = global as typeof globalThis & {
_mongoClientPromise?: Promise;
};
if (!globalWithMongo._mongoClientPromise) {
clientPromise = client.connect();
instrumentMongoClient(client, {
captureFilters: true,
peerName: new URL(process.env.MONGODB_URI).hostname,
peerPort: parseInt(new URL(process.env.MONGODB_URI).port || "27017"),
});
globalWithMongo._mongoClientPromise = clientPromise;
} else {
clientPromise = globalWithMongo._mongoClientPromise;
}
} else {
// In production, create a new connection
clientPromise = client.connect();
instrumentMongoClient(client, {
captureFilters: false, // Disable in production
peerName: new URL(process.env.MONGODB_URI).hostname,
peerPort: parseInt(new URL(process.env.MONGODB_URI).port || "27017"),
});
}
export default clientPromise;
export async function getDatabase() {
const client = await clientPromise;
return client.db("myapp");
}
export async function getCollection(name: string) {
const db = await getDatabase();
return db.collection(name);
}
```
### Usage in Server Actions
```typescript app/actions/users.ts theme={null}
"use server";
import { getCollection } from "@/lib/mongodb";
import { ObjectId } from "mongodb";
export async function getUser(userId: string) {
const users = await getCollection("users");
return await users.findOne({ _id: new ObjectId(userId) });
}
export async function createUser(data: { name: string; email: string }) {
const users = await getCollection("users");
const result = await users.insertOne({
...data,
createdAt: new Date(),
status: "active",
});
return { id: result.insertedId.toString() };
}
export async function updateUser(userId: string, data: Partial<{ name: string; email: string }>) {
const users = await getCollection("users");
const result = await users.updateOne(
{ _id: new ObjectId(userId) },
{ $set: { ...data, updatedAt: new Date() } }
);
return { success: result.modifiedCount > 0 };
}
export async function deleteUser(userId: string) {
const users = await getCollection("users");
const result = await users.deleteOne({ _id: new ObjectId(userId) });
return { success: result.deletedCount > 0 };
}
export async function getActiveUsers() {
const users = await getCollection("users");
return await users.find({ status: "active" }).toArray();
}
```
### Usage in API Routes
```typescript app/api/stats/route.ts theme={null}
import { NextResponse } from "next/server";
import { getCollection } from "@/lib/mongodb";
export async function GET() {
const users = await getCollection("users");
const stats = await users.aggregate([
{
$group: {
_id: "$status",
count: { $sum: 1 },
},
},
{
$project: {
status: "$_id",
count: 1,
_id: 0,
},
},
]).toArray();
return NextResponse.json({ stats });
}
```
## Best Practices
Always reuse the MongoDB client connection rather than creating new connections:
```typescript theme={null}
// Good: Reuse client
const client = await clientPromise;
// Bad: Create new client each time
const client = new MongoClient(uri);
await client.connect();
```
Only enable filter capture in development or when filters don't contain sensitive data:
```typescript theme={null}
instrumentMongoClient(client, {
captureFilters: process.env.NODE_ENV === "development",
});
```
Ensure proper indexes are created for frequently queried fields:
```typescript theme={null}
await users.createIndex({ email: 1 }, { unique: true });
await users.createIndex({ status: 1 });
```
Always handle MongoDB errors in your application:
```typescript theme={null}
try {
const user = await users.findOne({ email });
if (!user) {
throw new Error("User not found");
}
return user;
} catch (error) {
console.error("MongoDB error:", error);
throw error;
}
```
## Troubleshooting
Ensure OpenTelemetry is initialized before connecting to MongoDB:
```typescript theme={null}
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({
// ... configuration
});
sdk.start();
// Then connect to MongoDB
const client = await clientPromise;
```
Make sure your MongoDB URI is correct and the server is accessible:
```bash theme={null}
MONGODB_URI=mongodb://localhost:27017/myapp
# or for MongoDB Atlas:
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/myapp
```
Ensure you've configured peer information:
```typescript theme={null}
instrumentMongoClient(client, {
peerName: "mongodb.example.com",
peerPort: 27017,
});
```
## Resources
Learn more about MongoDB Node.js driver
View source code and examples
View package on npm
Found a bug? Let us know!
## License
MIT
# Resend Email
Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-resend
OpenTelemetry instrumentation for Resend email service
## Overview
`@kubiks/otel-resend` provides OpenTelemetry instrumentation for the [Resend](https://resend.com) email service Node.js SDK. Capture spans for every email operation with detailed metadata about recipients, subjects, and delivery status.
Visualize your email operations with detailed span information including recipients, subject lines, and delivery status—without capturing sensitive email content.
## Installation
```bash npm theme={null}
npm install @kubiks/otel-resend
```
```bash pnpm theme={null}
pnpm add @kubiks/otel-resend
```
```bash yarn theme={null}
yarn add @kubiks/otel-resend
```
**Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `resend` >= 3.0.0
## Quick Start
```typescript theme={null}
import { Resend } from "resend";
import { instrumentResend } from "@kubiks/otel-resend";
const resend = instrumentResend(new Resend(process.env.RESEND_API_KEY!));
await resend.emails.send({
from: "hello@example.com",
to: ["user@example.com"],
subject: "Welcome",
html: "
Hello world
",
});
```
`instrumentResend` wraps the instance you already use—no configuration changes needed. Every SDK call creates a client span with useful attributes.
## What Gets Traced
This instrumentation specifically wraps the `resend.emails.send` method (and its alias `resend.emails.create`), creating a single clean span for each email send operation.
Only metadata is captured—email content (HTML, text, attachments) is never included in traces for privacy and security.
## Span Attributes
Each span includes rich metadata about the email operation:
| Attribute | Description | Example |
| ------------------------ | --------------------------------------------------- | --------------------------------------- |
| `messaging.system` | Constant value `resend` | `resend` |
| `messaging.operation` | Operation type | `send` |
| `resend.resource` | Resource name | `emails` |
| `resend.target` | Full operation target | `emails.send` |
| `resend.to_addresses` | Comma-separated TO addresses | `user@example.com, another@example.com` |
| `resend.cc_addresses` | Comma-separated CC addresses (if present) | `cc@example.com` |
| `resend.bcc_addresses` | Comma-separated BCC addresses (if present) | `bcc@example.com` |
| `resend.recipient_count` | Total number of recipients | `3` |
| `resend.from` | Sender email address | `noreply@example.com` |
| `resend.subject` | Email subject | `Welcome to our service` |
| `resend.template_id` | Template ID (if using templates) | `tmpl_123` |
| `resend.message_id` | Message ID returned by Resend | `email_123` |
| `resend.message_count` | Number of messages sent (always 1 for single sends) | `1` |
The instrumentation captures email addresses and metadata to help with debugging and monitoring, while avoiding sensitive email content.
## Usage Examples
### Basic Email
```typescript Simple Email theme={null}
import { resend } from "@/lib/resend";
await resend.emails.send({
from: "noreply@example.com",
to: "user@example.com",
subject: "Welcome to our platform",
html: "