Unit): SessionMessage {
+internal fun IntegrationTestRule.Harness.recordSession(
+ simulateAppStartup: Boolean = false,
+ action: () -> Unit
+): SessionMessage {
// get the activity service & simulate the lifecycle event that triggers a new session.
val activityService = checkNotNull(Embrace.getImpl().activityService)
+ val activityController = if (simulateAppStartup) Robolectric.buildActivity(Activity::class.java) else null
+
+ activityController?.create()
+ activityController?.start()
activityService.onForeground()
+ activityController?.resume()
// assert a session was started.
val startSession = getLastSentSessionMessage()
@@ -74,12 +85,15 @@ internal fun IntegrationTestRule.Harness.recordSession(action: () -> Unit): Sess
// end session 30s later by entering background
fakeClock.tick(30000)
+ activityController?.pause()
activityService.onBackground()
+ activityController?.stop()
val endSession = getLastSentSessionMessage()
assertEquals("en", endSession.session.messageType)
// TODO: future: increase number of assertions on what is always in a start message?
+
// return the session end message for further assertions.
return endSession
}
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/EmbraceInternalInterfaceTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/EmbraceInternalInterfaceTest.kt
new file mode 100644
index 000000000..a4785c040
--- /dev/null
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/EmbraceInternalInterfaceTest.kt
@@ -0,0 +1,260 @@
+package io.embrace.android.embracesdk.testcases
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.embrace.android.embracesdk.EmbraceEvent
+import io.embrace.android.embracesdk.IntegrationTestRule
+import io.embrace.android.embracesdk.LogType
+import io.embrace.android.embracesdk.assertions.assertLogMessageReceived
+import io.embrace.android.embracesdk.getSentLogMessages
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
+import io.embrace.android.embracesdk.network.http.HttpMethod
+import io.embrace.android.embracesdk.recordSession
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import java.net.SocketException
+
+/**
+ * Validation of the internal API
+ */
+@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
+@RunWith(AndroidJUnit4::class)
+internal class EmbraceInternalInterfaceTest {
+ @Rule
+ @JvmField
+ val testRule: IntegrationTestRule = IntegrationTestRule(
+ harnessSupplier = {
+ IntegrationTestRule.newHarness(startImmediately = false)
+ }
+ )
+
+ @Test
+ fun `no NPEs when SDK not started`() {
+ assertFalse(testRule.embrace.isStarted)
+ with(testRule.embrace.internalInterface) {
+ logInfo("", null)
+ logWarning("", null, null)
+ logError("", null, null, false)
+ logHandledException(NullPointerException(), LogType.ERROR, null, null)
+ recordCompletedNetworkRequest(
+ url = "",
+ httpMethod = "GET",
+ startTime = 0L,
+ endTime = 1L,
+ bytesSent = 0L,
+ bytesReceived = 0L,
+ statusCode = 200,
+ traceId = null,
+ networkCaptureData = null
+ )
+
+ recordIncompleteNetworkRequest(
+ url = "",
+ httpMethod = "GET",
+ startTime = 0L,
+ endTime = 1L,
+ error = null,
+ traceId = null,
+ networkCaptureData = null
+ )
+
+ recordIncompleteNetworkRequest(
+ url = "",
+ httpMethod = "GET",
+ startTime = 0L,
+ endTime = 1L,
+ errorType = null,
+ errorMessage = null,
+ traceId = null,
+ networkCaptureData = null
+ )
+
+ recordAndDeduplicateNetworkRequest(
+ callId = "",
+ embraceNetworkRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ "",
+ HttpMethod.GET,
+ 0L,
+ 1L,
+ 0L,
+ 0L,
+ 200,
+ null
+ )
+ )
+
+ logComposeTap(android.util.Pair.create(0.0f, 0.0f), "")
+ assertFalse(shouldCaptureNetworkBody("", ""))
+ setProcessStartedByNotification()
+ assertFalse(isNetworkSpanForwardingEnabled())
+ getSdkCurrentTime()
+ }
+ }
+
+ @Test
+ fun `internal logging methods work as expected`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ val expectedProperties = mapOf(Pair("key", "value"))
+ harness.recordSession {
+ embrace.internalInterface.logInfo("info", expectedProperties)
+ embrace.internalInterface.logWarning("warning", expectedProperties, null)
+ embrace.internalInterface.logError("error", expectedProperties, null, false)
+ embrace.internalInterface.logHandledException(NullPointerException(), LogType.ERROR, expectedProperties, null)
+ val logs = harness.getSentLogMessages(4)
+
+ assertLogMessageReceived(
+ logs[0],
+ message = "info",
+ eventType = EmbraceEvent.Type.INFO_LOG,
+ properties = expectedProperties
+ )
+ assertLogMessageReceived(
+ logs[1],
+ message = "warning",
+ eventType = EmbraceEvent.Type.WARNING_LOG,
+ properties = expectedProperties
+ )
+ assertLogMessageReceived(
+ logs[2],
+ message = "error",
+ eventType = EmbraceEvent.Type.ERROR_LOG,
+ properties = expectedProperties
+ )
+ assertLogMessageReceived(
+ logs[3],
+ message = "",
+ eventType = EmbraceEvent.Type.ERROR_LOG,
+ properties = expectedProperties
+ )
+ }
+ }
+ }
+
+ @Test
+ fun `network recording methods work as expected`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ val session = harness.recordSession {
+ harness.fakeClock.tick()
+ harness.fakeConfigService.updateListeners()
+ harness.fakeClock.tick()
+ embrace.internalInterface.recordCompletedNetworkRequest(
+ url = URL,
+ httpMethod = "GET",
+ startTime = START_TIME,
+ endTime = END_TIME,
+ bytesSent = 0L,
+ bytesReceived = 0L,
+ statusCode = 500,
+ traceId = null,
+ networkCaptureData = null
+ )
+
+ embrace.internalInterface.recordIncompleteNetworkRequest(
+ url = URL,
+ httpMethod = "GET",
+ startTime = START_TIME,
+ endTime = END_TIME,
+ error = NullPointerException(),
+ traceId = null,
+ networkCaptureData = null
+ )
+
+ embrace.internalInterface.recordIncompleteNetworkRequest(
+ url = URL,
+ httpMethod = "GET",
+ startTime = START_TIME,
+ endTime = END_TIME,
+ errorType = SocketException::class.java.canonicalName,
+ errorMessage = "",
+ traceId = null,
+ networkCaptureData = null
+ )
+
+ embrace.internalInterface.recordAndDeduplicateNetworkRequest(
+ callId = "",
+ embraceNetworkRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.POST,
+ START_TIME,
+ END_TIME,
+ 99L,
+ 301L,
+ 200,
+ null
+ )
+ )
+ }
+
+ val requests = checkNotNull(session.performanceInfo?.networkRequests?.networkSessionV2?.requests)
+ assertEquals(
+ "Unexpected number of requests in sent session: ${requests.size}",
+ 4,
+ requests.size
+ )
+ }
+ }
+
+ @Test
+ fun `compose tap logging works as expected`() {
+ val expectedX = 10.0f
+ val expectedY = 99f
+ val expectedElementName = "button"
+
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ val session = harness.recordSession {
+ embrace.internalInterface.logComposeTap(android.util.Pair.create(expectedX, expectedY), expectedElementName)
+ }
+
+ val tapBreadcrumb = checkNotNull(session.breadcrumbs?.tapBreadcrumbs?.last())
+ assertEquals("10,99", tapBreadcrumb.location)
+ assertEquals(expectedElementName, tapBreadcrumb.tappedElementName)
+ }
+ }
+
+ @Test
+ fun `access check methods work as expected`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ harness.recordSession {
+ assertTrue(embrace.internalInterface.shouldCaptureNetworkBody("capture.me", "GET"))
+ assertFalse(embrace.internalInterface.shouldCaptureNetworkBody("capture.me", "POST"))
+ assertFalse(embrace.internalInterface.shouldCaptureNetworkBody(URL, "GET"))
+ assertTrue(embrace.internalInterface.isNetworkSpanForwardingEnabled())
+ }
+ }
+ }
+
+ @Test
+ fun `set process as started by notification works as expected`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ embrace.internalInterface.setProcessStartedByNotification()
+ harness.recordSession(simulateAppStartup = true) { }
+ assertEquals(EmbraceEvent.Type.START, harness.fakeDeliveryModule.deliveryService.lastEventSentAsync?.event?.type)
+ }
+ }
+
+ @Test
+ fun `test sdk time`() {
+ with(testRule) {
+ embrace.start(harness.fakeCoreModule.context)
+ assertEquals(harness.fakeClock.now(), embrace.internalInterface.getSdkCurrentTime())
+ harness.fakeClock.tick()
+ assertEquals(harness.fakeClock.now(), embrace.internalInterface.getSdkCurrentTime())
+ }
+ }
+
+ companion object {
+ private const val URL = "https://embrace.io"
+ private const val START_TIME = 1692201601L
+ private const val END_TIME = 1692202600L
+ }
+}
\ No newline at end of file
diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt
index f30ff1833..e57bba65b 100644
--- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt
+++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/NetworkRequestApiTest.kt
@@ -1,275 +1,362 @@
package io.embrace.android.embracesdk.testcases
-//
-//import android.os.Build
-//import androidx.test.ext.junit.runners.AndroidJUnit4
-//import io.embrace.android.embracesdk.IntegrationTestRule
-//import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
-//import io.embrace.android.embracesdk.network.http.HttpMethod
-//import io.embrace.android.embracesdk.network.http.NetworkCaptureData
-//import io.embrace.android.embracesdk.payload.NetworkCallV2
-//import io.embrace.android.embracesdk.recordSession
-//import org.junit.Assert.assertEquals
-//import org.junit.Rule
-//import org.junit.Test
-//import org.junit.runner.RunWith
-//import org.robolectric.annotation.Config
-//import kotlin.math.max
-//
-//@RunWith(AndroidJUnit4::class)
-//@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
-//internal class NetworkRequestApiTest {
-// @Rule
-// @JvmField
-// val testRule: IntegrationTestRule = IntegrationTestRule()
-//
-// @Test
-// fun `record basic completed GET request`() {
-// assertSingleNetworkRequestInSession(
-// EmbraceNetworkRequest.fromCompletedRequest(
-// URL,
-// HttpMethod.GET,
-// START_TIME,
-// END_TIME,
-// BYTES_SENT,
-// BYTES_RECEIVED,
-// 200
-// )
-// )
-// }
-//
-// @Test
-// fun `record completed POST request with traceId`() {
-// assertSingleNetworkRequestInSession(
-// expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
-// URL,
-// HttpMethod.POST,
-// START_TIME,
-// END_TIME,
-// BYTES_SENT,
-// BYTES_RECEIVED,
-// 200,
-// TRACE_ID,
-// )
-// )
-// }
-//
-// @Test
-// fun `record completed request that failed with captured response`() {
-// assertSingleNetworkRequestInSession(
-// expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
-// URL,
-// HttpMethod.GET,
-// START_TIME,
-// END_TIME,
-// BYTES_SENT,
-// BYTES_RECEIVED,
-// 500,
-// TRACE_ID,
-// NETWORK_CAPTURE_DATA
-// )
-// )
-// }
-//
-// @Test
-// fun `record completed request with traceparent`() {
-// assertSingleNetworkRequestInSession(
-// expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
-// URL,
-// HttpMethod.GET,
-// START_TIME,
-// END_TIME,
-// BYTES_SENT,
-// BYTES_RECEIVED,
-// 200,
-// TRACE_ID,
-// TRACEPARENT,
-// NETWORK_CAPTURE_DATA
-// )
-// )
-// }
-//
-// @Test
-// fun `record basic incomplete request`() {
-// assertSingleNetworkRequestInSession(
-// EmbraceNetworkRequest.fromIncompleteRequest(
-// URL,
-// HttpMethod.GET,
-// START_TIME,
-// END_TIME,
-// NullPointerException::class.toString(),
-// "Dang nothing there"
-// ),
-// completed = false
-// )
-// }
-//
-// @Test
-// fun `record incomplete POST request with trace ID`() {
-// assertSingleNetworkRequestInSession(
-// EmbraceNetworkRequest.fromIncompleteRequest(
-// URL,
-// HttpMethod.POST,
-// START_TIME,
-// END_TIME,
-// NullPointerException::class.toString(),
-// "Dang nothing there",
-// TRACE_ID
-// ),
-// completed = false
-// )
-// }
-//
-// @Test
-// fun `record incomplete request with network capture`() {
-// assertSingleNetworkRequestInSession(
-// EmbraceNetworkRequest.fromIncompleteRequest(
-// URL,
-// HttpMethod.GET,
-// START_TIME,
-// END_TIME,
-// NullPointerException::class.toString(),
-// "Dang nothing there",
-// TRACE_ID,
-// NETWORK_CAPTURE_DATA
-// ),
-// completed = false
-// )
-// }
-//
-// @Test
-// fun `record incomplete request with traceparent`() {
-// assertSingleNetworkRequestInSession(
-// EmbraceNetworkRequest.fromIncompleteRequest(
-// URL,
-// HttpMethod.GET,
-// START_TIME,
-// END_TIME,
-// NullPointerException::class.toString(),
-// "Dang nothing there",
-// TRACE_ID,
-// TRACEPARENT,
-// NETWORK_CAPTURE_DATA
-// ),
-// completed = false
-// )
-// }
-//
-// @Test
-// fun `disabled URLs not recorded`() {
-// with(testRule) {
-// harness.recordSession {
-// harness.fakeConfigService.updateListeners()
-// harness.fakeClock.tick(5)
-// embrace.recordNetworkRequest(
-// EmbraceNetworkRequest.fromCompletedRequest(
-// DISABLED_URL,
-// HttpMethod.GET,
-// START_TIME,
-// END_TIME,
-// BYTES_SENT,
-// BYTES_RECEIVED,
-// 200
-// )
-// )
-// harness.fakeClock.tick(5)
-// embrace.recordNetworkRequest(
-// EmbraceNetworkRequest.fromIncompleteRequest(
-// DISABLED_URL,
-// HttpMethod.GET,
-// START_TIME + 1,
-// END_TIME,
-// NullPointerException::class.toString(),
-// "Dang nothing there"
-// )
-// )
-// harness.fakeClock.tick(5)
-// embrace.recordNetworkRequest(
-// EmbraceNetworkRequest.fromCompletedRequest(
-// URL,
-// HttpMethod.GET,
-// START_TIME + 2,
-// END_TIME,
-// BYTES_SENT,
-// BYTES_RECEIVED,
-// 200
-// )
-// )
-// }
-//
-// val networkCall = validateAndReturnExpectedNetworkCall(harness)
-// assertEquals(URL, networkCall.url)
-// }
-// }
-//
-// private fun assertSingleNetworkRequestInSession(expectedRequest: EmbraceNetworkRequest, completed: Boolean = true) {
-// with(testRule) {
-// harness.recordSession {
-// harness.fakeConfigService.updateListeners()
-// harness.fakeClock.tick(5L)
-// embrace.recordNetworkRequest(expectedRequest)
-// }
-//
-// val networkCall = validateAndReturnExpectedNetworkCall(harness)
-// with(networkCall) {
-// assertEquals(expectedRequest.url, url)
-// assertEquals(expectedRequest.httpMethod, httpMethod)
-// assertEquals(expectedRequest.startTime, startTime)
-// assertEquals(expectedRequest.endTime, endTime)
-// assertEquals(max(expectedRequest.endTime - expectedRequest.startTime, 0L), duration)
-// assertEquals(expectedRequest.traceId, traceId)
-// assertEquals(expectedRequest.w3cTraceparent, w3cTraceparent)
-// if (completed) {
-// assertEquals(expectedRequest.responseCode, responseCode)
-// assertEquals(expectedRequest.bytesSent, bytesSent)
-// assertEquals(expectedRequest.bytesReceived, bytesReceived)
-// assertEquals(null, errorType)
-// assertEquals(null, errorMessage)
-// } else {
-// assertEquals(null, responseCode)
-// assertEquals(0, bytesSent)
-// assertEquals(0, bytesReceived)
-// assertEquals(expectedRequest.errorType, errorType)
-// assertEquals(expectedRequest.errorMessage, errorMessage)
-// }
-// }
-// }
-// }
-//
-// private fun validateAndReturnExpectedNetworkCall(harness: IntegrationTestRule.Harness): NetworkCallV2 {
-// val lastSavedSessionRequestCount =
-// harness.fakeDeliveryModule.deliveryService.lastSavedSession?.performanceInfo?.networkRequests?.networkSessionV2?.requests?.size
-// ?: -1
-// val session = harness.fakeDeliveryModule.deliveryService.lastSentSessions[1].first
-// val requests = checkNotNull(session.performanceInfo?.networkRequests?.networkSessionV2?.requests)
-// val requestCount = requests.size
-// val networkCall = requests.first()
-//
-// assertEquals(
-// "Unexpected number of requests in sent session: $requestCount. Last saved session requests: $lastSavedSessionRequestCount",
-// 1,
-// requestCount
-// )
-//
-// return networkCall
-// }
-//
-// companion object {
-// private const val URL = "https://embrace.io"
-// private const val DISABLED_URL = "https://dontlogmebro.pizza/yum"
-// private const val START_TIME = 1692201601L
-// private const val END_TIME = 1692202600L
-// private const val BYTES_SENT = 100L
-// private const val BYTES_RECEIVED = 500L
-// private const val TRACE_ID = "rAnDoM-traceId"
-// private const val TRACEPARENT = "00-c4ada96c31e1b6b9e351a1cffc99ae38-331f3a8acf49d295-01"
-//
-// private val NETWORK_CAPTURE_DATA = NetworkCaptureData(
-// requestHeaders = mapOf(Pair("x-emb-test", "holla")),
-// requestQueryParams = "trackMe=noooooo",
-// capturedRequestBody = "haha".toByteArray(),
-// responseHeaders = mapOf(Pair("x-emb-response-header", "alloh")),
-// capturedResponseBody = "woohoo".toByteArray(),
-// dataCaptureErrorMessage = null
-// )
-// }
-//}
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import io.embrace.android.embracesdk.IntegrationTestRule
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
+import io.embrace.android.embracesdk.network.http.HttpMethod
+import io.embrace.android.embracesdk.network.http.NetworkCaptureData
+import io.embrace.android.embracesdk.payload.NetworkCallV2
+import io.embrace.android.embracesdk.recordSession
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import java.util.UUID
+import kotlin.math.max
+
+@RunWith(AndroidJUnit4::class)
+@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
+internal class NetworkRequestApiTest {
+ @Rule
+ @JvmField
+ val testRule: IntegrationTestRule = IntegrationTestRule()
+
+ @Test
+ fun `record basic completed GET request`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ }
+
+ @Test
+ fun `record completed POST request with traceId`() {
+ assertSingleNetworkRequestInSession(
+ expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.POST,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200,
+ TRACE_ID,
+ )
+ )
+ }
+
+ @Test
+ fun `record completed request that failed with captured response`() {
+ assertSingleNetworkRequestInSession(
+ expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 500,
+ TRACE_ID,
+ NETWORK_CAPTURE_DATA
+ )
+ )
+ }
+
+ @Test
+ fun `record completed request with traceparent`() {
+ assertSingleNetworkRequestInSession(
+ expectedRequest = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200,
+ TRACE_ID,
+ TRACEPARENT,
+ NETWORK_CAPTURE_DATA
+ )
+ )
+ }
+
+ @Test
+ fun `record basic incomplete request`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there"
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `record incomplete POST request with trace ID`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.POST,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there",
+ TRACE_ID
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `record incomplete request with network capture`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there",
+ TRACE_ID,
+ NETWORK_CAPTURE_DATA
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `record incomplete request with traceparent`() {
+ assertSingleNetworkRequestInSession(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there",
+ TRACE_ID,
+ TRACEPARENT,
+ NETWORK_CAPTURE_DATA
+ ),
+ completed = false
+ )
+ }
+
+ @Test
+ fun `disabled URLs not recorded`() {
+ with(testRule) {
+ harness.recordSession {
+ harness.fakeConfigService.updateListeners()
+ harness.fakeClock.tick(5)
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromCompletedRequest(
+ DISABLED_URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ harness.fakeClock.tick(5)
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromIncompleteRequest(
+ DISABLED_URL,
+ HttpMethod.GET,
+ START_TIME + 1,
+ END_TIME,
+ NullPointerException::class.toString(),
+ "Dang nothing there"
+ )
+ )
+ harness.fakeClock.tick(5)
+ embrace.recordNetworkRequest(
+ EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME + 2,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ }
+
+ val networkCall = validateAndReturnExpectedNetworkCall()
+ assertEquals(URL, networkCall.url)
+ }
+ }
+
+ @Test
+ fun `ensure calls with same callId but different start times are deduped`() {
+ val expectedStartTime = START_TIME + 1
+ with(testRule) {
+ harness.recordSession {
+ harness.fakeConfigService.updateListeners()
+ harness.fakeClock.tick(5)
+
+ val callId = UUID.randomUUID().toString()
+ embrace.internalInterface.recordAndDeduplicateNetworkRequest(
+ callId,
+ EmbraceNetworkRequest.fromCompletedRequest(
+ "$URL/bad",
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ embrace.internalInterface.recordAndDeduplicateNetworkRequest(
+ callId,
+ EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ expectedStartTime,
+ expectedStartTime + 1,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+ )
+ }
+
+ val networkCall = validateAndReturnExpectedNetworkCall()
+ assertEquals(URL, networkCall.url)
+ assertEquals(expectedStartTime, networkCall.startTime)
+ }
+ }
+
+ /**
+ * This reproduces the bug that will be fixed. Uncomment when ready.
+ */
+ @Test
+ fun `ensure network calls with the same start time are recorded properly`() {
+ with(testRule) {
+ harness.recordSession {
+ harness.fakeConfigService.updateListeners()
+ harness.fakeClock.tick(5)
+
+ val request = EmbraceNetworkRequest.fromCompletedRequest(
+ URL,
+ HttpMethod.GET,
+ START_TIME,
+ END_TIME,
+ BYTES_SENT,
+ BYTES_RECEIVED,
+ 200
+ )
+
+ embrace.recordNetworkRequest(request)
+ embrace.recordNetworkRequest(request)
+ }
+
+ val session = testRule.harness.fakeDeliveryModule.deliveryService.lastSentSessions[1].first
+ val requests = checkNotNull(session.performanceInfo?.networkRequests?.networkSessionV2?.requests)
+ assertEquals(
+ "Unexpected number of requests in sent session: ${requests.size}",
+ 2,
+ requests.size
+ )
+ }
+ }
+
+ private fun assertSingleNetworkRequestInSession(
+ expectedRequest: EmbraceNetworkRequest,
+ completed: Boolean = true
+ ) {
+ with(testRule) {
+ harness.recordSession {
+ harness.fakeClock.tick(2L)
+ harness.fakeConfigService.updateListeners()
+ harness.fakeClock.tick(5L)
+ embrace.recordNetworkRequest(expectedRequest)
+ }
+
+ val networkCall = validateAndReturnExpectedNetworkCall()
+ with(networkCall) {
+ assertEquals(expectedRequest.url, url)
+ assertEquals(expectedRequest.httpMethod, httpMethod)
+ assertEquals(expectedRequest.startTime, startTime)
+ assertEquals(expectedRequest.endTime, endTime)
+ assertEquals(max(expectedRequest.endTime - expectedRequest.startTime, 0L), duration)
+ assertEquals(expectedRequest.traceId, traceId)
+ assertEquals(expectedRequest.w3cTraceparent, w3cTraceparent)
+ if (completed) {
+ assertEquals(expectedRequest.responseCode, responseCode)
+ assertEquals(expectedRequest.bytesSent, bytesSent)
+ assertEquals(expectedRequest.bytesReceived, bytesReceived)
+ assertEquals(null, errorType)
+ assertEquals(null, errorMessage)
+ } else {
+ assertEquals(null, responseCode)
+ assertEquals(0, bytesSent)
+ assertEquals(0, bytesReceived)
+ assertEquals(expectedRequest.errorType, errorType)
+ assertEquals(expectedRequest.errorMessage, errorMessage)
+ }
+ }
+ }
+ }
+
+ private fun validateAndReturnExpectedNetworkCall(): NetworkCallV2 {
+ val session = testRule.harness.fakeDeliveryModule.deliveryService.lastSentSessions[1].first
+
+ // Look for a specific error where the fetch from the cache returns a stale value
+ session.session.exceptionError?.exceptionErrors?.forEach { errorInfo ->
+ errorInfo.exceptions?.forEach { exception ->
+ val msg = exception.message
+ assertTrue(
+ "Wrong network call count returned: $msg",
+ msg?.startsWith("Cached network call count") == false
+ )
+ }
+ }
+
+ val requests = checkNotNull(session.performanceInfo?.networkRequests?.networkSessionV2?.requests)
+ assertEquals(
+ "Unexpected number of requests in sent session: ${requests.size}",
+ 1,
+ requests.size
+ )
+
+ return requests.first()
+ }
+
+ companion object {
+ private const val URL = "https://embrace.io"
+ private const val DISABLED_URL = "https://dontlogmebro.pizza/yum"
+ private const val START_TIME = 1692201601L
+ private const val END_TIME = 1692202600L
+ private const val BYTES_SENT = 100L
+ private const val BYTES_RECEIVED = 500L
+ private const val TRACE_ID = "rAnDoM-traceId"
+ private const val TRACEPARENT = "00-c4ada96c31e1b6b9e351a1cffc99ae38-331f3a8acf49d295-01"
+
+ private val NETWORK_CAPTURE_DATA = NetworkCaptureData(
+ requestHeaders = mapOf(Pair("x-emb-test", "holla")),
+ requestQueryParams = "trackMe=noooooo",
+ capturedRequestBody = "haha".toByteArray(),
+ responseHeaders = mapOf(Pair("x-emb-response-header", "alloh")),
+ capturedResponseBody = "woohoo".toByteArray(),
+ dataCaptureErrorMessage = null
+ )
+ }
+}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.java b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.java
index 0cb59d5f5..5f30fab7e 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.java
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/Embrace.java
@@ -2,8 +2,8 @@
import static io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger.logger;
+import android.annotation.SuppressLint;
import android.content.Context;
-import android.util.Pair;
import android.webkit.ConsoleMessage;
import androidx.annotation.NonNull;
@@ -13,6 +13,7 @@
import java.util.Map;
import io.embrace.android.embracesdk.config.ConfigService;
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface;
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger;
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger;
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest;
@@ -27,13 +28,16 @@
*
* Contains a singleton instance of itself, and is used for initializing the SDK.
*/
+@SuppressLint("EmbracePublicApiPackageRule")
@SuppressWarnings("unused")
public final class Embrace implements EmbraceAndroidApi {
/**
* Singleton instance of the Embrace SDK.
*/
+ @NonNull
private static final Embrace embrace = new Embrace();
+
private static EmbraceImpl impl = new EmbraceImpl();
@NonNull
@@ -41,9 +45,6 @@ public final class Embrace implements EmbraceAndroidApi {
static final String NULL_PARAMETER_ERROR_MESSAGE_TEMPLATE = " cannot be invoked because it contains null parameters";
- Embrace() {
- }
-
/**
* Gets the singleton instance of the Embrace SDK.
*
@@ -67,6 +68,9 @@ static void setImpl(@Nullable EmbraceImpl instance) {
impl = instance;
}
+ Embrace() {
+ }
+
@Override
public void start(@NonNull Context context) {
if (verifyNonNullParameters("start", context)) {
@@ -288,16 +292,6 @@ public void logError(@NonNull String message) {
}
}
- /**
- * Logs a React Native Redux Action.
- */
- public void logRnAction(@NonNull String name, long startTime, long endTime,
- @NonNull Map properties, int bytesSent, @NonNull String output) {
- if (verifyNonNullParameters("logRnAction", name, properties, output)) {
- impl.logRnAction(name, startTime, endTime, properties, bytesSent, output);
- }
- }
-
@Override
public void addBreadcrumb(@NonNull String message) {
if (verifyNonNullParameters("addBreadcrumb", message)) {
@@ -387,22 +381,6 @@ public void logCustomStacktrace(@NonNull StackTraceElement[] stacktraceElements,
}
}
- /**
- * Logs an internal error to the Embrace SDK - this is not intended for public use.
- */
- @InternalApi
- public void logInternalError(@Nullable String message, @Nullable String details) {
- impl.logInternalError(message, details);
- }
-
- /**
- * Logs an internal error to the Embrace SDK - this is not intended for public use.
- */
- @InternalApi
- public void logInternalError(@NonNull Throwable error) {
- impl.logInternalError(error);
- }
-
@Override
public synchronized void endSession() {
endSession(false);
@@ -435,30 +413,6 @@ public boolean endView(@NonNull String name) {
return false;
}
- /**
- * Logs the fact that a particular view was entered.
- *
- * If the previously logged view has the same name, a duplicate view breadcrumb will not be
- * logged.
- *
- * @param screen the name of the view to log
- */
- @InternalApi
- public void logRnView(@NonNull String screen) {
- impl.logRnView(screen);
- }
-
- @Nullable
- @InternalApi
- public ConfigService getConfigService() {
- return impl.getConfigService();
- }
-
- @InternalApi
- void installUnityThreadSampler() {
- getImpl().installUnityThreadSampler();
- }
-
@Override
public boolean isTracingAvailable() {
return impl.tracer.getValue().isTracingAvailable();
@@ -560,26 +514,86 @@ public boolean recordCompletedSpan(@NonNull String name, long startTimeNanos, lo
return false;
}
- /**
- * The AppFramework that is in use.
- */
- public enum AppFramework {
- NATIVE(1),
- REACT_NATIVE(2),
- UNITY(3),
- FLUTTER(4);
-
- private final int value;
+ @Override
+ public void logPushNotification(@Nullable String title,
+ @Nullable String body,
+ @Nullable String topic,
+ @Nullable String id,
+ @Nullable Integer notificationPriority,
+ @NonNull Integer messageDeliveredPriority,
+ @NonNull Boolean isNotification,
+ @NonNull Boolean hasData) {
+ if (verifyNonNullParameters("logPushNotification", messageDeliveredPriority, isNotification, hasData)) {
+ impl.logPushNotification(
+ title,
+ body,
+ topic,
+ id,
+ notificationPriority,
+ messageDeliveredPriority,
+ PushNotificationBreadcrumb.NotificationType.Builder.notificationTypeFor(hasData, isNotification)
+ );
+ }
+ }
- AppFramework(int value) {
- this.value = value;
+ @Override
+ public void trackWebViewPerformance(@NonNull String tag, @NonNull ConsoleMessage consoleMessage) {
+ if (verifyNonNullParameters("trackWebViewPerformance", tag, consoleMessage)) {
+ if (consoleMessage.message() != null) {
+ trackWebViewPerformance(tag, consoleMessage.message());
+ } else {
+ logger.logDebug("Empty WebView console message.");
+ }
}
+ }
- public int getValue() {
- return value;
+ @Override
+ public void trackWebViewPerformance(@NonNull String tag, @NonNull String message) {
+ if (verifyNonNullParameters("trackWebViewPerformance", tag, message)) {
+ impl.trackWebViewPerformance(tag, message);
}
}
+ @Nullable
+ @Override
+ public String getCurrentSessionId() {
+ return impl.getCurrentSessionId();
+ }
+
+ @NonNull
+ @Override
+ public LastRunEndState getLastRunEndState() {
+ return impl.getLastRunEndState();
+ }
+
+ @NonNull
+ @InternalApi
+ public EmbraceInternalInterface getInternalInterface() {
+ return impl.getEmbraceInternalInterface();
+ }
+
+ /**
+ * Logs an internal error to the Embrace SDK - this is not intended for public use.
+ */
+ @InternalApi
+ public void logInternalError(@Nullable String message, @Nullable String details) {
+ impl.logInternalError(message, details);
+ }
+
+ /**
+ * Logs an internal error to the Embrace SDK - this is not intended for public use.
+ */
+ @InternalApi
+ public void logInternalError(@NonNull Throwable error) {
+ impl.logInternalError(error);
+ }
+
+ @Nullable
+ @InternalApi
+ public ConfigService getConfigService() {
+ return impl.getConfigService();
+ }
+
/**
* Gets the {@link ReactNativeInternalInterface} that should be used as the sole source of
* communication with the Android SDK for React Native.
@@ -590,6 +604,30 @@ public ReactNativeInternalInterface getReactNativeInternalInterface() {
return impl.getReactNativeInternalInterface();
}
+ /**
+ * Logs a React Native Redux Action - this is not intended for public use.
+ */
+ @InternalApi
+ public void logRnAction(@NonNull String name, long startTime, long endTime,
+ @NonNull Map properties, int bytesSent, @NonNull String output) {
+ if (verifyNonNullParameters("logRnAction", name, properties, output)) {
+ impl.logRnAction(name, startTime, endTime, properties, bytesSent, output);
+ }
+ }
+
+ /**
+ * Logs the fact that a particular view was entered.
+ *
+ * If the previously logged view has the same name, a duplicate view breadcrumb will not be
+ * logged.
+ *
+ * @param screen the name of the view to log
+ */
+ @InternalApi
+ public void logRnView(@NonNull String screen) {
+ impl.logRnView(screen);
+ }
+
/**
* Gets the {@link UnityInternalInterface} that should be used as the sole source of
* communication with the Android SDK for Unity.
@@ -600,6 +638,11 @@ public UnityInternalInterface getUnityInternalInterface() {
return impl.getUnityInternalInterface();
}
+ @InternalApi
+ void installUnityThreadSampler() {
+ getImpl().installUnityThreadSampler();
+ }
+
/**
* Gets the {@link FlutterInternalInterface} that should be used as the sole source of
* communication with the Android SDK for Flutter.
@@ -610,6 +653,22 @@ public FlutterInternalInterface getFlutterInternalInterface() {
return impl.getFlutterInternalInterface();
}
+ /**
+ * Sets the Embrace Flutter SDK version - this is not intended for public use.
+ */
+ @InternalApi
+ public void setEmbraceFlutterSdkVersion(@Nullable String version) {
+ impl.setEmbraceFlutterSdkVersion(version);
+ }
+
+ /**
+ * Sets the Dart version - this is not intended for public use.
+ */
+ @InternalApi
+ public void setDartVersion(@Nullable String version) {
+ impl.setDartVersion(version);
+ }
+
/**
* Logs a handled Dart error to the Embrace SDK - this is not intended for public use.
*/
@@ -643,103 +702,41 @@ public void sampleCurrentThreadDuringAnrs() {
impl.sampleCurrentThreadDuringAnrs();
}
- /**
- * Logs taps from Compose views
- * @param point Position of the captured clicked
- * @param elementName Name of the clicked element
- */
- @InternalApi
- public void logComposeTap(@NonNull Pair point, @NonNull String elementName) {
- impl.getEmbraceInternalInterface().logComposeTap(point, elementName);
- }
-
- /**
- * Allows Unity customers to verify their integration.
- */
- void verifyUnityIntegration() {
- EmbraceSamples.verifyIntegration();
- }
-
- @Override
- public void logPushNotification(@Nullable String title,
- @Nullable String body,
- @Nullable String topic,
- @Nullable String id,
- @Nullable Integer notificationPriority,
- @NonNull Integer messageDeliveredPriority,
- @NonNull Boolean isNotification,
- @NonNull Boolean hasData) {
- if (verifyNonNullParameters("logPushNotification", messageDeliveredPriority, isNotification, hasData)) {
- impl.logPushNotification(
- title,
- body,
- topic,
- id,
- notificationPriority,
- messageDeliveredPriority,
- PushNotificationBreadcrumb.NotificationType.Builder.notificationTypeFor(hasData, isNotification)
- );
+ private boolean verifyNonNullParameters(@NonNull String functionName, @NonNull Object... params) {
+ for (Object param : params) {
+ if (param == null) {
+ final String errorMessage = functionName + NULL_PARAMETER_ERROR_MESSAGE_TEMPLATE;
+ if (isStarted()) {
+ internalEmbraceLogger.logError(errorMessage, new IllegalArgumentException(errorMessage), true);
+ } else {
+ internalEmbraceLogger.logSDKNotInitialized(errorMessage);
+ }
+ return false;
+ }
}
+ return true;
}
/**
- * Determine if a network call should be captured based on the network capture rules
- *
- * @param url the url of the network call
- * @param method the method of the network call
- * @return the network capture rule to apply or null
+ * The AppFramework that is in use.
*/
- @InternalApi
- public boolean shouldCaptureNetworkBody(@NonNull String url, @NonNull String method) {
- if (isStarted()) {
- return impl.shouldCaptureNetworkCall(url, method);
- } else {
- internalEmbraceLogger.logSDKNotInitialized("Embrace SDK is not initialized yet, cannot check for capture rules.");
- return false;
- }
- }
+ public enum AppFramework {
+ NATIVE(1),
+ REACT_NATIVE(2),
+ UNITY(3),
+ FLUTTER(4);
- @InternalApi
- public void setProcessStartedByNotification() {
- impl.setProcessStartedByNotification();
- }
+ private final int value;
- @Override
- public void trackWebViewPerformance(@NonNull String tag, @NonNull ConsoleMessage consoleMessage) {
- if (verifyNonNullParameters("trackWebViewPerformance", tag, consoleMessage)) {
- if (consoleMessage.message() != null) {
- trackWebViewPerformance(tag, consoleMessage.message());
- } else {
- logger.logDebug("Empty WebView console message.");
- }
+ AppFramework(int value) {
+ this.value = value;
}
- }
- @Override
- public void trackWebViewPerformance(@NonNull String tag, @NonNull String message) {
- if (verifyNonNullParameters("trackWebViewPerformance", tag, message)) {
- impl.trackWebViewPerformance(tag, message);
+ public int getValue() {
+ return value;
}
}
- /**
- * Get the ID for the current session.
- * Returns null if a session has not been started yet or the SDK hasn't been initialized.
- *
- * @return The ID for the current Session, if available.
- */
- @Nullable
- @Override
- public String getCurrentSessionId() {
- return impl.getCurrentSessionId();
- }
-
- @NonNull
- @Override
- public LastRunEndState getLastRunEndState() {
- return impl.getLastRunEndState();
- }
-
/**
* Enum representing the end state of the last run of the application.
*/
@@ -769,19 +766,4 @@ public int getValue() {
return value;
}
}
-
- private boolean verifyNonNullParameters(@NonNull String functionName, @NonNull Object... params) {
- for (Object param : params) {
- if (param == null) {
- final String errorMessage = functionName + NULL_PARAMETER_ERROR_MESSAGE_TEMPLATE;
- if (isStarted()) {
- internalEmbraceLogger.logError(errorMessage, new IllegalArgumentException(errorMessage), true);
- } else {
- internalEmbraceLogger.logSDKNotInitialized(errorMessage);
- }
- return false;
- }
- }
- return true;
- }
}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.java b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.java
index 7bf68d2c9..d664ba6ea 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.java
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceImpl.java
@@ -11,6 +11,7 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
@@ -53,12 +54,16 @@
import io.embrace.android.embracesdk.injection.InitModuleImpl;
import io.embrace.android.embracesdk.injection.SdkObservabilityModule;
import io.embrace.android.embracesdk.injection.SdkObservabilityModuleImpl;
+import io.embrace.android.embracesdk.injection.SessionModule;
+import io.embrace.android.embracesdk.injection.SessionModuleImpl;
import io.embrace.android.embracesdk.injection.SystemServiceModule;
import io.embrace.android.embracesdk.injection.SystemServiceModuleImpl;
import io.embrace.android.embracesdk.internal.ApkToolsConfig;
import io.embrace.android.embracesdk.internal.BuildInfo;
import io.embrace.android.embracesdk.internal.DeviceArchitecture;
import io.embrace.android.embracesdk.internal.DeviceArchitectureImpl;
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface;
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterfaceKt;
import io.embrace.android.embracesdk.internal.MessageType;
import io.embrace.android.embracesdk.internal.TraceparentGenerator;
import io.embrace.android.embracesdk.internal.crash.LastRunCrashVerifier;
@@ -87,8 +92,6 @@
import io.embrace.android.embracesdk.session.EmbraceActivityService;
import io.embrace.android.embracesdk.session.EmbraceSessionProperties;
import io.embrace.android.embracesdk.session.EmbraceSessionService;
-import io.embrace.android.embracesdk.injection.SessionModule;
-import io.embrace.android.embracesdk.injection.SessionModuleImpl;
import io.embrace.android.embracesdk.session.SessionService;
import io.embrace.android.embracesdk.utils.PropertyUtils;
import io.embrace.android.embracesdk.worker.ExecutorName;
@@ -609,6 +612,7 @@ private void startImpl(@NonNull Context context,
// initialize internal interfaces
InternalInterfaceModuleImpl internalInterfaceModule = new InternalInterfaceModuleImpl(
+ initModule,
coreModule,
androidServicesModule,
essentialServiceModule,
@@ -1010,9 +1014,9 @@ public void clearUsername() {
*
* The length of time a moment takes to execute is recorded.
*
- * @param name a name identifying the moment
- * @param identifier an identifier distinguishing between multiple moments with the same name
- * @param properties custom key-value pairs to provide with the moment
+ * @param name a name identifying the moment
+ * @param identifier an identifier distinguishing between multiple moments with the same name
+ * @param properties custom key-value pairs to provide with the moment
*/
public void startMoment(@NonNull String name,
@Nullable String identifier,
@@ -1071,14 +1075,19 @@ public String generateW3cTraceparent() {
}
public void recordNetworkRequest(@NonNull EmbraceNetworkRequest request) {
- internalEmbraceLogger.logDeveloper("Embrace", "recordNetworkRequest()");
+ if (isStarted() && embraceInternalInterface != null) {
+ embraceInternalInterface.recordAndDeduplicateNetworkRequest(UUID.randomUUID().toString(), request);
+ }
+ }
+ public void recordAndDeduplicateNetworkRequest(@NonNull String callId, @NonNull EmbraceNetworkRequest request) {
if (request == null) {
internalEmbraceLogger.logDeveloper("Embrace", "Request is null");
return;
}
logNetworkRequestImpl(
+ callId,
request.getNetworkCaptureData(),
request.getUrl(),
request.getHttpMethod(),
@@ -1094,7 +1103,8 @@ public void recordNetworkRequest(@NonNull EmbraceNetworkRequest request) {
);
}
- private void logNetworkRequestImpl(@Nullable NetworkCaptureData networkCaptureData,
+ private void logNetworkRequestImpl(@NonNull String callId,
+ @Nullable NetworkCaptureData networkCaptureData,
String url,
String httpMethod,
Long startTime,
@@ -1117,6 +1127,7 @@ private void logNetworkRequestImpl(@Nullable NetworkCaptureData networkCaptureDa
!errorType.isEmpty() &&
!errorMessage.isEmpty()) {
networkLoggingService.logNetworkError(
+ callId,
url,
httpMethod,
startTime,
@@ -1128,6 +1139,7 @@ private void logNetworkRequestImpl(@Nullable NetworkCaptureData networkCaptureDa
networkCaptureData);
} else {
networkLoggingService.logNetworkCall(
+ callId,
url,
httpMethod,
responseCode != null ? responseCode : 0,
@@ -1558,7 +1570,12 @@ private Map normalizeProperties(@Nullable Map pr
*/
@NonNull
EmbraceInternalInterface getEmbraceInternalInterface() {
- return embraceInternalInterface;
+ if (isStarted() && embraceInternalInterface != null) {
+ return embraceInternalInterface;
+ } else {
+ return EmbraceInternalInterfaceKt.getDefaultImpl();
+ }
+
}
/**
@@ -1596,6 +1613,26 @@ public void installUnityThreadSampler() {
}
}
+ /**
+ * Sets the Embrace Flutter SDK version - this is not intended for public use.
+ */
+ @InternalApi
+ public void setEmbraceFlutterSdkVersion(@Nullable String version) {
+ if (flutterInternalInterface != null) {
+ flutterInternalInterface.setEmbraceFlutterSdkVersion(version);
+ }
+ }
+
+ /**
+ * Sets the Dart version - this is not intended for public use.
+ */
+ @InternalApi
+ public void setDartVersion(@Nullable String version) {
+ if (flutterInternalInterface != null) {
+ flutterInternalInterface.setDartVersion(version);
+ }
+ }
+
/**
* Saves captured push notification information into session payload
*
@@ -1633,8 +1670,13 @@ private void onActivityReported() {
}
}
- public boolean shouldCaptureNetworkCall(String url, String method) {
- return !networkCaptureService.getNetworkCaptureRules(url, method).isEmpty();
+ public boolean shouldCaptureNetworkCall(@NonNull String url, @NonNull String method) {
+ if (isStarted() && networkCaptureService != null) {
+ return !networkCaptureService.getNetworkCaptureRules(url, method).isEmpty();
+ } else {
+ internalEmbraceLogger.logSDKNotInitialized("Embrace SDK is not initialized yet, cannot check for capture rules.");
+ return false;
+ }
}
public void setProcessStartedByNotification() {
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceInternalInterface.java b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceInternalInterface.java
deleted file mode 100644
index 6349808b6..000000000
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceInternalInterface.java
+++ /dev/null
@@ -1,215 +0,0 @@
-package io.embrace.android.embracesdk;
-
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.Map;
-
-import io.embrace.android.embracesdk.network.EmbraceNetworkRequest;
-import io.embrace.android.embracesdk.network.http.HttpMethod;
-import io.embrace.android.embracesdk.network.http.NetworkCaptureData;
-
-/**
- * Provides an internal interface to Embrace that is intended for use by hosted SDKs as their
- * sole source of communication with the Android SDK.
- */
-interface EmbraceInternalInterface {
-
- /**
- * {@see Embrace#logInfo}
- */
- void logInfo(@NonNull String message,
- @Nullable Map properties);
-
- /**
- * {@see Embrace#logWarning}
- */
- void logWarning(@NonNull String message,
- @Nullable Map properties,
- @Nullable String stacktrace);
-
- /**
- * {@see Embrace#logError}
- */
- void logError(@NonNull String message,
- @Nullable Map properties,
- @Nullable String stacktrace,
- boolean isException);
-
- /**
- * {@see Embrace#logHandledException}
- */
- void logHandledException(@NonNull Throwable throwable,
- @NonNull LogType type,
- @Nullable Map properties,
- @Nullable StackTraceElement[] customStackTrace);
-
- /**
- * {@see Embrace#logBreadcrumb}
- */
- void addBreadcrumb(@NonNull String message);
-
- /**
- * {@see Embrace#getDeviceId}
- */
- @NonNull
- String getDeviceId();
-
- /**
- * {@see Embrace#setUsername}
- */
- void setUsername(@Nullable String username);
-
- /**
- * {@see Embrace#clearUsername}
- */
- void clearUsername();
-
- /**
- * {@see Embrace#setUserIdentifier}
- */
- void setUserIdentifier(@Nullable String userId);
-
- /**
- * {@see Embrace#clearUserIdentifier}
- */
- void clearUserIdentifier();
-
- /**
- * {@see Embrace#setUserEmail}
- */
- void setUserEmail(@Nullable String email);
-
- /**
- * {@see Embrace#clearUserEmail}
- */
- void clearUserEmail();
-
- /**
- * {@see Embrace#setUserAsPayer}
- */
- void setUserAsPayer();
-
- /**
- * {@see Embrace#clearUserAsPayer}
- */
- void clearUserAsPayer();
-
- /**
- * {@see Embrace#addUserPersona}
- */
- void addUserPersona(@NonNull String persona);
-
- /**
- * {@see Embrace#clearUserPersona}
- */
- void clearUserPersona(@NonNull String persona);
-
- /**
- * {@see Embrace#clearAllUserPersonas}
- */
- void clearAllUserPersonas();
-
- /**
- * {@see Embrace#addSessionProperty}
- */
- boolean addSessionProperty(@NonNull String key,
- @NonNull String value,
- boolean permanent);
-
- /**
- * {@see Embrace#removeSessionProperty}
- */
- boolean removeSessionProperty(@NonNull String key);
-
- /**
- * {@see Embrace#getSessionProperties}
- */
- @Nullable
- Map getSessionProperties();
-
- /**
- * {@see Embrace#startEvent}
- */
- void startMoment(@NonNull String name,
- @Nullable String identifier,
- @Nullable Map properties);
-
- /**
- * {@see Embrace#endMoment}
- */
- void endMoment(@NonNull String name,
- @Nullable String identifier,
- @Nullable Map properties);
-
- /**
- * {@see Embrace#startFragment}
- */
- boolean startView(@NonNull String name);
-
- /**
- * {@see Embrace#endFragment}
- */
- boolean endView(@NonNull String name);
-
- /**
- * {@see Embrace#endAppStartup}
- */
- void endAppStartup(@NonNull Map properties);
-
- /**
- * {@see Embrace#logInternalError}
- */
- void logInternalError(@Nullable String message, @Nullable String details);
-
- /**
- * {@see Embrace#endSession}
- */
- void endSession(boolean clearUserInfo);
-
- /**
- * See {@link Embrace#recordNetworkRequest(EmbraceNetworkRequest)}
- */
- void recordCompletedNetworkRequest(@NonNull String url,
- @NonNull String httpMethod,
- long startTime,
- long endTime,
- long bytesSent,
- long bytesReceived,
- int statusCode,
- @Nullable String traceId,
- @Nullable NetworkCaptureData networkCaptureData);
-
- /**
- * See {@link Embrace#recordNetworkRequest(EmbraceNetworkRequest)}
- */
- void recordIncompleteNetworkRequest(@NonNull String url,
- @NonNull String httpMethod,
- long startTime,
- long endTime,
- @Nullable Throwable error,
- @Nullable String traceId,
- @Nullable NetworkCaptureData networkCaptureData);
-
- /**
- * See {@link Embrace#recordNetworkRequest(EmbraceNetworkRequest)}
- */
- void recordIncompleteNetworkRequest(@NonNull String url,
- @NonNull String httpMethod,
- long startTime,
- long endTime,
- @Nullable String errorType,
- @Nullable String errorMessage,
- @Nullable String traceId,
- @Nullable NetworkCaptureData networkCaptureData);
-
- /**
- * Logs a tap on a Compose screen element.
- *
- * @param point the coordinates of the screen tap
- * @param elementName the name of the element which was tapped
- */
- void logComposeTap(@NonNull Pair point, @NonNull String elementName);
-}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImpl.kt
index e988cb435..df71f0f4d 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImpl.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImpl.kt
@@ -1,17 +1,20 @@
package io.embrace.android.embracesdk
import android.util.Pair
+import io.embrace.android.embracesdk.injection.InitModule
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
import io.embrace.android.embracesdk.network.http.HttpMethod
import io.embrace.android.embracesdk.network.http.NetworkCaptureData
import io.embrace.android.embracesdk.payload.TapBreadcrumb
internal class EmbraceInternalInterfaceImpl(
- private val embrace: EmbraceImpl
+ private val embraceImpl: EmbraceImpl,
+ private val initModule: InitModule
) : EmbraceInternalInterface {
override fun logInfo(message: String, properties: Map?) {
- embrace.logMessage(
+ embraceImpl.logMessage(
EmbraceEvent.Type.INFO_LOG,
message,
properties,
@@ -28,7 +31,7 @@ internal class EmbraceInternalInterfaceImpl(
properties: Map?,
stacktrace: String?
) {
- embrace.logMessage(
+ embraceImpl.logMessage(
EmbraceEvent.Type.WARNING_LOG,
message,
properties,
@@ -46,7 +49,7 @@ internal class EmbraceInternalInterfaceImpl(
stacktrace: String?,
isException: Boolean,
) {
- embrace.logMessage(
+ embraceImpl.logMessage(
EmbraceEvent.Type.ERROR_LOG,
message,
properties,
@@ -62,9 +65,9 @@ internal class EmbraceInternalInterfaceImpl(
throwable: Throwable,
type: LogType,
properties: Map?,
- customStackTrace: Array?
+ customStackTrace: Array?
) {
- embrace.logMessage(
+ embraceImpl.logMessage(
type.toEventType(),
throwable.message ?: "",
properties,
@@ -76,104 +79,8 @@ internal class EmbraceInternalInterfaceImpl(
)
}
- override fun addBreadcrumb(message: String) {
- embrace.addBreadcrumb(message)
- }
-
- override fun getDeviceId(): String {
- return embrace.deviceId
- }
-
- override fun setUserIdentifier(userId: String?) {
- embrace.setUserIdentifier(userId)
- }
-
- override fun clearUserIdentifier() {
- embrace.clearUserIdentifier()
- }
-
- override fun setUsername(username: String?) {
- embrace.setUsername(username)
- }
-
- override fun clearUsername() {
- embrace.clearUsername()
- }
-
- override fun setUserEmail(email: String?) {
- embrace.setUserEmail(email)
- }
-
- override fun clearUserEmail() {
- embrace.clearUserEmail()
- }
-
- override fun setUserAsPayer() {
- embrace.setUserAsPayer()
- }
-
- override fun clearUserAsPayer() {
- embrace.clearUserAsPayer()
- }
-
- override fun addUserPersona(persona: String) {
- embrace.addUserPersona(persona)
- }
-
- override fun clearUserPersona(persona: String) {
- embrace.clearUserPersona(persona)
- }
-
- override fun clearAllUserPersonas() {
- embrace.clearAllUserPersonas()
- }
-
- override fun addSessionProperty(key: String, value: String, permanent: Boolean): Boolean {
- return embrace.addSessionProperty(key, value, permanent)
- }
-
- override fun removeSessionProperty(key: String): Boolean {
- return embrace.removeSessionProperty(key)
- }
-
- override fun getSessionProperties(): Map? {
- return embrace.sessionProperties
- }
-
- override fun startMoment(
- name: String,
- identifier: String?,
- properties: Map?
- ) {
- embrace.startMoment(name, identifier, properties)
- }
-
- override fun endMoment(name: String, identifier: String?, properties: Map?) {
- embrace.endMoment(name, identifier, properties)
- }
-
- override fun startView(name: String): Boolean {
- return embrace.startView(name)
- }
-
- override fun endView(name: String): Boolean {
- return embrace.endView(name)
- }
-
- override fun endAppStartup(properties: Map) {
- embrace.endAppStartup(properties)
- }
-
- override fun logInternalError(message: String?, details: String?) {
- embrace.logInternalError(message, details)
- }
-
- override fun endSession(clearUserInfo: Boolean) {
- embrace.endSession(clearUserInfo)
- }
-
override fun logComposeTap(point: Pair, elementName: String) {
- embrace.logTap(point, elementName, TapBreadcrumb.TapBreadcrumbType.TAP)
+ embraceImpl.logTap(point, elementName, TapBreadcrumb.TapBreadcrumbType.TAP)
}
override fun recordCompletedNetworkRequest(
@@ -187,7 +94,7 @@ internal class EmbraceInternalInterfaceImpl(
traceId: String?,
networkCaptureData: NetworkCaptureData?
) {
- embrace.recordNetworkRequest(
+ embraceImpl.recordNetworkRequest(
EmbraceNetworkRequest.fromCompletedRequest(
url,
HttpMethod.fromString(httpMethod),
@@ -212,7 +119,7 @@ internal class EmbraceInternalInterfaceImpl(
traceId: String?,
networkCaptureData: NetworkCaptureData?
) {
- embrace.recordNetworkRequest(
+ embraceImpl.recordNetworkRequest(
EmbraceNetworkRequest.fromIncompleteRequest(
url,
HttpMethod.fromString(httpMethod),
@@ -237,7 +144,7 @@ internal class EmbraceInternalInterfaceImpl(
traceId: String?,
networkCaptureData: NetworkCaptureData?
) {
- embrace.recordNetworkRequest(
+ embraceImpl.recordNetworkRequest(
EmbraceNetworkRequest.fromIncompleteRequest(
url,
HttpMethod.fromString(httpMethod),
@@ -251,4 +158,23 @@ internal class EmbraceInternalInterfaceImpl(
)
)
}
+
+ override fun recordAndDeduplicateNetworkRequest(
+ callId: String,
+ embraceNetworkRequest: EmbraceNetworkRequest
+ ) {
+ embraceImpl.recordAndDeduplicateNetworkRequest(callId, embraceNetworkRequest)
+ }
+
+ override fun shouldCaptureNetworkBody(url: String, method: String): Boolean = embraceImpl.shouldCaptureNetworkCall(url, method)
+
+ override fun setProcessStartedByNotification() {
+ embraceImpl.setProcessStartedByNotification()
+ }
+
+ override fun isNetworkSpanForwardingEnabled(): Boolean {
+ return embraceImpl.configService?.networkSpanForwardingBehavior?.isNetworkSpanForwardingEnabled() ?: false
+ }
+
+ override fun getSdkCurrentTime(): Long = initModule.clock.now()
}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterface.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterface.kt
index 1a2e4a69c..b39fd4f27 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterface.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterface.kt
@@ -1,5 +1,7 @@
package io.embrace.android.embracesdk
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
+
/**
* Provides an internal interface to Embrace that is intended for use by Flutter as its
* sole source of communication with the Android SDK.
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterfaceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterfaceImpl.kt
index 09f47f8f6..668844576 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterfaceImpl.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/FlutterInternalInterfaceImpl.kt
@@ -1,6 +1,7 @@
package io.embrace.android.embracesdk
import io.embrace.android.embracesdk.capture.metadata.MetadataService
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
internal class FlutterInternalInterfaceImpl(
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/InternalInterfaceModule.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/InternalInterfaceModule.kt
index 6bdfe6c31..13527911c 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/InternalInterfaceModule.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/InternalInterfaceModule.kt
@@ -4,7 +4,9 @@ import io.embrace.android.embracesdk.injection.AndroidServicesModule
import io.embrace.android.embracesdk.injection.CoreModule
import io.embrace.android.embracesdk.injection.CrashModule
import io.embrace.android.embracesdk.injection.EssentialServiceModule
+import io.embrace.android.embracesdk.injection.InitModule
import io.embrace.android.embracesdk.injection.singleton
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
internal interface InternalInterfaceModule {
val embraceInternalInterface: EmbraceInternalInterface
@@ -14,6 +16,7 @@ internal interface InternalInterfaceModule {
}
internal class InternalInterfaceModuleImpl(
+ initModule: InitModule,
coreModule: CoreModule,
androidServicesModule: AndroidServicesModule,
essentialServiceModule: EssentialServiceModule,
@@ -22,7 +25,7 @@ internal class InternalInterfaceModuleImpl(
) : InternalInterfaceModule {
override val embraceInternalInterface: EmbraceInternalInterface by singleton {
- EmbraceInternalInterfaceImpl(embrace)
+ EmbraceInternalInterfaceImpl(embrace, initModule)
}
override val reactNativeInternalInterface: ReactNativeInternalInterface by singleton {
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt
index 0cf73a40c..d6657936b 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt
@@ -1,6 +1,7 @@
package io.embrace.android.embracesdk
import android.content.Context
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
/**
* Provides an internal interface to Embrace that is intended for use by React Native as its
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt
index 67e94f494..104ba8a17 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt
@@ -4,6 +4,7 @@ import android.content.Context
import io.embrace.android.embracesdk.Embrace.AppFramework
import io.embrace.android.embracesdk.capture.crash.CrashService
import io.embrace.android.embracesdk.capture.metadata.MetadataService
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.payload.JsException
import io.embrace.android.embracesdk.prefs.PreferencesService
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterface.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterface.kt
index a10e3f0b3..a220aa052 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterface.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterface.kt
@@ -1,5 +1,7 @@
package io.embrace.android.embracesdk
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
+
/**
* Provides an internal interface to Embrace that is intended for use by Unity as its
* sole source of communication with the Android SDK.
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterfaceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterfaceImpl.kt
index c32e22413..ecf693a9f 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterfaceImpl.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/UnityInternalInterfaceImpl.kt
@@ -1,5 +1,6 @@
package io.embrace.android.embracesdk
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.prefs.PreferencesService
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/detection/TargetThreadHandler.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/detection/TargetThreadHandler.kt
index a1018a710..035d84908 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/detection/TargetThreadHandler.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/detection/TargetThreadHandler.kt
@@ -8,7 +8,6 @@ import androidx.annotation.VisibleForTesting
import io.embrace.android.embracesdk.clock.Clock
import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.internal.enforceThread
-import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger.Companion.logDebug
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger.Companion.logError
import java.util.concurrent.ExecutorService
import java.util.concurrent.atomic.AtomicReference
@@ -63,7 +62,6 @@ internal class TargetThreadHandler(
// but if it does then we just log an internal error & consider the ANR ended at
// this point.
if (messageQueue == null || !installed) {
- logDebug("Failed to obtain main thread MessageQueue - using fallback ANR strategy.")
onMainThreadUnblocked()
}
}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/EmbracePerformanceInfoService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/EmbracePerformanceInfoService.kt
index 2357316d6..30eef7143 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/EmbracePerformanceInfoService.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/EmbracePerformanceInfoService.kt
@@ -37,12 +37,7 @@ internal class EmbracePerformanceInfoService(
"EmbracePerformanceInfoService",
"Session performance info start time: $sessionStart"
)
- val requests = NetworkRequests(
- networkLoggingService.getNetworkCallsForSession(
- sessionStart,
- sessionLastKnownTime
- )
- )
+ val requests = NetworkRequests(networkLoggingService.getNetworkCallsForSession())
val info = getPerformanceInfo(sessionStart, sessionLastKnownTime, coldStart)
return info.copy(
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/PerformanceInfoService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/PerformanceInfoService.kt
index 596d0e878..17893217f 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/PerformanceInfoService.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/PerformanceInfoService.kt
@@ -26,7 +26,6 @@ internal interface PerformanceInfoService {
* * END
* * INTERRUPT
*
- *
* @param startTime the start time of the performance information to retrieve
* @param endTime the end time of the performance information to retrieve
* @return the performance information
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/EmbraceInternalInterface.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/EmbraceInternalInterface.kt
new file mode 100644
index 000000000..e21964352
--- /dev/null
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/EmbraceInternalInterface.kt
@@ -0,0 +1,196 @@
+package io.embrace.android.embracesdk.internal
+
+import android.util.Pair
+import io.embrace.android.embracesdk.Embrace
+import io.embrace.android.embracesdk.InternalApi
+import io.embrace.android.embracesdk.LogType
+import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
+import io.embrace.android.embracesdk.network.http.NetworkCaptureData
+
+/**
+ * Provides an internal interface to Embrace that is intended for use by hosted SDKs as their sole source of communication
+ * with the Android SDK. This is not publicly supported and methods can change at any time.
+ */
+@InternalApi
+public interface EmbraceInternalInterface {
+ /**
+ * See [Embrace.logInfo]
+ */
+ public fun logInfo(
+ message: String,
+ properties: Map?
+ )
+
+ /**
+ * See [Embrace.logWarning]
+ */
+ public fun logWarning(
+ message: String,
+ properties: Map?,
+ stacktrace: String?
+ )
+
+ /**
+ * See [Embrace.logError]
+ */
+ public fun logError(
+ message: String,
+ properties: Map?,
+ stacktrace: String?,
+ isException: Boolean
+ )
+
+ /**
+ * Backwards compatible way for hosted SDKs to log a handled exception with different log levels
+ */
+ public fun logHandledException(
+ throwable: Throwable,
+ type: LogType,
+ properties: Map?,
+ customStackTrace: Array?
+ )
+
+ /**
+ * See [Embrace.recordNetworkRequest]
+ */
+ public fun recordCompletedNetworkRequest(
+ url: String,
+ httpMethod: String,
+ startTime: Long,
+ endTime: Long,
+ bytesSent: Long,
+ bytesReceived: Long,
+ statusCode: Int,
+ traceId: String?,
+ networkCaptureData: NetworkCaptureData?
+ )
+
+ /**
+ * See [Embrace.recordNetworkRequest]
+ */
+ public fun recordIncompleteNetworkRequest(
+ url: String,
+ httpMethod: String,
+ startTime: Long,
+ endTime: Long,
+ error: Throwable?,
+ traceId: String?,
+ networkCaptureData: NetworkCaptureData?
+ )
+
+ /**
+ * See [Embrace.recordNetworkRequest]
+ */
+ public fun recordIncompleteNetworkRequest(
+ url: String,
+ httpMethod: String,
+ startTime: Long,
+ endTime: Long,
+ errorType: String?,
+ errorMessage: String?,
+ traceId: String?,
+ networkCaptureData: NetworkCaptureData?
+ )
+
+ /**
+ * Record a network request and overwrite any previously recorded request with the same callId
+ *
+ * @param callId the ID with which the request will be identified internally. The session will only contain one recorded
+ * request with a given ID - last writer wins.
+ * @param embraceNetworkRequest the request to be recorded
+ */
+ public fun recordAndDeduplicateNetworkRequest(
+ callId: String,
+ embraceNetworkRequest: EmbraceNetworkRequest
+ )
+
+ /**
+ * Logs a tap on a Compose screen element.
+ *
+ * @param point the coordinates of the screen tap
+ * @param elementName the name of the element which was tapped
+ */
+ public fun logComposeTap(point: Pair, elementName: String)
+
+ /**
+ * For the given URL and method, whether the response body should be captured for network request logging
+ */
+ public fun shouldCaptureNetworkBody(url: String, method: String): Boolean
+
+ /**
+ * Mark that this application process was created in response to a notification
+ */
+ public fun setProcessStartedByNotification()
+
+ /**
+ * Whether the Network Span Forwarding feature is enabled
+ */
+ public fun isNetworkSpanForwardingEnabled(): Boolean
+
+ /**
+ * Return internal time the SDK is using in milliseconds. It is equivalent to [System.currentTimeMillis] assuming the system clock did
+ * not change after the SDK has started.
+ */
+ public fun getSdkCurrentTime(): Long
+}
+
+internal val defaultImpl = object : EmbraceInternalInterface {
+
+ override fun logInfo(message: String, properties: Map?) { }
+
+ override fun logWarning(message: String, properties: Map?, stacktrace: String?) { }
+
+ override fun logError(message: String, properties: Map?, stacktrace: String?, isException: Boolean) { }
+
+ override fun logHandledException(
+ throwable: Throwable,
+ type: LogType,
+ properties: Map?,
+ customStackTrace: Array?
+ ) { }
+
+ override fun recordCompletedNetworkRequest(
+ url: String,
+ httpMethod: String,
+ startTime: Long,
+ endTime: Long,
+ bytesSent: Long,
+ bytesReceived: Long,
+ statusCode: Int,
+ traceId: String?,
+ networkCaptureData: NetworkCaptureData?
+ ) { }
+
+ override fun recordIncompleteNetworkRequest(
+ url: String,
+ httpMethod: String,
+ startTime: Long,
+ endTime: Long,
+ error: Throwable?,
+ traceId: String?,
+ networkCaptureData: NetworkCaptureData?
+ ) { }
+
+ override fun recordIncompleteNetworkRequest(
+ url: String,
+ httpMethod: String,
+ startTime: Long,
+ endTime: Long,
+ errorType: String?,
+ errorMessage: String?,
+ traceId: String?,
+ networkCaptureData: NetworkCaptureData?
+ ) { }
+
+ override fun recordAndDeduplicateNetworkRequest(callId: String, embraceNetworkRequest: EmbraceNetworkRequest) { }
+
+ override fun logComposeTap(point: Pair, elementName: String) { }
+
+ override fun shouldCaptureNetworkBody(url: String, method: String): Boolean = false
+
+ override fun setProcessStartedByNotification() { }
+
+ override fun isNetworkSpanForwardingEnabled(): Boolean = false
+
+ override fun getSdkCurrentTime(): Long = System.currentTimeMillis()
+}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverride.java b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverride.java
index 112e6210f..9c42fe6ba 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverride.java
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverride.java
@@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -91,6 +92,9 @@ class EmbraceUrlConnectionOverride
*/
private final Embrace embrace;
+ @NonNull
+ private final String callId;
+
/**
* A reference to the output stream wrapped in a counter, so we can determine the bytes sent.
*/
@@ -157,6 +161,7 @@ public EmbraceUrlConnectionOverride(@NonNull T connection, boolean enableWrapIoS
this.createdTime = System.currentTimeMillis();
this.enableWrapIoStreams = enableWrapIoStreams;
this.embrace = embrace;
+ this.callId = UUID.randomUUID().toString();
}
@Override
@@ -168,7 +173,7 @@ public void addRequestProperty(@NonNull String key, @Nullable String value) {
public void connect() throws IOException {
identifyTraceId();
try {
- if (NetworkUtils.isNetworkSpanForwardingEnabled(embrace.getConfigService())) {
+ if (embrace.getInternalInterface().isNetworkSpanForwardingEnabled()) {
traceparent = connection.getRequestProperty(TRACEPARENT_HEADER_NAME);
}
} catch (Exception e) {
@@ -568,7 +573,8 @@ synchronized void internalLogNetworkCall(long startTime, long endTime, boolean o
long contentLength = bytesIn == null ? Math.max(0, responseSize.get()) : bytesIn;
if (inputStreamAccessException == null && lastConnectionAccessException == null && responseCode.get() != 0) {
- embrace.recordNetworkRequest(
+ embrace.getInternalInterface().recordAndDeduplicateNetworkRequest(
+ callId,
EmbraceNetworkRequest.fromCompletedRequest(
url,
HttpMethod.fromString(getRequestMethod()),
@@ -598,7 +604,8 @@ synchronized void internalLogNetworkCall(long startTime, long endTime, boolean o
String errorType = exceptionClass != null ? exceptionClass : "UnknownState";
String errorMessage = exceptionMessage != null ? exceptionMessage : "HTTP response state unknown";
- embrace.recordNetworkRequest(
+ embrace.getInternalInterface().recordAndDeduplicateNetworkRequest(
+ callId,
EmbraceNetworkRequest.fromIncompleteRequest(
url,
HttpMethod.fromString(getRequestMethod()),
@@ -816,7 +823,7 @@ private boolean hasNetworkCaptureRules() {
String url = this.connection.getURL().toString();
String method = this.connection.getRequestMethod();
- return embrace.shouldCaptureNetworkBody(url, method);
+ return embrace.getInternalInterface().shouldCaptureNetworkBody(url, method);
}
/**
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandler.java b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandler.java
index d171f526b..ae3dec1c4 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandler.java
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandler.java
@@ -114,7 +114,7 @@ protected URLConnection openConnection(URL url, Proxy proxy) throws IOException
}
protected void injectTraceparent(@NonNull URLConnection connection) {
- boolean networkSpanForwardingEnabled = NetworkUtils.isNetworkSpanForwardingEnabled(embrace.getConfigService());
+ boolean networkSpanForwardingEnabled = embrace.getInternalInterface().isNetworkSpanForwardingEnabled();
if (networkSpanForwardingEnabled && !connection.getRequestProperties().containsKey(TRACEPARENT_HEADER_NAME)) {
connection.addRequestProperty(TRACEPARENT_HEADER_NAME, embrace.generateW3cTraceparent());
}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingService.kt
index 1f6d3cc29..7738a8acb 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingService.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingService.kt
@@ -14,7 +14,6 @@ import io.embrace.android.embracesdk.utils.NetworkUtils.getValidTraceId
import io.embrace.android.embracesdk.utils.NetworkUtils.isIpAddress
import io.embrace.android.embracesdk.utils.NetworkUtils.stripUrl
import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.ConcurrentSkipListMap
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.max
@@ -32,11 +31,14 @@ internal class EmbraceNetworkLoggingService(
private val networkCaptureService: NetworkCaptureService
) : NetworkLoggingService, MemoryCleanerListener {
+ private val callsStorageLastUpdate = AtomicInteger(0)
+
/**
* Network calls per domain prepared for the session.
*/
- private val sessionNetworkCalls = ConcurrentSkipListMap()
- private val networkCallCache = CacheableValue>(sessionNetworkCalls::size)
+ private val sessionNetworkCalls = ConcurrentHashMap()
+
+ private val networkCallCache = CacheableValue> { callsStorageLastUpdate.get() }
private val domainSettings = ConcurrentHashMap()
@@ -44,13 +46,16 @@ internal class EmbraceNetworkLoggingService(
private val ipAddressCount = AtomicInteger(0)
- override fun getNetworkCallsForSession(startTime: Long, lastKnownTime: Long): NetworkSessionV2 {
- logger.logDeveloper("EmbraceNetworkLoggingService", "getNetworkCallsForSession")
-
+ override fun getNetworkCallsForSession(): NetworkSessionV2 {
val calls = networkCallCache.value {
- ArrayList(sessionNetworkCalls.subMap(startTime, lastKnownTime).values)
+ synchronized(callsStorageLastUpdate) {
+ sessionNetworkCalls.values.toList()
+ }
}
+ val storedCallsSize = sessionNetworkCalls.size
+ val cachedCallsSize = calls.size
+
val overLimit = hashMapOf()
for ((key, value) in callsPerDomain) {
if (value.requestCount > value.captureLimit) {
@@ -58,12 +63,18 @@ internal class EmbraceNetworkLoggingService(
}
}
+ if (cachedCallsSize != storedCallsSize) {
+ val msg = "Cached network call count different than expected: $cachedCallsSize instead of $storedCallsSize"
+ logger.logError(msg, IllegalStateException(msg), true)
+ }
+
// clear calls per domain and session network calls lists before be used by the next session
callsPerDomain.clear()
return NetworkSessionV2(calls, overLimit)
}
override fun logNetworkCall(
+ callId: String,
url: String,
httpMethod: String,
statusCode: Int,
@@ -102,11 +113,12 @@ internal class EmbraceNetworkLoggingService(
)
}
- processNetworkCall(startTime, networkCall)
+ processNetworkCall(callId, networkCall)
storeSettings(url)
}
override fun logNetworkError(
+ callId: String,
url: String,
httpMethod: String,
startTime: Long,
@@ -143,19 +155,17 @@ internal class EmbraceNetworkLoggingService(
errorMessage
)
}
- processNetworkCall(startTime, networkCall)
+ processNetworkCall(callId, networkCall)
storeSettings(url)
}
/**
* Process network calls to be ready when the session requests them.
*
- * @param startTime is the time when the network call was captured
+ * @param callId the unique ID that identifies the specific network call instance being recorded
* @param networkCall that is going to be captured
*/
- private fun processNetworkCall(startTime: Long, networkCall: NetworkCallV2) {
- logger.logDeveloper("EmbraceNetworkLoggingService", "processNetworkCall at: $startTime")
-
+ private fun processNetworkCall(callId: String, networkCall: NetworkCallV2) {
// Get the domain, if it can be successfully parsed
val domain = networkCall.url?.let {
getDomain(it)
@@ -175,7 +185,7 @@ internal class EmbraceNetworkLoggingService(
if (ipAddressCount.getAndIncrement() < captureLimit) {
// only capture if the ipAddressCount has not exceeded defaultLimit
logger.logDeveloper("EmbraceNetworkLoggingService", "capturing network call")
- sessionNetworkCalls[startTime] = networkCall
+ storeNetworkCall(callId, networkCall)
} else {
logger.logDeveloper("EmbraceNetworkLoggingService", "capture limit exceeded")
}
@@ -185,7 +195,7 @@ internal class EmbraceNetworkLoggingService(
val settings = domainSettings[domain]
if (settings == null) {
logger.logDeveloper("EmbraceNetworkLoggingService", "no domain settings")
- sessionNetworkCalls[startTime] = networkCall
+ storeNetworkCall(callId, networkCall)
} else {
val suffix = settings.suffix
val limit = settings.limit
@@ -197,7 +207,7 @@ internal class EmbraceNetworkLoggingService(
// Exclude if the network call exceeds the limit
if (count.requestCount < limit) {
- sessionNetworkCalls[startTime] = networkCall
+ storeNetworkCall(callId, networkCall)
} else {
logger.logDeveloper("EmbraceNetworkLoggingService", "capture limit exceeded")
}
@@ -241,10 +251,24 @@ internal class EmbraceNetworkLoggingService(
}
}
+ private fun storeNetworkCall(callId: String, networkCall: NetworkCallV2) {
+ synchronized(callsStorageLastUpdate) {
+ callsStorageLastUpdate.incrementAndGet()
+ sessionNetworkCalls[callId] = networkCall
+ }
+ }
+
+ private fun clearNetworkCalls() {
+ synchronized(callsStorageLastUpdate) {
+ callsStorageLastUpdate.set(0)
+ sessionNetworkCalls.clear()
+ }
+ }
+
override fun cleanCollections() {
domainSettings.clear()
callsPerDomain.clear()
- sessionNetworkCalls.clear()
+ clearNetworkCalls()
// reset counters
ipAddressCount.set(0)
logger.logDeveloper("EmbraceNetworkLoggingService", "Collections cleaned")
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/NetworkLoggingService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/NetworkLoggingService.kt
index 0ce9e192e..9bd992e51 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/NetworkLoggingService.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/network/logging/NetworkLoggingService.kt
@@ -10,18 +10,16 @@ import io.embrace.android.embracesdk.payload.NetworkSessionV2
internal interface NetworkLoggingService {
/**
- * Get the calls and counts of network calls (which exceed the limit) within the specified time
- * range.
+ * Get the calls and counts of network calls (which exceed the limit) that haven't been associated with a session or background activity
*
- * @param startTime the start time
- * @param lastKnownTime the end time
* @return the network calls for the given session
*/
- fun getNetworkCallsForSession(startTime: Long, lastKnownTime: Long): NetworkSessionV2
+ fun getNetworkCallsForSession(): NetworkSessionV2
/**
* Logs a HTTP network call.
*
+ * @param callId the unique ID of the call used for deduplication purposes
* @param url the URL being called
* @param httpMethod the HTTP method
* @param statusCode the status code from the response
@@ -33,7 +31,9 @@ internal interface NetworkLoggingService {
* @param w3cTraceparent optional W3C-compliant traceparent representing the network call that is being recorded
* @param networkCaptureData the additional data captured if network body capture is enabled for the URL
*/
+ @Suppress("LongParameterList")
fun logNetworkCall(
+ callId: String,
url: String,
httpMethod: String,
statusCode: Int,
@@ -49,6 +49,7 @@ internal interface NetworkLoggingService {
/**
* Logs an exception which occurred when attempting to make a network call.
*
+ * @param callId the unique ID of the call used for deduplication purposes
* @param url the URL being called
* @param httpMethod the HTTP method
* @param startTime the start time of the request
@@ -60,6 +61,7 @@ internal interface NetworkLoggingService {
* @param networkCaptureData the additional data captured if network body capture is enabled for the URL
*/
fun logNetworkError(
+ callId: String,
url: String,
httpMethod: String,
startTime: Long,
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt
index c60cc711e..72a132ac1 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/SessionHandler.kt
@@ -335,7 +335,6 @@ internal class SessionHandler(
infoLogsAttemptedToSend = remoteLogger.getInfoLogsAttemptedToSend(),
warnLogsAttemptedToSend = remoteLogger.getWarnLogsAttemptedToSend(),
errorLogsAttemptedToSend = remoteLogger.getErrorLogsAttemptedToSend(),
- exceptionError = exceptionService.currentExceptionError,
lastHeartbeatTime = clock.now(),
properties = sessionProperties.get(),
endType = endType,
@@ -350,8 +349,7 @@ internal class SessionHandler(
startupThreshold = startupThreshold,
user = userService.getUserInfo(),
betaFeatures = betaFeatures,
- symbols = nativeThreadSamplerService?.getNativeSymbols(),
-
+ symbols = nativeThreadSamplerService?.getNativeSymbols()
)
val performanceInfo = performanceInfoService.getSessionPerformanceInfo(
@@ -361,13 +359,19 @@ internal class SessionHandler(
originSession.isReceivedTermination
)
+ val appInfo = metadataService.getAppInfo()
+ val deviceInfo = metadataService.getDeviceInfo()
+ val breadcrumbs = breadcrumbService.getBreadcrumbs(startTime, endTime)
+
+ val endSessionWithAllErrors = endSession.copy(exceptionError = exceptionService.currentExceptionError)
+
return SessionMessage(
- session = endSession,
- userInfo = endSession.user,
- appInfo = metadataService.getAppInfo(),
- deviceInfo = metadataService.getDeviceInfo(),
+ session = endSessionWithAllErrors,
+ userInfo = endSessionWithAllErrors.user,
+ appInfo = appInfo,
+ deviceInfo = deviceInfo,
performanceInfo = performanceInfo.copy(),
- breadcrumbs = breadcrumbService.getBreadcrumbs(startTime, endTime),
+ breadcrumbs = breadcrumbs,
spans = spans
)
}
diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/utils/NetworkUtils.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/utils/NetworkUtils.kt
index 1df11d48f..d35525d6f 100644
--- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/utils/NetworkUtils.kt
+++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/utils/NetworkUtils.kt
@@ -1,6 +1,5 @@
package io.embrace.android.embracesdk.utils
-import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger.Companion.logDebug
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger.Companion.logWarning
import java.net.MalformedURLException
@@ -101,8 +100,4 @@ internal object NetworkUtils {
(if (pathPos < 0) 0 else pathPos) + suffix.length.coerceAtMost(terminalPos)
)
}
-
- @JvmStatic
- fun isNetworkSpanForwardingEnabled(configService: ConfigService?): Boolean =
- configService?.networkSpanForwardingBehavior?.isNetworkSpanForwardingEnabled() ?: false
}
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImplTest.kt
index 006c2a17a..1a1b42eb9 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImplTest.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceInternalInterfaceImplTest.kt
@@ -2,6 +2,10 @@ package io.embrace.android.embracesdk
import android.net.Uri
import android.webkit.URLUtil
+import io.embrace.android.embracesdk.fakes.FakeClock
+import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
+import io.embrace.android.embracesdk.injection.InitModule
+import io.embrace.android.embracesdk.internal.defaultImpl
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
import io.embrace.android.embracesdk.network.http.HttpMethod
import io.mockk.every
@@ -11,6 +15,7 @@ import io.mockk.slot
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -18,11 +23,15 @@ internal class EmbraceInternalInterfaceImplTest {
private lateinit var impl: EmbraceInternalInterfaceImpl
private lateinit var embrace: EmbraceImpl
+ private lateinit var fakeClock: FakeClock
+ private lateinit var initModule: InitModule
@Before
fun setUp() {
embrace = mockk(relaxed = true)
- impl = EmbraceInternalInterfaceImpl(embrace)
+ fakeClock = FakeClock(currentTime = beforeObjectInitTime)
+ initModule = FakeInitModule(clock = fakeClock)
+ impl = EmbraceInternalInterfaceImpl(embrace, initModule)
}
@Test
@@ -94,144 +103,6 @@ internal class EmbraceInternalInterfaceImplTest {
}
}
- @Test
- fun testAddBreadcrumb() {
- impl.addBreadcrumb("")
- verify(exactly = 1) { embrace.addBreadcrumb("") }
- }
-
- @Test
- fun testGetDeviceId() {
- every { embrace.deviceId } returns "test"
- assertEquals("test", impl.deviceId)
- }
-
- @Test
- fun testSetUserIdentifier() {
- impl.setUserIdentifier("")
- verify(exactly = 1) { embrace.setUserIdentifier("") }
- }
-
- @Test
- fun testClearUserIdentifier() {
- impl.clearUserIdentifier()
- verify(exactly = 1) { embrace.clearUserIdentifier() }
- }
-
- @Test
- fun testSetUsername() {
- impl.setUsername("")
- verify(exactly = 1) { embrace.setUsername("") }
- }
-
- @Test
- fun testClearUsername() {
- impl.clearUsername()
- verify(exactly = 1) { embrace.clearUsername() }
- }
-
- @Test
- fun testSetUserEmail() {
- impl.setUserEmail("")
- verify(exactly = 1) { embrace.setUserEmail("") }
- }
-
- @Test
- fun testClearUserEmail() {
- impl.clearUserEmail()
- verify(exactly = 1) { embrace.clearUserEmail() }
- }
-
- @Test
- fun testSetUserAsPayer() {
- impl.setUserAsPayer()
- verify(exactly = 1) { embrace.setUserAsPayer() }
- }
-
- @Test
- fun testClearUserAsPayer() {
- impl.clearUserAsPayer()
- verify(exactly = 1) { embrace.clearUserAsPayer() }
- }
-
- @Test
- fun testAddUserPersona() {
- impl.addUserPersona("")
- verify(exactly = 1) { embrace.addUserPersona("") }
- }
-
- @Test
- fun testClearUserPersona() {
- impl.clearUserPersona("")
- verify(exactly = 1) { embrace.clearUserPersona("") }
- }
-
- @Test
- fun testClearAllUserPersonas() {
- impl.clearAllUserPersonas()
- verify(exactly = 1) { embrace.clearAllUserPersonas() }
- }
-
- @Test
- fun testAddSessionProperty() {
- impl.addSessionProperty("key", "value", true)
- verify(exactly = 1) { embrace.addSessionProperty("key", "value", true) }
- }
-
- @Test
- fun testRemoveSessionProperty() {
- impl.removeSessionProperty("key")
- verify(exactly = 1) { embrace.removeSessionProperty("key") }
- }
-
- @Test
- fun testGetSessionProperties() {
- every { embrace.sessionProperties } returns mapOf()
- assertEquals(mapOf(), impl.sessionProperties)
- }
-
- @Test
- fun testStartMoment() {
- impl.startMoment("name", "id", mapOf())
- verify(exactly = 1) { embrace.startMoment("name", "id", mapOf()) }
- }
-
- @Test
- fun testEndMoment() {
- impl.endMoment("name", "id", mapOf())
- verify(exactly = 1) { embrace.endMoment("name", "id", mapOf()) }
- }
-
- @Test
- fun testStartView() {
- impl.startView("")
- verify(exactly = 1) { embrace.startView("") }
- }
-
- @Test
- fun testEndView() {
- impl.endView("")
- verify(exactly = 1) { embrace.endView("") }
- }
-
- @Test
- fun testEndAppStartup() {
- impl.endAppStartup(emptyMap())
- verify(exactly = 1) { embrace.endAppStartup(emptyMap()) }
- }
-
- @Test
- fun testLogInternalError() {
- impl.logInternalError("msg", "details")
- verify(exactly = 1) { embrace.logInternalError("msg", "details") }
- }
-
- @Test
- fun testEndSession() {
- impl.endSession(true)
- verify(exactly = 1) { embrace.endSession(true) }
- }
-
@Test
fun testCompletedNetworkRequest() {
mockkStatic(Uri::class)
@@ -297,4 +168,39 @@ internal class EmbraceInternalInterfaceImplTest {
assertEquals("id-123", request.traceId)
assertNull(request.networkCaptureData)
}
+
+ @Test
+ fun testRecordAndDeduplicateNetworkRequest() {
+ val url = "https://embrace.io"
+ val callId = "testID"
+ val captor = slot()
+ val networkRequest: EmbraceNetworkRequest = mockk()
+ every { networkRequest.url } answers { url }
+
+ impl.recordAndDeduplicateNetworkRequest(callId, networkRequest)
+
+ verify(exactly = 1) {
+ embrace.recordAndDeduplicateNetworkRequest(callId, capture(captor))
+ }
+
+ assertEquals(url, captor.captured.url)
+ }
+
+ @Test
+ fun `check usage of SDK time`() {
+ assertEquals(beforeObjectInitTime, impl.getSdkCurrentTime())
+ assertTrue(impl.getSdkCurrentTime() < System.currentTimeMillis())
+ fakeClock.tick(10L)
+ assertEquals(fakeClock.now(), impl.getSdkCurrentTime())
+ }
+
+ @Test
+ fun `check default implementation`() {
+ assertTrue(beforeObjectInitTime < defaultImpl.getSdkCurrentTime())
+ assertTrue(defaultImpl.getSdkCurrentTime() <= System.currentTimeMillis())
+ }
+
+ companion object {
+ val beforeObjectInitTime = System.currentTimeMillis() - 1
+ }
}
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/InternalInterfaceModuleImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/InternalInterfaceModuleImplTest.kt
index d94f46a9f..158ef6621 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/InternalInterfaceModuleImplTest.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/InternalInterfaceModuleImplTest.kt
@@ -4,6 +4,7 @@ import io.embrace.android.embracesdk.fakes.injection.FakeAndroidServicesModule
import io.embrace.android.embracesdk.fakes.injection.FakeCoreModule
import io.embrace.android.embracesdk.fakes.injection.FakeCrashModule
import io.embrace.android.embracesdk.fakes.injection.FakeEssentialServiceModule
+import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
import org.junit.Assert.assertNotNull
import org.junit.Test
@@ -12,6 +13,7 @@ internal class InternalInterfaceModuleImplTest {
@Test
fun testModule() {
val module: InternalInterfaceModule = InternalInterfaceModuleImpl(
+ FakeInitModule(),
FakeCoreModule(),
FakeAndroidServicesModule(),
FakeEssentialServiceModule(),
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeClock.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeClock.kt
index 4b7981b58..d66ad4934 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeClock.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeClock.kt
@@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.fakes
import io.embrace.android.embracesdk.clock.Clock
internal class FakeClock(
+ @Volatile
private var currentTime: Long = 0
) : Clock {
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeNetworkLoggingService.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeNetworkLoggingService.kt
index 3c578ff34..fbab2fcad 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeNetworkLoggingService.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeNetworkLoggingService.kt
@@ -8,10 +8,11 @@ internal class FakeNetworkLoggingService : NetworkLoggingService {
var data: NetworkSessionV2 = NetworkSessionV2(emptyList(), emptyMap())
- override fun getNetworkCallsForSession(startTime: Long, lastKnownTime: Long): NetworkSessionV2 =
+ override fun getNetworkCallsForSession(): NetworkSessionV2 =
data
override fun logNetworkCall(
+ callId: String,
url: String,
httpMethod: String,
statusCode: Int,
@@ -27,6 +28,7 @@ internal class FakeNetworkLoggingService : NetworkLoggingService {
}
override fun logNetworkError(
+ callId: String,
url: String,
httpMethod: String,
startTime: Long,
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeCoreModule.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeCoreModule.kt
index 8130bee9e..9cdf0aab9 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeCoreModule.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/injection/FakeCoreModule.kt
@@ -8,6 +8,7 @@ import io.embrace.android.embracesdk.injection.CoreModule
import io.embrace.android.embracesdk.injection.isDebug
import io.embrace.android.embracesdk.internal.EmbraceSerializer
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
+import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger
import io.embrace.android.embracesdk.registry.ServiceRegistry
import io.mockk.isMockKMock
import io.mockk.mockk
@@ -22,7 +23,7 @@ internal class FakeCoreModule(
override val context: Context =
if (isMockKMock(application)) mockk(relaxed = true) else application.applicationContext,
override val appFramework: AppFramework = AppFramework.NATIVE,
- override val logger: InternalEmbraceLogger = InternalEmbraceLogger(),
+ override val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger,
override val serviceRegistry: ServiceRegistry = ServiceRegistry(),
override val jsonSerializer: EmbraceSerializer = EmbraceSerializer(),
override val resources: FakeAndroidResourcesService = FakeAndroidResourcesService(),
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverrideTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverrideTest.kt
index 3f74de50d..87c65f1c5 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverrideTest.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlConnectionOverrideTest.kt
@@ -1,11 +1,8 @@
package io.embrace.android.embracesdk.network.http
import io.embrace.android.embracesdk.Embrace
-import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.config.behavior.NetworkSpanForwardingBehavior.Companion.TRACEPARENT_HEADER_NAME
-import io.embrace.android.embracesdk.config.remote.NetworkSpanForwardingRemoteConfig
-import io.embrace.android.embracesdk.fakes.FakeConfigService
-import io.embrace.android.embracesdk.fakes.fakeNetworkSpanForwardingBehavior
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
import io.mockk.CapturingSlot
import io.mockk.every
@@ -13,37 +10,44 @@ import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import java.io.ByteArrayInputStream
import java.io.IOException
+import java.io.InputStream
import java.util.concurrent.TimeoutException
import javax.net.ssl.HttpsURLConnection
internal class EmbraceUrlConnectionOverrideTest {
private lateinit var mockEmbrace: Embrace
- private lateinit var fakeConfigService: ConfigService
+ private lateinit var mockInternalInterface: EmbraceInternalInterface
private lateinit var mockConnection: HttpsURLConnection
+ private lateinit var capturedCallId: MutableList
private lateinit var capturedEmbraceNetworkRequest: CapturingSlot
- private lateinit var remoteNetworkSpanForwardingConfig: NetworkSpanForwardingRemoteConfig
private lateinit var embraceUrlConnectionOverride: EmbraceUrlConnectionOverride
private lateinit var embraceUrlConnectionOverrideUnwrapped: EmbraceUrlConnectionOverride
+ private var shouldCaptureNetworkBody = false
+ private var isNetworkSpanForwardingEnabled = false
@Before
fun setup() {
mockEmbrace = mockk(relaxed = true)
+ every { mockEmbrace.internalInterface } answers { mockInternalInterface }
+ shouldCaptureNetworkBody = false
+ isNetworkSpanForwardingEnabled = false
+ capturedCallId = mutableListOf()
capturedEmbraceNetworkRequest = slot()
- remoteNetworkSpanForwardingConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 0f)
- fakeConfigService = FakeConfigService(
- networkSpanForwardingBehavior = fakeNetworkSpanForwardingBehavior(
- remoteConfig = { remoteNetworkSpanForwardingConfig }
- )
- )
- every { mockEmbrace.recordNetworkRequest(capture(capturedEmbraceNetworkRequest)) } answers { }
- every { mockEmbrace.configService } answers { fakeConfigService }
-
+ mockInternalInterface = mockk(relaxed = true)
+ every { mockInternalInterface.shouldCaptureNetworkBody(any(), any()) } answers { shouldCaptureNetworkBody }
+ every {
+ mockInternalInterface.recordAndDeduplicateNetworkRequest(capture(capturedCallId), capture(capturedEmbraceNetworkRequest))
+ } answers { }
+ every { mockInternalInterface.isNetworkSpanForwardingEnabled() } answers { isNetworkSpanForwardingEnabled }
mockConnection = createMockConnection()
embraceUrlConnectionOverride = EmbraceUrlConnectionOverride(mockConnection, true, mockEmbrace)
embraceUrlConnectionOverrideUnwrapped = EmbraceUrlConnectionOverride(mockConnection, false, mockEmbrace)
@@ -52,25 +56,41 @@ internal class EmbraceUrlConnectionOverrideTest {
@Test
fun `completed network call logged exactly once if connection connected with wrapped output stream`() {
executeRequest()
- verify(exactly = 1) { mockEmbrace.recordNetworkRequest(any()) }
+ verifyTwoCallsRecordedWithSameCallId()
with(capturedEmbraceNetworkRequest.captured) {
assertEquals(HttpMethod.POST.name, httpMethod)
assertEquals(HTTP_OK, responseCode)
- assertEquals(1L, bytesSent)
- assertEquals(100L, bytesReceived)
+ assertEquals(requestBodySize.toLong(), bytesSent)
+ assertEquals(responseBodySize.toLong(), bytesReceived)
assertNull(errorType)
}
}
@Test
- fun `completed network call logged exactly once if connection connected with unwrapped output stream`() {
+ fun `completed network call logged twice once if connection connected with wrapped output stream and network body captured`() {
+ shouldCaptureNetworkBody = true
+ executeRequest()
+ verifyTwoCallsRecordedWithSameCallId()
+ with(capturedEmbraceNetworkRequest.captured) {
+ assertEquals(HttpMethod.POST.name, httpMethod)
+ assertEquals(HTTP_OK, responseCode)
+ assertEquals(requestBodySize.toLong(), bytesSent)
+ assertEquals(responseBodySize.toLong(), bytesReceived)
+ assertNotNull(networkCaptureData)
+ assertNull(errorType)
+ }
+ }
+
+ @Test
+ fun `completed network call logged exactly once with no request size if connection connected with unwrapped output stream`() {
executeRequest(embraceOverride = embraceUrlConnectionOverrideUnwrapped)
- verify(exactly = 1) { mockEmbrace.recordNetworkRequest(any()) }
+ verify(exactly = 1) { mockInternalInterface.recordAndDeduplicateNetworkRequest(any(), any()) }
+ assertTrue(capturedCallId[0].isNotBlank())
with(capturedEmbraceNetworkRequest.captured) {
assertEquals(HttpMethod.POST.name, httpMethod)
assertEquals(HTTP_OK, responseCode)
assertEquals(0L, bytesSent)
- assertEquals(100L, bytesReceived)
+ assertEquals(responseBodySize.toLong(), bytesReceived)
assertNull(errorType)
}
}
@@ -78,7 +98,8 @@ internal class EmbraceUrlConnectionOverrideTest {
@Test
fun `incomplete network call logged exactly once and response data not accessed if connection connected`() {
executeRequest(exceptionOnInputStream = true)
- verify(exactly = 1) { mockEmbrace.recordNetworkRequest(any()) }
+ verify(exactly = 1) { mockInternalInterface.recordAndDeduplicateNetworkRequest(any(), any()) }
+ assertTrue(capturedCallId[0].isNotBlank())
verify(exactly = 0) { mockConnection.responseCode }
verify(exactly = 0) { mockConnection.contentLength }
verify(exactly = 0) { mockConnection.headerFields }
@@ -95,6 +116,8 @@ internal class EmbraceUrlConnectionOverrideTest {
fun `disconnect called with uninitialized connection results in error request capture and no response access`() {
embraceUrlConnectionOverride.disconnect()
verifyIncompleteRequestLogged()
+ verify(exactly = 1) { mockInternalInterface.recordAndDeduplicateNetworkRequest(any(), any()) }
+ assertEquals(1, capturedCallId.size)
}
@Test
@@ -102,6 +125,7 @@ internal class EmbraceUrlConnectionOverrideTest {
every { mockConnection.contentLength } answers { throw TimeoutException() }
executeRequest()
verifyIncompleteRequestLogged(errorType = TIMEOUT_ERROR, noResponseAccess = false)
+ verifyTwoCallsRecordedWithSameCallId()
}
@Test
@@ -109,6 +133,7 @@ internal class EmbraceUrlConnectionOverrideTest {
every { mockConnection.responseCode } answers { throw TimeoutException() }
executeRequest()
verifyIncompleteRequestLogged(errorType = TIMEOUT_ERROR, noResponseAccess = false)
+ verifyTwoCallsRecordedWithSameCallId()
}
@Test
@@ -116,6 +141,7 @@ internal class EmbraceUrlConnectionOverrideTest {
every { mockConnection.headerFields } answers { throw TimeoutException() }
executeRequest()
verifyIncompleteRequestLogged(errorType = TIMEOUT_ERROR, noResponseAccess = false)
+ verifyTwoCallsRecordedWithSameCallId()
}
@Test
@@ -145,7 +171,7 @@ internal class EmbraceUrlConnectionOverrideTest {
@Test
fun `check traceheaders are forwarded if feature flag is on`() {
- remoteNetworkSpanForwardingConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 100f)
+ isNetworkSpanForwardingEnabled = true
executeRequest()
assertEquals(HTTP_OK, capturedEmbraceNetworkRequest.captured.responseCode)
assertEquals(TRACEPARENT, capturedEmbraceNetworkRequest.captured.w3cTraceparent)
@@ -153,7 +179,7 @@ internal class EmbraceUrlConnectionOverrideTest {
@Test
fun `check traceheaders are forwarded on errors if feature flag is on`() {
- remoteNetworkSpanForwardingConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 100f)
+ isNetworkSpanForwardingEnabled = true
executeRequest(exceptionOnInputStream = true)
assertNull(capturedEmbraceNetworkRequest.captured.responseCode)
assertEquals(TRACEPARENT, capturedEmbraceNetworkRequest.captured.w3cTraceparent)
@@ -163,19 +189,21 @@ internal class EmbraceUrlConnectionOverrideTest {
private fun createMockConnection(): HttpsURLConnection {
val connection: HttpsURLConnection = mockk(relaxed = true)
val mockOutputStream: CountingOutputStream = mockk(relaxed = true)
- every { mockOutputStream.requestBody } answers { ByteArray(1) }
+ val inputStream: InputStream = ByteArrayInputStream(responseBody)
+ every { mockOutputStream.requestBody } answers { requestBody }
every { connection.outputStream } answers { mockOutputStream }
every { connection.getRequestProperty(TRACEPARENT_HEADER_NAME) } answers { TRACEPARENT }
every { connection.requestMethod } answers { HttpMethod.POST.name }
every { connection.responseCode } answers { HTTP_OK }
- every { connection.contentLength } answers { 100 }
+ every { connection.contentLength } answers { responseBodySize }
every { connection.headerFields } answers {
mapOf(
Pair("Content-Encoding", listOf("gzip")),
- Pair("Content-Length", listOf("100")),
+ Pair("Content-Length", listOf(responseBodySize.toString())),
Pair("myHeader", listOf("myValue"))
)
}
+ every { connection.inputStream } answers { inputStream }
return connection
}
@@ -185,14 +213,17 @@ internal class EmbraceUrlConnectionOverrideTest {
) {
with(embraceOverride) {
connect()
- outputStream?.write(8)
+ outputStream?.write(requestBody)
if (exceptionOnInputStream) {
every { mockConnection.inputStream } answers { throw IOException() }
assertThrows(IOException::class.java) { inputStream }
} else {
- inputStream
+ val input = inputStream
headerFields
responseCode
+ val b = ByteArray(8192)
+ input?.read(b)
+ assertEquals(-1, input?.read())
}
disconnect()
}
@@ -204,14 +235,23 @@ internal class EmbraceUrlConnectionOverrideTest {
verify(exactly = 0) { mockConnection.contentLength }
verify(exactly = 0) { mockConnection.headerFields }
}
- verify(exactly = 1) { mockEmbrace.recordNetworkRequest(any()) }
assertNull(capturedEmbraceNetworkRequest.captured.responseCode)
assertEquals(errorType, capturedEmbraceNetworkRequest.captured.errorType)
}
+ private fun verifyTwoCallsRecordedWithSameCallId() {
+ verify(exactly = 2) { mockInternalInterface.recordAndDeduplicateNetworkRequest(any(), any()) }
+ assertEquals(2, capturedCallId.size)
+ assertEquals(capturedCallId[0], capturedCallId[1])
+ }
+
companion object {
private const val TRACEPARENT = "00-3c72a77a7b51af6fb3778c06d4c165ce-4c1d710fffc88e35-01"
private const val HTTP_OK = 200
+ private val requestBody = "test".toByteArray()
+ private val requestBodySize = requestBody.size
+ private val responseBody = "responseresponse".toByteArray()
+ private val responseBodySize = responseBody.size
private val IO_ERROR = checkNotNull(IOException::class.java.canonicalName)
private val TIMEOUT_ERROR = checkNotNull(TimeoutException::class.java.canonicalName)
}
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandlerTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandlerTest.kt
index dc05ec5a6..a4fad739a 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandlerTest.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/http/EmbraceUrlStreamHandlerTest.kt
@@ -3,11 +3,8 @@ package io.embrace.android.embracesdk.network.http
import android.os.Build.VERSION_CODES.TIRAMISU
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.embrace.android.embracesdk.Embrace
-import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.config.behavior.NetworkSpanForwardingBehavior.Companion.TRACEPARENT_HEADER_NAME
-import io.embrace.android.embracesdk.config.remote.NetworkSpanForwardingRemoteConfig
-import io.embrace.android.embracesdk.fakes.FakeConfigService
-import io.embrace.android.embracesdk.fakes.fakeNetworkSpanForwardingBehavior
+import io.embrace.android.embracesdk.internal.EmbraceInternalInterface
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
import io.mockk.CapturingSlot
import io.mockk.every
@@ -25,23 +22,20 @@ import java.net.URL
@RunWith(AndroidJUnit4::class)
internal class EmbraceUrlStreamHandlerTest {
private lateinit var mockEmbrace: Embrace
- private lateinit var fakeConfigService: ConfigService
+ private lateinit var mockInternalInterface: EmbraceInternalInterface
private lateinit var capturedEmbraceNetworkRequest: CapturingSlot
- private lateinit var remoteNetworkSpanForwardingConfig: NetworkSpanForwardingRemoteConfig
+ private var isNetworkSpanForwardingEnabled = false
@Before
fun setup() {
mockEmbrace = mockk(relaxed = true)
+ mockInternalInterface = mockk(relaxed = true)
+ every { mockInternalInterface.isNetworkSpanForwardingEnabled() } answers { isNetworkSpanForwardingEnabled }
capturedEmbraceNetworkRequest = slot()
- remoteNetworkSpanForwardingConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 0f)
- fakeConfigService = FakeConfigService(
- networkSpanForwardingBehavior = fakeNetworkSpanForwardingBehavior(
- remoteConfig = { remoteNetworkSpanForwardingConfig }
- )
- )
every { mockEmbrace.recordNetworkRequest(capture(capturedEmbraceNetworkRequest)) } answers { }
- every { mockEmbrace.configService } answers { fakeConfigService }
+ every { mockEmbrace.internalInterface } answers { mockInternalInterface }
every { mockEmbrace.generateW3cTraceparent() } answers { TRACEPARENT }
+ isNetworkSpanForwardingEnabled = false
}
@Test
@@ -78,7 +72,7 @@ internal class EmbraceUrlStreamHandlerTest {
@Test
fun `check traceheader is injected into http request if feature flag is on`() {
- remoteNetworkSpanForwardingConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 100f)
+ isNetworkSpanForwardingEnabled = true
val url = URL(
"http",
"embrace.io",
@@ -95,7 +89,7 @@ internal class EmbraceUrlStreamHandlerTest {
@Test
fun `check traceheader is injected into https request if feature flag is on`() {
- remoteNetworkSpanForwardingConfig = NetworkSpanForwardingRemoteConfig(pctEnabled = 100f)
+ isNetworkSpanForwardingEnabled = true
val url = URL(
"https",
"embrace.io",
diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingServiceTest.kt
index 6b4eb3e1e..faecdfa04 100644
--- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingServiceTest.kt
+++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/network/logging/EmbraceNetworkLoggingServiceTest.kt
@@ -22,6 +22,7 @@ import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
+import java.util.UUID
internal class EmbraceNetworkLoggingServiceTest {
private lateinit var service: EmbraceNetworkLoggingService
@@ -51,6 +52,7 @@ internal class EmbraceNetworkLoggingServiceTest {
}
@AfterClass
+ @JvmStatic
fun tearDown() {
unmockkAll()
}
@@ -76,18 +78,20 @@ internal class EmbraceNetworkLoggingServiceTest {
}
@Test
- fun `test getNetworkCallsForSession only uses session between start and end time`() {
+ fun `test getNetworkCallsForSession returns all network calls current stored`() {
logNetworkCall("www.example1.com", 100, 200)
logNetworkCall("www.example2.com", 200, 300)
logNetworkCall("www.example3.com", 300, 400)
logNetworkCall("www.example4.com", 400, 500)
- val result = service.getNetworkCallsForSession(200, 301)
+ val result = service.getNetworkCallsForSession()
+ assertEquals(4, result.requests.size)
- // test use only session calls
- assertEquals(2, result.requests.size)
- assertEquals("www.example2.com", result.requests.at(0)?.url)
- assertEquals("www.example3.com", result.requests.at(1)?.url)
+ val sortedRequests = result.requests.sortedBy { it.startTime }
+ assertEquals("www.example1.com", sortedRequests.at(0)?.url)
+ assertEquals("www.example2.com", sortedRequests.at(1)?.url)
+ assertEquals("www.example3.com", sortedRequests.at(2)?.url)
+ assertEquals("www.example4.com", sortedRequests.at(3)?.url)
}
@Test
@@ -104,7 +108,7 @@ internal class EmbraceNetworkLoggingServiceTest {
logNetworkCall("www.overLimit2.com")
logNetworkCall("www.overLimit3.com")
- val result = service.getNetworkCallsForSession(0, Long.MAX_VALUE)
+ val result = service.getNetworkCallsForSession()
// overLimit1 has 4 calls. The limit is 2.
val expectedOverLimit = DomainCount(4, 2)
@@ -131,7 +135,7 @@ internal class EmbraceNetworkLoggingServiceTest {
logNetworkCall("www.overLimit2.com")
logNetworkCall("www.overLimit3.com")
- val result = service.getNetworkCallsForSession(0, Long.MAX_VALUE)
+ val result = service.getNetworkCallsForSession()
// overLimit1 has 4 calls. The local limit is 2.
val expectedOverLimit = DomainCount(4, 2)
@@ -150,6 +154,7 @@ internal class EmbraceNetworkLoggingServiceTest {
val endTime = 20000L
service.logNetworkError(
+ randomId(),
url,
httpMethod,
startTime,
@@ -161,7 +166,7 @@ internal class EmbraceNetworkLoggingServiceTest {
null
)
- val result = service.getNetworkCallsForSession(0, Long.MAX_VALUE)
+ val result = service.getNetworkCallsForSession()
assertEquals(url, result.requests.at(0)?.url)
}
@@ -169,6 +174,7 @@ internal class EmbraceNetworkLoggingServiceTest {
@Test
fun `test logNetworkCall sends the network body if necessary`() {
service.logNetworkCall(
+ randomId(),
"www.example.com",
"GET",
200,
@@ -196,6 +202,7 @@ internal class EmbraceNetworkLoggingServiceTest {
@Test
fun `test logNetworkCall doesn't send the network body if null`() {
service.logNetworkCall(
+ randomId(),
"www.example.com",
"GET",
200,
@@ -229,14 +236,51 @@ internal class EmbraceNetworkLoggingServiceTest {
service.cleanCollections()
- val result = service.getNetworkCallsForSession(0, Long.MAX_VALUE)
+ val result = service.getNetworkCallsForSession()
assertEquals(0, result.requests.size)
assertEquals(0, result.requestCounts.size)
}
- private fun logNetworkCall(url: String, startTime: Long = 100, endTime: Long = 200) {
+ @Test
+ fun `network requests with the same start time will be recorded each time`() {
+ val startTime = 99L
+ val endTime = 300L
+ repeat(2) {
+ logNetworkCall(url = "https://embrace.io", startTime = startTime, endTime = endTime)
+ }
+
+ repeat(2) {
+ logNetworkError(url = "https://embrace.io", startTime = startTime)
+ }
+
+ assertEquals(4, service.getNetworkCallsForSession().requests.size)
+ }
+
+ @Test
+ fun `network requests with the same callId will be logged once with last writer wins`() {
+ val callId = UUID.randomUUID().toString()
+ val expectedStartTime = 99L
+ val expectedEndTime = 300L
+ val expectedUrl = "https://embrace.io/forreal"
+
+ logNetworkCall(url = "https://embrace.io", startTime = 50, endTime = 100, callId = callId)
+ logNetworkError(url = "https://embrace.io", startTime = 50, callId = callId)
+ logNetworkCall(url = expectedUrl, startTime = expectedStartTime, endTime = expectedEndTime, callId = callId)
+
+ val result = service.getNetworkCallsForSession()
+ assertEquals(1, result.requests.size)
+ with(result.requests[0]) {
+ assertEquals(1, result.requests.size)
+ assertEquals(expectedStartTime, startTime)
+ assertEquals(expectedEndTime, endTime)
+ assertEquals(expectedUrl, url)
+ }
+ }
+
+ private fun logNetworkCall(url: String, startTime: Long = 100, endTime: Long = 200, callId: String = randomId()) {
service.logNetworkCall(
+ callId,
url,
"GET",
200,
@@ -249,4 +293,21 @@ internal class EmbraceNetworkLoggingServiceTest {
null
)
}
+
+ private fun logNetworkError(url: String, startTime: Long = 100, callId: String = randomId()) {
+ service.logNetworkError(
+ callId,
+ url,
+ "GET",
+ startTime,
+ 0,
+ NullPointerException::class.java.canonicalName,
+ "NPE baby",
+ null,
+ null,
+ null
+ )
+ }
+
+ private fun randomId(): String = UUID.randomUUID().toString()
}