@glimt.dev/otel-browser
v0.2.1
Published
Minimal browser-only OpenTelemetry bootstrap for Glimt.dev
Downloads
120
Readme
@glimt.dev/otel-browser
OpenTelemetry for the browser. Works out of the box, highly configurable.
Quick Start (3 lines)
npm install @glimt.dev/otel-browser @opentelemetry/api @opentelemetry/api-logsimport { registerOTelBrowser } from '@glimt.dev/otel-browser'
registerOTelBrowser({ serviceName: 'my-web-app' })Done. Traces go to https://ingest.glimt.dev/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: Glimt.dev Ingest
For production with Glimt authentication:
registerOTelBrowser({
serviceName: 'my-web-app',
// exporterUrl defaults to https://ingest.glimt.dev
organisationId: 'your-org-id',
publishableKey: 'your-publishable-key',
})Full Example (all options)
import { registerOTelBrowser } from '@glimt.dev/otel-browser'
const sdk = registerOTelBrowser({
// Identity
serviceName: 'my-web-app',
// Glimt auth
organisationId: 'org_xxx',
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.glimt.dev',
exporter: 'http/protobuf', // or 'http/json'
exporterHeaders: { 'x-tenant-id': 'acme' },
credentials: 'include', // for CORS
// Logs (auto-derived to /v1/logs)
logs: {
exporter: 'http/protobuf',
},
// 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)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 client'
import { useEffect, useRef } from 'react'
import { registerOTelBrowser, type BrowserSDK } from '@glimt.dev/otel-browser'
export function TelemetryProvider({ userId }: { userId?: string }) {
const sdk = useRef<BrowserSDK | null>(null)
useEffect(() => {
sdk.current = registerOTelBrowser({
serviceName: process.env.NEXT_PUBLIC_OTEL_SERVICE_NAME!,
exporterUrl: process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_ENDPOINT,
user: userId ? { userId } : undefined,
release: {
commit: process.env.NEXT_PUBLIC_COMMIT_SHA || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
branch: process.env.NEXT_PUBLIC_COMMIT_REF || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF,
environment: process.env.NEXT_PUBLIC_VERCEL_ENV,
}
})
}, [])
useEffect(() => {
sdk.current?.setUserContext(userId ? { userId } : null)
}, [userId])
return null
}// app/layout.tsx
export default async function RootLayout({ children }) {
const user = await getCurrentUser()
return (
<html>
<body>
<TelemetryProvider 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/protobuf → https://ingest.glimt.dev/v1/traces |
| Log exporter | http/protobuf → https://ingest.glimt.dev/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
