Skip to content

feat(otlp): add OTLP/JSON channel (@microsoft/applicationinsights-otlpchannel-js) - #2751

Draft
Jackson Weber (JacksonWeber) wants to merge 2 commits into
microsoft:mainfrom
JacksonWeber:jacksonweber/otlp-json-channel
Draft

feat(otlp): add OTLP/JSON channel (@microsoft/applicationinsights-otlpchannel-js)#2751
Jackson Weber (JacksonWeber) wants to merge 2 commits into
microsoft:mainfrom
JacksonWeber:jacksonweber/otlp-json-channel

Conversation

@JacksonWeber

@JacksonWeber Jackson Weber (JacksonWeber) commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds @microsoft/applicationinsights-otlpchannel-js (0.1.0), a preview browser channel that converts Application Insights telemetry to OTLP/JSON and exports it over OTLP/HTTP to /v1/traces and /v1/logs.

The implementation is dependency-light: it uses the SDK's existing core transport infrastructure and does not depend on any @opentelemetry/* package.

Telemetry mapping

Application Insights baseType OTLP representation
RequestData SERVER span
RemoteDependencyData CLIENT span, or INTERNAL for InProc
PageviewData INTERNAL span by default; configurable as a log
Native Common Schema OTelSpan Span preserving kind, parent, trace state, status, and attributes
MessageData LogRecord with severity
ExceptionData LogRecord with exception.* attributes and chained exceptions
EventData LogRecord with eventName
PageviewPerformanceData LogRecord
MetricData Optional LogRecord when metricsAsLogs is enabled

Context tags are promoted onto the OTLP Resource (service.name, service.instance.id, service.version, and related attributes). Remaining tags, Part A extensions, Part C data, custom properties, and measurements become record attributes; Application Insights-specific values use the microsoft. namespace.

Conversion and batching

  • Converts telemetry on processTelemetry, before the send path.
  • Normally pre-serializes each record and tracks its byte size incrementally.
  • Groups buffered records by resource and OTLP signal.
  • Splits batches by configurable byte and record limits.
  • Sends spans and logs to their signal-specific endpoints.
  • Retains compact original item summaries so lifecycle notifications and SDK Stats receive telemetry names/types without retaining the complete source object graph.
  • Supports pause, resume, synchronous/asynchronous flush, unload flush, teardown, and isCompletelyIdle().
  • Uses priority 1021, after the existing shipped channels, and continues the plugin chain unless consumeEvents is enabled.

Transport and reliability

Uses the existing SenderPostManager transport stack and includes:

  • Fetch/XHR transports and Beacon unload fallback.
  • Custom httpXHROverride support.
  • Custom headers/authentication, fetch credentials, XHR timeout, and explicit trace/log endpoint overrides.
  • Exponential retry with jitter and Retry-After support.
  • Configurable retry status codes, retry disablement, and bounded retry attempts.
  • Retry-attempt preservation across batch requeue/splitting.
  • Bounded synchronous unload retries.
  • OTLP partial-success parsing and aggregate accepted/discarded notifications.
  • Optional asynchronous gzip compression through CompressionStream, also honoring the SDK zipPayload feature flag.
  • Sender-equivalent deterministic percentage sampling; MetricData is never sampled out.
  • eventsSendRequest, eventsSent, eventsRetry, and eventsDiscarded notifications for SDK Stats and notification listeners.
  • Dynamic configuration updates for endpoints, transports, batching, conversion, resource attributes, privacy, sampling, retry, and compression behavior.

Privacy

OTLP does not have an equivalent of Common Schema PII/customer-content metadata. piiMode therefore defaults to drop; it can alternatively hash values or retain them with marker attributes for downstream scrubbing.

The instrumentation key is excluded from the resource by default and can be included explicitly with includeIKeyInResource.

Configuration highlights

Configuration is supplied under the OtlpChannel extension key. Major options include:

  • Signal endpoints, headers, resource attributes, and instrumentation scope.
  • pageViewAs, metricsAsLogs, piiMode, and samplingPercentage.
  • Batch bytes, batch records, interval, and in-memory event limits.
  • Transport order, unload transport order, fetch/XHR behavior, and custom transport.
  • Retry codes, retry limits, unload retry limits, and retry disablement.
  • Payload compression, telemetry disablement, event consumption, and instrumentation-key resource inclusion.

See channels/otlp-channel-js/README.md and IOtlpChannelConfig for the complete configuration reference.

Example and protocol validation

Adds examples/otlp, a multi-page test site that runs two isolated SDK instances per page against a local mock OTLP collector. It supports manual and headless execution and validates:

  • OTLP export envelopes and signal endpoints.
  • Resource/scope structure and expected attributes.
  • Trace/span IDs, kinds, status, parent relationships, and timestamp precision.
  • Log timestamps and severity mapping.
  • AnyValue correctness and duplicate-key prevention.
  • PII drop/hash behavior.
  • Multi-instance configuration and telemetry isolation.
  • Unloading one SDK instance without affecting another.

The example can also send through a real OpenTelemetry Collector so malformed payloads are rejected by the protocol implementation.

Tests

The OTLP package includes unit coverage for:

  • Channel lifecycle, batching, limits, transports, flush, unload, dynamic configuration, and chaining.
  • Sampling, compression, notifications, retry limits/codes, unload retries, partial success, idle state, and throwing transports.
  • Every supported telemetry conversion and deliberate omission.
  • Field-level fidelity, resources, attributes, PII modes, IDs, and nanosecond time utilities.

The current OTLP channel suite contains 117 passing tests locally.

Known draft limitations

  • The OTLP metrics signal (/v1/metrics) is not implemented; MetricData can currently be exported only as logs.
  • Span events and links are not populated.
  • Conversion from the Application Insights telemetry shape back to OTLP cannot be perfectly lossless, although custom span attributes are preserved through baseData.properties.
  • The channel exposes getOfflineSupport(), but the generic OfflineChannel persistence contract currently carries one payload URL and cannot correctly replay mixed /v1/traces and /v1/logs batches. Signal-aware persistent replay still needs follow-up work before offline durability parity is claimed.
  • Unlike the classic Sender, the channel does not yet include integrated session-storage sent/unsent recovery, redirect affinity, or Beacon payload splitting.
  • Custom headers cannot be attached when Beacon is used, and collectors must permit the CORS preflight required by OTLP/JSON with custom headers.

Repository wiring

Registers the new package in:

  • rush.json
  • gruntfile.js
  • version.json
  • .aiAutoMinify.json
  • tools/release-tools/package_groups.json
  • RELEASES.md

This PR remains draft while the limitations above and final package/release decisions are evaluated.

…pchannel-js)

Adds a new preview channel that converts Application Insights telemetry to
OTLP/JSON in memory and exports it to an OTLP HTTP endpoint (/v1/traces and
/v1/logs) with no @opentelemetry/* dependency.

- Conversion and serialization happen on the processTelemetry path, with
  records buffered pre-grouped by resource and signal and incremental byte
  accounting, so flushing a batch performs no conversion work
- RequestData / RemoteDependencyData / PageviewData export as spans;
  MessageData / ExceptionData / EventData / PageviewPerformanceData export as
  log records; native Common Schema OTelSpan items export as spans directly
- Context tags are promoted onto the OTLP Resource, other values become record
  attributes namespaced under microsoft.
- Values marked as PII or customer content are dropped by default (piiMode)
- Implements getOfflineSupport() for use with the offline channel
- Adds examples/otlp, a multi page test site running two isolated SDK
  instances per page against a local mock OTLP collector, runnable headlessly
- Registers the package in rush.json, gruntfile.js, version.json,
  .aiAutoMinify.json and package_groups.json

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
}

results.check(!seen[attr.key], path + " does not repeat the key '" + attr.key + "'");
seen[attr.key] = true;
seen[attr.key] = true;

validateAnyValue(results, attr.value, attrPath + "(" + attr.key + ").value");
map[attr.key] = attr.value;
const serviceName = resourceAttrs["service.name"] && resourceAttrs["service.name"].stringValue;
const marker = resourceAttrs["test.instance.marker"] && resourceAttrs["test.instance.marker"].stringValue;
if (serviceName) {
summary.services[serviceName] = (summary.services[serviceName] || 0) + 1;

if (isTrace) {
summary.spans++;
summary.spanNames[record.name] = (summary.spanNames[record.name] || 0) + 1;
const statusCode = record.status && typeof record.status.code !== "undefined"
? record.status.code : 0;

summary.spanKinds[kind] = (summary.spanKinds[kind] || 0) + 1;
? record.status.code : 0;

summary.spanKinds[kind] = (summary.spanKinds[kind] || 0) + 1;
summary.spanStatuses[statusCode] = (summary.spanStatuses[statusCode] || 0) + 1;
const telemetryType = attrs["microsoft.telemetry_type"] &&
attrs["microsoft.telemetry_type"].stringValue;
if (telemetryType) {
summary.telemetryTypes[telemetryType] = (summary.telemetryTypes[telemetryType] || 0) + 1;

if (serviceName && recordMarker) {
const bucket = summary.markersByService[serviceName] ||
(summary.markersByService[serviceName] = {});
if (serviceName && recordMarker) {
const bucket = summary.markersByService[serviceName] ||
(summary.markersByService[serviceName] = {});
bucket[recordMarker] = (bucket[recordMarker] || 0) + 1;
"Content-Length": Buffer.byteLength(payload),
"Access-Control-Allow-Origin": "*"
});
res.end(payload);
Add Sender-equivalent sampling, compression, lifecycle notifications, retry controls, unload retry accounting, and idle-state reporting. Preserve retry attempts and original telemetry summaries across OTLP batching.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

2 participants