Skip to content

RUM-13568: [Cronet]: Supporting RUM <> APM integration for Cronet#3136

Open
satween wants to merge 1 commit intodevelopfrom
tvaleev/feature/RUM-13568-apm-dependencies
Open

RUM-13568: [Cronet]: Supporting RUM <> APM integration for Cronet#3136
satween wants to merge 1 commit intodevelopfrom
tvaleev/feature/RUM-13568-apm-dependencies

Conversation

@satween
Copy link
Contributor

@satween satween commented Jan 20, 2026

What does this PR do?

In this PR we are copying the main logic from TracingInterceptor / DatadogInterceptor into the NetworkTracingInstrumentation in order to:

  1. Support RUM <> APM integration for Cronet
  2. Create a foundation for single network instrumentation logic that should be shared across several networking libraries in the future.

Motivation

Continuing to add an integration for Cronet library meanwhile preparing infrastructure to migrate OkHttp onto the same logic.

Additional Notes

Note that in this PR we are not supporting deep network instrumentation that is being done by TracingInterceptor in OkHttp. This logic will be supported in a further PR as this one is already pretty big.

NOTE: In this PR as an experiment an AI agent helped to write tests and some docs. I've made few iterations of a fixes for that but if you spot any issues - let me know. AI wasn't involved into production code development.

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)

@satween satween requested a review from a team as a code owner January 20, 2026 16:21
@satween
Copy link
Contributor Author

satween commented Jan 20, 2026

@codex

@chatgpt-codex-connector
Copy link

To use Codex here, create a Codex account and connect to github.

@kikoveiga
Copy link
Contributor

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ee7daf6e5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 36 to 41
override fun addHeader(header: String, value: String?): UrlRequest.Builder = apply {
headers[header] = listOfNotNull(value)
delegate.addHeader(header, value)
val currentValues = headers[header]?.toMutableList() ?: mutableListOf()
if (value != null) {
currentValues.add(value)
}
headers[header] = currentValues

Choose a reason for hiding this comment

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

P1 Badge Forward added headers to the Cronet delegate

DatadogUrlRequestBuilder.addHeader() now only updates the local headers map and never forwards to delegate.addHeader. Later, buildCronetRequest() calls addHeaders(headers), which invokes this same override again, so the delegate builder never receives any headers. This means every Cronet request built through DatadogCronetEngine will drop user-supplied headers and tracing headers, breaking authentication and trace propagation. Consider forwarding to delegate.addHeader (or ensuring addHeaders writes to the delegate) so the actual request carries the headers.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting catch, but the problem was bit deeper, fixed

@satween satween force-pushed the tvaleev/feature/RUM-13568-apm-dependencies branch 5 times, most recently from 21d8777 to 3db3a62 Compare January 22, 2026 13:41
@datadog-official

This comment has been minimized.

@codecov-commenter
Copy link

codecov-commenter commented Jan 22, 2026

Codecov Report

❌ Patch coverage is 75.07599% with 164 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.22%. Comparing base (39d8b4a) to head (0e81151).
⚠️ Report is 4 commits behind head on develop.

Files with missing lines Patch % Lines
...android/trace/internal/DatadogPropagationHelper.kt 62.24% 43 Missing and 11 partials ⚠️
...ndroid/trace/internal/ApmNetworkInstrumentation.kt 61.95% 27 Missing and 16 partials ⚠️
...oid/cronet/internal/DatadogCronetRequestContext.kt 89.22% 11 Missing ⚠️
...net/internal/DatadogRequestFinishedInfoListener.kt 16.67% 9 Missing and 1 partial ⚠️
.../com/datadog/android/cronet/DatadogCronetEngine.kt 72.73% 5 Missing and 4 partials ⚠️
...e/internal/net/NetworkTracingInstrumentationExt.kt 73.08% 4 Missing and 3 partials ⚠️
...tadog/android/cronet/internal/DatadogUrlRequest.kt 61.11% 1 Missing and 6 partials ⚠️
.../android/cronet/internal/DatadogRequestCallback.kt 76.00% 6 Missing ⚠️
...id/trace/ApmNetworkInstrumentationConfiguration.kt 93.94% 4 Missing ⚠️
.../instrumentation/network/HttpRequestInfoBuilder.kt 0.00% 3 Missing ⚠️
... and 7 more
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3136      +/-   ##
===========================================
+ Coverage    70.96%   71.22%   +0.27%     
===========================================
  Files          912      921       +9     
  Lines        33548    34109     +561     
  Branches      5640     5773     +133     
===========================================
+ Hits         23804    24294     +490     
- Misses        8173     8174       +1     
- Partials      1571     1641      +70     
Files with missing lines Coverage Δ
...droid/flags/openfeature/internal/FlagsClientExt.kt 85.71% <ø> (ø)
...uration/RumResourceInstrumentationConfiguration.kt 100.00% <100.00%> (ø)
.../android/rum/internal/monitor/DatadogRumMonitor.kt 88.64% <ø> (-0.11%) ⬇️
...oid/rum/internal/net/RumResourceInstrumentation.kt 81.48% <100.00%> (+81.48%) ⬆️
...om/datadog/trace/core/propagation/B3HttpCodec.java 6.38% <ø> (ø)
...tadog/trace/core/propagation/DatadogHttpCodec.java 13.68% <ø> (ø)
...m/datadog/trace/core/propagation/W3CHttpCodec.java 12.99% <ø> (ø)
...datadog/android/trace/DeterministicTraceSampler.kt 100.00% <100.00%> (ø)
...com/datadog/android/trace/TraceContextInjection.kt 100.00% <100.00%> (ø)
...og/android/trace/internal/DatadogTracingToolkit.kt 33.33% <100.00%> (+11.90%) ⬆️
... and 24 more

... and 34 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

private val tracedHosts = listOf(
"datadoghq.com",
"127.0.0.1",
"storage.googleapis.com"
Copy link
Member

Choose a reason for hiding this comment

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

is it added for some validation? we don't own this host, so sending trace context there is useless

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's added in order to check that cronet is working with QUIC, RUM <> APM integration will not work for this host, but I want to keep this for sample app in order to send spans for it ( and check that APM is working with QUICK)

return builder.setInsightsCollector(insightsCollector)
}

fun RumResourceInstrumentationConfiguration?.build(name: String): RumResourceInstrumentation? =
Copy link
Member

Choose a reason for hiding this comment

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

why is it a part of _RumInternalProxy? how it is supposed to be called?

Copy link
Contributor Author

@satween satween Feb 2, 2026

Choose a reason for hiding this comment

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

I want to hide build method from users. This made for several reasons:

  1. build() - basically useless method, it does not add any information to the builder
  2. Integrations (Cronet and OkHttp) should set their names into the instrumentation.

how it is supposed to be called?

val rumResourceInstrumentation = with(_RumInternalProxy) {       
  rumInstrumentationConfiguration.build(CRONET_NETWORK_INSTRUMENTATION_NAME)
}

Copy link
Member

@0xnm 0xnm Feb 4, 2026

Choose a reason for hiding this comment

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

I'm wondering how it looks like in a java bytecode. Are you sure that rumInstrumentationConfiguration.build(CRONET_NETWORK_INSTRUMENTATION_NAME) call is not possible outside of _RumInternalProxy receiver scope?

First time I see such trick given that visibility of extension function is public.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After several experiments with api calls - I decided to move back to more classic approach, as in current state build method is still visible for customers but they cannot call it which could be confusing

@satween satween force-pushed the tvaleev/feature/RUM-13568-apm-dependencies branch 2 times, most recently from 4ef7d94 to 6c726f5 Compare February 2, 2026 16:25
@satween satween requested review from 0xnm and ambushwork February 2, 2026 16:26
@satween satween force-pushed the tvaleev/feature/RUM-13568-apm-dependencies branch from 6c726f5 to 0e81151 Compare February 2, 2026 18:36
Copy link
Member

@0xnm 0xnm left a comment

Choose a reason for hiding this comment

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

I partially covered this PR, but will continue review later.

return builder.setInsightsCollector(insightsCollector)
}

fun RumResourceInstrumentationConfiguration?.build(name: String): RumResourceInstrumentation? =
Copy link
Member

@0xnm 0xnm Feb 4, 2026

Choose a reason for hiding this comment

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

I'm wondering how it looks like in a java bytecode. Are you sure that rumInstrumentationConfiguration.build(CRONET_NETWORK_INSTRUMENTATION_NAME) call is not possible outside of _RumInternalProxy receiver scope?

First time I see such trick given that visibility of extension function is public.

private val localTracerReference: AtomicReference<DatadogTracer> = AtomicReference()

@Synchronized
@Suppress("UnsafeThirdPartyFunctionCall") // updateAndGet is safe
Copy link
Member

Choose a reason for hiding this comment

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

there is no updateAndGet usage though

@satween satween force-pushed the tvaleev/feature/RUM-13568-apm-dependencies branch from 0e81151 to f636bd2 Compare February 5, 2026 16:09
@satween satween requested a review from 0xnm February 5, 2026 16:09
Comment on lines +107 to +109
val result = with(_RumInternalProxy.Companion) {
builder.createInstrumentation(fakeInstrumentationName)
}
Copy link
Member

Choose a reason for hiding this comment

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

need to update this

fun setTraceSampler(com.datadog.android.core.sampling.Sampler<com.datadog.android.trace.api.span.DatadogSpan>)
fun setTraceContextInjection(TraceContextInjection)
fun set404ResourcesRedacted(Boolean)
fun setTracingScope(ApmNetworkTracingScope)
Copy link
Member

Choose a reason for hiding this comment

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

very nit: tracing vs trace above, but maybe it is okay if we think of tracing as a process, not as of trace as item

* Sets the tracing scope for network instrumentation.
*
* This controls how detailed the tracing will be:
* - [ApmNetworkTracingScope.DETAILED]: Traces both application-level requests and internal
Copy link
Member

Choose a reason for hiding this comment

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

maybe ALL is a better name? Also this separation applies to OkHttp only, does Cronet also have similar?


/**
* Only application level request is gonna be traced.
* In this mode the Datadog SDK still able to link trace spans to Rum.Resources making possible to navigate
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* In this mode the Datadog SDK still able to link trace spans to Rum.Resources making possible to navigate
* In this mode the Datadog SDK still able to link trace spans to RUM Resources making possible to navigate

"onRequestIntercepted callback, leading to infinite recursion."

internal const val ERROR_REQUEST_INFO_IS_NOT_MUTABLE =
"RequestInfo is not mutable. Your requests won't be traced."
Copy link
Member

Choose a reason for hiding this comment

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

should it be HttpRequestInfo?

val builder = ApmNetworkInstrumentationConfiguration(fakeTracedHosts)

// When
val result: ApmNetworkInstrumentation = with(DatadogTracingToolkit) {
Copy link
Member

Choose a reason for hiding this comment

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

with is redundant here

@Test
fun `M return false W isExtractedContext() {non-extracted DatadogSpanContextAdapter}`() {
// Given
val mockAgentSpanContext: com.datadog.trace.bootstrap.instrumentation.api.AgentSpan.Context = mock()
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
val mockAgentSpanContext: com.datadog.trace.bootstrap.instrumentation.api.AgentSpan.Context = mock()
val mockAgentSpanContext = mock<AgentSpan.Context>()

Comment on lines +198 to +203
any<
(
HttpRequestInfo,
(String, String) -> Boolean
) -> Unit
>()
Copy link
Member

Choose a reason for hiding this comment

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

nit: seems like formatting gone wrong

@OptIn(ExperimentalRumApi::class)
@Test
fun `M propagate RumResourceAttributesProvider W setRumResourceAttributesProvider()`() {
fun `M propagate RumResourceAttributesProvider W setCustomRumInstrumentation()`() {
Copy link
Member

Choose a reason for hiding this comment

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

there is no such method

@OptIn(ExperimentalRumApi::class)
@Test
fun `M propagate sdkInstanceName W setSdkInstanceName()`(@StringForgery sdkInstanceName: String) {
fun `M propagate sdkInstanceName W setCustomRumInstrumentation()`(@StringForgery sdkInstanceName: String) {
Copy link
Member

Choose a reason for hiding this comment

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

same here

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.

5 participants