Skip to content
Draft
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
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,39 @@

### Features

- Drop incoming server transactions whose root HTTP response status matches `ignoredServerHttpStatusCodesForTracing`. Configure individual statuses such as `404`, inclusive ranges such as `400-499` or `500-599`, or combine them ([#6566](https://github.com/getsentry/sentry-java/pull/6566)).

Java:

```java
import io.sentry.HttpStatusCodeRange;
import java.util.Arrays;

options.setIgnoredServerHttpStatusCodesForTracing(
Arrays.asList(new HttpStatusCodeRange(404), new HttpStatusCodeRange(500, 599)));
```

`sentry.properties`:

```properties
ignored-server-http-status-codes-for-tracing=404,500-599
```

Spring Boot `application.properties`:

```properties
sentry.ignored-server-http-status-codes-for-tracing=404,500-599
```

Spring Boot `application.yml`:

```yaml
sentry:
ignored-server-http-status-codes-for-tracing:
- "404"
- "500-599"
```

- Monitor every Spring `@Scheduled` method with Sentry Crons by setting `sentry.monitor-scheduled-tasks=true`, without adding `@SentryCheckIn` to each method. Sentry creates each monitor from the method's `@Scheduled` schedule. Exclude methods with `sentry.exclude-scheduled-tasks` ([#6275](https://github.com/getsentry/sentry-java/pull/6275), [#6276](https://github.com/getsentry/sentry-java/pull/6276), [#6277](https://github.com/getsentry/sentry-java/pull/6277))
- Quartz `SentryJobListener` can send a monitor config derived from the trigger with check-ins, so Sentry creates or updates the monitor from code. Set the `sentry-upsert-monitor-config` job or trigger data key to `"true"` to send the schedule ([#6216](https://github.com/getsentry/sentry-java/pull/6216))
- Send a monitor config derived from Spring `@Scheduled` with `@SentryCheckIn` check-ins, so Sentry creates or updates the monitor from code. Set `@SentryCheckIn(upsertMonitorConfig = true)` to send the schedule ([#6215](https://github.com/getsentry/sentry-java/pull/6215))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.springframework.test.context.junit4.SpringRunner
properties =
[
"sentry.traces-sample-rate=1.0",
"sentry.ignored-server-http-status-codes-for-tracing=404",
"sentry.clients.ok-http-enabled=true",
"sentry.enable-cache-tracing=true",
"sentry.enable-queue-tracing=true",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ class PersonIntegrationTest : IntegrationTest() {
.isTrue()
}

@Test
fun `configured not found response does not send a transaction`() {
val path = "/missing-ignored-status"

val response = restTemplate.getForEntity(path, String::class.java)

assertThat(response.statusCode.value()).isEqualTo(404)
Sentry.flush(5000)
server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true }
}

@Test
fun `creating a person starts a profile linked to the transaction`() {
val response =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ class PersonWebfluxIntegrationTest : WebfluxIntegrationTest() {
.isEqualTo(200)
}

@Test
fun `configured not found response does not send a transaction`() {
val path = "/missing-ignored-status"

val response = restTemplate.getForEntity(path, String::class.java)

assertThat(response.statusCode.value()).isEqualTo(404)
Sentry.flush(5000)
server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true }
}

@Test
fun `creating a person starts a profile linked to the transaction`() {
val response =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.springframework.test.context.junit4.SpringRunner
properties =
[
"sentry.traces-sample-rate=1.0",
"sentry.ignored-server-http-status-codes-for-tracing=404",
"sentry.enable-cache-tracing=true",
"sentry.logs.enabled=true",
"sentry.reactive.thread-local-accessor-enabled=true",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.springframework.test.context.junit4.SpringRunner
properties =
[
"sentry.traces-sample-rate=1.0",
"sentry.ignored-server-http-status-codes-for-tracing=404",
"sentry.clients.ok-http-enabled=true",
"sentry.enable-cache-tracing=true",
"sentry.enable-queue-tracing=true",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ class PersonIntegrationTest : IntegrationTest() {
.isTrue()
}

@Test
fun `configured not found response does not send a transaction`() {
val path = "/missing-ignored-status"

val response = restTemplate.getForEntity(path, String::class.java)

assertThat(response.statusCode.value()).isEqualTo(404)
Sentry.flush(5000)
server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true }
}

@Test
fun `creating a person starts a profile linked to the transaction`() {
val response =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ class PersonWebfluxIntegrationTest : WebfluxIntegrationTest() {
.isEqualTo(200)
}

@Test
fun `configured not found response does not send a transaction`() {
val path = "/missing-ignored-status"

val response = restTemplate.getForEntity(path, String::class.java)

assertThat(response.statusCode.value()).isEqualTo(404)
Sentry.flush(5000)
server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true }
}

@Test
fun `creating a person starts a profile linked to the transaction`() {
val response =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.springframework.test.context.junit4.SpringRunner
properties =
[
"sentry.traces-sample-rate=1.0",
"sentry.ignored-server-http-status-codes-for-tracing=404",
"sentry.enable-cache-tracing=true",
"sentry.logs.enabled=true",
"sentry.reactive.thread-local-accessor-enabled=true",
Expand Down
13 changes: 13 additions & 0 deletions sentry-spring-boot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ This integration is ideal to Spring Boot users. It buids on top of [Sentry's cor
and also integrates well with logback or log4j2 if those are used in the application.

For more information check out [Sentry Spring Boot documentation](https://docs.sentry.io/platforms/java/guides/spring-boot/).

## Ignoring server transactions by HTTP status

Set `sentry.ignored-server-http-status-codes-for-tracing` to a comma-separated list of individual
status codes and inclusive ranges:

```properties
sentry.ignored-server-http-status-codes-for-tracing=404,500-599
```

Matching incoming server transactions are not sent to Sentry. The filter reads the root
transaction's HTTP response status; child span statuses do not affect it. The option is disabled
when it is unset or empty.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.springframework.test.context.junit4.SpringRunner
properties =
[
"sentry.traces-sample-rate=1.0",
"sentry.ignored-server-http-status-codes-for-tracing=404",
"sentry.clients.ok-http-enabled=true",
"sentry.enable-cache-tracing=true",
"sentry.enable-queue-tracing=true",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ class PersonIntegrationTest : IntegrationTest() {
.isTrue()
}

@Test
fun `configured not found response does not send a transaction`() {
val path = "/missing-ignored-status"

val response = restTemplate.getForEntity(path, String::class.java)

assertThat(response.statusCode.value()).isEqualTo(404)
Sentry.flush(5000)
server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true }
}

@Test
fun `creating a person starts a profile linked to the transaction`() {
val response =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ class PersonWebfluxIntegrationTest : WebfluxIntegrationTest() {
.isEqualTo(200)
}

@Test
fun `configured not found response does not send a transaction`() {
val path = "/missing-ignored-status"

val response = restTemplate.getForEntity(path, String::class.java)

assertThat(response.statusCode.value()).isEqualTo(404)
Sentry.flush(5000)
server.assertNoEnvelope { it.transaction()?.request?.url?.endsWith(path) == true }
}

@Test
fun `creating a person starts a profile linked to the transaction`() {
val response =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.springframework.test.context.junit4.SpringRunner
properties =
[
"sentry.traces-sample-rate=1.0",
"sentry.ignored-server-http-status-codes-for-tracing=404",
"sentry.logs.enabled=true",
"sentry.profile-session-sample-rate=1.0",
"sentry.profile-lifecycle=TRACE",
Expand Down
90 changes: 90 additions & 0 deletions sentry/src/main/java/io/sentry/SentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.sentry.transport.RateLimiter;
import io.sentry.util.*;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -1024,6 +1025,30 @@ public void captureSession(final @NotNull Session session, final @Nullable Hint
return SentryId.EMPTY_ID;
}

final @Nullable Integer ignoredHttpStatusCode = getIgnoredHttpStatusCode(transaction);
if (ignoredHttpStatusCode != null) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
"Transaction was dropped as HTTP status code %d is ignored",
ignoredHttpStatusCode);
options
.getClientReportRecorder()
.recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Transaction);
options
.getClientReportRecorder()
.recordLostEvent(
DiscardReason.EVENT_PROCESSOR, DataCategory.Span, transaction.getSpans().size() + 1);
if (profilingTraceData != null) {
options
.getClientReportRecorder()
.recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Profile);
}
deleteProfilingTraceFile(profilingTraceData);
return SentryId.EMPTY_ID;
}

SentryId sentryId = SentryId.EMPTY_ID;
if (transaction.getEventId() != null) {
sentryId = transaction.getEventId();
Expand Down Expand Up @@ -1096,6 +1121,71 @@ public void captureSession(final @NotNull Session session, final @Nullable Hint
return sentryId;
}

private @Nullable Integer getIgnoredHttpStatusCode(final @NotNull SentryTransaction transaction) {
final @Nullable List<HttpStatusCodeRange> ignoredHttpStatusCodes =
options.getIgnoredServerHttpStatusCodesForTracing();
if (ignoredHttpStatusCodes == null || ignoredHttpStatusCodes.isEmpty()) {
return null;
}

final @Nullable SpanContext trace = transaction.getContexts().getTrace();
if (trace == null
|| !"server".equals(trace.getData().get(SpanDataConvention.SENTRY_KIND_KEY))) {
return null;
}

final @Nullable Object httpStatusCodeValue =
trace.getData().get(SpanDataConvention.HTTP_STATUS_CODE_KEY);
final int httpStatusCode;
if (httpStatusCodeValue instanceof Integer) {
httpStatusCode = (Integer) httpStatusCodeValue;
} else if (httpStatusCodeValue instanceof Long) {
final long longHttpStatusCode = (Long) httpStatusCodeValue;
if (longHttpStatusCode < 100 || longHttpStatusCode > 599) {
return null;
}
httpStatusCode = (int) longHttpStatusCode;
} else {
return null;
}

if (httpStatusCode < 100 || httpStatusCode > 599) {
return null;
}
for (final @Nullable HttpStatusCodeRange ignoredHttpStatusCode : ignoredHttpStatusCodes) {
if (ignoredHttpStatusCode != null && ignoredHttpStatusCode.isInRange(httpStatusCode)) {
return httpStatusCode;
}
}
return null;
}

private void deleteProfilingTraceFile(final @Nullable ProfilingTraceData profilingTraceData) {
if (profilingTraceData == null) {
return;
}

final @NotNull File traceFile = profilingTraceData.getTraceFile();
try {
if (!traceFile.delete()) {
options
.getLogger()
.log(
SentryLevel.WARNING,
"Failed to delete profiling trace file %s for an ignored transaction",
traceFile.getPath());
}
} catch (SecurityException e) {
options
.getLogger()
.log(
SentryLevel.WARNING,
e,
"Failed to delete profiling trace file %s for an ignored transaction",
traceFile.getPath());
}
}

@ApiStatus.Internal
@Override
public @NotNull SentryId captureProfileChunk(
Expand Down
Loading
Loading