Skip to content

Commit 65d8e80

Browse files
committed
Add client report recorder and rate limiting case
1 parent 71a1fa0 commit 65d8e80

4 files changed

Lines changed: 143 additions & 0 deletions

File tree

sentry/src/main/java/io/sentry/clientreport/ClientReportRecorder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ private DataCategory categoryFromItemType(SentryItemType itemType) {
215215
if (SentryItemType.Log.equals(itemType)) {
216216
return DataCategory.LogItem;
217217
}
218+
if (SentryItemType.Span.equals(itemType)) {
219+
return DataCategory.Span;
220+
}
218221

219222
return DataCategory.Default;
220223
}

sentry/src/main/java/io/sentry/transport/RateLimiter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ private boolean isRetryAfter(final @NotNull String itemType) {
213213
return Collections.singletonList(DataCategory.Feedback);
214214
case "log":
215215
return Collections.singletonList(DataCategory.LogItem);
216+
case "span":
217+
return Collections.singletonList(DataCategory.Span);
216218
default:
217219
return Collections.singletonList(DataCategory.Unknown);
218220
}

sentry/src/test/java/io/sentry/protocol/SentryItemTypeSerializationTest.kt

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,103 @@ class SentryItemTypeSerializationTest {
7272
return SentryItemType.Deserializer().deserialize(reader, fixture.logger)
7373
}
7474
}
75+
76+
77+
78+
## 1. **Sentinel Object Pattern**
79+
80+
```dart
81+
// Create a private sentinel marker
82+
const _undefined = Object();
83+
84+
static Span startSpan(String name, {
85+
Map<String, SentryAttribute>? attributes,
86+
Object? parentSpan = _undefined, // Uses Object? to accept Span?, null, or undefined
87+
bool active = true,
88+
}) {
89+
final Span? actualParentSpan;
90+
if (identical(parentSpan, _undefined)) {
91+
// Not provided - use default behavior (e.g., get from current scope)
92+
actualParentSpan = _getCurrentSpan();
93+
} else {
94+
// Explicitly provided (could be null or a Span)
95+
actualParentSpan = parentSpan as Span?;
96+
}
97+
// ...
98+
}
99+
```
100+
101+
## 2. **Wrapper Class (Optional/Maybe)**
102+
103+
```dart
104+
class Optional<T> {
105+
final T? value;
106+
final bool isSet;
107+
108+
const Optional.absent() : value = null, isSet = false;
109+
const Optional.of(this.value) : isSet = true;
110+
}
111+
112+
static Span startSpan(String name, {
113+
Optional<Span> parentSpan = const Optional.absent(),
114+
}) {
115+
if (parentSpan.isSet) {
116+
// Explicitly provided (value could be null or a Span)
117+
} else {
118+
// Not provided
119+
}
120+
}
121+
```
122+
123+
## 3. **Sealed Classes (Dart 3+)**Most Type-Safe
124+
125+
```dart
126+
sealed class ParentSpanOption {}
127+
class UseCurrentSpan extends ParentSpanOption {} // undefined - use default
128+
class ExplicitSpan extends ParentSpanOption {
129+
final Span? span; // can be null or a span
130+
ExplicitSpan(this.span);
131+
}
132+
133+
static Span startSpan(String name, {
134+
ParentSpanOption parentSpan = const UseCurrentSpan(),
135+
}) {
136+
switch (parentSpan) {
137+
case UseCurrentSpan():
138+
// Not provided - use default behavior
139+
case ExplicitSpan(span: final span):
140+
// Explicitly provided (span or null)
141+
}
142+
}
143+
```
144+
145+
## 4. **Separate Boolean Flag**
146+
147+
```dart
148+
static Span startSpan(String name, {
149+
Span? parentSpan,
150+
bool parentSpanProvided = false, // true means parentSpan was explicitly set
151+
bool active = true,
152+
}) {
153+
if (parentSpanProvided) {
154+
// Use parentSpan (even if null)
155+
} else {
156+
// Not provided - use default
157+
}
158+
}
159+
```
160+
161+
## 5. **Record Type (Dart 3+)**
162+
163+
```dart
164+
static Span startSpan(String name, {
165+
(bool isSet, Span? value)? parentSpan, // null = undefined, (true, x) = explicit
166+
}) {
167+
if (parentSpan == null) {
168+
// undefined
169+
} else {
170+
final (_, span) = parentSpan;
171+
// explicitly set to `span` (which could be null)
172+
}
173+
}
174+
```

sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import io.sentry.ReplayRecording
1515
import io.sentry.SentryEnvelope
1616
import io.sentry.SentryEnvelopeHeader
1717
import io.sentry.SentryEnvelopeItem
18+
import io.sentry.SentryEnvelopeItemHeader
1819
import io.sentry.SentryEvent
20+
import io.sentry.SentryItemType
1921
import io.sentry.SentryLogEvent
2022
import io.sentry.SentryLogEvents
2123
import io.sentry.SentryLogLevel
@@ -534,6 +536,42 @@ class RateLimiterTest {
534536
verifyNoMoreInteractions(fixture.clientReportRecorder)
535537
}
536538

539+
@Test
540+
fun `drop span items as lost`() {
541+
val rateLimiter = fixture.getSUT()
542+
543+
// There is no span API yet so we'll create the envelope manually
544+
val spanItemHeader =
545+
SentryEnvelopeItemHeader(
546+
SentryItemType.Span,
547+
10,
548+
"application/vnd.sentry.items.span.v2+json",
549+
null,
550+
null,
551+
null,
552+
1,
553+
)
554+
val spanItem = SentryEnvelopeItem(spanItemHeader, ByteArray(10))
555+
val attachmentItem =
556+
SentryEnvelopeItem.fromAttachment(
557+
fixture.serializer,
558+
NoOpLogger.getInstance(),
559+
Attachment("{ \"number\": 10 }".toByteArray(), "log.json"),
560+
1000,
561+
)
562+
val envelope = SentryEnvelope(SentryEnvelopeHeader(), arrayListOf(spanItem, attachmentItem))
563+
564+
rateLimiter.updateRetryAfterLimits("60:span:key", null, 1)
565+
val result = rateLimiter.filter(envelope, Hint())
566+
567+
assertNotNull(result)
568+
assertEquals(1, result.items.toList().size)
569+
570+
verify(fixture.clientReportRecorder, times(1))
571+
.recordLostEnvelopeItem(eq(DiscardReason.RATELIMIT_BACKOFF), same(spanItem))
572+
verifyNoMoreInteractions(fixture.clientReportRecorder)
573+
}
574+
537575
@Test
538576
fun `apply rate limits notifies observers`() {
539577
val rateLimiter = fixture.getSUT()

0 commit comments

Comments
 (0)