Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Behavioural Changes

- Use `java.net.URI` for parsing URLs in `UrlUtils` ([#4210](https://github.com/getsentry/sentry-java/pull/4210))
- This could affect grouping for issues with messages containing URLs that fall in known corner cases that were handled incorrectly previously (e.g. email in URL path)

### Fixes

- Add support for setting in-app-includes/in-app-excludes via AndroidManifest.xml ([#4240](https://github.com/getsentry/sentry-java/pull/4240))
Expand All @@ -15,10 +20,11 @@
- Set `sentry.capture-open-telemetry-events=true` in Springs `application.properties` to enable it
- Set `sentry.captureOpenTelemetryEvents: true` in Springs `application.yml` to enable it

### Behavioural Changes
### Internal

- Use `java.net.URI` for parsing URLs in `UrlUtils` ([#4210](https://github.com/getsentry/sentry-java/pull/4210))
- This could affect grouping for issues with messages containing URLs that fall in known corner cases that were handled incorrectly previously (e.g. email in URL path)
- Also use port when checking if a request is made to Sentry DSN ([#4231](https://github.com/getsentry/sentry-java/pull/4231))
- For our OpenTelemetry integration we check if a span is for a request to Sentry
- We now also consider the port when performing this check

### Dependencies

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,20 @@ private static Map<String, String> collectHeaders(
return headers;
}

@SuppressWarnings("deprecation")
public @Nullable String extractUrl(
final @NotNull Attributes attributes, final @NotNull SentryOptions options) {
final @Nullable String urlFull = attributes.get(UrlAttributes.URL_FULL);
if (urlFull != null) {
return urlFull;
}

final @Nullable String deprecatedUrl =
attributes.get(io.opentelemetry.semconv.SemanticAttributes.HTTP_URL);
if (deprecatedUrl != null) {
return deprecatedUrl;
}

final String urlString = buildUrlString(attributes, options);
if (!urlString.isEmpty()) {
return urlString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.semconv.UrlAttributes;
import io.sentry.DsnUtil;
import io.sentry.IScopes;
import java.util.Arrays;
Expand All @@ -17,6 +16,8 @@ public final class OtelInternalSpanDetectionUtil {

private static final @NotNull List<SpanKind> spanKindsConsideredForSentryRequests =
Arrays.asList(SpanKind.CLIENT, SpanKind.INTERNAL);
private static final @NotNull OpenTelemetryAttributesExtractor attributesExtractor =
new OpenTelemetryAttributesExtractor();

@SuppressWarnings("deprecation")
public static boolean isSentryRequest(
Expand All @@ -27,14 +28,8 @@ public static boolean isSentryRequest(
return false;
}

final @Nullable String httpUrl =
attributes.get(io.opentelemetry.semconv.SemanticAttributes.HTTP_URL);
if (DsnUtil.urlContainsDsnHost(scopes.getOptions(), httpUrl)) {
return true;
}

final @Nullable String fullUrl = attributes.get(UrlAttributes.URL_FULL);
if (DsnUtil.urlContainsDsnHost(scopes.getOptions(), fullUrl)) {
String url = attributesExtractor.extractUrl(attributes, scopes.getOptions());
if (DsnUtil.urlContainsDsnHost(scopes.getOptions(), url)) {
return true;
}

Expand All @@ -43,10 +38,7 @@ public static boolean isSentryRequest(
final @NotNull String spotlightUrl =
optionsSpotlightUrl != null ? optionsSpotlightUrl : "http://localhost:8969/stream";

if (containsSpotlightUrl(fullUrl, spotlightUrl)) {
return true;
}
if (containsSpotlightUrl(httpUrl, spotlightUrl)) {
if (containsSpotlightUrl(url, spotlightUrl)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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.SemanticAttributes
import io.opentelemetry.semconv.ServerAttributes
import io.opentelemetry.semconv.UrlAttributes
import io.sentry.Scope
Expand Down Expand Up @@ -202,6 +203,19 @@ class OpenTelemetryAttributesExtractorTest {
assertEquals("https://sentry.io/some/path", url)
}

@Test
fun `returns deprecated URL if present`() {
givenAttributes(
mapOf(
SemanticAttributes.HTTP_URL to "https://sentry.io/some/path"
)
)

val url = whenExtractingUrl()

assertEquals("https://sentry.io/some/path", url)
}

@Test
fun `returns reconstructed URL if attributes present`() {
givenAttributes(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package io.sentry.opentelemetry

import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.sdk.internal.AttributesMap
import io.opentelemetry.semconv.HttpAttributes
import io.opentelemetry.semconv.SemanticAttributes
import io.opentelemetry.semconv.ServerAttributes
import io.opentelemetry.semconv.UrlAttributes
import io.sentry.IScopes
import io.sentry.SentryOptions
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class OtelInternalSpanDetectionUtilTest {

private class Fixture {
val scopes = mock<IScopes>()
val attributes = AttributesMap.create(100, 100)
val options = SentryOptions.empty()
var spanKind: SpanKind = SpanKind.INTERNAL

init {
whenever(scopes.options).thenReturn(options)
}
}

private val fixture = Fixture()

@Test
fun `detects split url as internal (span kind client)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.CLIENT)
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",
ServerAttributes.SERVER_ADDRESS to "io.sentry",
ServerAttributes.SERVER_PORT to 8081L
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `detects full url as internal (span kind client)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.CLIENT)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "https://io.sentry:8081"
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `detects deprecated url as internal (span kind client)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.CLIENT)
givenAttributes(
mapOf(
SemanticAttributes.HTTP_URL to "https://io.sentry:8081"
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `detects split url as internal (span kind internal)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.INTERNAL)
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",
ServerAttributes.SERVER_ADDRESS to "io.sentry",
ServerAttributes.SERVER_PORT to 8081L
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `detects full url as internal (span kind internal)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.INTERNAL)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "https://io.sentry:8081"
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `detects deprecated url as internal (span kind internal)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.INTERNAL)
givenAttributes(
mapOf(
SemanticAttributes.HTTP_URL to "https://io.sentry:8081"
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `does not detect full url as internal (span kind server)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.SERVER)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "https://io.sentry:8081"
)
)

thenRequestIsNotConsideredInternal()
}

@Test
fun `does not detect full url as internal (span kind producer)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.PRODUCER)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "https://io.sentry:8081"
)
)

thenRequestIsNotConsideredInternal()
}

@Test
fun `does not detect full url as internal (span kind consumer)`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpanKind(SpanKind.CONSUMER)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "https://io.sentry:8081"
)
)

thenRequestIsNotConsideredInternal()
}

@Test
fun `detects full spotlight url as internal`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpotlightEnabled(true)
givenSpanKind(SpanKind.CLIENT)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "http://localhost:8969/stream"
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `detects full spotlight url as internal with custom spotlight url`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpotlightEnabled(true)
givenSpotlightUrl("http://localhost:8090/stream")
givenSpanKind(SpanKind.CLIENT)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "http://localhost:8090/stream"
)
)

thenRequestIsConsideredInternal()
}

@Test
fun `does not detect mismatching full spotlight url as internal`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpotlightEnabled(true)
givenSpanKind(SpanKind.CLIENT)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "http://localhost:8080/stream"
)
)

thenRequestIsNotConsideredInternal()
}

@Test
fun `does not detect mismatching full customized spotlight url as internal`() {
givenDsn("https://publicKey:secretKey@io.sentry:8081/path/id?sample.rate=0.1")
givenSpotlightEnabled(true)
givenSpotlightUrl("http://localhost:8090/stream")
givenSpanKind(SpanKind.CLIENT)
givenAttributes(
mapOf(
UrlAttributes.URL_FULL to "http://localhost:8091/stream"
)
)

thenRequestIsNotConsideredInternal()
}

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

private fun givenDsn(dsn: String) {
fixture.options.dsn = dsn
}

private fun givenSpotlightEnabled(enabled: Boolean) {
fixture.options.isEnableSpotlight = enabled
}

private fun givenSpotlightUrl(url: String) {
fixture.options.spotlightConnectionUrl = url
}

private fun givenSpanKind(spanKind: SpanKind) {
fixture.spanKind = spanKind
}

private fun thenRequestIsConsideredInternal() {
assertTrue(checkIfInternal())
}

private fun thenRequestIsNotConsideredInternal() {
assertFalse(checkIfInternal())
}

private fun checkIfInternal(): Boolean {
return OtelInternalSpanDetectionUtil.isSentryRequest(fixture.scopes, fixture.spanKind, fixture.attributes)
}
}
9 changes: 8 additions & 1 deletion sentry/src/main/java/io/sentry/DsnUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public static boolean urlContainsDsnHost(@Nullable SentryOptions options, @Nulla
return false;
}

return url.toLowerCase(Locale.ROOT).contains(dsnHost.toLowerCase(Locale.ROOT));
final @NotNull String lowerCaseHost = dsnHost.toLowerCase(Locale.ROOT);
final int dsnPort = sentryUri.getPort();

if (dsnPort > 0) {
return url.toLowerCase(Locale.ROOT).contains(lowerCaseHost + ":" + dsnPort);
} else {
return url.toLowerCase(Locale.ROOT).contains(lowerCaseHost);
}
}
}
Loading
Loading