From 3572c9fa1836c49bccbbe6fd736bd4ef5e367480 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 12 Jun 2025 16:47:13 +0200 Subject: [PATCH 01/11] SentryLogcatAdapter now forwards output to Sentry Logs, if enabled --- .../android/core/SentryLogcatAdapter.java | 35 ++++ .../android/core/SentryLogcatAdapterTest.kt | 192 +++++++++++------- 2 files changed, 148 insertions(+), 79 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java index a942d51878..24d3831ed7 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java @@ -2,8 +2,12 @@ import android.util.Log; import io.sentry.Breadcrumb; +import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryLevel; +import io.sentry.SentryLogLevel; +import java.io.PrintWriter; +import java.io.StringWriter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,73 +48,104 @@ private static void addAsBreadcrumb( Sentry.addBreadcrumb(breadcrumb); } + private static void addAsLog( + @NotNull final SentryLogLevel level, + @Nullable final String msg, + @Nullable final Throwable tr) { + final @NotNull ScopesAdapter scopes = ScopesAdapter.getInstance(); + if (tr == null) { + scopes.logger().log(level, msg); + } else { + StringWriter sw = new StringWriter(256); + PrintWriter pw = new PrintWriter(sw, false); + tr.printStackTrace(pw); + pw.flush(); + scopes.logger().log(level, msg != null ? (msg + "\n" + sw.toString()) : sw.toString()); + pw.close(); + } + } + public static int v(@Nullable String tag, @Nullable String msg) { addAsBreadcrumb(tag, SentryLevel.DEBUG, msg); + addAsLog(SentryLogLevel.TRACE, msg, null); return Log.v(tag, msg); } public static int v(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.DEBUG, msg, tr); + addAsLog(SentryLogLevel.TRACE, msg, tr); return Log.v(tag, msg, tr); } public static int d(@Nullable String tag, @Nullable String msg) { addAsBreadcrumb(tag, SentryLevel.DEBUG, msg); + addAsLog(SentryLogLevel.DEBUG, msg, null); return Log.d(tag, msg); } public static int d(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.DEBUG, msg, tr); + addAsLog(SentryLogLevel.DEBUG, msg, tr); return Log.d(tag, msg, tr); } public static int i(@Nullable String tag, @Nullable String msg) { addAsBreadcrumb(tag, SentryLevel.INFO, msg); + addAsLog(SentryLogLevel.INFO, msg, null); return Log.i(tag, msg); } public static int i(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.INFO, msg, tr); + addAsLog(SentryLogLevel.INFO, msg, tr); return Log.i(tag, msg, tr); } public static int w(@Nullable String tag, @Nullable String msg) { addAsBreadcrumb(tag, SentryLevel.WARNING, msg); + addAsLog(SentryLogLevel.WARN, msg, null); return Log.w(tag, msg); } public static int w(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.WARNING, msg, tr); + addAsLog(SentryLogLevel.WARN, msg, tr); return Log.w(tag, msg, tr); } public static int w(@Nullable String tag, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.WARNING, tr); + addAsLog(SentryLogLevel.WARN, null, tr); return Log.w(tag, tr); } public static int e(@Nullable String tag, @Nullable String msg) { addAsBreadcrumb(tag, SentryLevel.ERROR, msg); + addAsLog(SentryLogLevel.ERROR, msg, null); return Log.e(tag, msg); } public static int e(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.ERROR, msg, tr); + addAsLog(SentryLogLevel.ERROR, msg, tr); return Log.e(tag, msg, tr); } public static int wtf(@Nullable String tag, @Nullable String msg) { addAsBreadcrumb(tag, SentryLevel.ERROR, msg); + addAsLog(SentryLogLevel.FATAL, msg, null); return Log.wtf(tag, msg); } public static int wtf(@Nullable String tag, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.ERROR, tr); + addAsLog(SentryLogLevel.FATAL, null, tr); return Log.wtf(tag, tr); } public static int wtf(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) { addAsBreadcrumb(tag, SentryLevel.ERROR, msg, tr); + addAsLog(SentryLogLevel.FATAL, msg, tr); return Log.wtf(tag, msg, tr); } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index 72e3d68dc0..7b9ed6ea14 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -5,22 +5,26 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.Breadcrumb import io.sentry.Sentry import io.sentry.SentryLevel +import io.sentry.SentryLogEvent +import io.sentry.SentryLogLevel import io.sentry.SentryOptions import io.sentry.android.core.performance.AppStartMetrics import org.junit.runner.RunWith -import java.lang.RuntimeException +import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) class SentryLogcatAdapterTest { - private val breadcrumbs = mutableListOf() private val tag = "my-tag" private val commonMsg = "SentryLogcatAdapter" private val throwable = RuntimeException("Test Exception") class Fixture { + val breadcrumbs = mutableListOf() + val logs = mutableListOf() fun initSut( options: Sentry.OptionsConfiguration? = null @@ -29,9 +33,17 @@ class SentryLogcatAdapterTest { putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") } val mockContext = ContextUtilsTestHelper.mockMetaData(metaData = metadata) - when { - options != null -> initForTest(mockContext, options) - else -> initForTest(mockContext) + initForTest(mockContext) { + it.beforeBreadcrumb = SentryOptions.BeforeBreadcrumbCallback { breadcrumb, _ -> + breadcrumbs.add(breadcrumb) + breadcrumb + } + it.logs.isEnabled = true + it.logs.beforeSend = SentryOptions.Logs.BeforeSendLogCallback { logEvent -> + logs.add(logEvent) + logEvent + } + options?.configure(it) } } } @@ -43,144 +55,166 @@ class SentryLogcatAdapterTest { Sentry.close() AppStartMetrics.getInstance().clear() ContextUtils.resetInstance() - breadcrumbs.clear() - - fixture.initSut { - it.beforeBreadcrumb = SentryOptions.BeforeBreadcrumbCallback { breadcrumb, _ -> - breadcrumbs.add(breadcrumb) - breadcrumb - } - } + fixture.breadcrumbs.clear() + fixture.logs.clear() + } - SentryLogcatAdapter.v(tag, "$commonMsg verbose") - SentryLogcatAdapter.i(tag, "$commonMsg info") - SentryLogcatAdapter.d(tag, "$commonMsg debug") - SentryLogcatAdapter.w(tag, "$commonMsg warning") - SentryLogcatAdapter.e(tag, "$commonMsg error") - SentryLogcatAdapter.wtf(tag, "$commonMsg wtf") + @AfterTest + fun `clean up`() { + AppStartMetrics.getInstance().clear() + ContextUtils.resetInstance() + Sentry.close() } @Test fun `verbose log message has expected content`() { - val breadcrumb = breadcrumbs.find { it.level == SentryLevel.DEBUG && it.message?.contains("verbose") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.data?.get("tag")) - assert(breadcrumb?.message?.contains("verbose") == true) + fixture.initSut() + SentryLogcatAdapter.v(tag, "$commonMsg verbose") + fixture.breadcrumbs.first().assert(tag, "$commonMsg verbose", SentryLevel.DEBUG) + fixture.logs.first().assert("$commonMsg verbose", SentryLogLevel.TRACE) } @Test fun `info log message has expected content`() { - val breadcrumb = breadcrumbs.find { it.level == SentryLevel.INFO && it.message?.contains("info") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.data?.get("tag")) - assert(breadcrumb?.message?.contains("info") == true) + fixture.initSut() + SentryLogcatAdapter.i(tag, "$commonMsg info") + fixture.breadcrumbs.first().assert(tag, "$commonMsg info", SentryLevel.INFO) + fixture.logs.first().assert("$commonMsg info", SentryLogLevel.INFO) } @Test fun `debug log message has expected content`() { - val breadcrumb = breadcrumbs.find { it.level == SentryLevel.DEBUG && it.message?.contains("debug") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.data?.get("tag")) - assert(breadcrumb?.message?.contains("debug") == true) + fixture.initSut() + SentryLogcatAdapter.d(tag, "$commonMsg debug") + fixture.breadcrumbs.first().assert(tag, "$commonMsg debug", SentryLevel.DEBUG) + fixture.logs.first().assert("$commonMsg debug", SentryLogLevel.DEBUG) } @Test fun `warning log message has expected content`() { - val breadcrumb = breadcrumbs.find { it.level == SentryLevel.WARNING && it.message?.contains("warning") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.data?.get("tag")) - assert(breadcrumb?.message?.contains("warning") == true) + fixture.initSut() + SentryLogcatAdapter.w(tag, "$commonMsg warning") + fixture.breadcrumbs.first().assert(tag, "$commonMsg warning", SentryLevel.WARNING) + fixture.logs.first().assert("$commonMsg warning", SentryLogLevel.WARN) } @Test fun `error log message has expected content`() { - val breadcrumb = breadcrumbs.find { it.level == SentryLevel.ERROR && it.message?.contains("error") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.data?.get("tag")) - assert(breadcrumb?.message?.contains("error") == true) + fixture.initSut() + SentryLogcatAdapter.e(tag, "$commonMsg error") + fixture.breadcrumbs.first().assert(tag, "$commonMsg error", SentryLevel.ERROR) + fixture.logs.first().assert("$commonMsg error", SentryLogLevel.ERROR) } @Test fun `wtf log message has expected content`() { - val breadcrumb = breadcrumbs.find { it.level == SentryLevel.ERROR && it.message?.contains("wtf") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.data?.get("tag")) - assert(breadcrumb?.message?.contains("wtf") == true) + fixture.initSut() + SentryLogcatAdapter.wtf(tag, "$commonMsg wtf") + fixture.breadcrumbs.first().assert(tag, "$commonMsg wtf", SentryLevel.ERROR) + fixture.logs.first().assert("$commonMsg wtf", SentryLogLevel.FATAL) } @Test fun `e log throwable has expected content`() { + fixture.initSut() SentryLogcatAdapter.e(tag, "$commonMsg error exception", throwable) - - val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.getData("tag")) - assertEquals(SentryLevel.ERROR, breadcrumb?.level) - assertEquals(throwable.message, breadcrumb?.getData("throwable")) + fixture.breadcrumbs.first().assert(tag, "$commonMsg error exception", SentryLevel.ERROR) + fixture.logs.first().assert("$commonMsg error exception\n${throwable.stackTraceToString()}", SentryLogLevel.ERROR) } @Test fun `v log throwable has expected content`() { + fixture.initSut() SentryLogcatAdapter.v(tag, "$commonMsg verbose exception", throwable) - - val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.getData("tag")) - assertEquals(SentryLevel.DEBUG, breadcrumb?.level) - assertEquals(throwable.message, breadcrumb?.getData("throwable")) + fixture.breadcrumbs.first().assert(tag, "$commonMsg verbose exception", SentryLevel.DEBUG) + fixture.logs.first().assert("$commonMsg verbose exception\n${throwable.stackTraceToString()}", SentryLogLevel.TRACE) } @Test fun `i log throwable has expected content`() { + fixture.initSut() SentryLogcatAdapter.i(tag, "$commonMsg info exception", throwable) - - val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.getData("tag")) - assertEquals(SentryLevel.INFO, breadcrumb?.level) - assertEquals(throwable.message, breadcrumb?.getData("throwable")) + fixture.breadcrumbs.first().assert(tag, "$commonMsg info exception", SentryLevel.INFO) + fixture.logs.first().assert("$commonMsg info exception\n${throwable.stackTraceToString()}", SentryLogLevel.INFO) } @Test fun `d log throwable has expected content`() { + fixture.initSut() SentryLogcatAdapter.d(tag, "$commonMsg debug exception", throwable) - - val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.getData("tag")) - assertEquals(SentryLevel.DEBUG, breadcrumb?.level) - assertEquals(throwable.message, breadcrumb?.getData("throwable")) + fixture.breadcrumbs.first().assert(tag, "$commonMsg debug exception", SentryLevel.DEBUG) + fixture.logs.first().assert("$commonMsg debug exception\n${throwable.stackTraceToString()}", SentryLogLevel.DEBUG) } @Test fun `w log throwable has expected content`() { + fixture.initSut() SentryLogcatAdapter.w(tag, "$commonMsg warning exception", throwable) - - val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.getData("tag")) - assertEquals(SentryLevel.WARNING, breadcrumb?.level) - assertEquals(throwable.message, breadcrumb?.getData("throwable")) + fixture.breadcrumbs.first().assert(tag, "$commonMsg warning exception", SentryLevel.WARNING) + fixture.logs.first().assert("$commonMsg warning exception\n${throwable.stackTraceToString()}", SentryLogLevel.WARN) } @Test fun `wtf log throwable has expected content`() { + fixture.initSut() + SentryLogcatAdapter.wtf(tag, "$commonMsg wtf exception", throwable) + fixture.breadcrumbs.first().assert(tag, "$commonMsg wtf exception", SentryLevel.ERROR) + fixture.logs.first().assert("$commonMsg wtf exception\n${throwable.stackTraceToString()}", SentryLogLevel.FATAL) + } + + @Test + fun `do not send logs if logs is disabled`() { + fixture.initSut { it.logs.isEnabled = false } + + SentryLogcatAdapter.v(tag, "$commonMsg verbose") + SentryLogcatAdapter.i(tag, "$commonMsg info") + SentryLogcatAdapter.d(tag, "$commonMsg debug") + SentryLogcatAdapter.w(tag, "$commonMsg warning") + SentryLogcatAdapter.e(tag, "$commonMsg error") + SentryLogcatAdapter.wtf(tag, "$commonMsg wtf") + SentryLogcatAdapter.e(tag, "$commonMsg error exception", throwable) + SentryLogcatAdapter.v(tag, "$commonMsg verbose exception", throwable) + SentryLogcatAdapter.i(tag, "$commonMsg info exception", throwable) + SentryLogcatAdapter.d(tag, "$commonMsg debug exception", throwable) + SentryLogcatAdapter.w(tag, "$commonMsg warning exception", throwable) SentryLogcatAdapter.wtf(tag, "$commonMsg wtf exception", throwable) - val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false } - assertEquals("Logcat", breadcrumb?.category) - assertEquals(tag, breadcrumb?.getData("tag")) - assertEquals(SentryLevel.ERROR, breadcrumb?.level) - assertEquals(throwable.message, breadcrumb?.getData("throwable")) + assertTrue(fixture.logs.isEmpty()) } @Test fun `logs add correct number of breadcrumb`() { + fixture.initSut() + SentryLogcatAdapter.v(tag, commonMsg) + SentryLogcatAdapter.d(tag, commonMsg) + SentryLogcatAdapter.i(tag, commonMsg) + SentryLogcatAdapter.w(tag, commonMsg) + SentryLogcatAdapter.e(tag, commonMsg) + SentryLogcatAdapter.wtf(tag, commonMsg) assertEquals( 6, - breadcrumbs.filter { + fixture.breadcrumbs.filter { it.message?.contains("SentryLogcatAdapter") ?: false }.size ) } + + private fun Breadcrumb.assert( + expectedTag: String, + expectedMessage: String, + expectedLevel: SentryLevel + ) { + assertEquals(expectedMessage, message) + assertEquals(expectedTag, data["tag"]) + assertEquals(expectedLevel, level) + assertEquals("Logcat", category) + } + + private fun SentryLogEvent.assert( + expectedMessage: String, + expectedLevel: SentryLogLevel + ) { + assertEquals(expectedMessage, body) + assertEquals(expectedLevel, level) + } } From ead3f38c938e706ffbabe1031974ec7a8c80b9bc Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 12 Jun 2025 16:51:35 +0200 Subject: [PATCH 02/11] updated changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0774abddf..b5900f815b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +### Features + +- 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. + ## 8.13.3 ### Fixes From c8e6a6ebb5ad1889479bf125f6351681a7037fa3 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 12 Jun 2025 19:09:13 +0200 Subject: [PATCH 03/11] fix tests --- .../io/sentry/android/core/InternalSentrySdkTest.kt | 3 +++ .../io/sentry/android/core/SentryLogcatAdapterTest.kt | 11 ++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt index 3174bd2824..7acccda2dd 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/InternalSentrySdkTest.kt @@ -241,6 +241,9 @@ class InternalSentrySdkTest { Sentry.configureScope { scope -> assertEquals(3, scope.breadcrumbs.size) } + + // Ensure we don't interfere with other tests + Sentry.configureScope(ScopeType.GLOBAL) { scope -> scope.clear() } } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index 7b9ed6ea14..ef85e0afe6 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -50,20 +50,13 @@ class SentryLogcatAdapterTest { private val fixture = Fixture() - @BeforeTest - fun `set up`() { - Sentry.close() - AppStartMetrics.getInstance().clear() - ContextUtils.resetInstance() - fixture.breadcrumbs.clear() - fixture.logs.clear() - } - @AfterTest fun `clean up`() { AppStartMetrics.getInstance().clear() ContextUtils.resetInstance() Sentry.close() + fixture.breadcrumbs.clear() + fixture.logs.clear() } @Test From eacca576dc27c84c415a8a82fc69fdb3c51087e0 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 12 Jun 2025 17:10:50 +0000 Subject: [PATCH 04/11] Format code --- .../test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index ef85e0afe6..1c35b022fa 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -11,7 +11,6 @@ import io.sentry.SentryOptions import io.sentry.android.core.performance.AppStartMetrics import org.junit.runner.RunWith import kotlin.test.AfterTest -import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue From e566455f8ffdadcec75fe53c50c3f79a9763b208 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Wed, 18 Jun 2025 16:59:30 +0200 Subject: [PATCH 05/11] add log enabled check in logcat adapter --- .../main/java/io/sentry/android/core/SentryLogcatAdapter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java index 24d3831ed7..7305719467 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java @@ -53,6 +53,10 @@ private static void addAsLog( @Nullable final String msg, @Nullable final Throwable tr) { final @NotNull ScopesAdapter scopes = ScopesAdapter.getInstance(); + // Check if logs are enabled before doing expensive operations + if (!scopes.getOptions().getLogs().isEnabled()) { + return; + } if (tr == null) { scopes.logger().log(level, msg); } else { From 77c5c538e95cc25788215e7d175b9706e224c0c7 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 20 Jun 2025 11:52:46 +0200 Subject: [PATCH 06/11] updated changelog with doc reference --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784dd3cfe7..cd64b3151a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - 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). - No longer send out empty log envelopes ([#4497](https://github.com/getsentry/sentry-java/pull/4497)) ### Dependencies From a42eb855652a3baf35fa68c3361dbeefa5402d6e Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Mon, 30 Jun 2025 18:40:44 +0200 Subject: [PATCH 07/11] merged main --- .../android/core/SentryLogcatAdapterTest.kt | 187 ++++++++++-------- 1 file changed, 100 insertions(+), 87 deletions(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index a7d0b7010a..b5749c6144 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -18,144 +18,162 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SentryLogcatAdapterTest { - private val tag = "my-tag" - private val commonMsg = "SentryLogcatAdapter" - private val throwable = RuntimeException("Test Exception") + private val tag = "my-tag" + private val commonMsg = "SentryLogcatAdapter" + private val throwable = RuntimeException("Test Exception") class Fixture { - val breadcrumbs = mutableListOf() - val logs = mutableListOf() + val breadcrumbs = mutableListOf() + val logs = mutableListOf() - fun initSut(options: Sentry.OptionsConfiguration? = null) { + fun initSut(options: Sentry.OptionsConfiguration? = null) { val metadata = Bundle().apply { putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123") } val mockContext = ContextUtilsTestHelper.mockMetaData(metaData = metadata) - initForTest(mockContext) { - it.beforeBreadcrumb = SentryOptions.BeforeBreadcrumbCallback { breadcrumb, _ -> - breadcrumbs.add(breadcrumb) - breadcrumb - } - it.logs.isEnabled = true - it.logs.beforeSend = SentryOptions.Logs.BeforeSendLogCallback { logEvent -> - logs.add(logEvent) - logEvent - } - options?.configure(it) + initForTest(mockContext) { + it.beforeBreadcrumb = + SentryOptions.BeforeBreadcrumbCallback { breadcrumb, _ -> + breadcrumbs.add(breadcrumb) + breadcrumb } + it.logs.isEnabled = true + it.logs.beforeSend = + SentryOptions.Logs.BeforeSendLogCallback { logEvent -> + logs.add(logEvent) + logEvent + } + options?.configure(it) } } } private val fixture = Fixture() - @AfterTest - fun `clean up`() { - AppStartMetrics.getInstance().clear() - ContextUtils.resetInstance() - Sentry.close() - fixture.breadcrumbs.clear() - fixture.logs.clear() - } - + @AfterTest + fun `clean up`() { + AppStartMetrics.getInstance().clear() + ContextUtils.resetInstance() + Sentry.close() + fixture.breadcrumbs.clear() + fixture.logs.clear() + } -@Test -fun `verbose log message has expected content`() { + @Test + fun `verbose log message has expected content`() { fixture.initSut() SentryLogcatAdapter.v(tag, "$commonMsg verbose") fixture.breadcrumbs.first().assert(tag, "$commonMsg verbose", SentryLevel.DEBUG) fixture.logs.first().assert("$commonMsg verbose", SentryLogLevel.TRACE) -} + } -@Test -fun `info log message has expected content`() { + @Test + fun `info log message has expected content`() { fixture.initSut() SentryLogcatAdapter.i(tag, "$commonMsg info") fixture.breadcrumbs.first().assert(tag, "$commonMsg info", SentryLevel.INFO) fixture.logs.first().assert("$commonMsg info", SentryLogLevel.INFO) -} + } -@Test -fun `debug log message has expected content`() { + @Test + fun `debug log message has expected content`() { fixture.initSut() SentryLogcatAdapter.d(tag, "$commonMsg debug") fixture.breadcrumbs.first().assert(tag, "$commonMsg debug", SentryLevel.DEBUG) fixture.logs.first().assert("$commonMsg debug", SentryLogLevel.DEBUG) -} + } -@Test -fun `warning log message has expected content`() { + @Test + fun `warning log message has expected content`() { fixture.initSut() SentryLogcatAdapter.w(tag, "$commonMsg warning") fixture.breadcrumbs.first().assert(tag, "$commonMsg warning", SentryLevel.WARNING) fixture.logs.first().assert("$commonMsg warning", SentryLogLevel.WARN) -} + } -@Test -fun `error log message has expected content`() { + @Test + fun `error log message has expected content`() { fixture.initSut() SentryLogcatAdapter.e(tag, "$commonMsg error") fixture.breadcrumbs.first().assert(tag, "$commonMsg error", SentryLevel.ERROR) fixture.logs.first().assert("$commonMsg error", SentryLogLevel.ERROR) -} + } -@Test -fun `wtf log message has expected content`() { + @Test + fun `wtf log message has expected content`() { fixture.initSut() SentryLogcatAdapter.wtf(tag, "$commonMsg wtf") fixture.breadcrumbs.first().assert(tag, "$commonMsg wtf", SentryLevel.ERROR) fixture.logs.first().assert("$commonMsg wtf", SentryLogLevel.FATAL) -} + } -@Test -fun `e log throwable has expected content`() { + @Test + fun `e log throwable has expected content`() { fixture.initSut() SentryLogcatAdapter.e(tag, "$commonMsg error exception", throwable) fixture.breadcrumbs.first().assert(tag, "$commonMsg error exception", SentryLevel.ERROR) - fixture.logs.first().assert("$commonMsg error exception\n${throwable.stackTraceToString()}", SentryLogLevel.ERROR) -} + fixture.logs + .first() + .assert("$commonMsg error exception\n${throwable.stackTraceToString()}", SentryLogLevel.ERROR) + } -@Test -fun `v log throwable has expected content`() { + @Test + fun `v log throwable has expected content`() { fixture.initSut() SentryLogcatAdapter.v(tag, "$commonMsg verbose exception", throwable) fixture.breadcrumbs.first().assert(tag, "$commonMsg verbose exception", SentryLevel.DEBUG) - fixture.logs.first().assert("$commonMsg verbose exception\n${throwable.stackTraceToString()}", SentryLogLevel.TRACE) -} + fixture.logs + .first() + .assert( + "$commonMsg verbose exception\n${throwable.stackTraceToString()}", + SentryLogLevel.TRACE, + ) + } -@Test -fun `i log throwable has expected content`() { + @Test + fun `i log throwable has expected content`() { fixture.initSut() SentryLogcatAdapter.i(tag, "$commonMsg info exception", throwable) fixture.breadcrumbs.first().assert(tag, "$commonMsg info exception", SentryLevel.INFO) - fixture.logs.first().assert("$commonMsg info exception\n${throwable.stackTraceToString()}", SentryLogLevel.INFO) -} + fixture.logs + .first() + .assert("$commonMsg info exception\n${throwable.stackTraceToString()}", SentryLogLevel.INFO) + } -@Test -fun `d log throwable has expected content`() { + @Test + fun `d log throwable has expected content`() { fixture.initSut() SentryLogcatAdapter.d(tag, "$commonMsg debug exception", throwable) fixture.breadcrumbs.first().assert(tag, "$commonMsg debug exception", SentryLevel.DEBUG) - fixture.logs.first().assert("$commonMsg debug exception\n${throwable.stackTraceToString()}", SentryLogLevel.DEBUG) -} + fixture.logs + .first() + .assert("$commonMsg debug exception\n${throwable.stackTraceToString()}", SentryLogLevel.DEBUG) + } -@Test -fun `w log throwable has expected content`() { + @Test + fun `w log throwable has expected content`() { fixture.initSut() SentryLogcatAdapter.w(tag, "$commonMsg warning exception", throwable) fixture.breadcrumbs.first().assert(tag, "$commonMsg warning exception", SentryLevel.WARNING) - fixture.logs.first().assert("$commonMsg warning exception\n${throwable.stackTraceToString()}", SentryLogLevel.WARN) -} + fixture.logs + .first() + .assert( + "$commonMsg warning exception\n${throwable.stackTraceToString()}", + SentryLogLevel.WARN, + ) + } -@Test -fun `wtf log throwable has expected content`() { + @Test + fun `wtf log throwable has expected content`() { fixture.initSut() SentryLogcatAdapter.wtf(tag, "$commonMsg wtf exception", throwable) fixture.breadcrumbs.first().assert(tag, "$commonMsg wtf exception", SentryLevel.ERROR) - fixture.logs.first().assert("$commonMsg wtf exception\n${throwable.stackTraceToString()}", SentryLogLevel.FATAL) -} + fixture.logs + .first() + .assert("$commonMsg wtf exception\n${throwable.stackTraceToString()}", SentryLogLevel.FATAL) + } -@Test -fun `do not send logs if logs is disabled`() { + @Test + fun `do not send logs if logs is disabled`() { fixture.initSut { it.logs.isEnabled = false } SentryLogcatAdapter.v(tag, "$commonMsg verbose") @@ -172,10 +190,10 @@ fun `do not send logs if logs is disabled`() { SentryLogcatAdapter.wtf(tag, "$commonMsg wtf exception", throwable) assertTrue(fixture.logs.isEmpty()) -} + } -@Test -fun `logs add correct number of breadcrumb`() { + @Test + fun `logs add correct number of breadcrumb`() { fixture.initSut() SentryLogcatAdapter.v(tag, commonMsg) SentryLogcatAdapter.d(tag, commonMsg) @@ -184,29 +202,24 @@ fun `logs add correct number of breadcrumb`() { SentryLogcatAdapter.e(tag, commonMsg) SentryLogcatAdapter.wtf(tag, commonMsg) assertEquals( - 6, - fixture.breadcrumbs.filter { - it.message?.contains("SentryLogcatAdapter") ?: false - }.size + 6, + fixture.breadcrumbs.filter { it.message?.contains("SentryLogcatAdapter") ?: false }.size, ) -} + } -private fun Breadcrumb.assert( + private fun Breadcrumb.assert( expectedTag: String, expectedMessage: String, - expectedLevel: SentryLevel -) { + expectedLevel: SentryLevel, + ) { assertEquals(expectedMessage, message) assertEquals(expectedTag, data["tag"]) assertEquals(expectedLevel, level) assertEquals("Logcat", category) -} + } -private fun SentryLogEvent.assert( - expectedMessage: String, - expectedLevel: SentryLogLevel -) { + private fun SentryLogEvent.assert(expectedMessage: String, expectedLevel: SentryLogLevel) { assertEquals(expectedMessage, body) assertEquals(expectedLevel, level) -} + } } From 67541defd59a37cb5aeadf960f2769eb47b896e4 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 4 Jul 2025 18:11:17 +0200 Subject: [PATCH 08/11] exceptions log message instead of stacktraces --- .../io/sentry/android/core/SentryLogcatAdapter.java | 10 +++------- .../sentry/android/core/SentryLogcatAdapterTest.kt | 12 ++++++------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java index 7305719467..a56df7b483 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java @@ -57,15 +57,11 @@ private static void addAsLog( if (!scopes.getOptions().getLogs().isEnabled()) { return; } - if (tr == null) { + final @Nullable String trMessage = tr != null ? tr.getMessage() : null; + if (tr == null || trMessage == null) { scopes.logger().log(level, msg); } else { - StringWriter sw = new StringWriter(256); - PrintWriter pw = new PrintWriter(sw, false); - tr.printStackTrace(pw); - pw.flush(); - scopes.logger().log(level, msg != null ? (msg + "\n" + sw.toString()) : sw.toString()); - pw.close(); + scopes.logger().log(level, msg != null ? (msg + "\n" + trMessage) : trMessage); } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index b5749c6144..f2e618b803 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -113,7 +113,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg error exception", SentryLevel.ERROR) fixture.logs .first() - .assert("$commonMsg error exception\n${throwable.stackTraceToString()}", SentryLogLevel.ERROR) + .assert("$commonMsg error exception\n${throwable.message}", SentryLogLevel.ERROR) } @Test @@ -124,7 +124,7 @@ class SentryLogcatAdapterTest { fixture.logs .first() .assert( - "$commonMsg verbose exception\n${throwable.stackTraceToString()}", + "$commonMsg verbose exception\n${throwable.message}", SentryLogLevel.TRACE, ) } @@ -136,7 +136,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg info exception", SentryLevel.INFO) fixture.logs .first() - .assert("$commonMsg info exception\n${throwable.stackTraceToString()}", SentryLogLevel.INFO) + .assert("$commonMsg info exception\n${throwable.message}", SentryLogLevel.INFO) } @Test @@ -146,7 +146,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg debug exception", SentryLevel.DEBUG) fixture.logs .first() - .assert("$commonMsg debug exception\n${throwable.stackTraceToString()}", SentryLogLevel.DEBUG) + .assert("$commonMsg debug exception\n${throwable.message}", SentryLogLevel.DEBUG) } @Test @@ -157,7 +157,7 @@ class SentryLogcatAdapterTest { fixture.logs .first() .assert( - "$commonMsg warning exception\n${throwable.stackTraceToString()}", + "$commonMsg warning exception\n${throwable.message}", SentryLogLevel.WARN, ) } @@ -169,7 +169,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg wtf exception", SentryLevel.ERROR) fixture.logs .first() - .assert("$commonMsg wtf exception\n${throwable.stackTraceToString()}", SentryLogLevel.FATAL) + .assert("$commonMsg wtf exception\n${throwable.message}", SentryLogLevel.FATAL) } @Test From c44c57e434d4c058b6024ba91085748e2c47d51f Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 4 Jul 2025 16:12:50 +0000 Subject: [PATCH 09/11] Format code --- .../io/sentry/android/core/SentryLogcatAdapter.java | 2 -- .../io/sentry/android/core/SentryLogcatAdapterTest.kt | 10 ++-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java index a56df7b483..ebf99e2b97 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java @@ -6,8 +6,6 @@ import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.SentryLogLevel; -import java.io.PrintWriter; -import java.io.StringWriter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index f2e618b803..1e58bbf6e7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -123,10 +123,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg verbose exception", SentryLevel.DEBUG) fixture.logs .first() - .assert( - "$commonMsg verbose exception\n${throwable.message}", - SentryLogLevel.TRACE, - ) + .assert("$commonMsg verbose exception\n${throwable.message}", SentryLogLevel.TRACE) } @Test @@ -156,10 +153,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg warning exception", SentryLevel.WARNING) fixture.logs .first() - .assert( - "$commonMsg warning exception\n${throwable.message}", - SentryLogLevel.WARN, - ) + .assert("$commonMsg warning exception\n${throwable.message}", SentryLogLevel.WARN) } @Test From 40b4ce19e732ea20a9a149b62ae5188300f1a528 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Fri, 4 Jul 2025 18:14:50 +0200 Subject: [PATCH 10/11] exceptions log message instead of stacktraces --- .../io/sentry/android/core/SentryLogcatAdapter.java | 2 -- .../io/sentry/android/core/SentryLogcatAdapterTest.kt | 10 ++-------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java index a56df7b483..ebf99e2b97 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryLogcatAdapter.java @@ -6,8 +6,6 @@ import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.SentryLogLevel; -import java.io.PrintWriter; -import java.io.StringWriter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt index f2e618b803..1e58bbf6e7 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryLogcatAdapterTest.kt @@ -123,10 +123,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg verbose exception", SentryLevel.DEBUG) fixture.logs .first() - .assert( - "$commonMsg verbose exception\n${throwable.message}", - SentryLogLevel.TRACE, - ) + .assert("$commonMsg verbose exception\n${throwable.message}", SentryLogLevel.TRACE) } @Test @@ -156,10 +153,7 @@ class SentryLogcatAdapterTest { fixture.breadcrumbs.first().assert(tag, "$commonMsg warning exception", SentryLevel.WARNING) fixture.logs .first() - .assert( - "$commonMsg warning exception\n${throwable.message}", - SentryLogLevel.WARN, - ) + .assert("$commonMsg warning exception\n${throwable.message}", SentryLogLevel.WARN) } @Test From 4a90aba315110c054518ead1ad6934154b3762d7 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Mon, 7 Jul 2025 11:12:05 +0200 Subject: [PATCH 11/11] merged main --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 741c93e60e..baf158193a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,17 @@ # Changelog -## 8.16.1-alpha.2 +## Unreleased ### Fixes - 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). + - 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). + +## 8.16.1-alpha.2 + +### Fixes + - Optimize scope when maxBreadcrumb is 0 ([#4504](https://github.com/getsentry/sentry-java/pull/4504)) - Fix javadoc on TransportResult ([#4528](https://github.com/getsentry/sentry-java/pull/4528)) - Session Replay: Fix `IllegalArgumentException` when `Bitmap` is initialized with non-positive values ([#4536](https://github.com/getsentry/sentry-java/pull/4536)) 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