Layers · Reference
OpenTelemetry
Server and browser OpenTelemetry for errors, traces, metrics, logs, and named events.
Last updated October 20, 2026
Setup
Server/browser telemetry infrastructure for errors, traces, metrics, logs and named events. Events adds consent UI, page tracking and optional SQL audit history.
bunx shadcn@latest add @krak-stack/service-opentelemetryServer
OTEL_SERVICE_NAME=my-app-api
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlpimport { Layer } from 'effect';
import { Otlp } from '@/services/opentelemetry';
import { HttpApiOtlp } from '@/services/opentelemetry/api';
const server = HttpApiOtlp.layer.pipe(Layer.provideMerge(Otlp.layer));Otlp.layer owns server exporters and works without HTTP in workers/CLIs. HttpApiOtlp.layer adds browser ingestion endpoints; it does not construct its own Otlp foundation. Provide Events.ingestionLayer for application-event enrichment/persistence as shown in Events setup.
Browser
Mount in your browser runtime, not during SSR:
import { BrowserOtlp } from '@/services/opentelemetry/browser';
const telemetry = BrowserOtlp.layer({ serviceName: 'my-app-web' });| Browser option | Default / behavior |
|---|---|
serviceName | Required |
traces | {}; automatic browser errors included; false disables both |
events | {}; false disables all event export |
logs, metrics | false; enable independently with {} |
baseUrl | /api/otel |
attributes, serviceVersion, headers | Optional resource/transport metadata |
BrowserOtlp creates no automatic page tracking or visitor/session identity. Automatic errors use generic messages and sanitized source locations. Do not attach sensitive identity/resource annotations to essential diagnostics.
Add analytics and consent
import { Layer } from 'effect';
import { BrowserEvents } from '@/services/events/browser';
import { TrackingNotice } from '@/services/events/client/tracking-notice';
import { BrowserWebVitals } from '@/services/opentelemetry/web-vitals';
const browser = Layer.mergeAll(
BrowserEvents.layer(),
BrowserWebVitals.layer(),
).pipe(Layer.provideMerge(telemetry));
// Mount once in a persistent footer. English/French defaults are included.
<TrackingNotice privacyHref="/privacy-policy" />BrowserEvents and Web Vitals require shared acceptance by default and honor DNT/GPC. Web Vitals requires consent before navigation begins and does not resume within a document after withdrawal. Essential automatic error reporting is independent of that choice. Generic events, custom spans, metrics/logs and third-party scripts are application-owned; do not use them to bypass preferences. See tracking consent for the policy and overrides.
Install
bunx shadcn@latest add @krak-stack/service-opentelemetryNamed events
Run these Effects inside the appropriate runtime:
yield* Otlp.event('order.completed', { total: 42 });
yield* BrowserOtlp.event('diagnostic.ready', { component: 'editor' });Events are INFO OTLP logs; they do not create spans or SQL history. Invalid inputs fail schema validation.
| Event option | Meaning |
|---|---|
userId, anonymousId, sessionId | Explicit identity attributes; never proof of authentication |
messageId | Correlation ID, not guaranteed collector deduplication |
context | Flat attributes |
timestamp | Effect DateTime.Utc occurrence time |
Attribute precedence: context, properties, explicit identity options, scoped log annotations. Values support strings, finite numbers, booleans and homogeneous arrays. Keep sensitive data out of telemetry.
Web Vitals
The optional BrowserWebVitals layer reports document-level LCP, INP, CLS, FCP and TTFB through the shared BrowserOtlp exporter. No SQL is required; these are not SPA navigation timings.
If using Web Vitals without Events UI, you must still integrate the shared consent preference or explicitly assess a deployment override:
const browser = BrowserWebVitals.layer({
requireConsent: false,
respectDoNotTrack: true,
}).pipe(Layer.provideMerge(telemetry));This override is not a claim of legal exemption. With requireConsent: true, shared consent honors DNT/GPC even if respectDoNotTrack is false.
Events use name browser.web_vital, scope krakstack.events and these attributes:
Attribute suffix under browser.web_vital.* | Meaning |
|---|---|
name, id | Metric name and instance ID |
value, delta | Cumulative value and change since the previous report |
rating | good / needs-improvement / poor |
navigation_type | Library navigation type |
Values are milliseconds except unitless CLS. Use the latest cumulative value or sum deduplicated deltas, not cumulative values. Unsupported metrics may be absent. Closing the layer releases its subscriber; library observers are document-scoped. No URLs, DOM attribution, performance entries or analytics identity are exported automatically.
Transport
const telemetry = BrowserOtlp.layer({
serviceName: 'my-app-web',
events: { exportInterval: '5 seconds', maxBatchSize: 20 },
});Default event batching is 30 seconds / 100 records. shutdownTimeout is also configurable. Page-hidden/shutdown flushing is best effort and uses browser keepalive. Consent is checked before batch serialization; already-started exports and their retries are not cancelled by withdrawal.
| Concern | Behavior |
|---|---|
| Browser endpoints | /api/otel/v1/{traces,metrics,logs} |
| Server overrides | OTEL_EXPORTER_OTLP_{LOGS,METRICS,TRACES}_{ENDPOINT,HEADERS} |
OTEL_SDK_DISABLED=true | Disables server export, not browser collection |
| Storage/credentials | Collector-owned; credentials remain server-side |
Apply edge limits, collector retention and access controls. Package imports: @krak-stack/registry/opentelemetry, /opentelemetry/api, /opentelemetry/browser and /opentelemetry/web-vitals.