Custom Diagnostics Providers
Custom Diagnostics Providers
The IDiagnosticsProvider interface is the single extension point for the
entire observability stack. Any backend — Datadog, New Relic, Honeycomb,
a custom in-house sink — can be plugged in without touching core Worker code.
Interface overview
classDiagram
class IDiagnosticsProvider {
+captureError(error, context?) void
+captureMessage(message, level?, context?) void
+startSpan(name, attributes?) ISpan
+recordMetric(name, value, tags?) void
+setUser(user) void
+setContext(name, context) void
+addBreadcrumb(breadcrumb) void
+flush() Promise~void~
}
class ISpan {
+end() void
+setAttribute(key, value) void
+setAttributes(attributes) void
+recordException(error) void
+addEvent(name, attributes?) void
}
class CompositeDiagnosticsProvider {
+size int
}
class SentryDiagnosticsProvider
class OpenTelemetryDiagnosticsProvider
class ConsoleDiagnosticsProvider
class NoOpDiagnosticsProvider
IDiagnosticsProvider <|.. CompositeDiagnosticsProvider
IDiagnosticsProvider <|.. SentryDiagnosticsProvider
IDiagnosticsProvider <|.. OpenTelemetryDiagnosticsProvider
IDiagnosticsProvider <|.. ConsoleDiagnosticsProvider
IDiagnosticsProvider <|.. NoOpDiagnosticsProvider
IDiagnosticsProvider ..> ISpan : returns
CompositeDiagnosticsProvider o-- IDiagnosticsProvider : delegates to *
Built-in providers
| Class | Activated by | Notes |
|---|---|---|
SentryDiagnosticsProvider | SENTRY_DSN secret | Error tracking + message capture |
OpenTelemetryDiagnosticsProvider | OTEL_EXPORTER_OTLP_ENDPOINT secret | Traces via OTLP HTTP |
ConsoleDiagnosticsProvider | fallback (no secrets set) | Structured JSON to console |
NoOpDiagnosticsProvider | test environments | Silent no-op |
CompositeDiagnosticsProvider | multiple backends active | Fan-out to all children |
Adding a custom provider
Step 1 — implement IDiagnosticsProvider
import type { DiagnosticsBreadcrumb, DiagnosticsLevel, DiagnosticsUser, IDiagnosticsProvider, ISpan,} from '../../src/diagnostics/index.ts';
export class DatadogDiagnosticsProvider implements IDiagnosticsProvider { constructor(private readonly apiKey: string) {}
captureError(error: Error, context?: Record<string, unknown>): void { // send to Datadog Logs / Error Tracking ... }
captureMessage(message: string, level?: DiagnosticsLevel): void { // send to Datadog Logs ... }
startSpan(name: string): ISpan { // return a Datadog APM span ... return { end: () => {}, setAttribute: () => {}, setAttributes: () => {}, recordException: (err) => { this.captureError(err); }, addEvent: () => {}, }; }
recordMetric(name: string, value: number): void { // send to Datadog StatsD / metrics API ... }
setUser(_user: DiagnosticsUser): void {} setContext(_name: string, _ctx: Record<string, unknown>): void {} addBreadcrumb(_b: DiagnosticsBreadcrumb): void {}
async flush(): Promise<void> { // flush any buffered payloads ... }}Step 2 — register at module-load time
// worker/worker.ts (top-level, before any handler)import { registerDiagnosticsProvider } from './services/diagnostics-factory.ts';import { DatadogDiagnosticsProvider } from './services/my-datadog-provider.ts';
registerDiagnosticsProvider((env) => env.DD_API_KEY ? new DatadogDiagnosticsProvider(env.DD_API_KEY) : null);registerDiagnosticsProvider is safe to call multiple times. Each registered
builder runs once per createDiagnosticsProvider() call. A builder that
returns null is silently skipped — use this to guard on missing env vars.
Step 3 — add the required secret
wrangler secret put DD_API_KEYNo changes to wrangler.toml are needed.
Provider registry flow
flowchart TD
boot["Worker boots / module load"]
boot --> reg["registerDiagnosticsProvider() x N"]
reg --> req["Inbound request"]
req --> factory["createDiagnosticsProvider(env)"]
factory --> iter["Iterate _providerBuilders[]"]
iter --> b1{"builder(env) !== null?"}
b1 -->|yes| add["Add to providers[]"]
b1 -->|no| skip["Skip"]
add --> next["next builder"]
skip --> next
next --> done{"All builders visited"}
done -->|0 providers| console["ConsoleDiagnosticsProvider (fallback)"]
done -->|1 provider| single["Return provider directly"]
done -->|N providers| composite["CompositeDiagnosticsProvider"]
composite --> handlers["Handler logic"]
single --> handlers
console --> handlers
handlers --> flush["ctx.waitUntil(diagnostics.flush())"]
flush() in Cloudflare Workers
Always call flush() via ctx.waitUntil() at the end of every handler so
buffered events are delivered before the isolate terminates:
export default { async fetch(request, env, ctx) { const diagnostics = createDiagnosticsProvider(env); try { // ...handler logic... } catch (err) { diagnostics.captureError(err as Error, { url: request.url }); throw err; } finally { ctx.waitUntil(diagnostics.flush()); } },};CompositeDiagnosticsProvider.flush() uses Promise.allSettled() internally
so a slow or failing backend never blocks the others.
Adding new methods to IDiagnosticsProvider
If you need to add a new method to the interface:
- Add the method signature to
IDiagnosticsProviderinsrc/diagnostics/IDiagnosticsProvider.ts - Implement it in
NoOpDiagnosticsProviderandConsoleDiagnosticsProvider(same file) - Add the fan-out delegation to
CompositeDiagnosticsProvider - Implement in
SentryDiagnosticsProviderandOpenTelemetryDiagnosticsProvider - Implement in any custom providers registered via
registerDiagnosticsProvider()
Bloqr AI™ — The privacy you didn't know you needed.
© 2026 Bloqr AI™, a trademark of Bloqr Systems™. Created by Bloqr Systems™, founded by Jayson Knight.
Internet Hygiene (n.) — the ongoing practices that keep your digital life clean, private, and safe.
Our product repos live in the BloqrAI org, part of the Bloqr Systems GitHub Enterprise. Product repos are internal-visibility — enterprise membership is required to view them.