Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ public static DatabaseDetails parse(final @Nullable String databaseConnectionUrl
String pathWithoutProperties = StringUtils.substringBefore(path, ";");
return new DatabaseDetails(dbSystem, pathWithoutProperties);
} catch (Throwable t) {
System.out.println(t.getMessage());
// ignore
}
return new DatabaseDetails(dbSystem, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencies {
api(libs.otel.extension.autoconfigure)
api(libs.otel.exporter.otlp)
compileOnly(libs.otel.extension.autoconfigure.spi)
// compileOnly(libs.otel.semconv)
implementation(libs.otel.semconv)
// compileOnly(libs.otel.semconv.incubating)

compileOnly(libs.jetbrains.annotations)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.sentry.SentryTraceHeader.SENTRY_TRACE_HEADER;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.TraceFlags;
Expand All @@ -11,14 +12,20 @@
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.semconv.ServerAttributes;
import io.opentelemetry.semconv.UrlAttributes;
import io.sentry.Baggage;
import io.sentry.BaggageHeader;
import io.sentry.IScopes;
import io.sentry.ScopesAdapter;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.SentryTraceHeader;
import io.sentry.exception.InvalidSentryTraceHeaderException;
import io.sentry.util.PropagationTargetsUtils;
import io.sentry.util.TracingUtils;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -61,6 +68,10 @@ public <C> void inject(final Context context, final C carrier, final TextMapSett
return;
}

if (!shouldInjectTracingHeaders(otelSpan)) {
return;
}

setter.set(
carrier,
SENTRY_TRACE_HEADER,
Expand All @@ -76,6 +87,50 @@ public <C> void inject(final Context context, final C carrier, final TextMapSett
}
}

private boolean shouldInjectTracingHeaders(final @NotNull Span otelSpan) {
final @NotNull SentryOptions options = scopes.getOptions();
final @Nullable String url = extractUrl(otelSpan, options);

return url == null
|| PropagationTargetsUtils.contain(options.getTracePropagationTargets(), url);
}

private @Nullable String extractUrl(
final @NotNull Span otelSpan, final @NotNull SentryOptions options) {
if (!(otelSpan instanceof ReadableSpan)) {
return null;
}

final @NotNull Attributes attributes = ((ReadableSpan) otelSpan).getAttributes();
final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL);
if (urlFull != null) {
return urlFull;
}

final @Nullable String scheme = attributes.get(UrlAttributes.URL_SCHEME);
final @Nullable String serverAddress = attributes.get(ServerAttributes.SERVER_ADDRESS);
final @Nullable Long serverPort = attributes.get(ServerAttributes.SERVER_PORT);
final @Nullable String path = attributes.get(UrlAttributes.URL_PATH);

if (scheme == null || serverAddress == null) {
return null;
}

try {
final @NotNull String pathToUse = path == null ? "" : path;
if (serverPort == null) {
return new URL(scheme, serverAddress, pathToUse).toString();
} else {
return new URL(scheme, serverAddress, serverPort.intValue(), pathToUse).toString();
}
} catch (Throwable t) {
options
.getLogger()
.log(SentryLevel.WARNING, "Unable to combine URL span attributes into one.", t);
return null;
}
}

@Override
public <C> Context extract(
final Context context, final C carrier, final TextMapGetter<C> getter) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.opentelemetry.api.trace.TraceState
import io.opentelemetry.context.Context
import io.opentelemetry.context.propagation.TextMapGetter
import io.opentelemetry.context.propagation.TextMapSetter
import io.opentelemetry.sdk.trace.SdkTracerProvider
import io.sentry.Baggage
import io.sentry.Sentry
import kotlin.test.AfterTest
Expand Down Expand Up @@ -171,6 +172,97 @@ class OpenTelemetryOtlpPropagatorTest {
)
}

@Test
fun `injects headers if URL in span attributes matches tracePropagationTargets`() {
Sentry.init { options ->
options.dsn = "https://key@sentry.io/proj"
options.setTracePropagationTargets(listOf("sentry.io"))
}
val propagator = OpenTelemetryOtlpPropagator()
val carrier = mutableMapOf<String, String>()
val tracerProvider = SdkTracerProvider.builder().build()
val otelSpan =
tracerProvider
.get("test")
.spanBuilder("test")
.setAttribute("url.full", "https://sentry.io/api/0/")
.startSpan()
val baggage =
Baggage.fromHeader(
"sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d"
)

try {
val context =
Context.root().with(otelSpan).with(OpenTelemetryOtlpPropagator.SENTRY_BAGGAGE_KEY, baggage)

propagator.inject(context, carrier, MapSetter())
} finally {
otelSpan.end()
tracerProvider.shutdown()
}

assertEquals(
"${otelSpan.spanContext.traceId}-${otelSpan.spanContext.spanId}-1",
carrier["sentry-trace"],
)
assertEquals(
"sentry-environment=production,sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rand=0.456789,sentry-sample_rate=0.5,sentry-sampled=true,sentry-trace_id=df71f5972f754b4c85af13ff5c07017d",
carrier["baggage"],
)
}

@Test
fun `does not inject headers if URL in span attributes does not match tracePropagationTargets`() {
Sentry.init { options ->
options.dsn = "https://key@sentry.io/proj"
options.setTracePropagationTargets(listOf("github.com"))
}
val propagator = OpenTelemetryOtlpPropagator()
val carrier = mutableMapOf<String, String>()
val tracerProvider = SdkTracerProvider.builder().build()
val otelSpan =
tracerProvider
.get("test")
.spanBuilder("test")
.setAttribute("url.full", "https://sentry.io/api/0/")
.startSpan()

try {
propagator.inject(Context.root().with(otelSpan), carrier, MapSetter())
} finally {
otelSpan.end()
tracerProvider.shutdown()
}

assertNull(carrier["sentry-trace"])
assertNull(carrier["baggage"])
}

@Test
fun `injects headers if tracePropagationTargets is restricted and URL is unavailable`() {
Sentry.init { options ->
options.dsn = "https://key@sentry.io/proj"
options.setTracePropagationTargets(listOf("sentry.io"))
}
val propagator = OpenTelemetryOtlpPropagator()
val carrier = mutableMapOf<String, String>()

val otelSpanContext =
SpanContext.create(
"f9118105af4a2d42b4124532cd1065ff",
"424cffc8f94feeee",
TraceFlags.getSampled(),
TraceState.getDefault(),
)
val otelSpan = Span.wrap(otelSpanContext)

propagator.inject(Context.root().with(otelSpan), carrier, MapSetter())

assertEquals("f9118105af4a2d42b4124532cd1065ff-424cffc8f94feeee-1", carrier["sentry-trace"])
assertNull(carrier["baggage"])
}

@Test
fun `does not inject headers when no span in context`() {
val propagator = OpenTelemetryOtlpPropagator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.sentry.util.Objects;
import io.sentry.util.SpanUtils;
import io.sentry.util.TracingUtils;
import io.sentry.util.UrlUtils;
import java.util.Locale;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -45,8 +46,9 @@ public SentrySpanClientWebRequestFilter(final @NotNull IScopes scopes) {
final @NotNull SpanOptions spanOptions = new SpanOptions();
spanOptions.setOrigin(TRACE_ORIGIN);
final ISpan span = activeSpan.startChild("http.client", null, spanOptions);
final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(request.url().toString());
final @NotNull String method = request.method().name();
span.setDescription(method + " " + request.url());
span.setDescription(method + " " + urlDetails.getUrlOrFallback());
span.setData(SpanDataConvention.HTTP_METHOD_KEY, method.toUpperCase(Locale.ROOT));

final @NotNull ClientRequest modifiedRequest = maybeAddTracingHeaders(request, span);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,8 @@ class SentrySpringIntegrationTest {
assertThat(transaction.spans).hasSize(1)
val span = transaction.spans.first()
assertThat(span.op).isEqualTo("http.client")
assertThat(span.description).isEqualTo("GET http://localhost:$port/hello")
assertThat(span.description)
.isEqualTo("GET http://[Filtered]:[Filtered]@localhost:$port/hello")
assertThat(span.data?.get(SpanDataConvention.HTTP_STATUS_CODE_KEY)).isEqualTo(200)
assertThat(span.status).isEqualTo(SpanStatus.OK)
},
Expand Down Expand Up @@ -519,7 +520,7 @@ class HelloController(private val webClient: WebClient, private val env: Environ
fun webClient(): String? {
return webClient
.get()
.uri("http://localhost:${env.getProperty("local.server.port")}/hello")
.uri("http://user:password@localhost:${env.getProperty("local.server.port")}/hello")
.retrieve()
.bodyToMono(String::class.java)
.block()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.sentry.util.Objects;
import io.sentry.util.SpanUtils;
import io.sentry.util.TracingUtils;
import io.sentry.util.UrlUtils;
import java.util.Locale;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -45,8 +46,9 @@ public SentrySpanClientWebRequestFilter(final @NotNull IScopes scopes) {
final @NotNull SpanOptions spanOptions = new SpanOptions();
spanOptions.setOrigin(TRACE_ORIGIN);
final ISpan span = activeSpan.startChild("http.client", null, spanOptions);
final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(request.url().toString());
final @NotNull String method = request.method().name();
span.setDescription(method + " " + request.url());
span.setDescription(method + " " + urlDetails.getUrlOrFallback());
span.setData(SpanDataConvention.HTTP_METHOD_KEY, method.toUpperCase(Locale.ROOT));

final @NotNull ClientRequest modifiedRequest = maybeAddTracingHeaders(request, span);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@ class SentrySpringIntegrationTest {
assertThat(transaction.spans).hasSize(1)
val span = transaction.spans.first()
assertThat(span.op).isEqualTo("http.client")
assertThat(span.description).isEqualTo("GET http://localhost:$port/hello")
assertThat(span.description)
.isEqualTo("GET http://[Filtered]:[Filtered]@localhost:$port/hello")
assertThat(span.data?.get(SpanDataConvention.HTTP_STATUS_CODE_KEY)).isEqualTo(200)
assertThat(span.status).isEqualTo(SpanStatus.OK)
},
Expand Down Expand Up @@ -517,7 +518,7 @@ class HelloController(private val webClient: WebClient, private val env: Environ
fun webClient(): String? {
return webClient
.get()
.uri("http://localhost:${env.getProperty("local.server.port")}/hello")
.uri("http://user:password@localhost:${env.getProperty("local.server.port")}/hello")
.retrieve()
.bodyToMono(String::class.java)
.block()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ public HttpConnection(
if (proxy != null && options.getProxy() != null) {
final String proxyUser = options.getProxy().getUser();
final String proxyPassword = options.getProxy().getPass();

if (proxyUser != null && proxyPassword != null) {
authenticatorWrapper.setDefault(new ProxyAuthenticator(proxyUser, proxyPassword));
final String proxyHost = options.getProxy().getHost();
if (proxyUser != null && proxyPassword != null && proxyHost != null) {
authenticatorWrapper.setDefault(
new ProxyAuthenticator(proxyUser, proxyPassword, proxyHost));

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could use ScopeAdapter in the ProxyAuthenticator to get the options and proxy url there. I feel this is cleaner though

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep it as-is.

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@
final class ProxyAuthenticator extends Authenticator {
private final @NotNull String user;
private final @NotNull String password;
private final @NotNull String proxyHost;

/**
* Proxy authenticator.
*
* @param user proxy username
* @param password proxy password
*/
ProxyAuthenticator(final @NotNull String user, final @NotNull String password) {
ProxyAuthenticator(
final @NotNull String user, final @NotNull String password, final @NotNull String proxyHost) {
this.user = Objects.requireNonNull(user, "user is required");
this.password = Objects.requireNonNull(password, "password is required");
this.proxyHost = Objects.requireNonNull(proxyHost, "proxyHost is required");
}

@Override
protected @Nullable PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY) {
if (getRequestorType() == RequestorType.PROXY && proxyHost.equals(getRequestingHost())) {
return new PasswordAuthentication(user, password.toCharArray());
}
return null;
Expand Down
11 changes: 11 additions & 0 deletions sentry/src/test/java/io/sentry/transport/HttpConnectionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ class HttpConnectionTest {
assertEquals(Type.SOCKS, transport.proxy!!.type())
}

@Test
fun `When Proxy username and password are given but host is missing, authenticator is not set`() {
fixture.proxy = Proxy(null, "8090", "some-user", "some-password")
val transport = fixture.getSUT()

transport.send(createEnvelope())

assertNull(transport.proxy)
verify(fixture.authenticatorWrapper, never()).setDefault(any())
}

@Test
fun `sets common headers and on http connection`() {
val transport = fixture.getSUT()
Expand Down
Loading
Loading