@aient/otel-browser
v0.4.0
Published
Minimal browser-only OpenTelemetry bootstrap for Aient
Readme
@aient/otel-browser
OpenTelemetry for the browser. Works out of the box, highly configurable.
Quick Start (3 lines)
npm install @aient/otel-browser @opentelemetry/api @opentelemetry/api-logsimport { registerOTelBrowser } from '@aient/otel-browser'
registerOTelBrowser({ serviceName: 'my-web-app' })Done. Traces go to https://ingest.aient.ai/v1/traces, logs to /v1/logs.
Local Development
For local development with a local collector:
registerOTelBrowser({
serviceName: 'my-web-app',
exporterUrl: 'http://localhost:4318',
})Production: Aient.dev Ingest
For production with Aient authentication:
registerOTelBrowser({
serviceName: 'my-web-app',
// exporterUrl defaults to https://ingest.aient.ai
publishableKey: 'pk_live_11...',
})Full Example (all options)
import { registerOTelBrowser } from '@aient/otel-browser'
const sdk = registerOTelBrowser({
// Identity
serviceName: 'my-web-app',
// Aient auth
publishableKey: 'pk_xxx',
// Release metadata (CRITICAL for source mapping)
release: {
commit: process.env.NEXT_PUBLIC_COMMIT_SHA, // or 'abc1234'
branch: process.env.NEXT_PUBLIC_COMMIT_REF, // or 'main'
version: '1.0.0',
environment: 'production',
},
// Export - base URL, SDK derives /v1/traces and /v1/logs
exporterUrl: 'https://ingest.aient.ai',
exporter: 'http/json',
exporterHeaders: { 'x-tenant-id': 'acme' },
credentials: 'include', // for CORS
// Logs (auto-derived to /v1/logs)
logs: {
exporter: 'http/json',
},
// Instrumentations
instrumentations: ['auto'], // document-load, user-interaction, xhr, fetch
fetch: {
ignoreUrls: [/\/healthz?$/, /analytics\.js$/],
propagateContextUrls: [/^https:\/\/api\.myapp\.com/],
},
// Capture
captureUnhandledErrors: true, // window.onerror, unhandledrejection (default: true)
captureConsoleLogs: true, // console.* as OTLP logs (default: false)
includeUserAgent: false, // privacy default
// Initial user context
user: { userId: 'user_123', role: 'admin' },
// Debug
logLevel: 'DEBUG',
})
// Update user on login/logout
sdk.setUserContext({ userId: 'user_456' })
sdk.setUserContext(null)http/json is the only bundled OTLP transport. Legacy http/protobuf values are ignored at runtime and fall back to JSON.
OTLP JSON protocol contract
The SDK exports traces and logs as OTLP/HTTP JSON protobuf payloads with Content-Type: application/json.
The serializer follows the OTLP JSON mapping used by the OpenTelemetry protocol:
- request envelopes are
ExportTraceServiceRequestandExportLogsServiceRequestJSON objects (resourceSpansandresourceLogs); - JSON field names are lowerCamelCase, for example
startTimeUnixNanoanddroppedAttributesCount; - trace IDs and span IDs are hex strings, not base64;
- 64-bit timestamps and integer attribute values are decimal strings;
- enum fields such as span kind, span status, and log severity are encoded as numbers;
- arbitrary byte attributes are base64 strings;
- spans and logs are grouped by resource and instrumentation scope, including schema URLs when present.
@aient/otel-browser does not bundle protobufjs, @opentelemetry/otlp-transformer, or @opentelemetry/otlp-exporter-base. Protocol-sensitive serializer behavior is covered by test/otlp-json-serializer.test.ts, and dependency regressions are covered by test/package-contract.test.ts.
Environment Variables (via Build Tools)
Browsers don't have runtime environment variables, but you can inject them at build time using your bundler:
Next.js (next.config.mjs):
// Automatically available as process.env.NEXT_PUBLIC_*
// Set in .env or CI environmentVite (vite.config.ts):
export default defineConfig({
define: {
'import.meta.env.VITE_OTEL_ENDPOINT': JSON.stringify(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
'import.meta.env.VITE_COMMIT_SHA': JSON.stringify(process.env.COMMIT_SHA),
}
})Webpack:
new webpack.DefinePlugin({
'process.env.OTEL_ENDPOINT': JSON.stringify(process.env.OTEL_EXPORTER_OTLP_ENDPOINT),
})React / Next.js Integration
Use a client-only component: registering during render can duplicate exporters (especially under React Strict Mode). Register once in useEffect, return shutdown() from the effect cleanup so remounts do not leak processors, and update the active user in a second effect with setUserContext when auth changes.
'use client'
import { useEffect, useRef } from 'react'
import { registerOTelBrowser, type BrowserSDK } from '@aient/otel-browser'
export function TelemetryInit({ userId }: { userId?: string | null }) {
const sdkRef = useRef<BrowserSDK | null>(null)
useEffect(() => {
const sdk = registerOTelBrowser({
publishableKey: process.env.NEXT_PUBLIC_AIENT_PUBLISHABLE_KEY!,
serviceName: process.env.NEXT_PUBLIC_OTEL_SERVICE_NAME ?? 'web',
...(process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT
? { exporterUrl: process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT }
: {}),
release: {
commit: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ?? 'dev',
branch: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF ?? 'local',
environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? 'development',
},
})
sdkRef.current = sdk
return () => {
void sdk.shutdown()
}
}, [])
useEffect(() => {
sdkRef.current?.setUserContext(userId ? { userId } : null)
}, [userId])
return null
}// app/layout.tsx (App Router)
export default async function RootLayout({ children }) {
const user = await getCurrentUser()
return (
<html>
<body>
<TelemetryInit userId={user?.id} />
{children}
</body>
</html>
)
}Server-side traceparent
Inject in your HTML to correlate browser ↔ server:
<meta name="traceparent" content="00-abc123...-def456...-01" />The SDK picks this up automatically as the parent span.
CORS Requirements
Your collector must allow:
Access-Control-Allow-Origin: * (or your origin)
Access-Control-Allow-Headers: content-type, traceparent, baggage, authorization
Access-Control-Allow-Credentials: true (if using credentials)Defaults
| Feature | Default |
|---------|---------|
| Trace exporter | http/json → https://ingest.aient.ai/v1/traces |
| Log exporter | http/json → https://ingest.aient.ai/v1/logs |
| Instrumentations | document-load, user-interaction, xhr, fetch |
| Error capture | window.onerror, unhandledrejection |
| User agent | NOT included (privacy) |
| Console capture | OFF |
User Context Attributes
When you call sdk.setUserContext(), these attributes are added to all spans:
| Attribute | Description |
|-----------|-------------|
| enduser.id | Primary user identifier |
| enduser.pseudo.id | Privacy-preserving hash |
| enduser.email | PII, use with caution |
| enduser.role | Permission scope |
License
MIT
