# 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. Autumn Trace Visualization 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. Better Auth Trace Visualization 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. ClickHouse Trace Visualization 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. Drizzle ORM Trace Visualization 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. E2B Trace Visualization 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. Inbound Request Trace Visualization 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. MongoDB Trace Visualization 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. Resend Trace Visualization 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: "

Welcome!

Thanks for signing up.

", }); // Traced with: // - resend.from: "noreply@example.com" // - resend.to_addresses: "user@example.com" // - resend.subject: "Welcome to our platform" // - resend.recipient_count: 1 ``` ```typescript Multiple Recipients theme={null} import { resend } from "@/lib/resend"; await resend.emails.send({ from: "newsletter@example.com", to: ["user1@example.com", "user2@example.com", "user3@example.com"], subject: "Monthly Newsletter", html: "

This Month's Updates

", }); // Traced with: // - resend.to_addresses: "user1@example.com, user2@example.com, user3@example.com" // - resend.recipient_count: 3 ``` ```typescript Plain Text Email theme={null} import { resend } from "@/lib/resend"; await resend.emails.send({ from: "support@example.com", to: "user@example.com", subject: "Password Reset", text: "Click here to reset your password: https://example.com/reset", }); ```
### With CC and BCC ```typescript theme={null} import { resend } from "@/lib/resend"; await resend.emails.send({ from: "sales@example.com", to: "customer@example.com", cc: ["manager@example.com", "team@example.com"], bcc: "archive@example.com", subject: "Project Proposal", html: "

Please find the proposal attached.

", }); // Traced with: // - resend.to_addresses: "customer@example.com" // - resend.cc_addresses: "manager@example.com, team@example.com" // - resend.bcc_addresses: "archive@example.com" // - resend.recipient_count: 4 ``` BCC addresses are included in the span but remain hidden from other recipients as expected. ### Using Email Templates ```typescript React Email Template theme={null} import { resend } from "@/lib/resend"; import { WelcomeEmail } from "@/emails/welcome"; await resend.emails.send({ from: "onboarding@example.com", to: "user@example.com", subject: "Welcome aboard!", react: WelcomeEmail({ name: "John" }), }); ``` ```typescript Resend Template theme={null} import { resend } from "@/lib/resend"; await resend.emails.send({ from: "notifications@example.com", to: "user@example.com", subject: "Order Confirmation", template: "order-confirmation", template_id: "tmpl_abc123", }); // Traced with: // - resend.template_id: "tmpl_abc123" ``` ### With Attachments ```typescript theme={null} import { resend } from "@/lib/resend"; import fs from "fs"; await resend.emails.send({ from: "documents@example.com", to: "user@example.com", subject: "Your Invoice", html: "

Please find your invoice attached.

", attachments: [ { filename: "invoice.pdf", content: fs.readFileSync("./invoice.pdf"), }, ], }); // Note: Attachment content is NOT captured in traces ``` ### Transactional Emails ```typescript Password Reset theme={null} import { resend } from "@/lib/resend"; export async function sendPasswordResetEmail(email: string, token: string) { await resend.emails.send({ from: "security@example.com", to: email, subject: "Reset your password", html: `

Password Reset Request

Click the link below to reset your password:

Reset Password

This link expires in 1 hour.

`, }); } ``` ```typescript Email Verification theme={null} import { resend } from "@/lib/resend"; export async function sendVerificationEmail(email: string, code: string) { await resend.emails.send({ from: "verify@example.com", to: email, subject: "Verify your email address", html: `

Verify Your Email

Your verification code is: ${code}

This code expires in 15 minutes.

`, }); } ``` ```typescript Order Confirmation theme={null} import { resend } from "@/lib/resend"; export async function sendOrderConfirmation( email: string, orderNumber: string, amount: number ) { await resend.emails.send({ from: "orders@example.com", to: email, subject: `Order Confirmation #${orderNumber}`, html: `

Thank You for Your Order!

Order Number: ${orderNumber}

Total: $${amount.toFixed(2)}

`, }); } ```
## Complete Integration Example Here's a complete example of Resend with OpenTelemetry in a Next.js application: ```typescript lib/resend.ts theme={null} import { Resend } from "resend"; import { instrumentResend } from "@kubiks/otel-resend"; export const resend = instrumentResend( new Resend(process.env.RESEND_API_KEY!) ); ``` ```typescript lib/email.ts theme={null} import { resend } from "@/lib/resend"; export async function sendWelcomeEmail(email: string, name: string) { try { const { data, error } = await resend.emails.send({ from: "onboarding@example.com", to: email, subject: `Welcome ${name}!`, html: `

Welcome to our platform, ${name}!

We're excited to have you on board.

`, }); if (error) { console.error("Failed to send welcome email:", error); return { success: false, error }; } return { success: true, messageId: data?.id }; } catch (error) { console.error("Error sending email:", error); return { success: false, error }; } } export async function sendNotification( email: string, subject: string, message: string ) { const { data, error } = await resend.emails.send({ from: "notifications@example.com", to: email, subject, html: `

${message}

`, }); return { data, error }; } ``` ```typescript app/api/auth/signup/route.ts theme={null} import { NextRequest, NextResponse } from "next/server"; import { sendWelcomeEmail } from "@/lib/email"; export async function POST(request: NextRequest) { const { email, name } = await request.json(); // Create user... // Send welcome email (automatically traced) const result = await sendWelcomeEmail(email, name); if (!result.success) { return NextResponse.json( { error: "Failed to send welcome email" }, { status: 500 } ); } return NextResponse.json({ success: true, messageId: result.messageId }); } ``` ```typescript app/actions/email.ts theme={null} "use server"; import { resend } from "@/lib/resend"; export async function sendContactFormEmail( name: string, email: string, message: string ) { const { data, error } = await resend.emails.send({ from: "contact@example.com", to: "support@example.com", replyTo: email, subject: `Contact Form: ${name}`, html: `

New Contact Form Submission

Name: ${name}

Email: ${email}

Message:

${message}

`, }); if (error) { return { success: false, error: error.message }; } return { success: true, messageId: data?.id }; } ``` ## Best Practices Always store API keys in environment variables: ```typescript theme={null} const resend = instrumentResend( new Resend(process.env.RESEND_API_KEY!) ); ``` Never commit API keys to version control. Always check for errors when sending emails: ```typescript theme={null} const { data, error } = await resend.emails.send({ from: "noreply@example.com", to: email, subject: "Test", html: "

Test

", }); if (error) { console.error("Email error:", error); // Handle error appropriately return; } console.log("Email sent:", data?.id); ```
Set up domain verification in Resend for production: ```typescript theme={null} // Use your verified domain from: "noreply@yourdomain.com" // Not: "noreply@example.com" ``` Be mindful of Resend rate limits and implement appropriate rate limiting: ```typescript theme={null} import { Ratelimit } from "@upstash/ratelimit"; import { Redis } from "@upstash/redis"; const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, "1 h"), // 10 emails per hour }); export async function sendEmail(to: string, subject: string, html: string) { const { success } = await ratelimit.limit(to); if (!success) { throw new Error("Rate limit exceeded"); } return await resend.emails.send({ from: "noreply@example.com", to, subject, html, }); } ``` Use React Email or Resend templates for maintainable email content: ```typescript theme={null} import { WelcomeEmail } from "@/emails/welcome"; await resend.emails.send({ from: "onboarding@example.com", to: email, subject: "Welcome!", react: WelcomeEmail({ name: userName }), }); ```
## Troubleshooting Ensure OpenTelemetry is properly configured: ```typescript theme={null} import { NodeSDK } from "@opentelemetry/sdk-node"; const sdk = new NodeSDK({ // ... configuration }); sdk.start(); ``` The message ID is only available after successful email sending. Check for errors: ```typescript theme={null} const { data, error } = await resend.emails.send({ ... }); if (error) { console.error("No message ID because of error:", error); } else { console.log("Message ID:", data?.id); } ``` Check your Resend dashboard for delivery status. Common issues: * Domain not verified * Invalid recipient address * Rate limits exceeded * API key issues ## Integration with React Email ```typescript Email Component theme={null} // emails/welcome.tsx import { Body, Container, Head, Heading, Html, Link, Preview, Text, } from "@react-email/components"; interface WelcomeEmailProps { name: string; } export function WelcomeEmail({ name }: WelcomeEmailProps) { return ( Welcome to our platform! Welcome, {name}! Thanks for joining us. We're excited to have you on board. Get Started ); } const main = { backgroundColor: "#f6f9fc", fontFamily: "sans-serif" }; const container = { margin: "0 auto", padding: "20px 0 48px" }; const h1 = { fontSize: "32px", fontWeight: "bold" }; const text = { fontSize: "16px", lineHeight: "26px" }; const link = { color: "#5e6ad2", textDecoration: "underline" }; ``` ```typescript Send Email theme={null} import { resend } from "@/lib/resend"; import { WelcomeEmail } from "@/emails/welcome"; await resend.emails.send({ from: "onboarding@example.com", to: "user@example.com", subject: "Welcome aboard!", react: WelcomeEmail({ name: "John" }), }); ``` ## Resources Learn more about Resend View source code and examples View package on npm Build emails with React components ## License MIT # Upstash QStash Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-upstash-queues OpenTelemetry instrumentation for Upstash QStash message queues ## Overview `@kubiks/otel-upstash-queues` provides comprehensive OpenTelemetry instrumentation for [Upstash QStash](https://upstash.com/docs/qstash). Capture spans for both message publishing and consumption with detailed operation metadata and delivery tracking. Upstash QStash Trace Visualization Visualize your message queue operations with detailed span information including message publishing, callbacks, and delivery tracking—from producer to consumer. ## Installation ```bash npm theme={null} npm install @kubiks/otel-upstash-queues ``` ```bash pnpm theme={null} pnpm add @kubiks/otel-upstash-queues ``` ```bash yarn theme={null} yarn add @kubiks/otel-upstash-queues ``` **Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `@upstash/qstash` >= 2.0.0 ## Quick Start ### Publishing Messages ```typescript theme={null} import { Client } from "@upstash/qstash"; import { instrumentUpstash } from "@kubiks/otel-upstash-queues"; const client = instrumentUpstash( new Client({ token: process.env.QSTASH_TOKEN! }) ); await client.publishJSON({ url: "https://your-api-endpoint.com/process-image", body: { imageId: "123" }, }); ``` `instrumentUpstash` wraps the QStash client instance you already use—no configuration changes needed. Every SDK call creates a client span with useful attributes. ### Consuming Messages ```typescript theme={null} // app/api/process/route.ts import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; import { instrumentConsumer } from "@kubiks/otel-upstash-queues"; async function handler(request: Request) { const data = await request.json(); // Process your message await processImage(data.imageId); return Response.json({ success: true }); } // Instrument first, then verify signature export const POST = verifySignatureAppRouter(instrumentConsumer(handler)); ``` `instrumentConsumer` wraps your message handler to trace message consumption, creating a SERVER span for each message received and processed. ## What Gets Traced This instrumentation provides two main functions: Wraps the QStash client to trace **message publishing** with `SpanKind.CLIENT` Wraps your message handler to trace **message consumption** with `SpanKind.SERVER` ## Configuration ### With Body Capture Optionally capture request/response bodies for debugging: ```typescript theme={null} const client = instrumentUpstash( new Client({ token: process.env.QSTASH_TOKEN! }), { captureBody: true, // Enable body capture (default: false) maxBodyLength: 2048, // Max characters to capture (default: 1024) } ); ``` Body capture is **disabled by default** to protect sensitive data. Only enable in secure, development environments. ## Span Attributes ### Publisher Spans (`instrumentUpstash`) | Attribute | Description | Example | | ----------------------------- | ------------------------------------------- | --------------------------------- | | `messaging.system` | Constant value `qstash` | `qstash` | | `messaging.operation` | Operation type | `publish` | | `qstash.resource` | Resource name | `messages` | | `qstash.target` | Full operation target | `messages.publish` | | `qstash.url` | Target URL for the message | `https://example.com/api/process` | | `qstash.method` | HTTP method (default: POST) | `POST`, `PUT`, `GET` | | `qstash.message_id` | Message ID returned by QStash | `msg_123` | | `qstash.delay` | Delay before processing (seconds or string) | `60` or `"1h"` | | `qstash.not_before` | Unix timestamp for earliest processing | `1672531200` | | `qstash.deduplication_id` | Deduplication ID for idempotent operations | `unique-id-123` | | `qstash.retries` | Number of retry attempts (max) | `3` | | `qstash.callback_url` | Success callback URL | `https://example.com/callback` | | `qstash.failure_callback_url` | Failure callback URL | `https://example.com/failure` | ### Consumer Spans (`instrumentConsumer`) | Attribute | Description | Example | | --------------------- | --------------------------------------- | ------------------ | | `messaging.system` | Constant value `qstash` | `qstash` | | `messaging.operation` | Operation type | `receive` | | `qstash.resource` | Resource name | `messages` | | `qstash.target` | Full operation target | `messages.receive` | | `qstash.message_id` | Message ID from QStash | `msg_456` | | `qstash.retried` | Number of times retried (actual count) | `2` | | `qstash.schedule_id` | Schedule ID (if from scheduled message) | `schedule_123` | | `qstash.caller_ip` | IP address of the caller | `192.168.1.1` | | `http.status_code` | HTTP response status code | `200` | ### Body/Payload Attributes (Optional) When `captureBody` is enabled: | Attribute | Description | Captured By | | ---------------------- | ---------------------------- | --------------------------- | | `qstash.request.body` | Request/message body content | Both publisher and consumer | | `qstash.response.body` | Response body content | Consumer only | ## Usage Examples ### Basic Message Publishing ```typescript Simple Message theme={null} import { client } from "@/lib/qstash"; await client.publishJSON({ url: "https://your-api.com/webhook", body: { userId: "user_123", action: "process_data", }, }); // Traced with: // - qstash.url: "https://your-api.com/webhook" // - qstash.method: "POST" // - qstash.message_id: "msg_..." ``` ```typescript Custom Method theme={null} await client.publishJSON({ url: "https://your-api.com/update", method: "PUT", body: { status: "completed" }, }); // Traced with: // - qstash.method: "PUT" ``` ```typescript With Headers theme={null} await client.publishJSON({ url: "https://your-api.com/process", headers: { "X-User-ID": "user_123", "X-Priority": "high", }, body: { taskId: "task_456" }, }); ``` ### Delayed Message Publishing ```typescript Delay in Seconds theme={null} // Delay message processing by 60 seconds await client.publishJSON({ url: "https://your-api.com/delayed-task", body: { taskId: "task_456" }, delay: 60, }); // Traced with: // - qstash.delay: 60 ``` ```typescript Human-Readable Delay theme={null} // Use human-readable delay format await client.publishJSON({ url: "https://your-api.com/delayed-task", body: { taskId: "task_789" }, delay: "1h", // 1 hour }); // Traced with: // - qstash.delay: "1h" // Other examples: // - "30s" (30 seconds) // - "5m" (5 minutes) // - "2h" (2 hours) // - "1d" (1 day) ``` ```typescript Scheduled Time theme={null} // Schedule for a specific time const scheduledTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now await client.publishJSON({ url: "https://your-api.com/scheduled-task", body: { reportId: "report_456" }, notBefore: scheduledTime, }); // Traced with: // - qstash.not_before: 1672531200 ``` ### Message with Callbacks ```typescript Success Callback theme={null} await client.publishJSON({ url: "https://your-api.com/process", body: { orderId: "order_123" }, callback: "https://your-api.com/success", }); // Traced with: // - qstash.callback_url: "https://your-api.com/success" ``` ```typescript Success and Failure Callbacks theme={null} await client.publishJSON({ url: "https://your-api.com/process", body: { orderId: "order_456" }, callback: "https://your-api.com/callbacks/success", failureCallback: "https://your-api.com/callbacks/failure", }); // Traced with: // - qstash.callback_url: "https://your-api.com/callbacks/success" // - qstash.failure_callback_url: "https://your-api.com/callbacks/failure" ``` ### Retries and Deduplication ```typescript With Retries theme={null} await client.publishJSON({ url: "https://your-api.com/critical-task", body: { taskId: "critical_123" }, retries: 5, }); // Traced with: // - qstash.retries: 5 ``` ```typescript With Deduplication theme={null} // Prevent duplicate processing await client.publishJSON({ url: "https://your-api.com/process", body: { orderId: "order_789" }, deduplicationId: `order-${orderId}`, }); // Traced with: // - qstash.deduplication_id: "order-order_789" ``` ```typescript Combined theme={null} await client.publishJSON({ url: "https://your-api.com/critical", body: { transactionId: "tx_123" }, retries: 3, deduplicationId: `tx-${transactionId}`, }); ``` ### Message Consumer ```typescript Basic Consumer theme={null} // app/api/process/route.ts import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; import { instrumentConsumer } from "@kubiks/otel-upstash-queues"; async function handler(request: Request) { const data = await request.json(); // Process your message console.log("Processing:", data); await processTask(data); return Response.json({ success: true }); } // Instrument first, then verify signature export const POST = verifySignatureAppRouter(instrumentConsumer(handler)); // Traced with: // - qstash.message_id: from QStash header // - qstash.retried: retry count // - http.status_code: response status ``` ```typescript With Body Capture theme={null} export const POST = verifySignatureAppRouter( instrumentConsumer(handler, { captureBody: true, maxBodyLength: 2048, }) ); // Additionally captures: // - qstash.request.body: message payload // - qstash.response.body: handler response ``` ```typescript With Error Handling theme={null} async function handler(request: Request) { try { const data = await request.json(); await processTask(data); return Response.json({ success: true }); } catch (error) { console.error("Processing failed:", error); // Error is captured in span return Response.json( { success: false, error: error.message }, { status: 500 } ); } } export const POST = verifySignatureAppRouter(instrumentConsumer(handler)); ``` ## Complete Integration Example Here's a complete example of QStash with OpenTelemetry in a Next.js application: ### Setup ```typescript lib/qstash.ts theme={null} import { Client } from "@upstash/qstash"; import { instrumentUpstash } from "@kubiks/otel-upstash-queues"; export const client = instrumentUpstash( new Client({ token: process.env.QSTASH_TOKEN!, }), { captureBody: process.env.NODE_ENV === "development", maxBodyLength: 2048, } ); ``` ### Publishing Messages ```typescript app/actions/tasks.ts theme={null} "use server"; import { client } from "@/lib/qstash"; export async function enqueueImageProcessing(imageId: string) { try { const result = await client.publishJSON({ url: `${process.env.NEXT_PUBLIC_URL}/api/process/image`, body: { imageId }, retries: 3, deduplicationId: `image-${imageId}`, }); return { success: true, messageId: result.messageId }; } catch (error) { console.error("Failed to enqueue task:", error); return { success: false, error: error.message }; } } export async function scheduleReportGeneration(reportId: string, delay: string) { const result = await client.publishJSON({ url: `${process.env.NEXT_PUBLIC_URL}/api/process/report`, body: { reportId }, delay, callback: `${process.env.NEXT_PUBLIC_URL}/api/callbacks/report-success`, failureCallback: `${process.env.NEXT_PUBLIC_URL}/api/callbacks/report-failure`, }); return { messageId: result.messageId }; } ``` ### Consuming Messages ```typescript app/api/process/image/route.ts theme={null} import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; import { instrumentConsumer } from "@kubiks/otel-upstash-queues"; async function handler(request: Request) { const { imageId } = await request.json(); console.log(`Processing image: ${imageId}`); // Simulate image processing await processImage(imageId); return Response.json({ success: true, imageId, processedAt: new Date().toISOString() }); } export const POST = verifySignatureAppRouter( instrumentConsumer(handler, { captureBody: true, maxBodyLength: 1024, }) ); async function processImage(imageId: string) { // Your image processing logic await new Promise(resolve => setTimeout(resolve, 1000)); console.log(`Image ${imageId} processed`); } ``` ```typescript app/api/process/report/route.ts theme={null} import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; import { instrumentConsumer } from "@kubiks/otel-upstash-queues"; async function handler(request: Request) { const { reportId } = await request.json(); try { const report = await generateReport(reportId); return Response.json({ success: true, report }); } catch (error) { console.error("Report generation failed:", error); return Response.json( { success: false, error: error.message }, { status: 500 } ); } } export const POST = verifySignatureAppRouter(instrumentConsumer(handler)); async function generateReport(reportId: string) { // Your report generation logic return { id: reportId, status: "completed" }; } ``` ### Callback Handlers ```typescript app/api/callbacks/report-success/route.ts theme={null} import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; async function handler(request: Request) { const data = await request.json(); console.log("Report generated successfully:", data); // Update database, send notification, etc. return Response.json({ received: true }); } export const POST = verifySignatureAppRouter(handler); ``` ```typescript app/api/callbacks/report-failure/route.ts theme={null} import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; async function handler(request: Request) { const data = await request.json(); console.error("Report generation failed:", data); // Log error, notify admin, etc. return Response.json({ received: true }); } export const POST = verifySignatureAppRouter(handler); ``` ## Best Practices Always verify QStash signatures to ensure messages are authentic: ```typescript theme={null} import { verifySignatureAppRouter } from "@upstash/qstash/nextjs"; export const POST = verifySignatureAppRouter( instrumentConsumer(handler) ); ``` Use deduplication IDs to prevent duplicate processing: ```typescript theme={null} await client.publishJSON({ url: "https://api.example.com/process", body: { orderId }, deduplicationId: `order-${orderId}`, }); ``` Set retry counts based on operation criticality: ```typescript theme={null} // Critical operations: more retries await client.publishJSON({ url: "https://api.example.com/payment", body: { paymentId }, retries: 5, }); // Non-critical operations: fewer retries await client.publishJSON({ url: "https://api.example.com/analytics", body: { event }, retries: 1, }); ``` Implement callbacks to track message processing: ```typescript theme={null} await client.publishJSON({ url: "https://api.example.com/process", body: { taskId }, callback: "https://api.example.com/callbacks/success", failureCallback: "https://api.example.com/callbacks/failure", }); ``` Always handle errors in consumer handlers: ```typescript theme={null} async function handler(request: Request) { try { const data = await request.json(); await processTask(data); return Response.json({ success: true }); } catch (error) { console.error("Processing failed:", error); return Response.json( { success: false, error: error.message }, { status: 500 } ); } } ``` ## Advanced Patterns ```typescript theme={null} async function enqueueBatch(tasks: Task[]) { const results = await Promise.allSettled( tasks.map(task => client.publishJSON({ url: `${process.env.API_URL}/process`, body: task, deduplicationId: `task-${task.id}`, }) ) ); return results; } ``` ```typescript theme={null} async function enqueueWithPriority(task: Task, priority: "high" | "normal") { await client.publishJSON({ url: `${process.env.API_URL}/process`, headers: { "X-Priority": priority, }, body: task, delay: priority === "high" ? 0 : 60, // High priority: immediate }); } ``` ```typescript theme={null} async function handler(request: Request) { const retryCount = parseInt( request.headers.get("Upstash-Retried") || "0" ); // If max retries reached, send to DLQ if (retryCount >= 3) { await sendToDeadLetterQueue(await request.json()); return Response.json({ handled: true }); } // Normal processing await processTask(await request.json()); return Response.json({ success: true }); } ``` ## Troubleshooting Ensure OpenTelemetry is initialized before using QStash: ```typescript theme={null} import { NodeSDK } from "@opentelemetry/sdk-node"; const sdk = new NodeSDK({ // ... configuration }); sdk.start(); ``` Make sure environment variables are set correctly: ```bash theme={null} QSTASH_TOKEN=your_token QSTASH_CURRENT_SIGNING_KEY=your_current_key QSTASH_NEXT_SIGNING_KEY=your_next_key ``` Check that your endpoint is: * Publicly accessible * Returns 2xx status codes * Responds within timeout (default: 2 minutes) * Has signature verification enabled Ensure `instrumentConsumer` is called before `verifySignatureAppRouter`: ```typescript theme={null} // Correct order export const POST = verifySignatureAppRouter( instrumentConsumer(handler) ); // Wrong order export const POST = instrumentConsumer( verifySignatureAppRouter(handler) ); ``` ## Resources Learn more about Upstash QStash View source code and examples View package on npm Found a bug? Let us know! ## License MIT # Upstash Workflow Source: https://docs.kubiks.ai/opentelemetry-integrations/otel-upstash-workflow OpenTelemetry instrumentation for Upstash Workflow SDK ## Overview `@kubiks/otel-upstash-workflow` provides comprehensive OpenTelemetry instrumentation for the [Upstash Workflow SDK](https://upstash.com/docs/workflow). Capture spans for workflow executions, steps, sleep operations, API calls, and event waiting with detailed performance metrics. Upstash Workflow Trace Visualization Visualize your workflow executions with detailed span information including steps, sleep operations, API calls, and performance metrics. **Pre-release Note:** This package instruments the Upstash Workflow SDK, which is currently in pre-release. The API may change as the Workflow SDK evolves. ## Installation ```bash npm theme={null} npm install @kubiks/otel-upstash-workflow ``` ```bash pnpm theme={null} pnpm add @kubiks/otel-upstash-workflow ``` ```bash yarn theme={null} yarn add @kubiks/otel-upstash-workflow ``` **Peer Dependencies:** `@opentelemetry/api` >= 1.9.0, `@upstash/workflow` >= 0.0.0 ## Quick Start ### Instrumenting Workflow Handlers ```typescript theme={null} import { serve as originalServe } from "@upstash/workflow"; import { instrumentWorkflowServe } from "@kubiks/otel-upstash-workflow"; const serve = instrumentWorkflowServe(originalServe); export const POST = serve(async (context) => { const result1 = await context.run("step-1", async () => { return await processData(); }); await context.sleep("wait-5s", 5); const result2 = await context.run("step-2", async () => { return await saveResults(result1); }); return result2; }); ``` `instrumentWorkflowServe` wraps the serve function to trace the entire workflow execution and all steps—no configuration changes needed. Every workflow execution creates a server span with child spans for each step. ### Instrumenting Workflow Client ```typescript theme={null} import { Client } from "@upstash/workflow"; import { instrumentWorkflowClient } from "@kubiks/otel-upstash-workflow"; const client = instrumentWorkflowClient( new Client({ baseUrl: process.env.QSTASH_URL!, token: process.env.QSTASH_TOKEN! }) ); await client.trigger({ url: "https://your-app.com/api/workflow", body: { data: "example" }, }); ``` `instrumentWorkflowClient` wraps the workflow client to trace workflow triggers, creating client spans for each trigger operation. ## Configuration ### With Step Data Capture Optionally capture step inputs and outputs for debugging: ```typescript theme={null} const serve = instrumentWorkflowServe(originalServe, { captureStepData: true, // Enable step data capture (default: false) maxStepDataLength: 2048, // Max characters to capture (default: 1024) }); export const POST = serve(async (context) => { // Your workflow - all steps are traced with input/output capture }); ``` Step data capture is **disabled by default** to protect sensitive data. Only enable in secure, development environments. ## What Gets Traced This instrumentation provides two main functions: Wraps the Workflow Client to trace workflow triggers with `SpanKind.CLIENT` Wraps the serve function to trace execution and all workflow steps with `SpanKind.SERVER` ### Workflow Handler Instrumentation The `instrumentWorkflowServe` function wraps the serve function, creating a span with `SpanKind.SERVER` for the entire workflow execution. All workflow steps (`context.run`, `context.sleep`, etc.) automatically create child spans. ### Client Instrumentation The `instrumentWorkflowClient` function wraps the client's trigger method, creating a span with `SpanKind.CLIENT` for each workflow trigger operation. ## Span Hierarchy The instrumentation creates the following span hierarchy: ``` [SERVER] workflow.execute ├─ [INTERNAL] workflow.step.step-1 (context.run) ├─ [INTERNAL] workflow.step.wait-5s (context.sleep) ├─ [CLIENT] workflow.step.api-call (context.call) └─ [INTERNAL] workflow.step.wait-event (context.waitForEvent) ``` Separate client-side triggers create independent traces: ``` [CLIENT] workflow.trigger ``` ## Span Attributes ### Workflow Handler Spans (`instrumentWorkflowServe`) | Attribute | Description | Example | | -------------------- | ---------------------------- | ---------------------------------- | | `workflow.system` | Constant value `upstash` | `upstash` | | `workflow.operation` | Operation type | `execute` | | `workflow.id` | Workflow ID from headers | `wf_123` | | `workflow.run_id` | Workflow run ID from headers | `run_456` | | `workflow.url` | Workflow URL from headers | `https://example.com/api/workflow` | | `http.status_code` | HTTP response status | `200` | ### Client Trigger Spans (`instrumentWorkflowClient`) | Attribute | Description | Example | | -------------------- | ----------------------------- | ---------------------------------- | | `workflow.system` | Constant value `upstash` | `upstash` | | `workflow.operation` | Operation type | `trigger` | | `workflow.url` | Target workflow URL | `https://example.com/api/workflow` | | `workflow.id` | Workflow ID from response | `wf_123` | | `workflow.run_id` | Workflow run ID from response | `run_456` | ### Step Spans (`context.run`) | Attribute | Description | Example | | --------------------------- | ------------------------- | ---------------------- | | `workflow.system` | Constant value `upstash` | `upstash` | | `workflow.operation` | Operation type | `step` | | `workflow.step.name` | Step name | `step-1` | | `workflow.step.type` | Step type | `run` | | `workflow.step.duration_ms` | Step execution time in ms | `150` | | `workflow.step.output` | Step output (if enabled) | `{"result":"success"}` | ### Sleep Spans (`context.sleep`, `context.sleepFor`, `context.sleepUntil`) | Attribute | Description | Example | | -------------------------------- | ------------------------------- | --------------- | | `workflow.system` | Constant value `upstash` | `upstash` | | `workflow.operation` | Operation type | `step` | | `workflow.step.name` | Step name (if named sleep) | `wait-5s` | | `workflow.step.type` | Step type | `sleep` | | `workflow.sleep.duration_ms` | Sleep duration in ms | `5000` | | `workflow.sleep.until_timestamp` | Target timestamp (`sleepUntil`) | `1704067200000` | ### Call Spans (`context.call`) | Attribute | Description | Example | | --------------------------- | -------------------------- | ------------------------------ | | `workflow.system` | Constant value `upstash` | `upstash` | | `workflow.operation` | Operation type | `step` | | `workflow.step.name` | Step name | `api-call` | | `workflow.step.type` | Step type | `call` | | `workflow.call.url` | Target URL | `https://api.example.com/data` | | `workflow.call.method` | HTTP method | `POST` | | `workflow.call.status_code` | Response status code | `200` | | `workflow.step.input` | Request body (if enabled) | `{"userId":"123"}` | | `workflow.step.output` | Response data (if enabled) | `{"status":"ok"}` | ### Event Spans (`context.waitForEvent`) | Attribute | Description | Example | | --------------------------- | ------------------------ | ------------------- | | `workflow.system` | Constant value `upstash` | `upstash` | | `workflow.operation` | Operation type | `step` | | `workflow.step.name` | Step name | `wait-event` | | `workflow.step.type` | Step type | `waitForEvent` | | `workflow.event.id` | Event ID | `evt_123` | | `workflow.event.timeout_ms` | Timeout in ms | `60000` | | `workflow.step.output` | Event data (if enabled) | `{"received":true}` | ### Step Data Attributes (Optional) When `captureStepData` is enabled: | Attribute | Description | Captured By | | ---------------------- | ---------------- | ------------------------------ | | `workflow.step.input` | Step input data | Client trigger, `context.call` | | `workflow.step.output` | Step output data | All context methods | The instrumentation captures workflow metadata and step details to help with debugging and monitoring. Step data capture is disabled by default to protect sensitive data. ## Usage Examples ### Basic Workflow Execution ```typescript theme={null} import { serve as originalServe } from "@upstash/workflow"; import { instrumentWorkflowServe } from "@kubiks/otel-upstash-workflow"; const serve = instrumentWorkflowServe(originalServe); export const POST = serve(async (context) => { const data = await context.run("fetch-data", async () => { return await fetchFromDatabase(); }); const processed = await context.run("process-data", async () => { return await processData(data); }); return { success: true, result: processed }; }); ``` ### Workflow with Sleep ```typescript theme={null} const serve = instrumentWorkflowServe(originalServe); export const POST = serve(async (context) => { await context.run("send-email", async () => { await sendEmail(); }); await context.sleep("wait-5s", 5); await context.run("check-status", async () => { return await checkEmailStatus(); }); return { done: true }; }); ``` ### Workflow with External API Calls ```typescript theme={null} const serve = instrumentWorkflowServe(originalServe); export const POST = serve(async (context) => { const apiResponse = await context.call("fetch-user", { url: "https://api.example.com/users/123", method: "GET", }); const result = await context.run("process-user", async () => { return await processUser(apiResponse); }); return result; }); ``` ### Workflow with Event Waiting ```typescript theme={null} const serve = instrumentWorkflowServe(originalServe); export const POST = serve(async (context) => { await context.run("start-process", async () => { await startLongRunningProcess(); }); const event = await context.waitForEvent("process-complete", { eventId: "evt_123", timeout: 60000, }); await context.run("finalize", async () => { return await finalizeProcess(event); }); return { success: true }; }); ``` ### Client Triggering Workflows ```typescript theme={null} import { Client } from "@upstash/workflow"; import { instrumentWorkflowClient } from "@kubiks/otel-upstash-workflow"; const client = instrumentWorkflowClient( new Client({ baseUrl: process.env.QSTASH_URL!, token: process.env.QSTASH_TOKEN!, }) ); const result = await client.trigger({ url: "https://your-app.vercel.app/api/workflow", body: { userId: "user_123", action: "process_data", }, }); console.log("Workflow triggered:", result.workflowId); ``` ### With Step Data Capture ```typescript theme={null} const serve = instrumentWorkflowServe(originalServe, { captureStepData: true, // Enable input/output capture maxStepDataLength: 2048, // Increase truncation limit }); export const POST = serve(async (context) => { const result = await context.run("complex-calculation", async () => { return { value: 42, timestamp: Date.now(), metadata: { processed: true }, }; }); return result; }); ``` ## Complete Next.js Integration Example ### Workflow Handler ```typescript app/api/workflow/route.ts theme={null} import { serve as originalServe } from "@upstash/workflow"; import { instrumentWorkflowServe } from "@kubiks/otel-upstash-workflow"; const serve = instrumentWorkflowServe(originalServe); export const POST = serve(async (context) => { const orderId = context.requestPayload.orderId; const result = await context.run("process-order", async () => { return await processOrder(orderId); }); await context.sleep("wait-1-minute", 60); await context.run("send-notification", async () => { return await sendNotification(orderId); }); return { success: true, order: result }; }); ``` ### Triggering Workflows ```typescript app/actions.ts theme={null} "use server"; import { Client } from "@upstash/workflow"; import { instrumentWorkflowClient } from "@kubiks/otel-upstash-workflow"; const workflowClient = instrumentWorkflowClient( new Client({ baseUrl: process.env.QSTASH_URL!, token: process.env.QSTASH_TOKEN!, }) ); export async function createOrder(orderId: string) { const result = await workflowClient.trigger({ url: "https://your-app.vercel.app/api/workflow", body: { orderId }, }); return { workflowId: result.workflowId, runId: result.workflowRunId, }; } ``` ## Configuration Options ```typescript theme={null} interface InstrumentationConfig { /** * Whether to capture step inputs/outputs in spans. * @default false */ captureStepData?: boolean; /** * Maximum length of step input/output to capture. * Data longer than this will be truncated. * @default 1024 */ maxStepDataLength?: number; /** * Custom tracer name. * @default "@kubiks/otel-upstash-workflow" */ tracerName?: string; } ``` ## Best Practices Use descriptive step names to make traces easier to understand: ```typescript theme={null} // Good: Descriptive names await context.run("fetch-user-from-database", async () => { ... }); await context.run("send-welcome-email", async () => { ... }); // Bad: Generic names await context.run("step1", async () => { ... }); await context.run("process", async () => { ... }); ``` Name sleep operations to understand workflow timing: ```typescript theme={null} await context.sleep("wait-for-email-delivery", 5); await context.sleep("cool-down-period", 60); ``` Only enable step data capture in development or when data doesn't contain sensitive information: ```typescript theme={null} const serve = instrumentWorkflowServe(originalServe, { captureStepData: process.env.NODE_ENV === "development", }); ``` Implement proper error handling within workflow steps: ```typescript theme={null} await context.run("risky-operation", async () => { try { return await performRiskyOperation(); } catch (error) { console.error("Operation failed:", error); throw error; } }); ``` ## Troubleshooting Ensure OpenTelemetry is initialized before using workflows: ```typescript theme={null} import { NodeSDK } from "@opentelemetry/sdk-node"; const sdk = new NodeSDK({ // ... configuration }); sdk.start(); ``` Make sure required environment variables are set: ```bash theme={null} QSTASH_URL=https://qstash.upstash.io QSTASH_TOKEN=your_token ``` Check that your endpoint is: * Publicly accessible * Returns 2xx status codes * Properly configured with Upstash Workflow ## Resources Learn more about Upstash Workflow View source code and examples View package on npm Found a bug? Let us know! ## License MIT # OpenTelemetry Integrations Source: https://docs.kubiks.ai/opentelemetry-integrations/overview Production-ready OpenTelemetry instrumentation packages for popular SDKs and frameworks Integrates with your favorite stack ## Available Integrations * **[Autumn Billing](/opentelemetry-integrations/otel-autumn)** - Instrument Autumn billing operations including feature checks, usage tracking, and checkout flows * **[Better Auth](/opentelemetry-integrations/otel-better-auth)** - Complete auth observability across all authentication flows, OAuth, sessions, and account management * **[ClickHouse](/opentelemetry-integrations/otel-clickhouse)** - Trace ClickHouse database operations and queries with detailed performance metrics * **[Drizzle ORM](/opentelemetry-integrations/otel-drizzle)** - Add distributed tracing to database queries with support for PostgreSQL, MySQL, and SQLite * **[E2B](/opentelemetry-integrations/otel-e2b)** - Instrument E2B code execution and sandbox operations with OpenTelemetry tracing * **[Inbound](/opentelemetry-integrations/otel-inbound)** - Automatically trace inbound HTTP requests with comprehensive request/response metadata * **[MongoDB](/opentelemetry-integrations/otel-mongodb)** - Trace MongoDB operations, queries, and aggregations with collection and execution details * **[Resend Email](/opentelemetry-integrations/otel-resend)** - Trace email operations with detailed recipient, subject, and delivery metadata * **[Upstash QStash](/opentelemetry-integrations/otel-upstash-queues)** - Monitor message queue operations for both publishers and consumers * **[Upstash Workflow](/opentelemetry-integrations/otel-upstash-workflow)** - Trace workflow executions, steps, sleep operations, and API calls with detailed performance metrics # Vercel Integration Source: https://docs.kubiks.ai/vercel-integration AI-powered observability for Vercel apps with zero-config setup ## Overview Kubiks is an AI-powered observability platform for Vercel apps. It automatically traces every part of your stack — API routes, database queries, background jobs, and LLM calls — using OpenTelemetry for full visibility with zero setup. **Key Features:** * **AI Agent** that monitors your stack, detects issues, and generates PRs with fixes * **Automatic Tracing** across logs, traces, and source code * **Real-time Root-Cause Analysis** and incident summaries * **Unified Dashboard** for complete system visibility * **Slack Integration** to chat with the agent and trigger actions Built for speed and clarity, Kubiks helps you understand and fix issues instantly. ## Quick Setup ### 1. Enable Log Drains (Required) Vercel Log Drains automatically forward your application's real-time logs (build, edge, serverless, runtime, and static) to Kubiks. **Log Drains are required** for Kubiks to display all requests and correlate them with traces. Navigate to the Integration page and click the **Add Drains** button. Install Kubiks integration Select **Logs** and click **Next**. Select logs option Configure the drain settings and click **Create Drain** to complete the setup. Configure and create drain Log drains are required for Kubiks to visualize requests data. ### 2. Enable Trace Drains Trace Drains send spans from your Vercel project to Kubiks, providing full request-level tracing and visibility. Navigate to the Integration page and click the **Add Drains** button. Add drains button Select **Traces** and click **Next**. Select traces option Configure the drain settings and click **Create Drain** to complete the setup. Configure and create drain ### 3. Install OpenTelemetry SDK 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 ``` ### 4. Configure Instrumentation Create an `instrumentation.ts` file in your **project root**: ```typescript instrumentation.ts theme={null} import { registerOTel } from '@vercel/otel'; export function register() { registerOTel(); } ``` That's it! The `@vercel/otel` package automatically: * Instruments HTTP requests, fetch calls, and Next.js internals * Sends traces to Kubiks ## Instrument Your Dependencies Enhance your observability by adding our OpenTelemetry SDKs for popular frameworks and services: 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 Verify that **Trace Drains are enabled** in your Vercel project settings. Navigate to **Settings** → **Integrations** → **OpenTelemetry** and confirm the Kubiks endpoint is configured. **Log drains are required** for Kubiks to work properly. 1. Navigate to Vercel **Settings** → **Log Drains** 2. Verify the Kubiks endpoint is listed and active 3. If missing, reinstall the Kubiks integration 4. Check the log drain status indicator (should be green/active) ## Resources View your application traces and metrics Install the Kubiks integration