Skip to content

Add callback to record the type and amount of data discarded before reaching Sentry #4612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Aug 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## Unreleased

### Features

- Add onDiscard to enable users to track the type and amount of data discarded before reaching Sentry ([#4612](https://github.com/getsentry/sentry-java/pull/4612))
- Stub for setting the callback on `Sentry.init`:
```java
Sentry.init(options -> {
...
options.setOnDiscard(
(reason, category, number) -> {
// Your logic to process discarded data
});
});
```

## 8.19.1

### Fixes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
package io.sentry.samples.console;

import io.sentry.Breadcrumb;
import io.sentry.EventProcessor;
import io.sentry.Hint;
import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.Sentry;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.SpanStatus;
import io.sentry.*;
import io.sentry.clientreport.DiscardReason;
import io.sentry.protocol.Message;
import io.sentry.protocol.User;
import java.util.Collections;

public class Main {

private static long numberOfDiscardedSpansDueToOverflow = 0;

public static void main(String[] args) throws InterruptedException {
Sentry.init(
options -> {
Expand Down Expand Up @@ -59,6 +54,18 @@ public static void main(String[] args) throws InterruptedException {
return breadcrumb;
});

// Record data being discarded, including the reason, type of data, and the number of
// items dropped
options.setOnDiscard(
(reason, category, number) -> {
// Only record the number of lost spans due to overflow conditions
if ((reason.equals(DiscardReason.CACHE_OVERFLOW)
|| reason.equals(DiscardReason.QUEUE_OVERFLOW))
&& category.equals(DataCategory.Span)) {
numberOfDiscardedSpansDueToOverflow += number;
}
});

// Configure the background worker which sends events to sentry:
// Wait up to 5 seconds before shutdown while there are events to send.
options.setShutdownTimeoutMillis(5000);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.sentry.samples.servlet;

import io.sentry.DataCategory;
import io.sentry.Sentry;
import io.sentry.clientreport.DiscardReason;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
Expand All @@ -9,6 +11,8 @@
@WebListener
public final class SentryInitializer implements ServletContextListener {

private static long numberOfDiscardedSpansDueToOverflow = 0;

@Override
public void contextInitialized(ServletContextEvent sce) {
Sentry.init(
Expand Down Expand Up @@ -57,6 +61,18 @@ public void contextInitialized(ServletContextEvent sce) {
return breadcrumb;
});

// Record data being discarded, including the reason, type of data, and the number of
// items dropped
options.setOnDiscard(
(reason, category, number) -> {
// Only record the number of lost spans due to overflow conditions
if ((reason.equals(DiscardReason.CACHE_OVERFLOW)
|| reason.equals(DiscardReason.QUEUE_OVERFLOW))
&& category.equals(DataCategory.Span)) {
numberOfDiscardedSpansDueToOverflow += number;
}
});

// Configure the background worker which sends events to sentry:
// Wait up to 5 seconds before shutdown while there are events to send.
options.setShutdownTimeoutMillis(5000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ static class HubConfiguration {
beforeSendLogsCallback,
final @NotNull ObjectProvider<SentryOptions.BeforeBreadcrumbCallback>
beforeBreadcrumbCallback,
final @NotNull ObjectProvider<SentryOptions.OnDiscardCallback> onDiscardCallback,
final @NotNull ObjectProvider<SentryOptions.TracesSamplerCallback> tracesSamplerCallback,
final @NotNull List<EventProcessor> eventProcessors,
final @NotNull List<Integration> integrations,
Expand All @@ -118,6 +119,7 @@ static class HubConfiguration {
beforeSendTransactionCallback.ifAvailable(options::setBeforeSendTransaction);
beforeSendLogsCallback.ifAvailable(callback -> options.getLogs().setBeforeSend(callback));
beforeBreadcrumbCallback.ifAvailable(options::setBeforeBreadcrumb);
onDiscardCallback.ifAvailable(options::setOnDiscard);
tracesSamplerCallback.ifAvailable(options::setTracesSampler);
eventProcessors.forEach(options::addEventProcessor);
integrations.forEach(options::addIntegration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.acme.MainBootClass
import io.opentelemetry.api.OpenTelemetry
import io.sentry.AsyncHttpTransportFactory
import io.sentry.Breadcrumb
import io.sentry.DataCategory
import io.sentry.EventProcessor
import io.sentry.FilterString
import io.sentry.Hint
Expand All @@ -19,6 +20,7 @@ import io.sentry.SentryLevel
import io.sentry.SentryLogEvent
import io.sentry.SentryOptions
import io.sentry.checkEvent
import io.sentry.clientreport.DiscardReason
import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider
import io.sentry.opentelemetry.agent.AgentMarker
import io.sentry.protocol.SentryTransaction
Expand Down Expand Up @@ -373,6 +375,17 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `registers onDiscardCallback on SentryOptions`() {
contextRunner
.withPropertyValues("sentry.dsn=http://key@localhost/proj")
.withUserConfiguration(CustomOnDiscardCallbackConfiguration::class.java)
.run {
assertThat(it.getBean(SentryOptions::class.java).onDiscard)
.isInstanceOf(CustomOnDiscardCallback::class.java)
}
}

@Test
fun `registers event processor on SentryOptions`() {
contextRunner
Expand Down Expand Up @@ -1137,6 +1150,16 @@ class SentryAutoConfigurationTest {
override fun execute(breadcrumb: Breadcrumb, hint: Hint): Breadcrumb? = null
}

@Configuration(proxyBeanMethods = false)
open class CustomOnDiscardCallbackConfiguration {

@Bean open fun onDiscardCallback() = CustomOnDiscardCallback()
}

class CustomOnDiscardCallback : SentryOptions.OnDiscardCallback {
override fun execute(reason: DiscardReason, category: DataCategory, countToAdd: Long) {}
}

@Configuration(proxyBeanMethods = false)
open class CustomEventProcessorConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ static class HubConfiguration {
beforeSendLogsCallback,
final @NotNull ObjectProvider<SentryOptions.BeforeBreadcrumbCallback>
beforeBreadcrumbCallback,
final @NotNull ObjectProvider<SentryOptions.OnDiscardCallback> onDiscardCallback,
final @NotNull ObjectProvider<SentryOptions.TracesSamplerCallback> tracesSamplerCallback,
final @NotNull List<EventProcessor> eventProcessors,
final @NotNull List<Integration> integrations,
Expand All @@ -116,6 +117,7 @@ static class HubConfiguration {
beforeSendTransactionCallback.ifAvailable(options::setBeforeSendTransaction);
beforeSendLogsCallback.ifAvailable(callback -> options.getLogs().setBeforeSend(callback));
beforeBreadcrumbCallback.ifAvailable(options::setBeforeBreadcrumb);
onDiscardCallback.ifAvailable(options::setOnDiscard);
tracesSamplerCallback.ifAvailable(options::setTracesSampler);
eventProcessors.forEach(options::addEventProcessor);
integrations.forEach(options::addIntegration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.acme.MainBootClass
import io.opentelemetry.api.OpenTelemetry
import io.sentry.AsyncHttpTransportFactory
import io.sentry.Breadcrumb
import io.sentry.DataCategory
import io.sentry.EventProcessor
import io.sentry.FilterString
import io.sentry.Hint
Expand All @@ -19,6 +20,7 @@ import io.sentry.SentryLevel
import io.sentry.SentryLogEvent
import io.sentry.SentryOptions
import io.sentry.checkEvent
import io.sentry.clientreport.DiscardReason
import io.sentry.opentelemetry.SentryAutoConfigurationCustomizerProvider
import io.sentry.opentelemetry.agent.AgentMarker
import io.sentry.protocol.SentryTransaction
Expand Down Expand Up @@ -372,6 +374,17 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `registers onDiscardCallback on SentryOptions`() {
contextRunner
.withPropertyValues("sentry.dsn=http://key@localhost/proj")
.withUserConfiguration(CustomOnDiscardCallbackConfiguration::class.java)
.run {
assertThat(it.getBean(SentryOptions::class.java).onDiscard)
.isInstanceOf(CustomOnDiscardCallback::class.java)
}
}

@Test
fun `registers event processor on SentryOptions`() {
contextRunner
Expand Down Expand Up @@ -1063,6 +1076,16 @@ class SentryAutoConfigurationTest {
override fun execute(breadcrumb: Breadcrumb, hint: Hint): Breadcrumb? = null
}

@Configuration(proxyBeanMethods = false)
open class CustomOnDiscardCallbackConfiguration {

@Bean open fun onDiscardCallback() = CustomOnDiscardCallback()
}

class CustomOnDiscardCallback : SentryOptions.OnDiscardCallback {
override fun execute(reason: DiscardReason, category: DataCategory, countToAdd: Long) {}
}

@Configuration(proxyBeanMethods = false)
open class CustomEventProcessorConfiguration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public SentryInitBeanPostProcessor() {
applicationContext
.getBeanProvider(SentryOptions.BeforeBreadcrumbCallback.class)
.ifAvailable(options::setBeforeBreadcrumb);
applicationContext
.getBeanProvider(SentryOptions.OnDiscardCallback.class)
.ifAvailable(options::setOnDiscard);
applicationContext
.getBeansOfType(EventProcessor.class)
.values()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ class EnableSentryTest {
@Bean fun beforeBreadcrumbCallback() = mock<SentryOptions.BeforeBreadcrumbCallback>()
}

@EnableSentry(dsn = "http://key@localhost/proj")
class AppConfigWithCustomOnDiscardCallback {
@Bean fun onDiscardCallback() = mock<SentryOptions.OnDiscardCallback>()
}

@EnableSentry(dsn = "http://key@localhost/proj")
class AppConfigWithCustomEventProcessors {
@Bean fun firstProcessor() = mock<EventProcessor>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public SentryInitBeanPostProcessor() {
applicationContext
.getBeanProvider(SentryOptions.BeforeBreadcrumbCallback.class)
.ifAvailable(options::setBeforeBreadcrumb);
applicationContext
.getBeanProvider(SentryOptions.OnDiscardCallback.class)
.ifAvailable(options::setOnDiscard);
applicationContext
.getBeansOfType(EventProcessor.class)
.values()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ class EnableSentryTest {
@Bean fun beforeBreadcrumbCallback() = mock<SentryOptions.BeforeBreadcrumbCallback>()
}

@EnableSentry(dsn = "http://key@localhost/proj")
class AppConfigWithCustomOnDiscardCallback {
@Bean fun onDiscardCallback() = mock<SentryOptions.OnDiscardCallback>()
}

@EnableSentry(dsn = "http://key@localhost/proj")
class AppConfigWithCustomEventProcessors {
@Bean fun firstProcessor() = mock<EventProcessor>()
Expand Down
6 changes: 6 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -3340,6 +3340,7 @@ public class io/sentry/SentryOptions {
public fun getMaxSpans ()I
public fun getMaxTraceFileSize ()J
public fun getModulesLoader ()Lio/sentry/internal/modules/IModulesLoader;
public fun getOnDiscard ()Lio/sentry/SentryOptions$OnDiscardCallback;
public fun getOpenTelemetryMode ()Lio/sentry/SentryOpenTelemetryMode;
public fun getOptionsObservers ()Ljava/util/List;
public fun getOutboxPath ()Ljava/lang/String;
Expand Down Expand Up @@ -3482,6 +3483,7 @@ public class io/sentry/SentryOptions {
public fun setMaxSpans (I)V
public fun setMaxTraceFileSize (J)V
public fun setModulesLoader (Lio/sentry/internal/modules/IModulesLoader;)V
public fun setOnDiscard (Lio/sentry/SentryOptions$OnDiscardCallback;)V
public fun setOpenTelemetryMode (Lio/sentry/SentryOpenTelemetryMode;)V
public fun setPrintUncaughtStackTrace (Z)V
public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V
Expand Down Expand Up @@ -3575,6 +3577,10 @@ public abstract interface class io/sentry/SentryOptions$Logs$BeforeSendLogCallba
public abstract fun execute (Lio/sentry/SentryLogEvent;)Lio/sentry/SentryLogEvent;
}

public abstract interface class io/sentry/SentryOptions$OnDiscardCallback {
public abstract fun execute (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;Ljava/lang/Long;)V
}

public abstract interface class io/sentry/SentryOptions$ProfilesSamplerCallback {
public abstract fun sample (Lio/sentry/SamplingContext;)Ljava/lang/Double;
}
Expand Down
2 changes: 0 additions & 2 deletions sentry/src/main/java/io/sentry/DataCategory.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package io.sentry;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

@ApiStatus.Internal
public enum DataCategory {
All("__all__"),
Default("default"), // same as Error
Expand Down
36 changes: 36 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.sentry.cache.IEnvelopeCache;
import io.sentry.cache.PersistingScopeObserver;
import io.sentry.clientreport.ClientReportRecorder;
import io.sentry.clientreport.DiscardReason;
import io.sentry.clientreport.IClientReportRecorder;
import io.sentry.clientreport.NoOpClientReportRecorder;
import io.sentry.internal.debugmeta.IDebugMetaLoader;
Expand Down Expand Up @@ -179,6 +180,9 @@ public class SentryOptions {
*/
private @Nullable BeforeBreadcrumbCallback beforeBreadcrumb;

/** Invoked when some data from the SDK is dropped before being consumed by Sentry */
private @Nullable OnDiscardCallback onDiscard;

/** The cache dir. path for caching offline events */
private @Nullable String cacheDirPath;

Expand Down Expand Up @@ -904,6 +908,24 @@ public void setBeforeBreadcrumb(@Nullable BeforeBreadcrumbCallback beforeBreadcr
this.beforeBreadcrumb = beforeBreadcrumb;
}

/**
* Returns the onDiscard callback
*
* @return the onDiscard callback or null if not set
*/
public @Nullable OnDiscardCallback getOnDiscard() {
return onDiscard;
}

/**
* Sets the onDiscard callback
*
* @param onDiscard the onDiscard callback
*/
public void setOnDiscard(@Nullable OnDiscardCallback onDiscard) {
this.onDiscard = onDiscard;
}

/**
* Returns the cache dir. path if set
*
Expand Down Expand Up @@ -2982,6 +3004,20 @@ public interface BeforeBreadcrumbCallback {
Breadcrumb execute(@NotNull Breadcrumb breadcrumb, @NotNull Hint hint);
}

/** The OnDiscard callback */
public interface OnDiscardCallback {

/**
* Best-effort record of data discarded before reaching Sentry
*
* @param reason the reason data was dropped
* @param category the type of data discarded
* @param number the number of discarded data items
*/
void execute(
@NotNull DiscardReason reason, @NotNull DataCategory category, @NotNull Long number);
}

/** The traces sampler callback. */
public interface TracesSamplerCallback {

Expand Down
Loading
Loading