Skip to content

Stop using OpenTelemetry SDK internal AttributesProcessor for pre-aggregated metrics - #4894

Draft
Rajkumar Rangaraj (rajkumar-rangaraj) wants to merge 3 commits into
mainfrom
remove-sdk-attributes-processor
Draft

Rajkumar Rangaraj (rajkumar-rangaraj) wants to merge 3 commits into
mainfrom
remove-sdk-attributes-processor

Conversation

@rajkumar-rangaraj

@rajkumar-rangaraj Rajkumar Rangaraj (rajkumar-rangaraj) commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Fix # .

Unblocks #4888 (and the closed #4876): OpenTelemetry SDK 1.67.0 removed the experimental AttributesProcessor / ViewBuilder.addAttributesProcessor() (open-telemetry/opentelemetry-java#8818). The agent was using them, by putting classes in the SDK's io.opentelemetry.sdk.metrics packages, to add Application Insights specific attributes to the pre-aggregated standard metrics, so :agent:agent-tooling:compileJava fails and all CI jobs fail on those dependency updates.

Changes

The processor had access to the measurement context and did 4 things for the http.{server,client}.request.duration and rpc.{server,client}.duration metrics. They are now done as follows:

Before (SDK internal AttributesProcessor) After
Limit metric attributes to the view keys processor public View.builder().setAttributeFilter(...)
Add connectionStringOverrides / roleNameOverrides / Classic SDK attributes processor (from context) PreAggregatedMetricsCustomizer attributes extractor onEnd() (from context)
Add is_synthetic (http server) processor (from user_agent.original) context customizer onStart() (sees all start attributes) + attributes extractor onEnd()
Mark span IS_PRE_AGGREGATED processor, at record time attributes extractor onEnd(), only when the duration metric will be recorded (see below)
  • PreAggregatedMetricsCustomizer is registered from the existing AiInstrumenterCustomizerProvider for instrumenters with hasType(HTTP_SERVER | HTTP_CLIENT | RPC_SERVER | RPC_CLIENT), only when preAggregatedStandardMetrics is enabled and the Azure Monitor metric exporter is used. With otel.metrics.exporter=none the SDK has no metric readers, so the duration metrics aren't collected; with otlp only, they don't reach Application Insights. In both cases the spans must stay unmarked so ingestion still generates the standard metrics from them. The exporter is created when the SDK is built, which is before any instrumenter is built. RPC instrumenters are only customized when the old RPC semconv is emitted. With otel.semconv-stability.opt-in=rpc, upstream records rpc.*.call.duration, which aren't pre-aggregated standard metrics, so marking those spans would remove them from standard metrics.
  • Instrumenter.doStart() runs context customizers before the operation listeners' onStart(), and doEnd() runs attributes extractors right before the operation listeners' onEnd() with the same context. So the upstream metrics record the duration with the added attributes.
  • Moved AiViewRegistry, MetricView and UserAgents to com.microsoft.applicationinsights.agent.internal.preagg, and deleted ViewBuilderAccessor and MetricViewAttributesProcessor. Nothing in the agent lives in the SDK packages anymore.

Review focus: MetricsState (reflection)

Some upstream instrumenters have http/rpc attributes but don't record the duration metrics. In v2.32.0: AWS SDK 1.11/2.2 (http + rpc client), RMI, Spring RMI, GWT and the Netty connection spans. Previously those spans were never marked IS_PRE_AGGREGATED because no measurement was recorded for them. Marking them based on hasType() alone would make the exporter skip them, and ingestion would no longer count them in the standard dependency metrics.

To keep exact parity, MetricsState checks whether the upstream Http{Server,Client}Metrics / Rpc{Server,Client}Metrics per-operation state is in the context, since that's exactly when they record the duration:

  • The context keys are private, so they're looked up by reflection once. The names are unchanged in v2.33.1, and metricsStateIsResolved fails if they're renamed in a future update.
  • The context customizer captures the state that was already in the parent context, and only a state created by the current operation counts. Otherwise a nested metric-less span (for example with span suppression disabled) would inherit its parent's state and be marked.
  • If a key can't be found, a warning is logged and it falls back to assuming the metric is recorded. Not marking would double count all http requests in standard metrics instead.

Alternatives considered: recording our own duration metrics (larger change, behavior change for AWS SDK/RMI) or wrapping the global MeterProvider (fragile in the javaagent). Open to other ideas.

Known limitations

  • Duration metrics recorded by OpenTelemetry instrumentation libraries bundled in the application (through the API bridge, not the agent's instrumenters) only go through the views now. Their spans aren't marked and they don't get the override/synthetic attributes. Previously the processor handled every measurement of these instruments.
  • IS_SYNTHETIC is now also set on http server spans. The exporter ignores applicationinsights.internal.* span attributes (SpanDataMapper.ignorePrefix), so there's no telemetry change. The internal attributes also reach other metrics on the same instrumenter, such as rpc request/response size, but those have attributes advice, so the SDK drops non-advised keys.

Notes for #4888

  • With this PR on top of Bump the opentelemetry group across 1 directory with 11 updates #4888 (SDK 1.68.0, instrumentation 2.33.1), agent-tooling compiles and the preagg tests pass. 35 other unit tests fail with PropagatedSpan cannot be cast to ReadableSpan (SpanExporterWithAttributeProcessorTest, ExporterWithSpanProcessorTest, LogExporterWithAttributeProcessorTest, AlertTriggerSpanProcessorTest). Those tests use OpenTelemetrySdk.builder().build() without a tracer provider, so they get no-op spans on the new SDK and need an explicit SdkTracerProvider. That's not related to this change, so it's left for Bump the opentelemetry group across 1 directory with 11 updates #4888.
  • 2.33 adds rpc.{client,server}.call.duration for the new RPC semconv, which MetricView and the exporter don't cover yet. Until they do, rpc spans aren't marked in stable-only mode (see above).

Validation (on current main versions: SDK 1.66.0, instrumentation 2.32.0)

  • ./gradlew :agent:agent-tooling:check: passed (334 tests, 0 failures)
  • ./gradlew :agent:agent-tooling:test --tests "*preagg*": passed (20 tests)
    • PreAggregatedMetricsInstrumenterTest (6) uses real Instrumenters with the customizer applied through the customizer provider mechanism: http server (synthetic + override attributes, span marked), rpc client with metrics, rpc client without metrics (RMI/AWS SDK case, not marked), stable-only rpc semconv (not customized), and with/without the Azure Monitor metric exporter.
    • PreAggregatedMetricsTest (14) covers the existing http client/server and rpc cases, a span not marked when no metric is recorded (each type), a nested span inheriting its parent's metrics state (not marked), reflection resolution, and a missing field falling back without failing.
    • Mutation check: reverting the stable-rpc gate or the inherited-state check each makes the corresponding test fail.
  • ./gradlew :smoke-tests:apps:HttpPreaggregatedMetrics:smokeTest --tests "*HttpPreaggregatedMetricsTest$Tomcat8Java8Test": passed
  • ./gradlew :smoke-tests:apps:HttpPreaggregatedMetrics:smokeTest --tests "*HttpPreaggregatedMetricsTestOtlpEnabled$Tomcat8Java8Test": passed
  • ./gradlew :smoke-tests:apps:PreAggMetricsWithRoleNameOverridesAndSampling:smokeTest --tests "*PreAggMetricsWithRoleNameOverridesAndSamplingTest$Tomcat8Java8Test": passed
  • ./gradlew :smoke-tests:apps:gRPC:smokeTest --tests "*GrpcTest$Java8Test": passed
  • ./gradlew :smoke-tests:apps:ConnectionStringOverrides:smokeTest --tests "*ConnectionStringOverridesTest$Tomcat8Java8Test": passed
  • Merged on top of Bump the opentelemetry group across 1 directory with 11 updates #4888 (SDK 1.68.0, instrumentation 2.33.1): ./gradlew :agent:agent-tooling:test --tests "*preagg*" passes (18/18, run before the Azure Monitor metric exporter gate was added). Other failures are listed above.

For significant contributions please make sure you have completed the following items:

  • Design discussion issue #
  • Changes in public surface reviewed (no public surface change, internal refactoring only)
  • CHANGELOG.md updated (not needed, no user-visible change)

…regated metrics

AttributesProcessor and ViewBuilder.addAttributesProcessor() were removed in
OpenTelemetry SDK 1.67.0 (open-telemetry/opentelemetry-java#8818), which breaks
the build for the OpenTelemetry dependency updates (#4876, #4888).

Add the Application Insights specific attributes (connection string and role name
overrides, synthetic) and mark the span as pre-aggregated from an InstrumenterCustomizer
instead, and limit the metric attributes using the public View attribute filter.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Don't customize rpc instrumenters when only stable rpc semconv is emitted, since
  rpc.*.call.duration are not pre-aggregated standard metrics
- Only mark the span when the metrics state was created by the current operation,
  not inherited from a parent operation
- Don't use the static logger from the enum constructors, it isn't initialized yet
- Add tests using real Instrumenters

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

HTTP/RPC spans can be marked pre-aggregated without replacement duration metrics, causing standard-metric loss in nested-operation, RPC opt-in, and no-reader configurations.

4 open issues
What changed in this PR

Refactors Application Insights’ pre-aggregated HTTP/RPC metrics to avoid removed OpenTelemetry SDK internals and help unblock dependency upgrade #4888.

Changes:

  • Replaces internal attribute processing with public view filters and instrumenter hooks.
  • Moves metric helpers into an agent-owned package and adds operation-state detection.
  • Extends tests for synthetic traffic, overrides, missing duration metrics, and reflection resolution.
File Description
agent/​agent-tooling/​src/​test/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​preagg/​PreAggregatedMetricsTest.java Relocates and extends pre-aggregated metric tests.
agent/​agent-tooling/​src/​main/​java/​io/​opentelemetry/​sdk/​metrics/​ViewBuilderAccessor.java Removes the SDK access shim.
agent/​agent-tooling/​src/​main/​java/​io/​opentelemetry/​sdk/​metrics/​internal/​view/​MetricViewAttributesProcessor.java Removes the SDK-internal attribute processor.
agent/​agent-tooling/​src/​main/​java/​io/​opentelemetry/​sdk/​metrics/​internal/​view/​AiViewRegistry.java Removes the old SDK-package view registry.
agent/​agent-tooling/​src/​main/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​preagg/​UserAgents.java Moves synthetic user-agent detection into the agent package.
agent/​agent-tooling/​src/​main/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​preagg/​PreAggregatedMetricsCustomizer.java Adds metric enrichment and span-marking hooks.
agent/​agent-tooling/​src/​main/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​preagg/​MetricView.java Defines retained metric and internal attribute keys.
agent/​agent-tooling/​src/​main/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​preagg/​MetricsState.java Detects upstream operation state through reflection.
agent/​agent-tooling/​src/​main/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​preagg/​AiViewRegistry.java Registers views using public attribute filters.
agent/​agent-tooling/​src/​main/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​init/​SecondEntryPoint.java Imports the relocated view registry.
agent/​agent-tooling/​src/​main/​java/​com/​microsoft/​applicationinsights/​agent/​internal/​init/​AiInstrumenterCustomizerProvider.java Registers metric customization when pre-aggregation is enabled.

Review effort: Max

// fall back to assuming the metric will be recorded
return true;
}
return context.get(contextKey) != null;
// fall back to assuming the metric will be recorded
return true;
}
return context.get(contextKey) != null;

// this is needed for detecting telemetry signals that will trigger pre-aggregated metrics via
// auto instrumentations
Span.fromContext(context).setAttribute(AiSemanticAttributes.IS_PRE_AGGREGATED, true);
field.setAccessible(true);
return (ContextKey<?>) field.get(null);
} catch (ReflectiveOperationException | RuntimeException e) {
logger.warn(
…ter is used

With otel.metrics.exporter=none (or otlp only), the pre-aggregated metrics don't reach
Application Insights, so the spans must not be marked as pre-aggregated, otherwise the
standard metrics are not generated from them either.

Co-authored-by: Copilot <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