diff --git a/CHANGELOG.md b/CHANGELOG.md index baf158193a..ef77be1743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Fixes +- Send Timber logs through Sentry Logs ([#4490](https://github.com/getsentry/sentry-java/pull/4490)) + - 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. + - The SDK will automatically detect Timber and use it to send logs to Sentry. - Send logcat through Sentry Logs ([#4487](https://github.com/getsentry/sentry-java/pull/4487)) - 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. - To set the logcat level check the [Logcat integration documentation](https://docs.sentry.io/platforms/android/integrations/logcat/#configure). diff --git a/sentry-android-timber/api/sentry-android-timber.api b/sentry-android-timber/api/sentry-android-timber.api index 2d71f67570..8ae2f49c28 100644 --- a/sentry-android-timber/api/sentry-android-timber.api +++ b/sentry-android-timber/api/sentry-android-timber.api @@ -9,16 +9,18 @@ public final class io/sentry/android/timber/BuildConfig { public final class io/sentry/android/timber/SentryTimberIntegration : io/sentry/Integration, java/io/Closeable { public fun ()V - public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;)V - public synthetic fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V + public synthetic fun (Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun close ()V public final fun getMinBreadcrumbLevel ()Lio/sentry/SentryLevel; public final fun getMinEventLevel ()Lio/sentry/SentryLevel; + public final fun getMinLogsLevel ()Lio/sentry/SentryLogLevel; public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V } public final class io/sentry/android/timber/SentryTimberTree : timber/log/Timber$Tree { - public fun (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;)V + public fun (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;)V + public synthetic fun (Lio/sentry/IScopes;Lio/sentry/SentryLevel;Lio/sentry/SentryLevel;Lio/sentry/SentryLogLevel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun d (Ljava/lang/String;[Ljava/lang/Object;)V public fun d (Ljava/lang/Throwable;)V public fun d (Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt index 59edeac869..521fe15127 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberIntegration.kt @@ -5,6 +5,7 @@ import io.sentry.IScopes import io.sentry.Integration import io.sentry.SentryIntegrationPackageStorage import io.sentry.SentryLevel +import io.sentry.SentryLogLevel import io.sentry.SentryOptions import io.sentry.android.timber.BuildConfig.VERSION_NAME import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion @@ -15,6 +16,7 @@ import timber.log.Timber public class SentryTimberIntegration( public val minEventLevel: SentryLevel = SentryLevel.ERROR, public val minBreadcrumbLevel: SentryLevel = SentryLevel.INFO, + public val minLogsLevel: SentryLogLevel = SentryLogLevel.INFO, ) : Integration, Closeable { private lateinit var tree: SentryTimberTree private lateinit var logger: ILogger @@ -29,7 +31,7 @@ public class SentryTimberIntegration( override fun register(scopes: IScopes, options: SentryOptions) { logger = options.logger - tree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel) + tree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel) Timber.plant(tree) logger.log(SentryLevel.DEBUG, "SentryTimberIntegration installed.") diff --git a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt index 7ff65242e9..c0e996bdba 100644 --- a/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt +++ b/sentry-android-timber/src/main/java/io/sentry/android/timber/SentryTimberTree.kt @@ -5,6 +5,7 @@ import io.sentry.Breadcrumb import io.sentry.IScopes import io.sentry.SentryEvent import io.sentry.SentryLevel +import io.sentry.SentryLogLevel import io.sentry.protocol.Message import timber.log.Timber @@ -14,6 +15,7 @@ public class SentryTimberTree( private val scopes: IScopes, private val minEventLevel: SentryLevel, private val minBreadcrumbLevel: SentryLevel, + private val minLogLevel: SentryLogLevel = SentryLogLevel.INFO, ) : Timber.Tree() { private val pendingTag = ThreadLocal() @@ -168,6 +170,7 @@ public class SentryTimberTree( } val level = getSentryLevel(priority) + val logLevel = getSentryLogLevel(priority) val sentryMessage = Message().apply { this.message = message @@ -179,12 +182,17 @@ public class SentryTimberTree( captureEvent(level, tag, sentryMessage, throwable) addBreadcrumb(level, sentryMessage, throwable) + addLog(logLevel, message, throwable, *args) } /** do not log if it's lower than min. required level. */ private fun isLoggable(level: SentryLevel, minLevel: SentryLevel): Boolean = level.ordinal >= minLevel.ordinal + /** do not log if it's lower than min. required level. */ + private fun isLoggable(level: SentryLogLevel, minLevel: SentryLogLevel): Boolean = + level.ordinal >= minLevel.ordinal + /** Captures an event with the given attributes */ private fun captureEvent( sentryLevel: SentryLevel, @@ -227,6 +235,25 @@ public class SentryTimberTree( } } + /** Send a Sentry Logs */ + private fun addLog( + sentryLogLevel: SentryLogLevel, + msg: String?, + throwable: Throwable?, + vararg args: Any?, + ) { + // checks the log level + if (isLoggable(sentryLogLevel, minLogLevel)) { + val throwableMsg = throwable?.message + when { + msg != null && throwableMsg != null -> + scopes.logger().log(sentryLogLevel, "$msg\n$throwableMsg", *args) + msg != null -> scopes.logger().log(sentryLogLevel, msg, *args) + throwableMsg != null -> scopes.logger().log(sentryLogLevel, throwableMsg, *args) + } + } + } + /** Converts from Timber priority to SentryLevel. Fallback to SentryLevel.DEBUG. */ private fun getSentryLevel(priority: Int): SentryLevel = when (priority) { @@ -238,4 +265,17 @@ public class SentryTimberTree( Log.VERBOSE -> SentryLevel.DEBUG else -> SentryLevel.DEBUG } + + /** Converts from Timber priority to SentryLogLevel. Fallback to SentryLogLevel.DEBUG. */ + private fun getSentryLogLevel(priority: Int): SentryLogLevel { + return when (priority) { + Log.ASSERT -> SentryLogLevel.FATAL + Log.ERROR -> SentryLogLevel.ERROR + Log.WARN -> SentryLogLevel.WARN + Log.INFO -> SentryLogLevel.INFO + Log.DEBUG -> SentryLogLevel.DEBUG + Log.VERBOSE -> SentryLogLevel.TRACE + else -> SentryLogLevel.DEBUG + } + } } diff --git a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt index 924512951d..43a45da7bb 100644 --- a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt +++ b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberIntegrationTest.kt @@ -2,6 +2,7 @@ package io.sentry.android.timber import io.sentry.IScopes import io.sentry.SentryLevel +import io.sentry.SentryLogLevel import io.sentry.SentryOptions import io.sentry.protocol.SdkVersion import kotlin.test.BeforeTest @@ -21,10 +22,12 @@ class SentryTimberIntegrationTest { fun getSut( minEventLevel: SentryLevel = SentryLevel.ERROR, minBreadcrumbLevel: SentryLevel = SentryLevel.INFO, + minLogsLevel: SentryLogLevel = SentryLogLevel.INFO, ): SentryTimberIntegration = SentryTimberIntegration( minEventLevel = minEventLevel, minBreadcrumbLevel = minBreadcrumbLevel, + minLogsLevel = minLogsLevel, ) } @@ -78,11 +81,16 @@ class SentryTimberIntegrationTest { @Test fun `Integrations pass the right min levels`() { val sut = - fixture.getSut(minEventLevel = SentryLevel.INFO, minBreadcrumbLevel = SentryLevel.DEBUG) + fixture.getSut( + minEventLevel = SentryLevel.INFO, + minBreadcrumbLevel = SentryLevel.DEBUG, + minLogsLevel = SentryLogLevel.TRACE, + ) sut.register(fixture.scopes, fixture.options) assertEquals(sut.minEventLevel, SentryLevel.INFO) assertEquals(sut.minBreadcrumbLevel, SentryLevel.DEBUG) + assertEquals(sut.minLogsLevel, SentryLogLevel.TRACE) } @Test diff --git a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt index 40a2d419f7..e77e33e42d 100644 --- a/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt +++ b/sentry-android-timber/src/test/java/io/sentry/android/timber/SentryTimberTreeTest.kt @@ -1,8 +1,10 @@ package io.sentry.android.timber import io.sentry.Breadcrumb -import io.sentry.IScopes +import io.sentry.Scopes import io.sentry.SentryLevel +import io.sentry.SentryLogLevel +import io.sentry.logger.ILoggerApi import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -10,19 +12,28 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import org.mockito.kotlin.any import org.mockito.kotlin.check +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.whenever import timber.log.Timber class SentryTimberTreeTest { private class Fixture { - val scopes = mock() + val scopes = mock() + val logs = mock() + + init { + whenever(scopes.logger()).thenReturn(logs) + } fun getSut( minEventLevel: SentryLevel = SentryLevel.ERROR, minBreadcrumbLevel: SentryLevel = SentryLevel.INFO, - ): SentryTimberTree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel) + minLogsLevel: SentryLogLevel = SentryLogLevel.INFO, + ): SentryTimberTree = SentryTimberTree(scopes, minEventLevel, minBreadcrumbLevel, minLogsLevel) } private val fixture = Fixture() @@ -231,4 +242,72 @@ class SentryTimberTreeTest { val sut = fixture.getSut() sut.d("test %s, %s", 1, 1) } + + @Test + fun `Tree adds a log with message and arguments, when provided`() { + val sut = fixture.getSut() + sut.e("test count: %d %d", 32, 5) + + verify(fixture.logs).log(eq(SentryLogLevel.ERROR), eq("test count: %d %d"), eq(32), eq(5)) + } + + @Test + fun `Tree adds a log if min level is equal`() { + val sut = fixture.getSut() + sut.i(Throwable("test")) + verify(fixture.logs).log(any(), any()) + } + + @Test + fun `Tree adds a log if min level is higher`() { + val sut = fixture.getSut() + sut.e(Throwable("test")) + verify(fixture.logs).log(any(), any(), any()) + } + + @Test + fun `Tree won't add a log if min level is lower`() { + val sut = fixture.getSut(minLogsLevel = SentryLogLevel.ERROR) + sut.i(Throwable("test")) + verifyNoInteractions(fixture.logs) + } + + @Test + fun `Tree adds an info log`() { + val sut = fixture.getSut() + sut.i("message") + + verify(fixture.logs).log(eq(SentryLogLevel.INFO), eq("message")) + } + + @Test + fun `Tree adds an error log`() { + val sut = fixture.getSut() + sut.e(Throwable("test")) + + verify(fixture.logs).log(eq(SentryLogLevel.ERROR), eq("test")) + } + + @Test + fun `Tree does not add a log, if no message or throwable is provided`() { + val sut = fixture.getSut() + sut.e(null as String?) + verifyNoInteractions(fixture.logs) + } + + @Test + fun `Tree logs throwable`() { + val sut = fixture.getSut() + sut.e(Throwable("throwable message")) + + verify(fixture.logs).log(eq(SentryLogLevel.ERROR), eq("throwable message")) + } + + @Test + fun `Tree logs throwable and message`() { + val sut = fixture.getSut() + sut.e(Throwable("throwable message"), "My message") + + verify(fixture.logs).log(eq(SentryLogLevel.ERROR), eq("My message\nthrowable message")) + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy