Direct Cronet usage currently gets no out-of-the-box network metrics from Sentry. In OkHttp-over-Cronet setups where Sentry's interceptor runs, the HTTP request is captured, but DNS/connect/TLS timings are missing. Some OkHttp bridges also prevent the interceptor from running, which means trace propagation and failed-request capture are missing too. Cronet's RequestFinishedInfo.Metrics is the source for transport timings, so OkHttp-side instrumentation alone can't fill that gap.
Raised by an Android SDK user who runs OkHttp over Cronet and currently uses reflection on SentryOkHttpEventListener internals (eventMap, internal attribute keys) to attach Cronet timings to the Sentry span.
Current behavior
- Direct Cronet: there is no out-of-the-box Sentry network instrumentation, so no request-level network metrics are captured.
- OkHttp over Cronet, with Sentry's interceptor reached: the HTTP request is captured, but Cronet-owned DNS/connect/TLS details are not.
- Google's cronet-transport-for-okhttp (and forks of it):
CronetInterceptor is the last application interceptor and never calls chain.proceed. SAGP appends SentryOkHttpInterceptor after app interceptors (ResponseWithInterceptorChainMethodVisitor.kt), so it never runs: no trace headers, no failed-request capture. SentryOkHttpEventListener only sees callStart/callEnd.
- Bridges that swap transport at
ConnectInterceptor: our interceptor runs and header/body events may be replayed, but dnsStart, connectStart, secureConnectStart and connectionAcquired never fire because Cronet owns the socket.
- Other Cronet wrappers: no Sentry Cronet integration exists today.
Why OkHttp-side changes aren't enough
EventListener callbacks carry no timestamps and SentryOkHttpEvent stamps them with now(), so a bridge replaying DNS/connect/TLS events after the fact would record ~0ms durations.
- Moving
SentryOkHttpInterceptor to the front of the chain globally isn't viable: it would record the request before other interceptors modify it (auth headers, URL rewrites) and change span semantics for every OkHttp app.
Cronet findings (source reading only, not validated on device)
UrlRequest.Builder.addRequestAnnotation, UrlRequest.Builder.setRequestFinishedListener and CronetEngine.addRequestFinishedListener are in the base API since cronet-api 113; older versions need the Experimental* classes.
- These base methods are silent no-ops on providers that don't implement them (e.g. the fallback Java provider), so missing support shows up as a callback that never arrives.
- Embedded Cronet: engine-level and per-request listeners, annotations and metrics all work (
CronetUrlRequest.maybeReportMetrics).
- Platform
HttpEngine (Android 14+): listeners and annotations work, but getMetrics() returns CronetMetrics.empty() (AndroidRequestFinishedInfoWrapper), so phase timings are unavailable. Default CronetProvider selection can pick HttpEngine before Play Services.
- Play Services Cronet: closed source, unverified.
RequestFinishedInfo is delivered asynchronously and can arrive after the OkHttp span has finished (Google's bridge doesn't wait for it).
Proposed direction (from discussion, not final)
- A public API to record transport timings on an
http.client span using absolute timestamps (DNS, connect, TLS, send, TTFB) plus protocol, socketReused, QUIC error codes and wire byte counts, with Sentry-owned attribute names.
- A
sentry-cronet integration covering two cases:
- Direct Cronet: full HTTP client instrumentation, on par with the other HTTP integrations — create the
http.client span, add trace headers, record request/response data (method, URL, status, sizes), breadcrumbs and failed-request capture, plus transport timings via the API above.
- Cronet as OkHttp transport: don't create a second span; enrich the existing OkHttp span with transport timings only.
- Correlation via a request annotation carrying the Sentry span, plus an engine-level
RequestFinishedInfo.Listener (so it doesn't replace per-request listeners set by bridges) that reads it.
- Manual wiring first (bridge mapping hooks, wrapper listener hooks); SAGP call-site instrumentation of
UrlRequest.Builder.build() / CronetEngine.Builder.build() later.
Open questions
- How to correlate a
UrlRequest with the originating OkHttp Call (e.g. a thread-local set around getResponseWithInterceptorChain), given it relies on the bridge building the request on the call's thread.
- Play Services provider support for annotations, listeners and metrics.
- How long to hold a span open for late
RequestFinishedInfo, and what to do when metrics are empty.
via roman.
--
View Junior Session [Sentry]
Direct Cronet usage currently gets no out-of-the-box network metrics from Sentry. In OkHttp-over-Cronet setups where Sentry's interceptor runs, the HTTP request is captured, but DNS/connect/TLS timings are missing. Some OkHttp bridges also prevent the interceptor from running, which means trace propagation and failed-request capture are missing too. Cronet's
RequestFinishedInfo.Metricsis the source for transport timings, so OkHttp-side instrumentation alone can't fill that gap.Raised by an Android SDK user who runs OkHttp over Cronet and currently uses reflection on
SentryOkHttpEventListenerinternals (eventMap,internalattribute keys) to attach Cronet timings to the Sentry span.Current behavior
CronetInterceptoris the last application interceptor and never callschain.proceed. SAGP appendsSentryOkHttpInterceptorafter app interceptors (ResponseWithInterceptorChainMethodVisitor.kt), so it never runs: no trace headers, no failed-request capture.SentryOkHttpEventListeneronly seescallStart/callEnd.ConnectInterceptor: our interceptor runs and header/body events may be replayed, butdnsStart,connectStart,secureConnectStartandconnectionAcquirednever fire because Cronet owns the socket.Why OkHttp-side changes aren't enough
EventListenercallbacks carry no timestamps andSentryOkHttpEventstamps them withnow(), so a bridge replaying DNS/connect/TLS events after the fact would record ~0ms durations.SentryOkHttpInterceptorto the front of the chain globally isn't viable: it would record the request before other interceptors modify it (auth headers, URL rewrites) and change span semantics for every OkHttp app.Cronet findings (source reading only, not validated on device)
UrlRequest.Builder.addRequestAnnotation,UrlRequest.Builder.setRequestFinishedListenerandCronetEngine.addRequestFinishedListenerare in the base API since cronet-api 113; older versions need theExperimental*classes.CronetUrlRequest.maybeReportMetrics).HttpEngine(Android 14+): listeners and annotations work, butgetMetrics()returnsCronetMetrics.empty()(AndroidRequestFinishedInfoWrapper), so phase timings are unavailable. DefaultCronetProviderselection can pickHttpEnginebefore Play Services.RequestFinishedInfois delivered asynchronously and can arrive after the OkHttp span has finished (Google's bridge doesn't wait for it).Proposed direction (from discussion, not final)
http.clientspan using absolute timestamps (DNS, connect, TLS, send, TTFB) plus protocol,socketReused, QUIC error codes and wire byte counts, with Sentry-owned attribute names.sentry-cronetintegration covering two cases:http.clientspan, add trace headers, record request/response data (method, URL, status, sizes), breadcrumbs and failed-request capture, plus transport timings via the API above.RequestFinishedInfo.Listener(so it doesn't replace per-request listeners set by bridges) that reads it.UrlRequest.Builder.build()/CronetEngine.Builder.build()later.Open questions
UrlRequestwith the originating OkHttpCall(e.g. a thread-local set aroundgetResponseWithInterceptorChain), given it relies on the bridge building the request on the call's thread.RequestFinishedInfo, and what to do when metrics are empty.via roman.
--
View Junior Session [Sentry]