Skip to content

Commit

Permalink
Merge 0f77ec2 into 28a7e56
Browse files Browse the repository at this point in the history
  • Loading branch information
adinauer authored Jan 24, 2025
2 parents 28a7e56 + 0f77ec2 commit 01d4a6e
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
public final class io/sentry/opentelemetry/OpenTelemetryAttributesExtractor {
public fun <init> ()V
public fun extract (Lio/opentelemetry/sdk/trace/data/SpanData;Lio/sentry/ISpan;Lio/sentry/IScope;)V
public fun extract (Lio/opentelemetry/sdk/trace/data/SpanData;Lio/sentry/ISpan;Lio/sentry/IScope;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/opentelemetry/OpenTelemetryLinkErrorEventProcessor : io/sentry/EventProcessor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,39 @@
import io.opentelemetry.semconv.UrlAttributes;
import io.sentry.IScope;
import io.sentry.ISpan;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.protocol.Request;
import io.sentry.util.HttpUtils;
import io.sentry.util.StringUtils;
import io.sentry.util.UrlUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public final class OpenTelemetryAttributesExtractor {

private static final String HTTP_REQUEST_HEADER_PREFIX = "http.request.header.";

public void extract(
final @NotNull SpanData otelSpan,
final @NotNull ISpan sentrySpan,
final @NotNull IScope scope) {
final @NotNull IScope scope,
final @NotNull SentryOptions options) {
final @NotNull Attributes attributes = otelSpan.getAttributes();
addRequestAttributesToScope(attributes, scope);
if (attributes.get(HttpAttributes.HTTP_REQUEST_METHOD) != null) {
addRequestAttributesToScope(attributes, scope, options);
}
}

private void addRequestAttributesToScope(Attributes attributes, IScope scope) {
private void addRequestAttributesToScope(
final @NotNull Attributes attributes,
final @NotNull IScope scope,
final @NotNull SentryOptions options) {
if (scope.getRequest() == null) {
scope.setRequest(new Request());
}
Expand Down Expand Up @@ -56,9 +71,53 @@ private void addRequestAttributesToScope(Attributes attributes, IScope scope) {
request.setQueryString(query);
}
}

if (request.getHeaders() == null) {
Map<String, String> headers = collectHeaders(attributes, options);
if (!headers.isEmpty()) {
request.setHeaders(headers);
}
}
}
}

@SuppressWarnings("unchecked")
private static Map<String, String> collectHeaders(
final @NotNull Attributes attributes, final @NotNull SentryOptions options) {
Map<String, String> headers = new HashMap<>();

attributes.forEach(
(key, value) -> {
final @NotNull String attributeKeyAsString = key.getKey();
if (attributeKeyAsString.startsWith(HTTP_REQUEST_HEADER_PREFIX)) {
final @NotNull String headerName =
StringUtils.removePrefix(attributeKeyAsString, HTTP_REQUEST_HEADER_PREFIX);
if (options.isSendDefaultPii() || !HttpUtils.containsSensitiveHeader(headerName)) {
if (value instanceof List) {
try {
final @NotNull List<String> headerValues = (List<String>) value;
headers.put(
headerName,
toString(
HttpUtils.filterOutSecurityCookiesFromHeader(
headerValues, headerName, null)));
throw new RuntimeException("hey");
} catch (Throwable t) {
options
.getLogger()
.log(SentryLevel.WARNING, "Expected a List<String> as header", t);
}
}
}
}
});
return headers;
}

private static @Nullable String toString(final @Nullable List<String> list) {
return list != null ? String.join(",", list) : null;
}

private @NotNull String buildUrlString(final @NotNull Attributes attributes) {
final @Nullable String scheme = attributes.get(UrlAttributes.URL_SCHEME);
final @Nullable String serverAddress = attributes.get(ServerAttributes.SERVER_ADDRESS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,9 @@ private void transferSpanDetails(
transferSpanDetails(sentrySpanMaybe, sentryTransaction);

scopesToUse.configureScope(
ScopeType.CURRENT, scope -> attributesExtractor.extract(span, sentryTransaction, scope));
ScopeType.CURRENT,
scope ->
attributesExtractor.extract(span, sentryTransaction, scope, scopesToUse.getOptions()));

return sentryTransaction;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.sentry.opentelemetry
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.sdk.internal.AttributesMap
import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.semconv.HttpAttributes
import io.opentelemetry.semconv.ServerAttributes
import io.opentelemetry.semconv.UrlAttributes
import io.sentry.ISpan
Expand All @@ -13,6 +14,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull

Expand All @@ -36,6 +38,7 @@ class OpenTelemetryAttributesExtractorTest {
fun `sets URL based on OTel attributes`() {
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
UrlAttributes.URL_SCHEME to "https",
UrlAttributes.URL_PATH to "/path/to/123",
UrlAttributes.URL_QUERY to "q=123456&b=X",
Expand All @@ -56,6 +59,7 @@ class OpenTelemetryAttributesExtractorTest {
fixture.scope.request = Request().also { it.bodySize = 123L }
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
UrlAttributes.URL_SCHEME to "https",
UrlAttributes.URL_PATH to "/path/to/123",
UrlAttributes.URL_QUERY to "q=123456&b=X",
Expand All @@ -80,6 +84,7 @@ class OpenTelemetryAttributesExtractorTest {
}
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
UrlAttributes.URL_SCHEME to "https",
UrlAttributes.URL_PATH to "/path/to/123",
UrlAttributes.URL_QUERY to "q=123456&b=X",
Expand Down Expand Up @@ -118,6 +123,7 @@ class OpenTelemetryAttributesExtractorTest {
fun `sets URL based on OTel attributes without port`() {
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
UrlAttributes.URL_SCHEME to "https",
UrlAttributes.URL_PATH to "/path/to/123",
ServerAttributes.SERVER_ADDRESS to "io.sentry"
Expand All @@ -134,6 +140,7 @@ class OpenTelemetryAttributesExtractorTest {
fun `sets URL based on OTel attributes without path`() {
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
UrlAttributes.URL_SCHEME to "https",
ServerAttributes.SERVER_ADDRESS to "io.sentry"
)
Expand All @@ -149,6 +156,7 @@ class OpenTelemetryAttributesExtractorTest {
fun `does not set URL if server address is missing`() {
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
UrlAttributes.URL_SCHEME to "https"
)
)
Expand All @@ -163,6 +171,7 @@ class OpenTelemetryAttributesExtractorTest {
fun `does not set URL if scheme is missing`() {
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
ServerAttributes.SERVER_ADDRESS to "io.sentry"
)
)
Expand All @@ -173,20 +182,67 @@ class OpenTelemetryAttributesExtractorTest {
thenUrlIsNotSet()
}

@Test
fun `sets server request headers based on OTel attributes and merges list of values`() {
givenAttributes(
mapOf(
HttpAttributes.HTTP_REQUEST_METHOD to "GET",
AttributeKey.stringArrayKey("http.request.header.baggage") to listOf("sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d", "another-baggage=abc,more=def"),
AttributeKey.stringArrayKey("http.request.header.sentry-trace") to listOf("f9118105af4a2d42b4124532cd176588-4542d085bb0b4de5"),
AttributeKey.stringArrayKey("http.response.header.some-header") to listOf("some-value")
)
)

whenExtractingAttributes()

thenRequestIsSet()
thenHeaderIsPresentOnRequest("baggage", "sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d,another-baggage=abc,more=def")
thenHeaderIsPresentOnRequest("sentry-trace", "f9118105af4a2d42b4124532cd176588-4542d085bb0b4de5")
thenHeaderIsNotPresentOnRequest("some-header")
}

@Test
fun `if there are no header attributes does not set headers on request`() {
givenAttributes(mapOf(HttpAttributes.HTTP_REQUEST_METHOD to "GET"))

whenExtractingAttributes()

thenRequestIsSet()
assertNull(fixture.scope.request!!.headers)
}

@Test
fun `if there is no request method attribute does not set request on scope`() {
givenAttributes(
mapOf(
UrlAttributes.URL_SCHEME to "https",
ServerAttributes.SERVER_ADDRESS to "io.sentry"
)
)

whenExtractingAttributes()

thenRequestIsNotSet()
}

private fun givenAttributes(map: Map<AttributeKey<out Any>, Any>) {
map.forEach { k, v ->
fixture.attributes.put(k, v)
}
}

private fun whenExtractingAttributes() {
OpenTelemetryAttributesExtractor().extract(fixture.spanData, fixture.sentrySpan, fixture.scope)
OpenTelemetryAttributesExtractor().extract(fixture.spanData, fixture.sentrySpan, fixture.scope, fixture.options)
}

private fun thenRequestIsSet() {
assertNotNull(fixture.scope.request)
}

private fun thenRequestIsNotSet() {
assertNull(fixture.scope.request)
}

private fun thenUrlIsSetTo(expected: String) {
assertEquals(expected, fixture.scope.request!!.url)
}
Expand All @@ -198,4 +254,12 @@ class OpenTelemetryAttributesExtractorTest {
private fun thenQueryIsSetTo(expected: String) {
assertEquals(expected, fixture.scope.request!!.queryString)
}

private fun thenHeaderIsPresentOnRequest(headerName: String, expectedValue: String) {
assertEquals(expectedValue, fixture.scope.request!!.headers!!.get(headerName))
}

private fun thenHeaderIsNotPresentOnRequest(headerName: String) {
assertFalse(fixture.scope.request!!.headers!!.containsKey(headerName))
}
}

0 comments on commit 01d4a6e

Please sign in to comment.