Skip to content

[Refactoring] Split containers/api-proxy/otel.js into focused modules #4388

@github-actions

Description

@github-actions

Refactoring Opportunity

Summary

  • File: containers/api-proxy/otel.js
  • Current size: 541 lines
  • Responsibilities identified: 4 distinct concerns

Evidence

otel.js is the OpenTelemetry integration for the api-proxy. It was written as a single module but has grown to contain four independent layers:

1. OTLP serialization utilities (lines 77–186, ~110 lines)

Ten pure functions that convert Node.js OpenTelemetry SDK objects into OTLP protobuf-style wire format. They have no side effects and no dependency on global state:

function parseOtlpHeaders(raw) { ... }
function hrTimeToNanoString(hrTime) { ... }
function serializeAttrValue(val) { ... }
function serializeAttributes(attrs) { ... }
function serializeEvent(event) { ... }
function toOtlpKind(kind) { ... }
function serializeStatus(status) { ... }
function serializeSpan(span) { ... }
function buildResourceSpans(spans, resource) { ... }

2. Span exporter classes (lines 187–342, ~156 lines)

Two independent SpanExporter implementations:

  • ProxyAwareOtlpExporter (~99 lines, lines 187–285): HTTP/HTTPS exporter with proxy-agent support, OTLP batching, and retry logic. Its size alone triggers the functions->80-lines threshold.
  • FileSpanExporter (~57 lines, lines 286–342): writes spans to a JSON-lines log file. A completely independent strategy.

Both classes depend only on the serialization utilities above, not on the global tracer state initialized below.

3. Tracer initialization and context building (lines 343–410, ~68 lines)

_init() and _buildParentContext() set up the global NodeTracerProvider and wire the exporters to span processors. This is the only section that mutates module-level state (_provider, _tracer, _enabled).

4. Span lifecycle API (lines 411–542, ~132 lines)

Public functions consumed by proxy-request.js and server.js:

function startRequestSpan({ provider, method, path, requestId }) { ... }   // ~34 lines
function setTokenAttributes(span, { ... }) { ... }                          // ~32 lines
function endSpan(span, statusCode) { ... }                                  // ~20 lines
function endSpanError(span, err, statusCode) { ... }                        // ~29 lines
function shutdown() { ... }
function isEnabled() { ... }

Proposed Split

New module Content Est. lines
otel-serialization.js parseOtlpHeaders, hrTimeToNanoString, serializeAttrValue, serializeAttributes, serializeEvent, toOtlpKind, serializeStatus, serializeSpan, buildResourceSpans ~115
otel-exporters.js ProxyAwareOtlpExporter, FileSpanExporter (requires otel-serialization.js) ~160
otel.js (trimmed) _init, _buildParentContext, startRequestSpan, setTokenAttributes, endSpan, endSpanError, shutdown, isEnabled ~270

otel.js would require('./otel-serialization') and require('./otel-exporters') in its _init, keeping all call sites unchanged.

Affected Callers

grep -rn "require.*otel\|from.*otel" containers/api-proxy/ 2>/dev/null | grep -v test

Current callers:

  • containers/api-proxy/proxy-request.jsotel.startRequestSpan, otel.endSpan, otel.endSpanError
  • containers/api-proxy/server.jsotelShutdown

Neither caller needs the exporter classes or serialization layer directly; the public API surface is unchanged.

Effort Estimate

Low — no logic changes required; only require() declarations and module.exports need updating.

Benefits

  • Serialization utilities become independently testable without instantiating a provider or exporter
  • ProxyAwareOtlpExporter (99 lines) and FileSpanExporter (57 lines) get their own file with focused tests
  • OTLP serialization is a maintenance hotspot whenever the Copilot LLM usage schema changes; isolating it reduces cognitive load during updates
  • otel.js drops from 541 to ~270 lines while retaining its role as the module's public facade

Detected by Refactoring Scanner workflow. Run date: 2026-06-05

Generated by Refactoring Opportunity Scanner · sonnet46 3.2M ·

  • expires on Aug 4, 2026, 4:02 PM UTC

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions