Skip to content

Commit a3c7c46

Browse files
authored
Merge c8f3814 into eacca57
2 parents eacca57 + c8f3814 commit a3c7c46

File tree

6 files changed

+155
-13
lines changed

6 files changed

+155
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
### Features
66

7+
- Send Timber logs through Sentry Logs ([#4490](https://github.com/getsentry/sentry-java/pull/4490))
8+
- Enable the Logs feature in your `SentryOptions` or with the `io.sentry.logs.enabled` manifest option and the SDK will automatically send Timber logs to Sentry, if the TimberIntegration is enabled.
9+
- The SDK will automatically detect Timber and use it to send logs to Sentry.
710
- Send logcat through Sentry Logs ([#4487](https://github.com/getsentry/sentry-java/pull/4487))
811
- Enable the Logs feature in your `SentryOptions` or with the `io.sentry.logs.enabled` manifest option and the SDK will automatically send logcat logs to Sentry, if the Sentry Android Gradle plugin is applied.
912

sentry-android-timber/api/sentry-android-timber.api

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@ public final class io/sentry/android/timber/BuildConfig {
99

1010
public final class io/sentry/android/timber/SentryTimberIntegration : io/sentry/Integration, java/io/Closeable {
1111
public fun <init> ()V
12-
public fun <init> (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;)V
13-
public synthetic fun <init> (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
12+
public fun <init> (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V
13+
public synthetic fun <init> (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
1414
public fun close ()V
1515
public final fun getMinBreadcrumbLevel ()Lio/sentry/SentryLevel;
1616
public final fun getMinEventLevel ()Lio/sentry/SentryLevel;
17+
public final fun getMinLogsLevel ()Lio/sentry/SentryLogLevel;
1718
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
1819
}
1920

2021
public final class io/sentry/android/timber/SentryTimberTree : timber/log/Timber$Tree {
21-
public fun <init> (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;)V
22+
public fun <init> (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V
2223
public fun d (Ljava/lang/String;[Ljava/lang/Object;)V
2324
public fun d (Ljava/lang/Throwable;)V
2425
public fun d (Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
2526
public fun e (Ljava/lang/String;[Ljava/lang/Object;)V
2627
public fun e (Ljava/lang/Throwable;)V
2728
public fun e (Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V
29+
public final fun getMinLogsLevel ()Lio/sentry/SentryLogLevel;
2830
public fun i (Ljava/lang/String;[Ljava/lang/Object;)V
2931
public fun i (Ljava/lang/Throwable;)V
3032
public fun i (Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.sentry.IScopes
55
import io.sentry.Integration
66
import io.sentry.SentryIntegrationPackageStorage
77
import io.sentry.SentryLevel
8+
import io.sentry.SentryLogLevel
89
import io.sentry.SentryOptions
910
import io.sentry.android.timber.BuildConfig.VERSION_NAME
1011
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
@@ -16,7 +17,8 @@ import java.io.Closeable
1617
*/
1718
public class SentryTimberIntegration(
1819
public val minEventLevel: SentryLevel = SentryLevel.ERROR,
19-
public val minBreadcrumbLevel: SentryLevel = SentryLevel.INFO
20+
public val minBreadcrumbLevel: SentryLevel = SentryLevel.INFO,
21+
public val minLogsLevel: SentryLogLevel = SentryLogLevel.INFO
2022
) : Integration, Closeable {
2123
private lateinit var tree: SentryTimberTree
2224
private lateinit var logger: ILogger
@@ -31,7 +33,7 @@ public class SentryTimberIntegration(
3133
override fun register(scopes: IScopes, options: SentryOptions) {
3234
logger = options.logger
3335

34-
tree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel)
36+
tree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel)
3537
Timber.plant(tree)
3638

3739
logger.log(SentryLevel.DEBUG, "SentryTimberIntegration installed.")

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.sentry.Breadcrumb
55
import io.sentry.IScopes
66
import io.sentry.SentryEvent
77
import io.sentry.SentryLevel
8+
import io.sentry.SentryLogLevel
89
import io.sentry.protocol.Message
910
import timber.log.Timber
1011

@@ -15,7 +16,8 @@ import timber.log.Timber
1516
public class SentryTimberTree(
1617
private val scopes: IScopes,
1718
private val minEventLevel: SentryLevel,
18-
private val minBreadcrumbLevel: SentryLevel
19+
private val minBreadcrumbLevel: SentryLevel,
20+
private val minLogsLevel: SentryLogLevel = SentryLogLevel.INFO
1921
) : Timber.Tree() {
2022
private val pendingTag = ThreadLocal<String?>()
2123

@@ -229,6 +231,7 @@ public class SentryTimberTree(
229231
}
230232

231233
val level = getSentryLevel(priority)
234+
val logLevel = getSentryLogLevel(priority)
232235
val sentryMessage = Message().apply {
233236
this.message = message
234237
if (!message.isNullOrEmpty() && args.isNotEmpty()) {
@@ -239,6 +242,7 @@ public class SentryTimberTree(
239242

240243
captureEvent(level, tag, sentryMessage, throwable)
241244
addBreadcrumb(level, sentryMessage, throwable)
245+
addLog(logLevel, message, throwable, *args)
242246
}
243247

244248
/**
@@ -249,6 +253,14 @@ public class SentryTimberTree(
249253
minLevel: SentryLevel
250254
): Boolean = level.ordinal >= minLevel.ordinal
251255

256+
/**
257+
* do not log if it's lower than min. required level.
258+
*/
259+
private fun isLoggable(
260+
level: SentryLogLevel,
261+
minLevel: SentryLogLevel
262+
): Boolean = level.ordinal >= minLevel.ordinal
263+
252264
/**
253265
* Captures an event with the given attributes
254266
*/
@@ -300,6 +312,23 @@ public class SentryTimberTree(
300312
}
301313
}
302314

315+
/** Send a Sentry Logs */
316+
private fun addLog(
317+
sentryLogLevel: SentryLogLevel,
318+
msg: String?,
319+
throwable: Throwable?,
320+
vararg args: Any?
321+
) {
322+
// checks the log level
323+
if (isLoggable(sentryLogLevel, minLogsLevel)) {
324+
val throwableMsg = throwable?.message
325+
when {
326+
msg != null -> scopes.logger().log(sentryLogLevel, msg, *args)
327+
throwableMsg != null -> scopes.logger().log(sentryLogLevel, throwableMsg, *args)
328+
}
329+
}
330+
}
331+
303332
/**
304333
* Converts from Timber priority to SentryLevel.
305334
* Fallback to SentryLevel.DEBUG.
@@ -315,4 +344,20 @@ public class SentryTimberTree(
315344
else -> SentryLevel.DEBUG
316345
}
317346
}
347+
348+
/**
349+
* Converts from Timber priority to SentryLogLevel.
350+
* Fallback to SentryLogLevel.DEBUG.
351+
*/
352+
private fun getSentryLogLevel(priority: Int): SentryLogLevel {
353+
return when (priority) {
354+
Log.ASSERT -> SentryLogLevel.FATAL
355+
Log.ERROR -> SentryLogLevel.ERROR
356+
Log.WARN -> SentryLogLevel.WARN
357+
Log.INFO -> SentryLogLevel.INFO
358+
Log.DEBUG -> SentryLogLevel.DEBUG
359+
Log.VERBOSE -> SentryLogLevel.TRACE
360+
else -> SentryLogLevel.DEBUG
361+
}
362+
}
318363
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.sentry.android.timber
22

33
import io.sentry.IScopes
44
import io.sentry.SentryLevel
5+
import io.sentry.SentryLogLevel
56
import io.sentry.SentryOptions
67
import io.sentry.protocol.SdkVersion
78
import org.mockito.kotlin.any
@@ -23,11 +24,13 @@ class SentryTimberIntegrationTest {
2324

2425
fun getSut(
2526
minEventLevel: SentryLevel = SentryLevel.ERROR,
26-
minBreadcrumbLevel: SentryLevel = SentryLevel.INFO
27+
minBreadcrumbLevel: SentryLevel = SentryLevel.INFO,
28+
minLogsLevel: SentryLogLevel = SentryLogLevel.INFO
2729
): SentryTimberIntegration {
2830
return SentryTimberIntegration(
2931
minEventLevel = minEventLevel,
30-
minBreadcrumbLevel = minBreadcrumbLevel
32+
minBreadcrumbLevel = minBreadcrumbLevel,
33+
minLogsLevel = minLogsLevel
3134
)
3235
}
3336
}
@@ -82,12 +85,14 @@ class SentryTimberIntegrationTest {
8285
fun `Integrations pass the right min levels`() {
8386
val sut = fixture.getSut(
8487
minEventLevel = SentryLevel.INFO,
85-
minBreadcrumbLevel = SentryLevel.DEBUG
88+
minBreadcrumbLevel = SentryLevel.DEBUG,
89+
minLogsLevel = SentryLogLevel.TRACE
8690
)
8791
sut.register(fixture.scopes, fixture.options)
8892

8993
assertEquals(sut.minEventLevel, SentryLevel.INFO)
9094
assertEquals(sut.minBreadcrumbLevel, SentryLevel.DEBUG)
95+
assertEquals(sut.minLogsLevel, SentryLogLevel.TRACE)
9196
}
9297

9398
@Test

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

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package io.sentry.android.timber
22

33
import io.sentry.Breadcrumb
4-
import io.sentry.IScopes
4+
import io.sentry.Scopes
55
import io.sentry.SentryLevel
6+
import io.sentry.SentryLogLevel
7+
import io.sentry.logger.ILoggerApi
68
import org.mockito.kotlin.any
79
import org.mockito.kotlin.check
10+
import org.mockito.kotlin.eq
811
import org.mockito.kotlin.mock
912
import org.mockito.kotlin.never
1013
import org.mockito.kotlin.verify
14+
import org.mockito.kotlin.verifyNoInteractions
15+
import org.mockito.kotlin.whenever
1116
import timber.log.Timber
1217
import kotlin.test.BeforeTest
1318
import kotlin.test.Test
@@ -18,13 +23,19 @@ import kotlin.test.assertNull
1823
class SentryTimberTreeTest {
1924

2025
private class Fixture {
21-
val scopes = mock<IScopes>()
26+
val scopes = mock<Scopes>()
27+
val logs = mock<ILoggerApi>()
28+
29+
init {
30+
whenever(scopes.logger()).thenReturn(logs)
31+
}
2232

2333
fun getSut(
2434
minEventLevel: SentryLevel = SentryLevel.ERROR,
25-
minBreadcrumbLevel: SentryLevel = SentryLevel.INFO
35+
minBreadcrumbLevel: SentryLevel = SentryLevel.INFO,
36+
minLogsLevel: SentryLogLevel = SentryLogLevel.INFO
2637
): SentryTimberTree {
27-
return SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel)
38+
return SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel)
2839
}
2940
}
3041

@@ -281,4 +292,78 @@ class SentryTimberTreeTest {
281292
val sut = fixture.getSut()
282293
sut.d("test %s, %s", 1, 1)
283294
}
295+
296+
@Test
297+
fun `Tree adds a log with message and arguments, when provided`() {
298+
val sut = fixture.getSut()
299+
sut.e("test count: %d %d", 32, 5)
300+
301+
verify(fixture.logs).log(
302+
eq(SentryLogLevel.ERROR),
303+
eq("test count: %d %d"),
304+
eq(32),
305+
eq(5)
306+
)
307+
}
308+
309+
@Test
310+
fun `Tree adds a log if min level is equal`() {
311+
val sut = fixture.getSut()
312+
sut.i(Throwable("test"))
313+
verify(fixture.logs).log(any(), any())
314+
}
315+
316+
@Test
317+
fun `Tree adds a log if min level is higher`() {
318+
val sut = fixture.getSut()
319+
sut.e(Throwable("test"))
320+
verify(fixture.logs).log(any(), any<String>(), any())
321+
}
322+
323+
@Test
324+
fun `Tree won't add a log if min level is lower`() {
325+
val sut = fixture.getSut(minLogsLevel = SentryLogLevel.ERROR)
326+
sut.i(Throwable("test"))
327+
verifyNoInteractions(fixture.logs)
328+
}
329+
330+
@Test
331+
fun `Tree adds an info log`() {
332+
val sut = fixture.getSut()
333+
sut.i("message")
334+
335+
verify(fixture.logs).log(
336+
eq(SentryLogLevel.INFO),
337+
eq("message")
338+
)
339+
}
340+
341+
@Test
342+
fun `Tree adds an error log`() {
343+
val sut = fixture.getSut()
344+
sut.e(Throwable("test"))
345+
346+
verify(fixture.logs).log(
347+
eq(SentryLogLevel.ERROR),
348+
eq("test")
349+
)
350+
}
351+
352+
@Test
353+
fun `Tree does not add a log, if no message or throwable is provided`() {
354+
val sut = fixture.getSut()
355+
sut.e(null as String?)
356+
verifyNoInteractions(fixture.logs)
357+
}
358+
359+
@Test
360+
fun `Tree logs throwable`() {
361+
val sut = fixture.getSut()
362+
sut.e(Throwable("throwable message"))
363+
364+
verify(fixture.logs).log(
365+
eq(SentryLogLevel.ERROR),
366+
eq("throwable message")
367+
)
368+
}
284369
}

0 commit comments

Comments
 (0)