Skip to content

Commit

Permalink
OTel network request capture (#768)
Browse files Browse the repository at this point in the history
  • Loading branch information
leandro-godon committed Apr 18, 2024
1 parent ccdfdca commit 9312e15
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ internal sealed class EmbType(type: String, subtype: String?) : TelemetryType {
}

internal object LowPower : System("low_power")

internal object NetworkCapturedRequest : System("network_captured_request")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.arch.schema
import io.embrace.android.embracesdk.internal.clock.millisToNanos
import io.embrace.android.embracesdk.internal.utils.toNonNullMap
import io.embrace.android.embracesdk.payload.AppExitInfoData
import io.embrace.android.embracesdk.payload.NetworkCapturedCall

/**
* The collections of attribute schemas used by the associated telemetry types.
Expand Down Expand Up @@ -233,4 +234,31 @@ internal sealed class SchemaType(
) {
override val schemaAttributes = emptyMap<String, String>()
}

internal class NetworkCapturedRequest(networkCapturedCall: NetworkCapturedCall) : SchemaType(
telemetryType = EmbType.System.NetworkCapturedRequest
) {
override val schemaAttributes = mapOf(
"duration" to networkCapturedCall.duration.toString(),
"end-time" to networkCapturedCall.endTime.toString(),
"http-method" to networkCapturedCall.httpMethod,
"matched-url" to networkCapturedCall.matchedUrl,
"network-id" to networkCapturedCall.networkId,
"request-body" to networkCapturedCall.requestBody,
"request-body-size" to networkCapturedCall.requestBodySize.toString(),
"request-query" to networkCapturedCall.requestQuery,
"request-query-headers" to networkCapturedCall.requestQueryHeaders.toString(),
"request-size" to networkCapturedCall.requestSize.toString(),
"response-body" to networkCapturedCall.responseBody,
"response-body-size" to networkCapturedCall.responseBodySize.toString(),
"response-headers" to networkCapturedCall.responseHeaders.toString(),
"response-size" to networkCapturedCall.responseSize.toString(),
"response-status" to networkCapturedCall.responseStatus.toString(),
"session-id" to networkCapturedCall.sessionId,
"start-time" to networkCapturedCall.startTime.toString(),
"url" to networkCapturedCall.url,
"error-message" to networkCapturedCall.errorMessage,
"encrypted-payload" to networkCapturedCall.encryptedPayload
).toNonNullMap()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import io.embrace.android.embracesdk.internal.logs.LogOrchestratorImpl
import io.embrace.android.embracesdk.internal.logs.LogService
import io.embrace.android.embracesdk.network.logging.EmbraceNetworkCaptureService
import io.embrace.android.embracesdk.network.logging.EmbraceNetworkLoggingService
import io.embrace.android.embracesdk.network.logging.NetworkCaptureDataSource
import io.embrace.android.embracesdk.network.logging.NetworkCaptureDataSourceImpl
import io.embrace.android.embracesdk.network.logging.NetworkCaptureService
import io.embrace.android.embracesdk.network.logging.NetworkLoggingService
import io.embrace.android.embracesdk.worker.WorkerName
Expand All @@ -19,6 +21,7 @@ import io.embrace.android.embracesdk.worker.WorkerThreadModule
*/
internal interface CustomerLogModule {
val networkCaptureService: NetworkCaptureService
val networkCaptureDataSource: NetworkCaptureDataSource
val networkLoggingService: NetworkLoggingService
val logMessageService: LogMessageService
val logOrchestrator: LogOrchestrator
Expand Down Expand Up @@ -47,6 +50,13 @@ internal class CustomerLogModuleImpl(
)
}

override val networkCaptureDataSource: NetworkCaptureDataSource by singleton {
NetworkCaptureDataSourceImpl(
essentialServiceModule.logWriter,
initModule.logger
)
}

override val networkLoggingService: NetworkLoggingService by singleton {
EmbraceNetworkLoggingService(
essentialServiceModule.configService,
Expand Down Expand Up @@ -87,6 +97,7 @@ internal class CustomerLogModuleImpl(
CompositeLogService(
{ v1LogService },
{ v2LogService },
{ networkCaptureDataSource },
essentialServiceModule.configService,
initModule.logger,
coreModule.jsonSerializer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.embrace.android.embracesdk.event.LogMessageService
import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer
import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.network.logging.NetworkCaptureDataSource
import io.embrace.android.embracesdk.payload.NetworkCapturedCall

/**
Expand All @@ -19,6 +20,7 @@ import io.embrace.android.embracesdk.payload.NetworkCapturedCall
internal class CompositeLogService(
private val v1LogService: Provider<LogMessageService>,
private val v2LogService: Provider<LogService>,
private val networkCaptureDataSource: Provider<NetworkCaptureDataSource>,
private val configService: ConfigService,
private val logger: InternalEmbraceLogger,
private val serializer: EmbraceSerializer
Expand All @@ -31,8 +33,13 @@ internal class CompositeLogService(
get() = if (useV2LogService) v2LogService() else v1LogService()

override fun logNetwork(networkCaptureCall: NetworkCapturedCall?) {
// Network logs are still always handled by the v1 LogMessageService
v1LogService().logNetwork(networkCaptureCall)
if (useV2LogService) {
networkCaptureCall?.let {
networkCaptureDataSource().logNetworkCapturedCall(it)
}
} else {
v1LogService().logNetwork(networkCaptureCall)
}
}

override fun log(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.embrace.android.embracesdk.network.logging

import io.embrace.android.embracesdk.arch.datasource.LogDataSource
import io.embrace.android.embracesdk.payload.NetworkCapturedCall

internal interface NetworkCaptureDataSource : LogDataSource {

fun logNetworkCapturedCall(networkCapturedCall: NetworkCapturedCall)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.embrace.android.embracesdk.network.logging

import io.embrace.android.embracesdk.Severity
import io.embrace.android.embracesdk.arch.datasource.LogDataSourceImpl
import io.embrace.android.embracesdk.arch.destination.LogEventData
import io.embrace.android.embracesdk.arch.destination.LogEventMapper
import io.embrace.android.embracesdk.arch.destination.LogWriter
import io.embrace.android.embracesdk.arch.limits.NoopLimitStrategy
import io.embrace.android.embracesdk.arch.schema.SchemaType
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.payload.NetworkCapturedCall

internal class NetworkCaptureDataSourceImpl(
private val logWriter: LogWriter,
logger: InternalEmbraceLogger
) : NetworkCaptureDataSource,
LogEventMapper<NetworkCapturedCall>, LogDataSourceImpl(
destination = logWriter,
logger = logger,
limitStrategy = NoopLimitStrategy,
) {

/**
* Creates a log with data from a captured network request.
*
* @param networkCapturedCall the captured network information
*/
override fun logNetworkCapturedCall(networkCapturedCall: NetworkCapturedCall) {
logWriter.addLog(networkCapturedCall, ::toLogEventData)
}

override fun toLogEventData(obj: NetworkCapturedCall): LogEventData {
val schemaType = SchemaType.NetworkCapturedRequest(obj)
return LogEventData(
schemaType = schemaType,
severity = Severity.INFO,
message = obj.networkId
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.embrace.android.embracesdk.fakes

import io.embrace.android.embracesdk.arch.destination.LogWriter
import io.embrace.android.embracesdk.network.logging.NetworkCaptureDataSource
import io.embrace.android.embracesdk.payload.NetworkCapturedCall

internal class FakeNetworkCaptureDataSource : NetworkCaptureDataSource {

val loggedCalls = mutableListOf<NetworkCapturedCall>()

override fun logNetworkCapturedCall(networkCapturedCall: NetworkCapturedCall) {
loggedCalls.add(networkCapturedCall)
}

override fun alterSessionSpan(
inputValidation: () -> Boolean,
captureAction: LogWriter.() -> Unit
): Boolean {
TODO("Not yet implemented")
}

override fun enableDataCapture() {
TODO("Not yet implemented")
}

override fun disableDataCapture() {
TODO("Not yet implemented")
}

override fun resetDataCaptureLimits() {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.embrace.android.embracesdk.fakes

import io.embrace.android.embracesdk.payload.NetworkCapturedCall

internal fun fakeNetworkCapturedCall(): NetworkCapturedCall {
return NetworkCapturedCall(
duration = 100,
endTime = 1713453000,
httpMethod = "GET",
matchedUrl = "httpbin.*",
requestBody = "body",
requestBodySize = 10,
networkId = "id",
requestQuery = "query",
requestQueryHeaders = mapOf("query-header" to "value"),
requestSize = 5,
responseBody = "response",
responseBodySize = 8,
responseHeaders = mapOf("response-header" to "value"),
responseSize = 300,
responseStatus = 200,
sessionId = "session-id",
startTime = 1713452000,
url = "https://httpbin.org/get",
errorMessage = "",
encryptedPayload = "encrypted-payload"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import io.embrace.android.embracesdk.fakes.FakeConfigService
import io.embrace.android.embracesdk.fakes.FakeGatingService
import io.embrace.android.embracesdk.fakes.FakeLogOrchestrator
import io.embrace.android.embracesdk.fakes.FakeMetadataService
import io.embrace.android.embracesdk.fakes.FakeNetworkCaptureDataSource
import io.embrace.android.embracesdk.fakes.FakeNetworkLoggingService
import io.embrace.android.embracesdk.fakes.FakeSessionIdTracker
import io.embrace.android.embracesdk.fakes.FakeUserService
import io.embrace.android.embracesdk.fakes.fakeEmbraceSessionProperties
import io.embrace.android.embracesdk.injection.CustomerLogModule
import io.embrace.android.embracesdk.internal.logs.LogOrchestrator
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.network.logging.NetworkCaptureDataSource
import io.embrace.android.embracesdk.network.logging.NetworkCaptureService
import io.embrace.android.embracesdk.network.logging.NetworkLoggingService
import io.embrace.android.embracesdk.worker.BackgroundWorker
Expand Down Expand Up @@ -44,4 +46,7 @@ internal class FakeCustomerLogModule(

override val logOrchestrator: LogOrchestrator
get() = FakeLogOrchestrator()

override val networkCaptureDataSource: NetworkCaptureDataSource
get() = FakeNetworkCaptureDataSource()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import io.embrace.android.embracesdk.config.remote.RemoteConfig
import io.embrace.android.embracesdk.fakes.FakeConfigService
import io.embrace.android.embracesdk.fakes.FakeLogMessageService
import io.embrace.android.embracesdk.fakes.FakeLogService
import io.embrace.android.embracesdk.fakes.FakeNetworkCaptureDataSource
import io.embrace.android.embracesdk.fakes.fakeNetworkCapturedCall
import io.embrace.android.embracesdk.fakes.fakeOTelBehavior
import io.embrace.android.embracesdk.internal.serialization.EmbraceSerializer
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
Expand All @@ -21,6 +23,7 @@ internal class CompositeLogServiceTest {
private lateinit var compositeLogService: CompositeLogService
private lateinit var v1LogService: FakeLogMessageService
private lateinit var v2LogService: FakeLogService
private lateinit var networkCaptureDataSource: FakeNetworkCaptureDataSource

@Before
fun setUp() {
Expand All @@ -33,9 +36,11 @@ internal class CompositeLogServiceTest {
)
v1LogService = FakeLogMessageService()
v2LogService = FakeLogService()
networkCaptureDataSource = FakeNetworkCaptureDataSource()
compositeLogService = CompositeLogService(
v1LogService = { v1LogService },
v2LogService = { v2LogService },
networkCaptureDataSource = { networkCaptureDataSource },
configService = configService,
logger = InternalEmbraceLogger(),
serializer = EmbraceSerializer()
Expand Down Expand Up @@ -81,6 +86,26 @@ internal class CompositeLogServiceTest {
assertEquals(1, v2LogService.logs.size)
}

@Test
fun testNetworkCaptureV1() {
oTelConfig = OTelRemoteConfig(isBetaEnabled = false)
compositeLogService.logNetwork(
fakeNetworkCapturedCall()
)
assertEquals(1, v1LogService.networkCalls.size)
assertEquals(0, networkCaptureDataSource.loggedCalls.size)
}

@Test
fun testNetworkCaptureV2() {
oTelConfig = OTelRemoteConfig(isBetaEnabled = true)
compositeLogService.logNetwork(
fakeNetworkCapturedCall()
)
assertEquals(0, v1LogService.networkCalls.size)
assertEquals(1, networkCaptureDataSource.loggedCalls.size)
}

@Test
fun testLogExceptionV1() {
compositeLogService.log(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.embrace.android.embracesdk.network.logging

import io.embrace.android.embracesdk.arch.schema.SchemaType
import io.embrace.android.embracesdk.fakes.FakeLogWriter
import io.embrace.android.embracesdk.fakes.fakeNetworkCapturedCall
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import org.junit.Assert.assertEquals
import org.junit.Test

internal class NetworkCaptureDataSourceTest {

@Test
fun `test network capture is sent as log`() {
val logWriter = FakeLogWriter()
val dataSource = NetworkCaptureDataSourceImpl(
logWriter,
InternalEmbraceLogger()
)
val capturedCall = fakeNetworkCapturedCall()
dataSource.logNetworkCapturedCall(capturedCall)

assertEquals(1, logWriter.logEvents.size)
val log = logWriter.logEvents[0]
assertEquals(SchemaType.NetworkCapturedRequest::class.java, log.schemaType.javaClass)
assertEquals(100L, capturedCall.duration)
assertEquals(1713453000L, capturedCall.endTime)
assertEquals("GET", capturedCall.httpMethod)
assertEquals("httpbin.*", capturedCall.matchedUrl)
assertEquals("body", capturedCall.requestBody)
assertEquals(10, capturedCall.requestBodySize)
assertEquals("id", capturedCall.networkId)
assertEquals("query", capturedCall.requestQuery)
assertEquals(mapOf("query-header" to "value"), capturedCall.requestQueryHeaders)
assertEquals(5, capturedCall.requestSize)
assertEquals("response", capturedCall.responseBody)
assertEquals(8, capturedCall.responseBodySize)
assertEquals(mapOf("response-header" to "value"), capturedCall.responseHeaders)
assertEquals(300, capturedCall.responseSize)
assertEquals(200, capturedCall.responseStatus)
assertEquals("session-id", capturedCall.sessionId)
assertEquals(1713452000L, capturedCall.startTime)
assertEquals("https://httpbin.org/get", capturedCall.url)
assertEquals("", capturedCall.errorMessage)
assertEquals("encrypted-payload", capturedCall.encryptedPayload)
}
}

0 comments on commit 9312e15

Please sign in to comment.