Skip to content

Commit bc8caf7

Browse files
committed
Add batch processor for logs
1 parent 0c75319 commit bc8caf7

11 files changed

Lines changed: 213 additions & 24 deletions

File tree

sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.sentry.Sentry
1717
import io.sentry.SentryEnvelope
1818
import io.sentry.SentryEvent
1919
import io.sentry.SentryLogEvent
20+
import io.sentry.SentryLogEvents
2021
import io.sentry.SentryOptions
2122
import io.sentry.SentryReplayEvent
2223
import io.sentry.Session
@@ -190,6 +191,10 @@ class SessionTrackingIntegrationTest {
190191
TODO("Not yet implemented")
191192
}
192193

194+
override fun captureBatchedLogEvents(logEvents: SentryLogEvents) {
195+
TODO("Not yet implemented")
196+
}
197+
193198
override fun getRateLimiter(): RateLimiter? {
194199
TODO("Not yet implemented")
195200
}

sentry/api/sentry.api

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,7 @@ public abstract interface class io/sentry/IScopesStorage {
987987
}
988988

989989
public abstract interface class io/sentry/ISentryClient {
990+
public abstract fun captureBatchedLogEvents (Lio/sentry/SentryLogEvents;)V
990991
public abstract fun captureCheckIn (Lio/sentry/CheckIn;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
991992
public fun captureEnvelope (Lio/sentry/SentryEnvelope;)Lio/sentry/protocol/SentryId;
992993
public abstract fun captureEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
@@ -2718,6 +2719,7 @@ public final class io/sentry/SentryBaseEvent$Serializer {
27182719

27192720
public final class io/sentry/SentryClient : io/sentry/ISentryClient {
27202721
public fun <init> (Lio/sentry/SentryOptions;)V
2722+
public fun captureBatchedLogEvents (Lio/sentry/SentryLogEvents;)V
27212723
public fun captureCheckIn (Lio/sentry/CheckIn;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
27222724
public fun captureEnvelope (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
27232725
public fun captureEvent (Lio/sentry/SentryEvent;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId;
@@ -3071,6 +3073,7 @@ public final class io/sentry/SentryLogEventAttributeValue$JsonKeys {
30713073

30723074
public final class io/sentry/SentryLogEvents : io/sentry/JsonSerializable, io/sentry/JsonUnknown {
30733075
public fun <init> (Ljava/util/List;)V
3076+
public fun getItems ()Ljava/util/List;
30743077
public fun getUnknown ()Ljava/util/Map;
30753078
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
30763079
public fun setUnknown (Ljava/util/Map;)V
@@ -4666,6 +4669,11 @@ public abstract interface class io/sentry/logger/ILoggerApi {
46664669
public abstract fun warn (Ljava/lang/String;[Ljava/lang/Object;)V
46674670
}
46684671

4672+
public abstract interface class io/sentry/logger/ILoggerBatchProcessor {
4673+
public abstract fun add (Lio/sentry/SentryLogEvent;)V
4674+
public abstract fun close (Z)V
4675+
}
4676+
46694677
public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi {
46704678
public fun <init> (Lio/sentry/Scopes;)V
46714679
public fun debug (Ljava/lang/String;[Ljava/lang/Object;)V
@@ -4678,6 +4686,14 @@ public final class io/sentry/logger/LoggerApi : io/sentry/logger/ILoggerApi {
46784686
public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V
46794687
}
46804688

4689+
public final class io/sentry/logger/LoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor {
4690+
public static final field FLUSH_AFTER_MS I
4691+
public static final field MAX_BATCH_SIZE I
4692+
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/ISentryClient;)V
4693+
public fun add (Lio/sentry/SentryLogEvent;)V
4694+
public fun close (Z)V
4695+
}
4696+
46814697
public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi {
46824698
public fun debug (Ljava/lang/String;[Ljava/lang/Object;)V
46834699
public fun error (Ljava/lang/String;[Ljava/lang/Object;)V
@@ -4690,6 +4706,12 @@ public final class io/sentry/logger/NoOpLoggerApi : io/sentry/logger/ILoggerApi
46904706
public fun warn (Ljava/lang/String;[Ljava/lang/Object;)V
46914707
}
46924708

4709+
public final class io/sentry/logger/NoOpLoggerBatchProcessor : io/sentry/logger/ILoggerBatchProcessor {
4710+
public fun add (Lio/sentry/SentryLogEvent;)V
4711+
public fun close (Z)V
4712+
public static fun getInstance ()Lio/sentry/logger/NoOpLoggerBatchProcessor;
4713+
}
4714+
46934715
public final class io/sentry/opentelemetry/OpenTelemetryUtil {
46944716
public fun <init> ()V
46954717
public static fun applyIgnoredSpanOrigins (Lio/sentry/SentryOptions;)V

sentry/src/main/java/io/sentry/ISentryClient.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ SentryId captureProfileChunk(
295295
@ApiStatus.Experimental
296296
void captureLog(@NotNull SentryLogEvent logEvent, @Nullable IScope scope, @Nullable Hint hint);
297297

298+
@ApiStatus.Experimental
299+
void captureBatchedLogEvents(@NotNull SentryLogEvents logEvents);
300+
298301
@ApiStatus.Internal
299302
@Nullable
300303
RateLimiter getRateLimiter();

sentry/src/main/java/io/sentry/NoOpSentryClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ public void captureLog(
8484
// do nothing
8585
}
8686

87+
@ApiStatus.Experimental
88+
@Override
89+
public void captureBatchedLogEvents(@NotNull SentryLogEvents logEvents) {
90+
// do nothing
91+
}
92+
8793
@Override
8894
public @Nullable RateLimiter getRateLimiter() {
8995
return null;

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

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import io.sentry.hints.Backfillable;
77
import io.sentry.hints.DiskFlushNotification;
88
import io.sentry.hints.TransactionEnd;
9+
import io.sentry.logger.ILoggerBatchProcessor;
10+
import io.sentry.logger.LoggerBatchProcessor;
11+
import io.sentry.logger.NoOpLoggerBatchProcessor;
912
import io.sentry.protocol.Contexts;
1013
import io.sentry.protocol.DebugMeta;
1114
import io.sentry.protocol.SentryId;
@@ -16,7 +19,6 @@
1619
import java.io.Closeable;
1720
import java.io.IOException;
1821
import java.util.ArrayList;
19-
import java.util.Arrays;
2022
import java.util.Collection;
2123
import java.util.Collections;
2224
import java.util.Comparator;
@@ -36,6 +38,7 @@ public final class SentryClient implements ISentryClient {
3638
private final @NotNull SentryOptions options;
3739
private final @NotNull ITransport transport;
3840
private final @NotNull SortBreadcrumbsByDate sortBreadcrumbsByDate = new SortBreadcrumbsByDate();
41+
private final @NotNull ILoggerBatchProcessor loggerBatchProcessor;
3942

4043
@Override
4144
public boolean isEnabled() {
@@ -55,6 +58,11 @@ public SentryClient(final @NotNull SentryOptions options) {
5558

5659
final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options);
5760
transport = transportFactory.create(options, requestDetailsResolver.resolve());
61+
if (options.getExperimental().getLogs().isEnabled()) {
62+
loggerBatchProcessor = new LoggerBatchProcessor(options, this);
63+
} else {
64+
loggerBatchProcessor = NoOpLoggerBatchProcessor.getInstance();
65+
}
5866
}
5967

6068
private boolean shouldApplyScopeData(
@@ -625,16 +633,15 @@ public void captureUserFeedback(final @NotNull UserFeedback userFeedback) {
625633
return new SentryEnvelope(envelopeHeader, envelopeItems);
626634
}
627635

628-
private @NotNull SentryEnvelope buildEnvelope(
629-
final @NotNull SentryLogEvents logEvents, final @Nullable TraceContext traceContext) {
636+
private @NotNull SentryEnvelope buildEnvelope(final @NotNull SentryLogEvents logEvents) {
630637
final List<SentryEnvelopeItem> envelopeItems = new ArrayList<>();
631638

632639
final SentryEnvelopeItem logItem =
633640
SentryEnvelopeItem.fromLogs(options.getSerializer(), logEvents);
634641
envelopeItems.add(logItem);
635642

636643
final SentryEnvelopeHeader envelopeHeader =
637-
new SentryEnvelopeHeader(null, options.getSdkVersion(), traceContext);
644+
new SentryEnvelopeHeader(null, options.getSdkVersion(), null);
638645

639646
return new SentryEnvelope(envelopeHeader, envelopeItems);
640647
}
@@ -1018,17 +1025,17 @@ public void captureLog(
10181025
hint = new Hint();
10191026
}
10201027

1021-
@Nullable TraceContext traceContext = null;
1022-
if (scope != null) {
1023-
final @Nullable ITransaction transaction = scope.getTransaction();
1024-
if (transaction != null) {
1025-
traceContext = transaction.traceContext();
1026-
} else {
1027-
final @NotNull PropagationContext propagationContext =
1028-
TracingUtils.maybeUpdateBaggage(scope, options);
1029-
traceContext = propagationContext.traceContext();
1030-
}
1031-
}
1028+
// @Nullable TraceContext traceContext = null;
1029+
// if (scope != null) {
1030+
// final @Nullable ITransaction transaction = scope.getTransaction();
1031+
// if (transaction != null) {
1032+
// traceContext = transaction.traceContext();
1033+
// } else {
1034+
// final @NotNull PropagationContext propagationContext =
1035+
// TracingUtils.maybeUpdateBaggage(scope, options);
1036+
// traceContext = propagationContext.traceContext();
1037+
// }
1038+
// }
10321039

10331040
if (logEvent != null) {
10341041
logEvent = executeBeforeSendLog(logEvent, hint);
@@ -1040,15 +1047,18 @@ public void captureLog(
10401047
.recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.LogItem);
10411048
return;
10421049
}
1050+
1051+
loggerBatchProcessor.add(logEvent);
10431052
}
10441053

1045-
try {
1046-
final @NotNull SentryEnvelope envelope =
1047-
buildEnvelope(new SentryLogEvents(Arrays.asList(logEvent)), traceContext);
1054+
hint.clear();
1055+
}
10481056

1049-
hint.clear();
1050-
// TODO buffer
1051-
sendEnvelope(envelope, hint);
1057+
@Override
1058+
public void captureBatchedLogEvents(final @NotNull SentryLogEvents logEvents) {
1059+
try {
1060+
final @NotNull SentryEnvelope envelope = buildEnvelope(logEvents);
1061+
sendEnvelope(envelope, null);
10521062
} catch (IOException e) {
10531063
options.getLogger().log(SentryLevel.WARNING, e, "Capturing log failed.");
10541064
}
@@ -1307,6 +1317,7 @@ public void close(final boolean isRestarting) {
13071317
options.getLogger().log(SentryLevel.INFO, "Closing SentryClient.");
13081318
try {
13091319
flush(isRestarting ? 0 : options.getShutdownTimeoutMillis());
1320+
loggerBatchProcessor.close(isRestarting);
13101321
transport.close(isRestarting);
13111322
} catch (IOException e) {
13121323
options

sentry/src/main/java/io/sentry/SentryEnvelopeItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ public static SentryEnvelopeItem fromLogs(
503503
null,
504504
null,
505505
null,
506-
1);
506+
logEvents.getItems().size());
507507

508508
// avoid method refs on Android due to some issues with older AGP setups
509509
// noinspection Convert2MethodRef

sentry/src/main/java/io/sentry/SentryLogEvents.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public SentryLogEvents(final @NotNull List<SentryLogEvent> items) {
1717
this.items = items;
1818
}
1919

20+
public @NotNull List<SentryLogEvent> getItems() {
21+
return items;
22+
}
23+
2024
// region json
2125
public static final class JsonKeys {
2226
public static final String ITEMS = "items";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.sentry.logger;
2+
3+
import io.sentry.SentryLogEvent;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
public interface ILoggerBatchProcessor {
7+
void add(@NotNull SentryLogEvent event);
8+
9+
void close(boolean isRestarting);
10+
}

sentry/src/main/java/io/sentry/logger/LoggerApi.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public LoggerApi(final @NotNull Scopes scopes) {
3232

3333
@Override
3434
public void trace(final @Nullable String message, final @Nullable Object... args) {
35-
// TODO SentryLevel.TRACE does not exists yet
36-
// log(SentryLevel.TRACE, message, args);
35+
// TODO SentryLevel.TRACE does not exists yet so we just report it as DEBUG for now
36+
log(SentryLevel.DEBUG, message, args);
3737
}
3838

3939
@Override
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.sentry.logger;
2+
3+
import io.sentry.ISentryClient;
4+
import io.sentry.ISentryLifecycleToken;
5+
import io.sentry.SentryLogEvent;
6+
import io.sentry.SentryLogEvents;
7+
import io.sentry.SentryOptions;
8+
import io.sentry.util.AutoClosableReentrantLock;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Queue;
12+
import java.util.concurrent.ConcurrentLinkedQueue;
13+
import java.util.concurrent.Future;
14+
import org.jetbrains.annotations.NotNull;
15+
import org.jetbrains.annotations.Nullable;
16+
17+
public final class LoggerBatchProcessor implements ILoggerBatchProcessor {
18+
19+
public static final int FLUSH_AFTER_MS = 5000;
20+
public static final int MAX_BATCH_SIZE = 100;
21+
22+
private final @NotNull SentryOptions options;
23+
private final @NotNull ISentryClient client;
24+
private final @NotNull Queue<SentryLogEvent> queue;
25+
private volatile @Nullable Future<?> scheduledFlush;
26+
private static final @NotNull AutoClosableReentrantLock scheduleLock =
27+
new AutoClosableReentrantLock();
28+
29+
public LoggerBatchProcessor(
30+
final @NotNull SentryOptions options, final @NotNull ISentryClient client) {
31+
this.options = options;
32+
this.client = client;
33+
this.queue = new ConcurrentLinkedQueue<>();
34+
}
35+
36+
@Override
37+
public void add(final @NotNull SentryLogEvent logEvent) {
38+
queue.offer(logEvent);
39+
maybeSchedule(false, false);
40+
}
41+
42+
@Override
43+
public void close(final boolean isRestarting) {
44+
if (isRestarting) {
45+
maybeSchedule(true, true);
46+
} else {
47+
while (!queue.isEmpty()) {
48+
flushBatch();
49+
}
50+
}
51+
}
52+
53+
private void maybeSchedule(boolean forceSchedule, boolean immediately) {
54+
try (final @NotNull ISentryLifecycleToken ignored = scheduleLock.acquire()) {
55+
final @Nullable Future<?> latestScheduledFlush = scheduledFlush;
56+
if (forceSchedule
57+
|| latestScheduledFlush == null
58+
|| latestScheduledFlush.isDone()
59+
|| latestScheduledFlush.isCancelled()) {
60+
final int flushAfterMs = immediately ? 0 : FLUSH_AFTER_MS;
61+
scheduledFlush = options.getExecutorService().schedule(new BatchRunnable(), flushAfterMs);
62+
}
63+
}
64+
}
65+
66+
private void flush() {
67+
flushInternal();
68+
try (final @NotNull ISentryLifecycleToken ignored = scheduleLock.acquire()) {
69+
if (!queue.isEmpty()) {
70+
maybeSchedule(true, false);
71+
}
72+
}
73+
}
74+
75+
private void flushInternal() {
76+
flushBatch();
77+
if (queue.size() >= MAX_BATCH_SIZE) {
78+
flushInternal();
79+
}
80+
}
81+
82+
private void flushBatch() {
83+
final @NotNull List<SentryLogEvent> logEvents = new ArrayList<>(MAX_BATCH_SIZE);
84+
do {
85+
final @Nullable SentryLogEvent logEvent = queue.poll();
86+
if (logEvent != null) {
87+
logEvents.add(logEvent);
88+
}
89+
} while (!queue.isEmpty() && logEvents.size() < MAX_BATCH_SIZE);
90+
91+
client.captureBatchedLogEvents(new SentryLogEvents(logEvents));
92+
}
93+
94+
private class BatchRunnable implements Runnable {
95+
96+
@Override
97+
public void run() {
98+
flush();
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)