Skip to content

Commit da975c1

Browse files
committed
[Android] Add tracing interceptor
1 parent 148517f commit da975c1

File tree

22 files changed

+691
-18
lines changed

22 files changed

+691
-18
lines changed

Cargo.Bazel.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class OkHttpEventListenerMethodVisitor(
5353
) {
5454
private val captureOkHttpEventListenerFactory =
5555
"io/bitdrift/capture/network/okhttp/CaptureOkHttpEventListenerFactory"
56+
private val captureOkHttpTracingInterceptor =
57+
"io/bitdrift/capture/network/okhttp/CaptureOkHttpTracingInterceptor"
5658

5759
override fun onMethodEnter() {
5860
super.onMethodEnter()
@@ -64,6 +66,8 @@ class OkHttpEventListenerMethodVisitor(
6466
}
6567

6668
private fun addOverwritingEventListener() {
69+
addTracingInterceptor()
70+
6771
// Add the following call at the beginning of the constructor with the Builder parameter:
6872
// builder.eventListenerFactory(new CaptureOkHttpEventListenerFactory());
6973

@@ -95,9 +99,12 @@ class OkHttpEventListenerMethodVisitor(
9599
"(Lokhttp3/EventListener\$Factory;)Lokhttp3/OkHttpClient\$Builder;",
96100
false,
97101
)
102+
visitInsn(Opcodes.POP)
98103
}
99104

100105
private fun addProxyingEventListener() {
106+
addTracingInterceptor()
107+
101108
// Add the following call at the beginning of the constructor with the Builder parameter:
102109
// builder.eventListenerFactory(new CaptureOkHttpEventListenerFactory(builder.eventListenerFactory));
103110

@@ -141,5 +148,35 @@ class OkHttpEventListenerMethodVisitor(
141148
"(Lokhttp3/EventListener\$Factory;)Lokhttp3/OkHttpClient\$Builder;",
142149
false,
143150
)
151+
visitInsn(Opcodes.POP)
152+
}
153+
154+
private fun addTracingInterceptor() {
155+
// Add the following call at the beginning of the constructor with the Builder parameter:
156+
// builder.addInterceptor(new CaptureOkHttpTracingInterceptor());
157+
158+
// OkHttpClient.Builder is the parameter, retrieved here
159+
visitVarInsn(Opcodes.ALOAD, 1)
160+
161+
// Create CaptureOkHttpTracingInterceptor instance
162+
visitTypeInsn(Opcodes.NEW, captureOkHttpTracingInterceptor)
163+
visitInsn(Opcodes.DUP)
164+
visitMethodInsn(
165+
Opcodes.INVOKESPECIAL,
166+
captureOkHttpTracingInterceptor,
167+
"<init>",
168+
"()V",
169+
false,
170+
)
171+
172+
// Call addInterceptor(Interceptor)
173+
visitMethodInsn(
174+
Opcodes.INVOKEVIRTUAL,
175+
"okhttp3/OkHttpClient\$Builder",
176+
"addInterceptor",
177+
"(Lokhttp3/Interceptor;)Lokhttp3/OkHttpClient\$Builder;",
178+
false,
179+
)
180+
visitInsn(Opcodes.POP)
144181
}
145182
}

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/Capture.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,14 @@ object Capture {
225225
val deviceId: String?
226226
get() = logger()?.deviceId
227227

228+
/**
229+
* Whether workflow-controlled tracing is currently active for this session.
230+
* Returns `null` prior to SDK start.
231+
*/
232+
@JvmStatic
233+
val isTracingActive: Boolean?
234+
get() = logger()?.isTracingActive
235+
228236
/**
229237
* Defines the initialization of a new session within the currently running logger
230238
* If no logger is started, this is a no-op.

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/CaptureJniLibrary.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ internal object CaptureJniLibrary : IBridge, IStreamingReportProcessor {
111111
*/
112112
external fun getDeviceId(loggerId: Long): String?
113113

114+
/**
115+
* Returns true when workflow-controlled tracing is active for the current session.
116+
*/
117+
external fun isTracingActive(loggerId: Long): Boolean
118+
114119
/**
115120
* Adds a field that should be attached to all logs emitted by the logger going forward.
116121
* If a field with a given key has already been registered with the logger, its value is

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/ILogger.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ interface ILogger {
3737
*/
3838
val deviceId: String
3939

40+
/**
41+
* Whether workflow-controlled tracing is currently active for this session.
42+
*/
43+
val isTracingActive: Boolean
44+
4045
/**
4146
* Defines the initialization of a new session within the current logger.
4247
*/
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// capture-sdk - bitdrift's client SDK
2+
// Copyright Bitdrift, Inc. All rights reserved.
3+
//
4+
// Use of this source code is governed by a source available license that can be found in the
5+
// LICENSE file or at:
6+
// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt
7+
8+
package io.bitdrift.capture
9+
10+
import io.bitdrift.capture.common.RuntimeConfig
11+
import io.bitdrift.capture.common.RuntimeFeature
12+
import io.bitdrift.capture.common.RuntimeStringConfig
13+
14+
internal interface IRuntimeProvider {
15+
fun isRuntimeFeatureEnabled(feature: RuntimeFeature): Boolean
16+
17+
fun getRuntimeConfigValue(config: RuntimeConfig): Int
18+
19+
fun getRuntimeStringConfigValue(config: RuntimeStringConfig): String
20+
}
21+
22+
internal object CaptureRuntimeProvider : IRuntimeProvider {
23+
override fun isRuntimeFeatureEnabled(feature: RuntimeFeature): Boolean =
24+
getLoggerProvider()?.isRuntimeFeatureEnabled(feature) ?: feature.defaultValue
25+
26+
override fun getRuntimeConfigValue(config: RuntimeConfig): Int =
27+
getLoggerProvider()?.getRuntimeConfigValue(config) ?: config.defaultValue
28+
29+
override fun getRuntimeStringConfigValue(config: RuntimeStringConfig): String =
30+
getLoggerProvider()?.getRuntimeStringConfigValue(config) ?: config.defaultValue
31+
32+
private fun getLoggerProvider(): IRuntimeProvider? = Capture.logger() as? IRuntimeProvider
33+
}

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/JniRuntime.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ package io.bitdrift.capture
1010
import io.bitdrift.capture.common.Runtime
1111
import io.bitdrift.capture.common.RuntimeConfig
1212
import io.bitdrift.capture.common.RuntimeFeature
13+
import io.bitdrift.capture.common.RuntimeStringConfig
1314

1415
internal class JniRuntime(
1516
private val logger: LoggerId,
1617
) : Runtime {
1718
override fun isEnabled(feature: RuntimeFeature): Boolean = Jni.isRuntimeEnabled(logger, feature.featureName, feature.defaultValue)
1819

1920
override fun getConfigValue(config: RuntimeConfig): Int = Jni.runtimeValue(logger, config.configName, config.defaultValue)
21+
22+
override fun getConfigValue(config: RuntimeStringConfig): String =
23+
Jni.runtimeStringValue(logger, config.configName, config.defaultValue)
2024
}
2125

2226
internal object Jni {
@@ -31,4 +35,10 @@ internal object Jni {
3135
variableName: String,
3236
defaultValue: Int,
3337
): Int
38+
39+
external fun runtimeStringValue(
40+
logger: LoggerId,
41+
variableName: String,
42+
defaultValue: String,
43+
): String
3444
}

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/LoggerImpl.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import androidx.lifecycle.ProcessLifecycleOwner
1616
import io.bitdrift.capture.attributes.ClientAttributes
1717
import io.bitdrift.capture.attributes.NetworkAttributes
1818
import io.bitdrift.capture.common.IWindowManager
19+
import io.bitdrift.capture.common.RuntimeConfig
1920
import io.bitdrift.capture.common.RuntimeFeature
21+
import io.bitdrift.capture.common.RuntimeStringConfig
2022
import io.bitdrift.capture.common.WindowManager
2123
import io.bitdrift.capture.error.ErrorReporterService
2224
import io.bitdrift.capture.error.IErrorReporter
@@ -90,7 +92,8 @@ internal class LoggerImpl(
9092
private val eventListenerDispatcher: CaptureDispatchers.CommonBackground = CaptureDispatchers.CommonBackground,
9193
windowManager: IWindowManager = WindowManager(errorHandler),
9294
) : IInternalLogger,
93-
ICompletedReportsProcessor {
95+
ICompletedReportsProcessor,
96+
IRuntimeProvider {
9497
@OptIn(ExperimentalBitdriftApi::class)
9598
internal val webViewConfiguration: WebViewConfiguration? = configuration.webViewConfiguration
9699

@@ -311,6 +314,9 @@ internal class LoggerImpl(
311314
override val deviceId: String
312315
get() = CaptureJniLibrary.getDeviceId(this.loggerId) ?: "unknown"
313316

317+
override val isTracingActive: Boolean
318+
get() = CaptureJniLibrary.isTracingActive(this.loggerId)
319+
314320
override fun startNewSession() {
315321
CaptureJniLibrary.startNewSession(this.loggerId)
316322
}
@@ -538,6 +544,12 @@ internal class LoggerImpl(
538544
)
539545
}
540546

547+
override fun isRuntimeFeatureEnabled(feature: RuntimeFeature): Boolean = runtime.isEnabled(feature)
548+
549+
override fun getRuntimeConfigValue(config: RuntimeConfig): Int = runtime.getConfigValue(config)
550+
551+
override fun getRuntimeStringConfigValue(config: RuntimeStringConfig): String = runtime.getConfigValue(config)
552+
541553
override fun logSessionReplayScreenshot(
542554
fields: Array<Field>,
543555
duration: Duration,

platform/jvm/capture/src/main/kotlin/io/bitdrift/capture/network/okhttp/CaptureOkHttpEventListener.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.bitdrift.capture.network.HttpRequestMetrics
1616
import io.bitdrift.capture.network.HttpResponse
1717
import io.bitdrift.capture.network.HttpResponseInfo
1818
import io.bitdrift.capture.network.HttpUrlPath
19+
import io.bitdrift.capture.network.okhttp.CaptureOkHttpTracingInterceptor.Companion.TRACE_ID_HEADER
1920
import okhttp3.Call
2021
import okhttp3.Connection
2122
import okhttp3.EventListener
@@ -102,7 +103,11 @@ internal class CaptureOkHttpEventListener internal constructor(
102103
callStartTimeMs = clock.elapsedRealtime()
103104

104105
val request = call.request()
105-
val extraFields = requestExtraFieldsProvider.provideExtraFields(request)
106+
val extraFields =
107+
buildExtraFieldsWithOptionalTraceId(
108+
request,
109+
requestExtraFieldsProvider.provideExtraFields(request),
110+
)
106111

107112
val pathTemplateHeaderValues = request.headers.values("x-capture-path-template")
108113
val pathTemplate =
@@ -356,6 +361,12 @@ internal class CaptureOkHttpEventListener internal constructor(
356361
// successful or not to keep iOS and Android implementation in sync.
357362
val isSuccess = (statusCode in 200..<400)
358363

364+
val extraFields =
365+
buildExtraFieldsWithOptionalTraceId(
366+
request,
367+
responseExtraFieldsProvider.provideExtraFields(response),
368+
)
369+
359370
// Capture response URL attributes in case there was a redirect and attributes such as host,
360371
// path, and query have different values for the original request and the response.
361372
// https://square.github.io/okhttp/features/interceptors/#application-interceptors
@@ -380,7 +391,7 @@ internal class CaptureOkHttpEventListener internal constructor(
380391
response = httpResponse,
381392
durationMs = (clock.elapsedRealtime() - callStartTimeMs),
382393
metrics = getMetrics(),
383-
extraFields = responseExtraFieldsProvider.provideExtraFields(response),
394+
extraFields = extraFields,
384395
)
385396

386397
logger?.log(httpResponseInfo)
@@ -398,6 +409,8 @@ internal class CaptureOkHttpEventListener internal constructor(
398409
// them i.e., redirected request.
399410
val request = lastResponse?.request ?: call.request()
400411

412+
val extraFields = buildExtraFieldsWithOptionalTraceId(request)
413+
401414
// Capture response URL attributes in case there was a redirect and attributes such as host,
402415
// path, and query have different values for the original request and the response.
403416
// https://square.github.io/okhttp/features/interceptors/#application-interceptors
@@ -421,6 +434,7 @@ internal class CaptureOkHttpEventListener internal constructor(
421434
response = httpResponse,
422435
durationMs = (clock.elapsedRealtime() - callStartTimeMs),
423436
metrics = getMetrics(),
437+
extraFields = extraFields,
424438
)
425439
logger?.log(httpResponseInfo)
426440
}
@@ -497,6 +511,18 @@ internal class CaptureOkHttpEventListener internal constructor(
497511
}
498512
}
499513

514+
private fun buildExtraFieldsWithOptionalTraceId(
515+
request: Request,
516+
baseFields: Map<String, String> = emptyMap(),
517+
): Map<String, String> {
518+
val traceId = request.header(TRACE_ID_HEADER) ?: return baseFields
519+
return if (baseFields.isEmpty()) {
520+
mapOf(TraceContextFactory.TRACE_ID_FIELD_KEY to traceId)
521+
} else {
522+
baseFields + (TraceContextFactory.TRACE_ID_FIELD_KEY to traceId)
523+
}
524+
}
525+
500526
private companion object {
501527
const val TRAFFIC_STATS_TAG = 0x0017D21F7
502528
}

0 commit comments

Comments
 (0)