Skip to content

Commit 73f341a

Browse files
authored
Merge 486fc42 into a416a65
2 parents a416a65 + 486fc42 commit 73f341a

File tree

5 files changed

+707
-0
lines changed

5 files changed

+707
-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: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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.
21+
*/
22+
@ApiStatus.Internal
23+
public final class EventSizeLimitingUtils {
24+
25+
private static final long MAX_EVENT_SIZE_BYTES = 1024 * 1024;
26+
private static final int FRAMES_PER_SIDE = 250;
27+
28+
private EventSizeLimitingUtils() {}
29+
30+
/**
31+
* Limits the size of an event by incrementally dropping fields when it exceeds the limit.
32+
*
33+
* @param event the event to limit
34+
* @param hint the hint
35+
* @param options the SentryOptions
36+
* @return the potentially reduced event
37+
*/
38+
public static @Nullable SentryEvent limitEventSize(
39+
final @NotNull SentryEvent event,
40+
final @NotNull Hint hint,
41+
final @NotNull SentryOptions options) {
42+
if (!options.isEnableEventSizeLimiting()) {
43+
return event;
44+
}
45+
46+
if (isSizeOk(event, options)) {
47+
return event;
48+
}
49+
50+
options
51+
.getLogger()
52+
.log(
53+
SentryLevel.INFO,
54+
"Event %s exceeds %d bytes limit. Reducing size by dropping fields.",
55+
event.getEventId(),
56+
MAX_EVENT_SIZE_BYTES);
57+
58+
@NotNull SentryEvent reducedEvent = event;
59+
60+
final @Nullable SentryOptions.OnOversizedErrorCallback callback = options.getOnOversizedError();
61+
if (callback != null) {
62+
try {
63+
reducedEvent = callback.execute(reducedEvent, hint);
64+
if (isSizeOk(reducedEvent, options)) {
65+
return reducedEvent;
66+
}
67+
} catch (Exception e) {
68+
options
69+
.getLogger()
70+
.log(
71+
SentryLevel.ERROR,
72+
"The onOversizedError callback threw an exception. It will be ignored and automatic reduction will continue.",
73+
e);
74+
reducedEvent = event;
75+
}
76+
}
77+
78+
reducedEvent = removeAllBreadcrumbs(reducedEvent, options);
79+
if (isSizeOk(reducedEvent, options)) {
80+
return reducedEvent;
81+
}
82+
83+
reducedEvent = truncateStackFrames(reducedEvent, options);
84+
if (!isSizeOk(reducedEvent, options)) {
85+
options
86+
.getLogger()
87+
.log(
88+
SentryLevel.WARNING,
89+
"Event %s still exceeds size limit after reducing all fields. Event may be rejected by server.",
90+
event.getEventId());
91+
}
92+
93+
return reducedEvent;
94+
}
95+
96+
private static boolean isSizeOk(
97+
final @NotNull SentryEvent event, final @NotNull SentryOptions options) {
98+
final long size =
99+
JsonSerializationUtils.byteSizeOf(options.getSerializer(), options.getLogger(), event);
100+
return size <= MAX_EVENT_SIZE_BYTES;
101+
}
102+
103+
private static @NotNull SentryEvent removeAllBreadcrumbs(
104+
final @NotNull SentryEvent event, final @NotNull SentryOptions options) {
105+
final List<Breadcrumb> breadcrumbs = event.getBreadcrumbs();
106+
if (breadcrumbs != null && !breadcrumbs.isEmpty()) {
107+
event.setBreadcrumbs(null);
108+
options
109+
.getLogger()
110+
.log(
111+
SentryLevel.DEBUG,
112+
"Removed breadcrumbs to reduce size of event %s",
113+
event.getEventId());
114+
}
115+
return event;
116+
}
117+
118+
private static @NotNull SentryEvent truncateStackFrames(
119+
final @NotNull SentryEvent event, final @NotNull SentryOptions options) {
120+
final @Nullable List<SentryException> exceptions = event.getExceptions();
121+
if (exceptions != null) {
122+
for (final @NotNull SentryException exception : exceptions) {
123+
final @Nullable SentryStackTrace stacktrace = exception.getStacktrace();
124+
if (stacktrace != null) {
125+
final @Nullable List<SentryStackFrame> frames = stacktrace.getFrames();
126+
if (frames != null && frames.size() > (FRAMES_PER_SIDE * 2)) {
127+
final @NotNull List<SentryStackFrame> truncatedFrames = new ArrayList<>();
128+
truncatedFrames.addAll(frames.subList(0, FRAMES_PER_SIDE));
129+
truncatedFrames.addAll(frames.subList(frames.size() - FRAMES_PER_SIDE, frames.size()));
130+
stacktrace.setFrames(truncatedFrames);
131+
options
132+
.getLogger()
133+
.log(
134+
SentryLevel.DEBUG,
135+
"Truncated exception stack frames of event %s",
136+
event.getEventId());
137+
}
138+
}
139+
}
140+
}
141+
142+
final @Nullable List<SentryThread> threads = event.getThreads();
143+
if (threads != null) {
144+
for (final SentryThread thread : threads) {
145+
final @Nullable SentryStackTrace stacktrace = thread.getStacktrace();
146+
if (stacktrace != null) {
147+
final @Nullable List<SentryStackFrame> frames = stacktrace.getFrames();
148+
if (frames != null && frames.size() > (FRAMES_PER_SIDE * 2)) {
149+
final @NotNull List<SentryStackFrame> truncatedFrames = new ArrayList<>();
150+
truncatedFrames.addAll(frames.subList(0, FRAMES_PER_SIDE));
151+
truncatedFrames.addAll(frames.subList(frames.size() - FRAMES_PER_SIDE, frames.size()));
152+
stacktrace.setFrames(truncatedFrames);
153+
options
154+
.getLogger()
155+
.log(
156+
SentryLevel.DEBUG,
157+
"Truncated thread stack frames for event %s",
158+
event.getEventId());
159+
}
160+
}
161+
}
162+
}
163+
164+
return event;
165+
}
166+
}

0 commit comments

Comments
 (0)