Skip to content

Commit 72f1911

Browse files
Feat: Add client reports (#1982)
* Track lost events using client reports * Add feature to changelog * Add client report to envelope on FATAL event * Fix assertion message * Annotate non null for ClientReportKey * Update sentry/src/main/java/io/sentry/SentryOptions.java Co-authored-by: Manoel Aranda Neto <[email protected]> * Update CHANGELOG.md Co-authored-by: Manoel Aranda Neto <[email protected]> * Use primitive boolean for options * Code Review * Restore only client report counts from retryables on error * Rename ClientReportRecorder interface and Impl * Generate api files again * Add final keyword * Always use ClientReportRecorder via options * Do not restore client report from retryable on error Retryables happen on startup where counts are 0 so no need. Also it would have to prevent double restoring from persisted envelopes that retry multiple times. * Attach client report on event with DiskFlushNotification hint * Check for http status code >= 400 except 429 * Remove debug functionality from recorder and storage; add tests for storage * Remove init count for list * Api dump * Remove logger param from RateLimiter ctor * Rename ClientReportStorage interface to IClientReportRecorder * Change ClientReportRecorder from Singleton to single instance on options * Remove locking client report storage * Add internal annotations and move DataCategory Co-authored-by: Manoel Aranda Neto <[email protected]>
1 parent be0ebe0 commit 72f1911

38 files changed

+2283
-97
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* Feat: Add sentry-servlet-jakarta module (#1987)
66
* Ref: Make options.printUncaughtStackTrace primitive type (#1995)
7+
* Feat: Add client reports (#1982)
78

89
## 6.0.0-alpha.5
910

sentry-apache-http-client-5/src/main/java/io/sentry/transport/apache/ApacheHttpClientTransport.java

Lines changed: 89 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import io.sentry.SentryEnvelope;
77
import io.sentry.SentryLevel;
88
import io.sentry.SentryOptions;
9+
import io.sentry.clientreport.DiscardReason;
10+
import io.sentry.hints.Retryable;
911
import io.sentry.transport.ITransport;
1012
import io.sentry.transport.RateLimiter;
1113
import io.sentry.transport.ReusableCountLatch;
@@ -71,68 +73,100 @@ public void send(final @NotNull SentryEnvelope envelope, final @Nullable Map<Str
7173
final SentryEnvelope filteredEnvelope = rateLimiter.filter(envelope, sentrySdkHint);
7274

7375
if (filteredEnvelope != null) {
74-
currentlyRunning.increment();
75-
76-
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
77-
final GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
78-
options.getSerializer().serialize(filteredEnvelope, gzip);
79-
80-
final SimpleHttpRequest request =
81-
SimpleHttpRequests.post(requestDetails.getUrl().toString());
82-
request.setBody(
83-
outputStream.toByteArray(), ContentType.create("application/x-sentry-envelope"));
84-
request.setHeader("Content-Encoding", "gzip");
85-
request.setHeader("Accept", "application/json");
86-
87-
for (Map.Entry<String, String> header : requestDetails.getHeaders().entrySet()) {
88-
request.setHeader(header.getKey(), header.getValue());
89-
}
76+
final SentryEnvelope envelopeWithClientReport =
77+
options.getClientReportRecorder().attachReportToEnvelope(filteredEnvelope);
78+
79+
if (envelopeWithClientReport != null) {
80+
currentlyRunning.increment();
81+
82+
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
83+
final GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
84+
options.getSerializer().serialize(envelopeWithClientReport, gzip);
85+
86+
final SimpleHttpRequest request =
87+
SimpleHttpRequests.post(requestDetails.getUrl().toString());
88+
request.setBody(
89+
outputStream.toByteArray(), ContentType.create("application/x-sentry-envelope"));
90+
request.setHeader("Content-Encoding", "gzip");
91+
request.setHeader("Accept", "application/json");
92+
93+
for (Map.Entry<String, String> header : requestDetails.getHeaders().entrySet()) {
94+
request.setHeader(header.getKey(), header.getValue());
95+
}
96+
97+
if (options.getLogger().isEnabled(DEBUG)) {
98+
options
99+
.getLogger()
100+
.log(DEBUG, "Currently running %d requests", currentlyRunning.getCount());
101+
}
102+
103+
httpclient.execute(
104+
request,
105+
new FutureCallback<SimpleHttpResponse>() {
106+
@Override
107+
public void completed(SimpleHttpResponse response) {
108+
if (response.getCode() != 200) {
109+
options
110+
.getLogger()
111+
.log(ERROR, "Request failed, API returned %s", response.getCode());
112+
113+
if (response.getCode() >= 400 && response.getCode() != 429) {
114+
if (!(sentrySdkHint instanceof Retryable)) {
115+
options
116+
.getClientReportRecorder()
117+
.recordLostEnvelope(
118+
DiscardReason.NETWORK_ERROR, envelopeWithClientReport);
119+
}
120+
}
121+
} else {
122+
options.getLogger().log(INFO, "Envelope sent successfully.");
123+
}
124+
final Header retryAfter = response.getFirstHeader("Retry-After");
125+
final Header rateLimits = response.getFirstHeader("X-Sentry-Rate-Limits");
126+
rateLimiter.updateRetryAfterLimits(
127+
rateLimits != null ? rateLimits.getValue() : null,
128+
retryAfter != null ? retryAfter.getValue() : null,
129+
response.getCode());
130+
currentlyRunning.decrement();
131+
}
90132

91-
if (options.getLogger().isEnabled(DEBUG)) {
92-
options
93-
.getLogger()
94-
.log(DEBUG, "Currently running %d requests", currentlyRunning.getCount());
95-
}
133+
@Override
134+
public void failed(Exception ex) {
135+
options.getLogger().log(ERROR, "Error while sending an envelope", ex);
136+
if (!(sentrySdkHint instanceof Retryable)) {
137+
options
138+
.getClientReportRecorder()
139+
.recordLostEnvelope(
140+
DiscardReason.NETWORK_ERROR, envelopeWithClientReport);
141+
}
142+
currentlyRunning.decrement();
143+
}
96144

97-
httpclient.execute(
98-
request,
99-
new FutureCallback<SimpleHttpResponse>() {
100-
@Override
101-
public void completed(SimpleHttpResponse response) {
102-
if (response.getCode() != 200) {
103-
options
104-
.getLogger()
105-
.log(ERROR, "Request failed, API returned %s", response.getCode());
106-
} else {
107-
options.getLogger().log(INFO, "Envelope sent successfully.");
145+
@Override
146+
public void cancelled() {
147+
options.getLogger().log(WARNING, "Request cancelled");
148+
if (!(sentrySdkHint instanceof Retryable)) {
149+
options
150+
.getClientReportRecorder()
151+
.recordLostEnvelope(
152+
DiscardReason.NETWORK_ERROR, envelopeWithClientReport);
153+
}
154+
currentlyRunning.decrement();
108155
}
109-
final Header retryAfter = response.getFirstHeader("Retry-After");
110-
final Header rateLimits = response.getFirstHeader("X-Sentry-Rate-Limits");
111-
rateLimiter.updateRetryAfterLimits(
112-
rateLimits != null ? rateLimits.getValue() : null,
113-
retryAfter != null ? retryAfter.getValue() : null,
114-
response.getCode());
115-
currentlyRunning.decrement();
116-
}
117-
118-
@Override
119-
public void failed(Exception ex) {
120-
options.getLogger().log(ERROR, "Error while sending an envelope", ex);
121-
currentlyRunning.decrement();
122-
}
123-
124-
@Override
125-
public void cancelled() {
126-
options.getLogger().log(WARNING, "Request cancelled");
127-
currentlyRunning.decrement();
128-
}
129-
});
130-
} catch (Throwable e) {
131-
options.getLogger().log(ERROR, "Error when sending envelope", e);
156+
});
157+
} catch (Throwable e) {
158+
options.getLogger().log(ERROR, "Error when sending envelope", e);
159+
if (!(sentrySdkHint instanceof Retryable)) {
160+
options
161+
.getClientReportRecorder()
162+
.recordLostEnvelope(DiscardReason.NETWORK_ERROR, envelopeWithClientReport);
163+
}
164+
}
132165
}
133166
}
134167
} else {
135168
options.getLogger().log(SentryLevel.WARNING, "Submit cancelled");
169+
options.getClientReportRecorder().recordLostEnvelope(DiscardReason.QUEUE_OVERFLOW, envelope);
136170
}
137171
}
138172

sentry-apache-http-client-5/src/main/java/io/sentry/transport/apache/ApacheHttpClientTransportFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public ApacheHttpClientTransportFactory(final @NotNull TimeValue connectionTimeT
6262
.setResponseTimeout(options.getReadTimeoutMillis(), TimeUnit.MILLISECONDS)
6363
.build())
6464
.build();
65-
final RateLimiter rateLimiter = new RateLimiter(options.getLogger());
65+
final RateLimiter rateLimiter = new RateLimiter(options);
6666

6767
return new ApacheHttpClientTransport(options, requestDetails, httpclient, rateLimiter);
6868
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.sentry
2+
3+
import io.sentry.clientreport.IClientReportRecorder
4+
5+
class SentryOptionsManipulator {
6+
7+
companion object {
8+
fun setClientReportRecorder(options: SentryOptions, clientReportRecorder: IClientReportRecorder) {
9+
options.clientReportRecorder = clientReportRecorder
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)