Skip to content

Commit eb7bd56

Browse files
authored
Merge branch 'main' into markushi/feat/log-timber-tags
2 parents 53554e1 + 2124a46 commit eb7bd56

File tree

8 files changed

+224
-75
lines changed

8 files changed

+224
-75
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# Changelog
22

3-
## Unreleased
3+
## 8.25.0
44

55
### Fixes
66

77
- [ANR] Removed AndroidTransactionProfiler lock ([#4817](https://github.com/getsentry/sentry-java/pull/4817))
8+
- Avoid ExecutorService for DefaultCompositePerformanceCollector timeout ([#4841](https://github.com/getsentry/sentry-java/pull/4841))
9+
- This avoids infinite data collection for never stopped transactions, leading to OOMs
10+
- Fix wrong .super() call in SentryTimberTree ([#4844](https://github.com/getsentry/sentry-java/pull/4844))
811

912
### Improvements
1013

1114
- [ANR] Defer some class availability checks ([#4825](https://github.com/getsentry/sentry-java/pull/4825))
15+
- Collect PerformanceCollectionData only for sampled transactions ([#4834](https://github.com/getsentry/sentry-java/pull/4834))
16+
- **Breaking change**: Transactions with a deferred sampling decision (`sampled == null`) won't be collecting any performance data anymore (CPU, RAM, slow/frozen frames).
1217
- Report Timber.tag() as `timber.tag` log attribute ([#4845](https://github.com/getsentry/sentry-java/pull/4845))
1318

1419
### Dependencies

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
1111
android.useAndroidX=true
1212

1313
# Release information
14-
versionName=8.24.0
14+
versionName=8.25.0
1515

1616
# Override the SDK name on native crashes on Android
1717
sentryAndroidSdkName=sentry.native.android

sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public class SentryTimberTree(
6868

6969
/** Log an info message with optional format args. */
7070
override fun i(message: String?, vararg args: Any?) {
71-
super.d(message, *args)
71+
super.i(message, *args)
7272
logWithSentry(Log.INFO, null, message, *args)
7373
}
7474

sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.android.timber
22

3+
import android.util.Log
34
import io.sentry.Breadcrumb
45
import io.sentry.Scopes
56
import io.sentry.SentryLevel
@@ -23,18 +24,19 @@ import timber.log.Timber
2324

2425
class SentryTimberTreeTest {
2526
private class Fixture {
26-
val scopes = mock<Scopes>()
27-
val logs = mock<ILoggerApi>()
28-
29-
init {
30-
whenever(scopes.logger()).thenReturn(logs)
31-
}
27+
lateinit var scopes: Scopes
28+
lateinit var logs: ILoggerApi
3229

3330
fun getSut(
3431
minEventLevel: SentryLevel = SentryLevel.ERROR,
3532
minBreadcrumbLevel: SentryLevel = SentryLevel.INFO,
3633
minLogsLevel: SentryLogLevel = SentryLogLevel.INFO,
37-
): SentryTimberTree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel)
34+
): SentryTimberTree {
35+
logs = mock<ILoggerApi>()
36+
scopes = mock<Scopes>()
37+
whenever(scopes.logger()).thenReturn(logs)
38+
return SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel)
39+
}
3840
}
3941

4042
private val fixture = Fixture()
@@ -139,6 +141,56 @@ class SentryTimberTreeTest {
139141
verify(fixture.scopes).captureEvent(check { assertEquals("tag", it.getTag("TimberTag")) })
140142
}
141143

144+
@Test
145+
fun `Tree captures an event with TimberTag tag for debug events`() {
146+
val sut = fixture.getSut(minEventLevel = SentryLevel.INFO)
147+
Timber.plant(sut)
148+
// only available thru static class
149+
Timber.tag("infoTag").i("message")
150+
verify(fixture.scopes).captureEvent(check { assertEquals("infoTag", it.getTag("TimberTag")) })
151+
}
152+
153+
@Test
154+
fun `Tree captures an event with chained tag usage`() {
155+
val sut = fixture.getSut(minEventLevel = SentryLevel.INFO)
156+
Timber.plant(sut)
157+
// only available thru static class
158+
Timber.tag("infoTag").log(Log.INFO, "message")
159+
verify(fixture.scopes).captureEvent(check { assertEquals("infoTag", it.getTag("TimberTag")) })
160+
}
161+
162+
@Test
163+
fun `Tree properly propagates all levels`() {
164+
val levels =
165+
listOf(
166+
Pair(Log.DEBUG, SentryLevel.DEBUG),
167+
Pair(Log.VERBOSE, SentryLevel.DEBUG),
168+
Pair(Log.INFO, SentryLevel.INFO),
169+
Pair(Log.WARN, SentryLevel.WARNING),
170+
Pair(Log.ERROR, SentryLevel.ERROR),
171+
Pair(Log.ASSERT, SentryLevel.FATAL),
172+
)
173+
174+
for (level in levels) {
175+
Timber.uprootAll()
176+
177+
val logLevel = level.first
178+
val sentryLevel = level.second
179+
180+
val sut = fixture.getSut(minEventLevel = sentryLevel)
181+
Timber.plant(sut)
182+
// only available thru static class
183+
Timber.tag("tag").log(logLevel, "message")
184+
verify(fixture.scopes)
185+
.captureEvent(
186+
check {
187+
assertEquals("tag", it.getTag("TimberTag"))
188+
assertEquals(sentryLevel, it.level)
189+
}
190+
)
191+
}
192+
}
193+
142194
@Test
143195
fun `Tree captures an event without TimberTag tag`() {
144196
val sut = fixture.getSut()

sentry/src/main/java/io/sentry/DefaultCompositePerformanceCollector.java

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import java.util.Timer;
99
import java.util.TimerTask;
1010
import java.util.concurrent.ConcurrentHashMap;
11-
import java.util.concurrent.RejectedExecutionException;
11+
import java.util.concurrent.TimeUnit;
1212
import java.util.concurrent.atomic.AtomicBoolean;
1313
import org.jetbrains.annotations.ApiStatus;
1414
import org.jetbrains.annotations.NotNull;
@@ -20,8 +20,7 @@ public final class DefaultCompositePerformanceCollector implements CompositePerf
2020
private static final long TRANSACTION_COLLECTION_TIMEOUT_MILLIS = 30000;
2121
private final @NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock();
2222
private volatile @Nullable Timer timer = null;
23-
private final @NotNull Map<String, List<PerformanceCollectionData>> performanceDataMap =
24-
new ConcurrentHashMap<>();
23+
private final @NotNull Map<String, CompositeData> compositeDataMap = new ConcurrentHashMap<>();
2524
private final @NotNull List<IPerformanceSnapshotCollector> snapshotCollectors;
2625
private final @NotNull List<IPerformanceContinuousCollector> continuousCollectors;
2726
private final boolean hasNoCollectors;
@@ -65,23 +64,11 @@ public void start(final @NotNull ITransaction transaction) {
6564
collector.onSpanStarted(transaction);
6665
}
6766

68-
if (!performanceDataMap.containsKey(transaction.getEventId().toString())) {
69-
performanceDataMap.put(transaction.getEventId().toString(), new ArrayList<>());
70-
// We schedule deletion of collected performance data after a timeout
71-
try {
72-
options
73-
.getExecutorService()
74-
.schedule(() -> stop(transaction), TRANSACTION_COLLECTION_TIMEOUT_MILLIS);
75-
} catch (RejectedExecutionException e) {
76-
options
77-
.getLogger()
78-
.log(
79-
SentryLevel.ERROR,
80-
"Failed to call the executor. Performance collector will not be automatically finished. Did you call Sentry.close()?",
81-
e);
82-
}
67+
final @NotNull String id = transaction.getEventId().toString();
68+
if (!compositeDataMap.containsKey(id)) {
69+
compositeDataMap.put(id, new CompositeData(transaction));
8370
}
84-
start(transaction.getEventId().toString());
71+
start(id);
8572
}
8673

8774
@Override
@@ -95,8 +82,10 @@ public void start(final @NotNull String id) {
9582
return;
9683
}
9784

98-
if (!performanceDataMap.containsKey(id)) {
99-
performanceDataMap.put(id, new ArrayList<>());
85+
if (!compositeDataMap.containsKey(id)) {
86+
// Transactions are added in start(ITransaction). If we are here, it means we don't come from
87+
// a transaction
88+
compositeDataMap.put(id, new CompositeData(null));
10089
}
10190
if (!isStarted.getAndSet(true)) {
10291
try (final @NotNull ISentryLifecycleToken ignored = timerLock.acquire()) {
@@ -118,6 +107,7 @@ public void run() {
118107
// and collect() calls.
119108
// This way ICollectors that collect average stats based on time intervals, like
120109
// AndroidCpuCollector, can have an actual time interval to evaluate.
110+
final @NotNull List<ITransaction> timedOutTransactions = new ArrayList<>();
121111
TimerTask timerTask =
122112
new TimerTask() {
123113
@Override
@@ -129,16 +119,31 @@ public void run() {
129119
if (now - lastCollectionTimestamp <= 10) {
130120
return;
131121
}
122+
timedOutTransactions.clear();
123+
132124
lastCollectionTimestamp = now;
133125
final @NotNull PerformanceCollectionData tempData =
134-
new PerformanceCollectionData(new SentryNanotimeDate().nanoTimestamp());
126+
new PerformanceCollectionData(options.getDateProvider().now().nanoTimestamp());
135127

128+
// Enrich tempData using collectors
136129
for (IPerformanceSnapshotCollector collector : snapshotCollectors) {
137130
collector.collect(tempData);
138131
}
139132

140-
for (List<PerformanceCollectionData> data : performanceDataMap.values()) {
141-
data.add(tempData);
133+
// Add the enriched tempData to all transactions/profiles/objects that collect data.
134+
// Then Check if that object timed out.
135+
for (CompositeData data : compositeDataMap.values()) {
136+
if (data.addDataAndCheckTimeout(tempData)) {
137+
// timed out
138+
if (data.transaction != null) {
139+
timedOutTransactions.add(data.transaction);
140+
}
141+
}
142+
}
143+
// Stop timed out transactions outside compositeDataMap loop, as stop() modifies the
144+
// map
145+
for (final @NotNull ITransaction t : timedOutTransactions) {
146+
stop(t);
142147
}
143148
}
144149
};
@@ -183,13 +188,14 @@ public void onSpanFinished(@NotNull ISpan span) {
183188

184189
@Override
185190
public @Nullable List<PerformanceCollectionData> stop(final @NotNull String id) {
186-
final @Nullable List<PerformanceCollectionData> data = performanceDataMap.remove(id);
191+
final @Nullable CompositeData data = compositeDataMap.remove(id);
192+
options.getLogger().log(SentryLevel.DEBUG, "stop collecting performance info for " + id);
187193

188-
// close if they are no more running requests
189-
if (performanceDataMap.isEmpty()) {
194+
// close if there are no more running requests
195+
if (compositeDataMap.isEmpty()) {
190196
close();
191197
}
192-
return data;
198+
return data != null ? data.dataList : null;
193199
}
194200

195201
@Override
@@ -198,7 +204,7 @@ public void close() {
198204
.getLogger()
199205
.log(SentryLevel.DEBUG, "stop collecting all performance info for transactions");
200206

201-
performanceDataMap.clear();
207+
compositeDataMap.clear();
202208
for (final @NotNull IPerformanceContinuousCollector collector : continuousCollectors) {
203209
collector.clear();
204210
}
@@ -211,4 +217,30 @@ public void close() {
211217
}
212218
}
213219
}
220+
221+
private class CompositeData {
222+
private final @NotNull List<PerformanceCollectionData> dataList;
223+
private final @Nullable ITransaction transaction;
224+
private final long startTimestamp;
225+
226+
private CompositeData(final @Nullable ITransaction transaction) {
227+
this.dataList = new ArrayList<>();
228+
this.transaction = transaction;
229+
this.startTimestamp = options.getDateProvider().now().nanoTimestamp();
230+
}
231+
232+
/**
233+
* Adds the data to the internal list of PerformanceCollectionData. Then it checks if data
234+
* collection timed out (for transactions only).
235+
*
236+
* @return true if data collection timed out (for transactions only).
237+
*/
238+
boolean addDataAndCheckTimeout(final @NotNull PerformanceCollectionData data) {
239+
dataList.add(data);
240+
return transaction != null
241+
&& options.getDateProvider().now().nanoTimestamp()
242+
> startTimestamp
243+
+ TimeUnit.MILLISECONDS.toNanos(TRANSACTION_COLLECTION_TIMEOUT_MILLIS);
244+
}
245+
}
214246
}

sentry/src/main/java/io/sentry/SentryTracer.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ public SentryTracer(
7777
this.name = context.getName();
7878
this.instrumenter = context.getInstrumenter();
7979
this.scopes = scopes;
80-
this.compositePerformanceCollector = compositePerformanceCollector;
80+
// Let's collect performance data (cpu, ram, frames) only when the transaction is sampled
81+
this.compositePerformanceCollector =
82+
Boolean.TRUE.equals(isSampled()) ? compositePerformanceCollector : null;
8183
this.transactionNameSource = context.getTransactionNameSource();
8284
this.transactionOptions = transactionOptions;
8385

@@ -90,9 +92,9 @@ public SentryTracer(
9092
}
9193

9294
// We are currently sending the performance data only in profiles, but we are always sending
93-
// performance measurements.
94-
if (compositePerformanceCollector != null) {
95-
compositePerformanceCollector.start(this);
95+
// performance measurements (frames data in spans).
96+
if (this.compositePerformanceCollector != null) {
97+
this.compositePerformanceCollector.start(this);
9698
}
9799

98100
if (transactionOptions.getIdleTimeout() != null

0 commit comments

Comments
 (0)