Skip to content

Commit

Permalink
Set network spans with non-successful error codes as failed (#962)
Browse files Browse the repository at this point in the history
## Goal

Set the status of a network span based on the http status code

## Testing

Updated unit and integration tests to verify this
  • Loading branch information
bidetofevil committed Jun 13, 2024
2 parents 348fc70 + b0e66b3 commit e3ab4c7
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
import io.embrace.android.embracesdk.network.http.HttpMethod
import io.embrace.android.embracesdk.recordSession
import io.opentelemetry.api.trace.StatusCode
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -263,12 +264,20 @@ internal class NetworkRequestApiTest {
assertEquals(expectedRequest.bytesReceived.toString(), this.attributes["http.response.body.size"])
assertEquals(null, this.attributes["error.type"])
assertEquals(null, this.attributes["error.message"])
val statusCode = expectedRequest.responseCode
val expectedStatus = if (statusCode != null && statusCode >= 200 && statusCode < 400) {
StatusCode.OK
} else {
StatusCode.ERROR
}
assertEquals(expectedStatus, status)
} else {
assertEquals(null, this.attributes["http.response.status_code"])
assertEquals(null, this.attributes["http.request.body.size"])
assertEquals(null, this.attributes["http.response.body.size"])
assertEquals(expectedRequest.errorType, this.attributes["error.type"])
assertEquals(expectedRequest.errorMessage, this.attributes["error.message"])
assertEquals(StatusCode.ERROR, status)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.embrace.android.embracesdk.arch.schema.SchemaType
import io.embrace.android.embracesdk.internal.spans.SpanService
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
import io.embrace.android.embracesdk.network.logging.EmbraceNetworkCaptureService.Companion.NETWORK_ERROR_CODE
import io.embrace.android.embracesdk.spans.ErrorCode
import io.embrace.android.embracesdk.utils.NetworkUtils.getDomain
import io.embrace.android.embracesdk.utils.NetworkUtils.getUrlPath
import io.embrace.android.embracesdk.utils.NetworkUtils.stripUrl
Expand Down Expand Up @@ -60,11 +61,17 @@ internal class EmbraceNetworkLoggingService(
val strippedUrl = stripUrl(networkRequest.url)

val networkRequestSchemaType = SchemaType.NetworkRequest(networkRequest)
val statusCode = networkRequest.responseCode
val errorCode = if (statusCode == null || statusCode <= 0 || statusCode >= 400) {
ErrorCode.FAILURE
} else {
null
}
spanService.recordCompletedSpan(
name = "${networkRequest.httpMethod} ${getUrlPath(strippedUrl)}",
startTimeMs = networkRequest.startTime,
endTimeMs = networkRequest.endTime,
errorCode = null,
errorCode = errorCode,
parent = null,
attributes = networkRequestSchemaType.attributes(),
type = EmbType.Performance.Network,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.embrace.android.embracesdk.network.logging

import io.embrace.android.embracesdk.arch.schema.EmbType
import io.embrace.android.embracesdk.fakes.FakeDomainCountLimiter
import io.embrace.android.embracesdk.fakes.FakeNetworkCaptureService
import io.embrace.android.embracesdk.fakes.FakeSpanService
import io.embrace.android.embracesdk.internal.clock.nanosToMillis
import io.embrace.android.embracesdk.internal.network.http.NetworkCaptureData
import io.embrace.android.embracesdk.internal.payload.Span
import io.embrace.android.embracesdk.network.EmbraceNetworkRequest
import io.embrace.android.embracesdk.network.http.HttpMethod
import io.embrace.android.embracesdk.utils.at
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
Expand All @@ -16,7 +18,6 @@ internal class EmbraceNetworkLoggingServiceTest {
private lateinit var domainCountLimiter: FakeDomainCountLimiter
private lateinit var networkCaptureService: FakeNetworkCaptureService
private lateinit var spanService: FakeSpanService

private lateinit var networkLoggingService: EmbraceNetworkLoggingService

@Before
Expand All @@ -33,23 +34,68 @@ internal class EmbraceNetworkLoggingServiceTest {

@Test
fun `multiple network requests are recorded to the span service correctly`() {
logNetworkRequest("www.example1.com", 100, 200)
logNetworkRequest("www.example2.com", 200, 300)
logNetworkRequest("www.example3.com", 300, 400)
logNetworkRequest("www.example4.com", 400, 500)

val spans = spanService
.createdSpans
.filter { it.attributes.containsKey("http.request.method") }
.mapNotNull { it.snapshot() }

assertEquals(4, spans.size)

val sortedRequests = spans.sortedBy { it.startTimeUnixNano }
assertEquals("www.example1.com", sortedRequests.at(0)?.attributes?.first { it.key == "url.full" }?.data)
assertEquals("www.example2.com", sortedRequests.at(1)?.attributes?.first { it.key == "url.full" }?.data)
assertEquals("www.example3.com", sortedRequests.at(2)?.attributes?.first { it.key == "url.full" }?.data)
assertEquals("www.example4.com", sortedRequests.at(3)?.attributes?.first { it.key == "url.full" }?.data)
logNetworkRequest(
url = "www.example1.com",
startTime = 100,
endTime = 200,
)
logNetworkRequest(
url = "www.example2.com",
startTime = 200,
endTime = 300,
statusCode = 404,
)
logNetworkRequest(
url = "www.example3.com",
startTime = 300,
endTime = 400,
statusCode = 500,
)
logNetworkRequest(
url = "www.example4.com",
startTime = 400,
endTime = 500,
statusCode = 203,
)
networkLoggingService.logNetworkRequest(
EmbraceNetworkRequest.fromIncompleteRequest(
"www.example5.com",
HttpMethod.GET,
600L,
650L,
"RuntimeException",
""
)
)

val spans = getNetworkSpans()
assertEquals(5, spans.size)

val requestSpans = spans.associateBy { it.attributes?.single { attr -> attr.key == "url.full" }?.data }
checkNotNull(requestSpans["www.example1.com"]).assertNetworkRequest(
expectedStartTimeMs = 100L,
expectedEndTimeMs = 200L,
expectedStatus = Span.Status.OK
)
checkNotNull(requestSpans["www.example2.com"]).assertNetworkRequest(
expectedStartTimeMs = 200L,
expectedEndTimeMs = 300L,
expectedStatus = Span.Status.ERROR
)
checkNotNull(requestSpans["www.example3.com"]).assertNetworkRequest(
expectedStartTimeMs = 300L,
expectedEndTimeMs = 400L,
expectedStatus = Span.Status.ERROR
)
checkNotNull(requestSpans["www.example4.com"]).assertNetworkRequest(
expectedStartTimeMs = 400L,
expectedEndTimeMs = 500L,
)
checkNotNull(requestSpans["www.example5.com"]).assertNetworkRequest(
expectedStartTimeMs = 600L,
expectedEndTimeMs = 650L,
expectedStatus = Span.Status.ERROR
)
}

@Test
Expand All @@ -63,14 +109,14 @@ internal class EmbraceNetworkLoggingServiceTest {
dataCaptureErrorMessage = null
)

logNetworkRequest("www.example.com", 100, 200, networkCaptureData)
logNetworkRequest(
url = "www.example.com",
startTime = 100,
endTime = 200,
networkCaptureData = networkCaptureData
)

// Network request is recorded correctly
val spans = spanService
.createdSpans
.filter { it.attributes.containsKey("http.request.method") }
.mapNotNull { it.snapshot() }
assertEquals(1, spans.size)
assertEquals(1, getNetworkSpans().size)

// Network captured data is sent to the networkCaptureService
assertTrue(networkCaptureService.urls.contains("www.example.com"))
Expand All @@ -79,26 +125,14 @@ internal class EmbraceNetworkLoggingServiceTest {
@Test
fun `network requests are not recorded if the URL domain is invalid`() {
logNetworkRequest("examplecom", 100, 200)

val spans = spanService
.createdSpans
.filter { it.attributes.containsKey("http.request.method") }
.mapNotNull { it.snapshot() }

assertTrue(spans.isEmpty())
assertTrue(getNetworkSpans().isEmpty())
}

@Test
fun `network requests are not recorded if the domain count limiter does not allow it`() {
domainCountLimiter.canLog = false
logNetworkRequest("www.example.com", 100, 200)

val spans = spanService
.createdSpans
.filter { it.attributes.containsKey("http.request.method") }
.mapNotNull { it.snapshot() }

assertTrue(spans.isEmpty())
assertTrue(getNetworkSpans().isEmpty())
}

@Test
Expand All @@ -110,13 +144,14 @@ internal class EmbraceNetworkLoggingServiceTest {
logNetworkRequest(url = "https://embrace.io", startTime = startTime, endTime = endTime)
}

assertEquals(2, spanService.createdSpans.size)
assertEquals(2, getNetworkSpans().size)
}

private fun logNetworkRequest(
url: String,
startTime: Long = 100,
endTime: Long = 200,
statusCode: Int = 200,
networkCaptureData: NetworkCaptureData? = null
) {
networkLoggingService.logNetworkRequest(
Expand All @@ -127,11 +162,26 @@ internal class EmbraceNetworkLoggingServiceTest {
endTime,
100L,
1000L,
200,
statusCode,
null,
null,
networkCaptureData
)
)
}

private fun getNetworkSpans() = spanService
.createdSpans
.filter { it.type == EmbType.Performance.Network }
.mapNotNull { it.snapshot() }

private fun Span.assertNetworkRequest(
expectedStartTimeMs: Long,
expectedEndTimeMs: Long,
expectedStatus: Span.Status = Span.Status.OK
) {
assertEquals(expectedStartTimeMs, startTimeUnixNano?.nanosToMillis())
assertEquals(expectedEndTimeMs, endTimeUnixNano?.nanosToMillis())
assertEquals(expectedStatus, status)
}
}

0 comments on commit e3ab4c7

Please sign in to comment.