Skip to content

Commit 606e793

Browse files
authored
Merge e6e75dc into a416a65
2 parents a416a65 + e6e75dc commit 606e793

File tree

5 files changed

+739
-0
lines changed

5 files changed

+739
-0
lines changed

sentry/api/sentry.api

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3417,6 +3417,7 @@ public class io/sentry/SentryOptions {
34173417
public fun getMaxTraceFileSize ()J
34183418
public fun getModulesLoader ()Lio/sentry/internal/modules/IModulesLoader;
34193419
public fun getOnDiscard ()Lio/sentry/SentryOptions$OnDiscardCallback;
3420+
public fun getOnOversizedError ()Lio/sentry/SentryOptions$OnOversizedErrorCallback;
34203421
public fun getOpenTelemetryMode ()Lio/sentry/SentryOpenTelemetryMode;
34213422
public fun getOptionsObservers ()Ljava/util/List;
34223423
public fun getOutboxPath ()Ljava/lang/String;
@@ -3468,6 +3469,7 @@ public class io/sentry/SentryOptions {
34683469
public fun isEnableAutoSessionTracking ()Z
34693470
public fun isEnableBackpressureHandling ()Z
34703471
public fun isEnableDeduplication ()Z
3472+
public fun isEnableEventSizeLimiting ()Z
34713473
public fun isEnableExternalConfiguration ()Z
34723474
public fun isEnablePrettySerializationOutput ()Z
34733475
public fun isEnableScopePersistence ()Z
@@ -3524,6 +3526,7 @@ public class io/sentry/SentryOptions {
35243526
public fun setEnableAutoSessionTracking (Z)V
35253527
public fun setEnableBackpressureHandling (Z)V
35263528
public fun setEnableDeduplication (Z)V
3529+
public fun setEnableEventSizeLimiting (Z)V
35273530
public fun setEnableExternalConfiguration (Z)V
35283531
public fun setEnablePrettySerializationOutput (Z)V
35293532
public fun setEnableScopePersistence (Z)V
@@ -3566,6 +3569,7 @@ public class io/sentry/SentryOptions {
35663569
public fun setMaxTraceFileSize (J)V
35673570
public fun setModulesLoader (Lio/sentry/internal/modules/IModulesLoader;)V
35683571
public fun setOnDiscard (Lio/sentry/SentryOptions$OnDiscardCallback;)V
3572+
public fun setOnOversizedError (Lio/sentry/SentryOptions$OnOversizedErrorCallback;)V
35693573
public fun setOpenTelemetryMode (Lio/sentry/SentryOpenTelemetryMode;)V
35703574
public fun setPrintUncaughtStackTrace (Z)V
35713575
public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V
@@ -3676,6 +3680,10 @@ public abstract interface class io/sentry/SentryOptions$OnDiscardCallback {
36763680
public abstract fun execute (Lio/sentry/clientreport/DiscardReason;Lio/sentry/DataCategory;Ljava/lang/Long;)V
36773681
}
36783682

3683+
public abstract interface class io/sentry/SentryOptions$OnOversizedErrorCallback {
3684+
public abstract fun execute (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
3685+
}
3686+
36793687
public abstract interface class io/sentry/SentryOptions$ProfilesSamplerCallback {
36803688
public abstract fun sample (Lio/sentry/SamplingContext;)Ljava/lang/Double;
36813689
}
@@ -7124,6 +7132,10 @@ public final class io/sentry/util/EventProcessorUtils {
71247132
public static fun unwrap (Ljava/util/List;)Ljava/util/List;
71257133
}
71267134

7135+
public final class io/sentry/util/EventSizeLimitingUtils {
7136+
public static fun limitEventSize (Lio/sentry/SentryEvent;Lio/sentry/Hint;Lio/sentry/SentryOptions;)Lio/sentry/SentryEvent;
7137+
}
7138+
71277139
public final class io/sentry/util/ExceptionUtils {
71287140
public fun <init> ()V
71297141
public static fun findRootCause (Ljava/lang/Throwable;)Ljava/lang/Throwable;

sentry/src/main/java/io/sentry/SentryClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul
164164
}
165165
}
166166

167+
if (event != null) {
168+
event = EventSizeLimitingUtils.limitEventSize(event, hint, options);
169+
}
170+
167171
if (event == null) {
168172
return SentryId.EMPTY_ID;
169173
}

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,18 @@ public class SentryOptions {
353353
*/
354354
private boolean enableDeduplication = true;
355355

356+
/**
357+
* Enables event size limiting with {@link EventSizeLimitingEventProcessor}. When enabled, events
358+
* exceeding 1MB will have breadcrumbs and stack frames reduced to stay under the limit.
359+
*/
360+
private boolean enableEventSizeLimiting = false;
361+
362+
/**
363+
* Callback invoked when an oversized event is detected. This allows custom handling of oversized
364+
* events before the automatic reduction steps are applied.
365+
*/
366+
private @Nullable OnOversizedErrorCallback onOversizedError;
367+
356368
/** Maximum number of spans that can be atteched to single transaction. */
357369
private int maxSpans = 1000;
358370

@@ -1752,6 +1764,44 @@ public void setEnableDeduplication(final boolean enableDeduplication) {
17521764
this.enableDeduplication = enableDeduplication;
17531765
}
17541766

1767+
/**
1768+
* Returns if event size limiting is enabled.
1769+
*
1770+
* @return true if event size limiting is enabled, false otherwise
1771+
*/
1772+
public boolean isEnableEventSizeLimiting() {
1773+
return enableEventSizeLimiting;
1774+
}
1775+
1776+
/**
1777+
* Enables or disables event size limiting. When enabled, events exceeding 1MB will have
1778+
* breadcrumbs and stack frames reduced to stay under the limit.
1779+
*
1780+
* @param enableEventSizeLimiting true to enable, false to disable
1781+
*/
1782+
public void setEnableEventSizeLimiting(final boolean enableEventSizeLimiting) {
1783+
this.enableEventSizeLimiting = enableEventSizeLimiting;
1784+
}
1785+
1786+
/**
1787+
* Returns the onOversizedError callback.
1788+
*
1789+
* @return the onOversizedError callback or null if not set
1790+
*/
1791+
public @Nullable OnOversizedErrorCallback getOnOversizedError() {
1792+
return onOversizedError;
1793+
}
1794+
1795+
/**
1796+
* Sets the onOversizedError callback. This callback is invoked when an oversized event is
1797+
* detected, before the automatic reduction steps are applied.
1798+
*
1799+
* @param onOversizedError the onOversizedError callback
1800+
*/
1801+
public void setOnOversizedError(@Nullable OnOversizedErrorCallback onOversizedError) {
1802+
this.onOversizedError = onOversizedError;
1803+
}
1804+
17551805
/**
17561806
* Returns if tracing should be enabled. If tracing is disabled, starting transactions returns
17571807
* {@link NoOpTransaction}.
@@ -3136,6 +3186,21 @@ public interface BeforeBreadcrumbCallback {
31363186
Breadcrumb execute(@NotNull Breadcrumb breadcrumb, @NotNull Hint hint);
31373187
}
31383188

3189+
/** The OnOversizedError callback */
3190+
public interface OnOversizedErrorCallback {
3191+
3192+
/**
3193+
* Called when an oversized event is detected. This callback allows custom handling of oversized
3194+
* events before automatic reduction steps are applied.
3195+
*
3196+
* @param event the oversized event
3197+
* @param hint the hints
3198+
* @return the modified event (should ideally be reduced in size)
3199+
*/
3200+
@NotNull
3201+
SentryEvent execute(@NotNull SentryEvent event, @NotNull Hint hint);
3202+
}
3203+
31393204
/** The OnDiscard callback */
31403205
public interface OnDiscardCallback {
31413206

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package io.sentry.util;
2+
3+
import io.sentry.Breadcrumb;
4+
import io.sentry.Hint;
5+
import io.sentry.SentryEvent;
6+
import io.sentry.SentryLevel;
7+
import io.sentry.SentryOptions;
8+
import io.sentry.protocol.SentryException;
9+
import io.sentry.protocol.SentryStackFrame;
10+
import io.sentry.protocol.SentryStackTrace;
11+
import io.sentry.protocol.SentryThread;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import org.jetbrains.annotations.ApiStatus;
15+
import org.jetbrains.annotations.NotNull;
16+
import org.jetbrains.annotations.Nullable;
17+
18+
/**
19+
* Utility class that limits event size to 1MB by incrementally dropping fields when the event
20+
* exceeds the limit. This runs after beforeSend and right before sending the event.
21+
*
22+
* <p>Fields are reduced in order of least importance:
23+
*
24+
* <ol>
25+
* <li>All breadcrumbs
26+
* <li>Exception stack frames (keep 250 frames from start and 250 frames from end, removing
27+
* middle)
28+
* </ol>
29+
*
30+
* <p>Note: Extras, tags, threads, request data, debug meta, and contexts are preserved.
31+
*/
32+
@ApiStatus.Internal
33+
public final class EventSizeLimitingUtils {
34+
35+
private static final long MAX_EVENT_SIZE_BYTES = 1024 * 1024; // 1MB
36+
private static final int FRAMES_PER_SIDE = 250; // Keep 250 frames from start and 250 from end
37+
38+
private EventSizeLimitingUtils() {}
39+
40+
/**
41+
* Limits the size of an event by incrementally dropping fields when it exceeds the limit.
42+
*
43+
* @param event the event to limit
44+
* @param hint the hint
45+
* @param options the SentryOptions
46+
* @return the potentially reduced event
47+
*/
48+
public static @Nullable SentryEvent limitEventSize(
49+
final @NotNull SentryEvent event,
50+
final @NotNull Hint hint,
51+
final @NotNull SentryOptions options) {
52+
if (!options.isEnableEventSizeLimiting()) {
53+
return event;
54+
}
55+
56+
if (!isTooLarge(event, options)) {
57+
return event;
58+
}
59+
60+
long eventSize = byteSizeOf(event, options);
61+
options
62+
.getLogger()
63+
.log(
64+
SentryLevel.INFO,
65+
"Event size (%d bytes) exceeds %d bytes limit. Reducing size by dropping fields.",
66+
eventSize,
67+
MAX_EVENT_SIZE_BYTES);
68+
69+
SentryEvent reducedEvent = event;
70+
71+
// Step 0: Invoke custom callback if defined
72+
final SentryOptions.OnOversizedErrorCallback onOversizedError = options.getOnOversizedError();
73+
if (onOversizedError != null) {
74+
try {
75+
reducedEvent = onOversizedError.execute(reducedEvent, hint);
76+
if (!isTooLarge(reducedEvent, options)) {
77+
return reducedEvent;
78+
}
79+
} catch (Exception e) {
80+
options
81+
.getLogger()
82+
.log(
83+
SentryLevel.ERROR,
84+
"The onOversizedError callback threw an exception. It will be ignored and automatic reduction will continue.",
85+
e);
86+
// Continue with automatic reduction if callback fails
87+
reducedEvent = event;
88+
}
89+
}
90+
91+
// Step 1: Remove all breadcrumbs
92+
reducedEvent = removeAllBreadcrumbs(reducedEvent, options);
93+
if (!isTooLarge(reducedEvent, options)) {
94+
return reducedEvent;
95+
}
96+
97+
// Step 2: Truncate stack frames (keep 250 from start and 250 from end)
98+
reducedEvent = truncateStackFrames(reducedEvent, options);
99+
if (isTooLarge(reducedEvent, options)) {
100+
long finalEventSize = byteSizeOf(reducedEvent, options);
101+
options
102+
.getLogger()
103+
.log(
104+
SentryLevel.WARNING,
105+
"Event size (%d bytes) still exceeds limit after reducing all fields. Event may be rejected by server.",
106+
finalEventSize);
107+
}
108+
109+
return reducedEvent;
110+
}
111+
112+
/**
113+
* Checks if the event exceeds the size limit.
114+
*
115+
* @param event the event to check
116+
* @param options the SentryOptions
117+
* @return true if the event exceeds the size limit
118+
*/
119+
private static boolean isTooLarge(
120+
final @NotNull SentryEvent event, final @NotNull SentryOptions options) {
121+
return byteSizeOf(event, options) > MAX_EVENT_SIZE_BYTES;
122+
}
123+
124+
/** Calculates the size of the event when serialized to JSON without actually storing the data. */
125+
private static long byteSizeOf(
126+
final @NotNull SentryEvent event, final @NotNull SentryOptions options) {
127+
return JsonSerializationUtils.byteSizeOf(options.getSerializer(), options.getLogger(), event);
128+
}
129+
130+
private static @NotNull SentryEvent removeAllBreadcrumbs(
131+
final @NotNull SentryEvent event, final @NotNull SentryOptions options) {
132+
final List<Breadcrumb> breadcrumbs = event.getBreadcrumbs();
133+
if (breadcrumbs != null && !breadcrumbs.isEmpty()) {
134+
event.setBreadcrumbs(null);
135+
options
136+
.getLogger()
137+
.log(
138+
SentryLevel.DEBUG, "Removed %d breadcrumbs to reduce event size", breadcrumbs.size());
139+
}
140+
return event;
141+
}
142+
143+
private static @NotNull SentryEvent truncateStackFrames(
144+
final @NotNull SentryEvent event, final @NotNull SentryOptions options) {
145+
final List<SentryException> exceptions = event.getExceptions();
146+
if (exceptions != null) {
147+
for (final SentryException exception : exceptions) {
148+
final SentryStackTrace stacktrace = exception.getStacktrace();
149+
if (stacktrace != null) {
150+
final List<SentryStackFrame> frames = stacktrace.getFrames();
151+
if (frames != null && frames.size() > FRAMES_PER_SIDE * 2) {
152+
// Keep first 250 frames and last 250 frames, removing middle
153+
final List<SentryStackFrame> truncatedFrames = new ArrayList<>();
154+
truncatedFrames.addAll(frames.subList(0, FRAMES_PER_SIDE));
155+
truncatedFrames.addAll(frames.subList(frames.size() - FRAMES_PER_SIDE, frames.size()));
156+
stacktrace.setFrames(truncatedFrames);
157+
options
158+
.getLogger()
159+
.log(
160+
SentryLevel.DEBUG,
161+
"Truncated stack frames from %d to %d (removed middle) for exception %s",
162+
frames.size(),
163+
truncatedFrames.size(),
164+
exception.getType());
165+
}
166+
}
167+
}
168+
}
169+
170+
// Also truncate thread stack traces
171+
final List<SentryThread> threads = event.getThreads();
172+
if (threads != null) {
173+
for (final SentryThread thread : threads) {
174+
final SentryStackTrace stacktrace = thread.getStacktrace();
175+
if (stacktrace != null) {
176+
final List<SentryStackFrame> frames = stacktrace.getFrames();
177+
if (frames != null && frames.size() > FRAMES_PER_SIDE * 2) {
178+
// Keep first 250 frames and last 250 frames, removing middle
179+
final List<SentryStackFrame> truncatedFrames = new ArrayList<>();
180+
truncatedFrames.addAll(frames.subList(0, FRAMES_PER_SIDE));
181+
truncatedFrames.addAll(frames.subList(frames.size() - FRAMES_PER_SIDE, frames.size()));
182+
stacktrace.setFrames(truncatedFrames);
183+
options
184+
.getLogger()
185+
.log(
186+
SentryLevel.DEBUG,
187+
"Truncated stack frames from %d to %d (removed middle) for thread %d",
188+
frames.size(),
189+
truncatedFrames.size(),
190+
thread.getId());
191+
}
192+
}
193+
}
194+
}
195+
196+
return event;
197+
}
198+
}

0 commit comments

Comments
 (0)