feat: Add OpenTelemetry integration for distributed tracing#324
feat: Add OpenTelemetry integration for distributed tracing#324jaypatrick merged 4 commits intomainfrom
Conversation
Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
adblock-compiler | 9b8958d | Feb 13 2026, 04:52 AM |
Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
There was a problem hiding this comment.
Overview
This PR adds OpenTelemetry integration for distributed tracing by implementing an OpenTelemetryExporter class that bridges the existing IDiagnosticsCollector interface with OpenTelemetry's standard tracing API. The implementation is clean, well-tested with 16 test cases, and maintains backward compatibility with existing diagnostics collectors.
Concerns
- The
spanContextsmap is populated but never used for context propagation - appears to be dead code or incomplete functionality - Documentation example has a missing import (
SpanStatusCode) - Method naming (
hashKey) is misleading as it truncates rather than hashes
Verdict
Found: 0 critical, 2 important, 2 suggestions, 1 nit
Approve with nits — solid implementation with minor issues that should be addressed
| private readonly spanContexts = new Map<string, unknown>(); | ||
| private readonly operationNames = new Map<string, string>(); |
There was a problem hiding this comment.
spanContexts is populated on line 66 but never read anywhere - this is either dead code or incomplete context propagation. If context propagation is needed for nested spans, the stored context should be used. If not needed, remove this map and the trace.setSpan() call on line 66 to avoid confusion.
| } else if (Array.isArray(value)) { | ||
| // OpenTelemetry API only supports string arrays, not mixed types | ||
| // Stringify arrays for simplicity | ||
| span.setAttribute(key, JSON.stringify(value)); |
There was a problem hiding this comment.
string[], number[], boolean[]), not just string arrays. Currently all arrays are stringified which loses type information. Consider checking if array is homogeneous primitive before stringifying.
| } else if (Array.isArray(value)) { | |
| // OpenTelemetry API only supports string arrays, not mixed types | |
| // Stringify arrays for simplicity | |
| span.setAttribute(key, JSON.stringify(value)); | |
| } else if (Array.isArray(value)) { | |
| // OpenTelemetry supports homogeneous primitive arrays | |
| // Stringify mixed/complex arrays for consistency | |
| span.setAttribute(key, JSON.stringify(value)); |
| private hashKey(key: string): string { | ||
| // Simple hash for privacy - first 8 chars of key or hash | ||
| return key.substring(0, 8) + '...'; | ||
| } |
There was a problem hiding this comment.
💡 [SUGGESTION] Method named hashKey but it truncates, not hashes. Consider renaming to truncateKey or implementing actual hashing if privacy is the goal:
| private hashKey(key: string): string { | |
| // Simple hash for privacy - first 8 chars of key or hash | |
| return key.substring(0, 8) + '...'; | |
| } | |
| private truncateKey(key: string): string { | |
| // Truncate key for privacy - show first 8 chars only | |
| return key.substring(0, 8) + '...'; | |
| } |
| if (!statusCode || statusCode < 400) { | ||
| span.setStatus({ code: SpanStatusCode.OK }); | ||
| } |
There was a problem hiding this comment.
💡 [SUGGESTION] Setting OK status when statusCode is undefined assumes success without confirmation. Consider only setting status when we have definitive information:
| if (!statusCode || statusCode < 400) { | |
| span.setStatus({ code: SpanStatusCode.OK }); | |
| } | |
| if (statusCode !== undefined && statusCode < 400) { | |
| span.setStatus({ code: SpanStatusCode.OK }); | |
| } |
| You can also create custom spans for fine-grained tracing: | ||
|
|
||
| ```typescript | ||
| import { trace } from '@opentelemetry/api'; |
There was a problem hiding this comment.
🧹 [NIT] SpanStatusCode is used at line 922 but missing from this import. Add it for completeness:
| import { trace } from '@opentelemetry/api'; | |
| import { SpanStatusCode, trace } from '@opentelemetry/api'; |
There was a problem hiding this comment.
Pull request overview
This PR adds industry-standard OpenTelemetry distributed tracing to the adblock-compiler, enabling integration with major observability platforms (Datadog, Honeycomb, Jaeger, etc.). The implementation maintains backward compatibility by providing OpenTelemetryExporter as an alternative implementation of the existing IDiagnosticsCollector interface.
Changes:
- Adds
OpenTelemetryExporterclass that bridges the existing diagnostics system with OpenTelemetry's standard tracing API - Includes comprehensive test coverage with 16 test cases covering span lifecycle, metrics, errors, and edge cases
- Adds
@opentelemetry/api@^1.9.0dependency leveraging Deno 2.2+ native OTLP support - Provides usage examples and complete documentation for the OpenTelemetry integration
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/diagnostics/OpenTelemetryExporter.ts | Core implementation of OpenTelemetry exporter implementing IDiagnosticsCollector interface |
| src/diagnostics/OpenTelemetryExporter.test.ts | Comprehensive test suite with 16 tests and mock implementations |
| src/diagnostics/index.ts | Exports OpenTelemetryExporter and related types |
| src/index.ts | Adds public API exports for OpenTelemetry integration |
| examples/opentelemetry-example.ts | Complete usage example demonstrating both automatic and manual tracing |
| deno.json | Adds @opentelemetry/api dependency and nodeModulesDir configuration |
| deno.lock | Lock file update for new dependency |
| README.md | Comprehensive documentation section for OpenTelemetry integration |
| /** | ||
| * Emits a custom diagnostic event as a span event | ||
| */ | ||
| public emit(event: AnyDiagnosticEvent): void { |
There was a problem hiding this comment.
The emit method signature uses AnyDiagnosticEvent but the IDiagnosticsCollector interface defines it as DiagnosticEvent. For consistency with the interface and other implementations like DiagnosticsCollector, this should be changed to event: DiagnosticEvent.
| You can also create custom spans for fine-grained tracing: | ||
|
|
||
| ```typescript | ||
| import { trace } from '@opentelemetry/api'; |
There was a problem hiding this comment.
The code example uses SpanStatusCode.OK and SpanStatusCode.ERROR but doesn't import SpanStatusCode from '@opentelemetry/api'. Add SpanStatusCode to the import statement on line 906.
| import { trace } from '@opentelemetry/api'; | |
| import { SpanStatusCode, trace } from '@opentelemetry/api'; |
| "name": "@jk-com/adblock-compiler", | ||
| "version": "0.13.0", | ||
| "exports": "./src/index.ts", | ||
| "nodeModulesDir": "auto", |
There was a problem hiding this comment.
The nodeModulesDir: "auto" setting has been added but it's not documented why this is necessary for the OpenTelemetry integration. If this is required for the @opentelemetry/api npm package to work correctly, please document this in the PR description or a code comment. If it's not needed, consider removing it to keep the configuration minimal.
| "nodeModulesDir": "auto", |
| * Hashes a cache key for privacy | ||
| */ | ||
| private hashKey(key: string): string { | ||
| // Simple hash for privacy - first 8 chars of key or hash | ||
| return key.substring(0, 8) + '...'; |
There was a problem hiding this comment.
The method name hashKey and comment say it "hashes" the key for privacy, but it only truncates to the first 8 characters plus "...". This is not a cryptographic hash and doesn't provide true privacy protection - if sensitive data is in the first 8 characters, it will be exposed. Consider either using an actual hash function (e.g., a simple hash of the entire key), or renaming to truncateKey to accurately reflect what it does.
| * Hashes a cache key for privacy | |
| */ | |
| private hashKey(key: string): string { | |
| // Simple hash for privacy - first 8 chars of key or hash | |
| return key.substring(0, 8) + '...'; | |
| * Hashes a cache key for privacy (non-cryptographic). | |
| * | |
| * Produces a deterministic hash of the full key without exposing any part | |
| * of the original value. This is intended for observability/logging, not | |
| * for cryptographic security. | |
| */ | |
| private hashKey(key: string): string { | |
| // FNV-1a 32-bit hash | |
| let hash = 0x811c9dc5; | |
| for (let i = 0; i < key.length; i++) { | |
| hash ^= key.charCodeAt(i); | |
| hash = Math.imul(hash, 0x01000193); | |
| } | |
| const unsignedHash = hash >>> 0; | |
| return `key_${unsignedHash.toString(16)}`; |
| private readonly tracer: Tracer; | ||
| private readonly serviceName: string; | ||
| private readonly enableConsoleLogging: boolean; | ||
| private readonly activeSpans = new Map<string, Span>(); |
There was a problem hiding this comment.
The spanContexts map is populated in operationStart (line 66) but is never read or used anywhere in the code - it's only written to and deleted from. This suggests either incomplete implementation or unnecessary state tracking. If context propagation is not needed for the current use case, consider removing this map to reduce memory overhead. If it's meant for future use, add a comment explaining its purpose.
| private readonly activeSpans = new Map<string, Span>(); | |
| private readonly activeSpans = new Map<string, Span>(); | |
| // Stores OpenTelemetry context objects associated with each span. | |
| // This is reserved for future context propagation (e.g. linking child spans | |
| // or crossing async boundaries) and may be unused in the current implementation. |
Adds industry-standard OpenTelemetry tracing to replace custom diagnostics with OTLP-compatible observability across Datadog, Honeycomb, Jaeger, and other platforms.
Implementation
OpenTelemetryExporter- ImplementsIDiagnosticsCollectorinterface, bridging existing diagnostics events to OpenTelemetry spans@opentelemetry/api@^1.9.0to leverage Deno 2.2+ native OTLP supportUsage
Enable with Deno:
Architecture
Maintains backward compatibility -
DiagnosticsCollector(in-memory),NoOpDiagnosticsCollector(disabled), and newOpenTelemetryExporter(OTLP) all implement the same interface. Drop-in replacement for existing diagnostics with zero breaking changes.Traces include operation spans, performance metrics, error tracking with stack traces, cache events, and network requests with sanitized URLs.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.