Skip to content

feat: Add OpenTelemetry integration for distributed tracing#324

Merged
jaypatrick merged 4 commits intomainfrom
copilot/implement-opentelemetry-tracing
Feb 13, 2026
Merged

feat: Add OpenTelemetry integration for distributed tracing#324
jaypatrick merged 4 commits intomainfrom
copilot/implement-opentelemetry-tracing

Conversation

Copy link

Copilot AI commented Feb 13, 2026

Adds industry-standard OpenTelemetry tracing to replace custom diagnostics with OTLP-compatible observability across Datadog, Honeycomb, Jaeger, and other platforms.

Implementation

  • OpenTelemetryExporter - Implements IDiagnosticsCollector interface, bridging existing diagnostics events to OpenTelemetry spans
  • Dependency - Added @opentelemetry/api@^1.9.0 to leverage Deno 2.2+ native OTLP support
  • Automatic instrumentation - Compilation operations, downloads, transformations emit structured spans with attributes
  • 16 test cases - Full coverage of span lifecycle, metrics, errors, and edge cases

Usage

import { createOpenTelemetryExporter, SourceCompiler } from '@jk-com/adblock-compiler';

const diagnostics = createOpenTelemetryExporter({
    serviceName: 'adblock-compiler',
});

const compiler = new SourceCompiler({ diagnostics });
const rules = await compiler.compile(source); // Automatically traced

Enable with Deno:

OTEL_DENO=true deno run --unstable-otel your-script.ts

Architecture

Maintains backward compatibility - DiagnosticsCollector (in-memory), NoOpDiagnosticsCollector (disabled), and new OpenTelemetryExporter (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

This section details on the original issue you should resolve

<issue_title>Implement OpenTelemetry for distributed tracing</issue_title>
<issue_description>Priority: Critical
Category: Observability
Reference: FEATURE-009

Description:
Current tracing system is custom and not compatible with standard observability platforms. OpenTelemetry is industry standard.

Why:

  • Compatible with all major platforms (Datadog, Honeycomb, Jaeger)
  • Distributed tracing across services
  • Standard instrumentation
  • Rich ecosystem of integrations

Affected Files:

  • Create src/diagnostics/OpenTelemetryExporter.ts
  • src/compiler/SourceCompiler.ts
  • worker/worker.ts
  • deno.json (add dependency)

Proposed Implementation:

import { SpanStatusCode, trace } from "@opentelemetry/api";

const tracer = trace.getTracer('adblock-compiler', VERSION);

async function compileWithTracing(config: IConfiguration): Promise<string> {
    return tracer.startActiveSpan('compile', async (span) => {
        try {
            span.setAttribute('config.name', config.name);
            span.setAttribute('config.sources.count', config.sources.length);
            
            const result = await compile(config);
            
            span.setStatus({ code: SpanStatusCode.OK });
            return result;
        } catch (error) {
            span.recordException(error);
            span.setStatus({ code: SpanStatusCode.ERROR });
            throw error;
        } finally {
            span.end();
        }
    });
}

Acceptance Criteria:

  • OpenTelemetry dependencies added
  • Tracer configuration
  • Spans added to compilation operations
  • Integration with existing tracing context
  • Exporter configuration (OTLP, console)
  • Tests added
  • Documentation updated</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 13, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
adblock-compiler 9b8958d Feb 13 2026, 04:52 AM

return `${parsed.protocol}//${parsed.hostname}${parsed.pathname}`;
} catch {
// If URL parsing fails, return domain only or sanitized version
return url.replace(/[?#].*$/, '').substring(0, 100);

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
library input
may run slow on strings starting with '#' and with many repetitions of '#'.
This
regular expression
that depends on
library input
may run slow on strings starting with '#' and with many repetitions of '#'.
Copilot AI and others added 2 commits February 13, 2026 04:50
Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Co-authored-by: jaypatrick <1800595+jaypatrick@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement OpenTelemetry for distributed tracing feat: Add OpenTelemetry integration for distributed tracing Feb 13, 2026
Copilot AI requested a review from jaypatrick February 13, 2026 04:55
@jaypatrick jaypatrick marked this pull request as ready for review February 13, 2026 06:20
Copilot AI review requested due to automatic review settings February 13, 2026 06:20
@jaypatrick jaypatrick merged commit 82a1fe6 into main Feb 13, 2026
9 of 10 checks passed
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 spanContexts map 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

Comment on lines +39 to +40
private readonly spanContexts = new Map<string, unknown>();
private readonly operationNames = new Map<string, string>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] 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.

Comment on lines +369 to +372
} else if (Array.isArray(value)) {
// OpenTelemetry API only supports string arrays, not mixed types
// Stringify arrays for simplicity
span.setAttribute(key, JSON.stringify(value));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] Comment is misleading - OpenTelemetry API does support homogeneous primitive arrays (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.

Suggested change
} 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));

Comment on lines +399 to +402
private hashKey(key: string): string {
// Simple hash for privacy - first 8 chars of key or hash
return key.substring(0, 8) + '...';
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 [SUGGESTION] Method named hashKey but it truncates, not hashes. Consider renaming to truncateKey or implementing actual hashing if privacy is the goal:

Suggested change
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) + '...';
}

Comment on lines +259 to +261
if (!statusCode || statusCode < 400) {
span.setStatus({ code: SpanStatusCode.OK });
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 [SUGGESTION] Setting OK status when statusCode is undefined assumes success without confirmation. Consider only setting status when we have definitive information:

Suggested change
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';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 [NIT] SpanStatusCode is used at line 922 but missing from this import. Add it for completeness:

Suggested change
import { trace } from '@opentelemetry/api';
import { SpanStatusCode, trace } from '@opentelemetry/api';

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 OpenTelemetryExporter class 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.0 dependency 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 {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
You can also create custom spans for fine-grained tracing:

```typescript
import { trace } from '@opentelemetry/api';
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import { trace } from '@opentelemetry/api';
import { SpanStatusCode, trace } from '@opentelemetry/api';

Copilot uses AI. Check for mistakes.
"name": "@jk-com/adblock-compiler",
"version": "0.13.0",
"exports": "./src/index.ts",
"nodeModulesDir": "auto",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
"nodeModulesDir": "auto",

Copilot uses AI. Check for mistakes.
Comment on lines +397 to +401
* 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) + '...';
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
* 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)}`;

Copilot uses AI. Check for mistakes.
private readonly tracer: Tracer;
private readonly serviceName: string;
private readonly enableConsoleLogging: boolean;
private readonly activeSpans = new Map<string, Span>();
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement OpenTelemetry for distributed tracing

2 participants