Skip to content

Commit 17ab223

Browse files
feat: logcat integration (#2620)
* add logcat adapter * add changelog * Format code * fix changelog * add ApiStatus and comment * fix comment * set SentryLogcatAdapter to final * fix build * make parameters nullable and use final * add integration test * Format code * remove autoinit on SentryLogcatAdapterTest --------- Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
1 parent 2ec7569 commit 17ab223

File tree

4 files changed

+319
-0
lines changed

4 files changed

+319
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Add `name` and `geo` to `User` ([#2556](https://github.com/getsentry/sentry-java/pull/2556))
88
- Add time-to-initial-display and time-to-full-display measurements to Activity transactions ([#2611](https://github.com/getsentry/sentry-java/pull/2611))
99
- Read integration list written by sentry gradle plugin from manifest ([#2598](https://github.com/getsentry/sentry-java/pull/2598))
10+
- Add Logcat adapter ([#2620](https://github.com/getsentry/sentry-java/pull/2620))
1011

1112
### Fixes
1213

sentry-android-core/api/sentry-android-core.api

+18
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,24 @@ public final class io/sentry/android/core/SentryInitProvider {
220220
public fun shutdown ()V
221221
}
222222

223+
public final class io/sentry/android/core/SentryLogcatAdapter {
224+
public fun <init> ()V
225+
public static fun d (Ljava/lang/String;Ljava/lang/String;)I
226+
public static fun d (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I
227+
public static fun e (Ljava/lang/String;Ljava/lang/String;)I
228+
public static fun e (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I
229+
public static fun i (Ljava/lang/String;Ljava/lang/String;)I
230+
public static fun i (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I
231+
public static fun v (Ljava/lang/String;Ljava/lang/String;)I
232+
public static fun v (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I
233+
public static fun w (Ljava/lang/String;Ljava/lang/String;)I
234+
public static fun w (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I
235+
public static fun w (Ljava/lang/String;Ljava/lang/Throwable;)I
236+
public static fun wtf (Ljava/lang/String;Ljava/lang/String;)I
237+
public static fun wtf (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I
238+
public static fun wtf (Ljava/lang/String;Ljava/lang/Throwable;)I
239+
}
240+
223241
public final class io/sentry/android/core/SentryPerformanceProvider : android/app/Application$ActivityLifecycleCallbacks {
224242
public fun <init> ()V
225243
public fun attachInfo (Landroid/content/Context;Landroid/content/pm/ProviderInfo;)V
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package io.sentry.android.core;
2+
3+
import android.util.Log;
4+
import io.sentry.Breadcrumb;
5+
import io.sentry.Sentry;
6+
import io.sentry.SentryLevel;
7+
import org.jetbrains.annotations.ApiStatus;
8+
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
/**
12+
* This class replaces {@link android.util.Log} with its own implementations which creates a {@link
13+
* io.sentry.Breadcrumb} for each log. It only replaces log functions that meet a minimum level set
14+
* by the user on the Sentry Android Gradle Plugin.
15+
*/
16+
@ApiStatus.Internal
17+
public final class SentryLogcatAdapter {
18+
19+
private static void addAsBreadcrumb(
20+
@Nullable String tag, @NotNull SentryLevel level, @Nullable String msg) {
21+
addAsBreadcrumb(tag, level, msg, null);
22+
}
23+
24+
private static void addAsBreadcrumb(
25+
@Nullable String tag, @NotNull SentryLevel level, @Nullable Throwable tr) {
26+
addAsBreadcrumb(tag, level, null, tr);
27+
}
28+
29+
private static void addAsBreadcrumb(
30+
@Nullable final String tag,
31+
@NotNull final SentryLevel level,
32+
@Nullable final String msg,
33+
@Nullable final Throwable tr) {
34+
Breadcrumb breadcrumb = new Breadcrumb();
35+
breadcrumb.setCategory("Logcat");
36+
breadcrumb.setMessage(msg);
37+
breadcrumb.setLevel(level);
38+
if (tag != null) {
39+
breadcrumb.setData("tag", tag);
40+
}
41+
if (tr != null && tr.getMessage() != null) {
42+
breadcrumb.setData("throwable", tr.getMessage());
43+
}
44+
Sentry.addBreadcrumb(breadcrumb);
45+
}
46+
47+
public static int v(@Nullable String tag, @Nullable String msg) {
48+
addAsBreadcrumb(tag, SentryLevel.DEBUG, msg);
49+
return Log.v(tag, msg);
50+
}
51+
52+
public static int v(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
53+
addAsBreadcrumb(tag, SentryLevel.DEBUG, msg, tr);
54+
return Log.v(tag, msg, tr);
55+
}
56+
57+
public static int d(@Nullable String tag, @Nullable String msg) {
58+
addAsBreadcrumb(tag, SentryLevel.DEBUG, msg);
59+
return Log.d(tag, msg);
60+
}
61+
62+
public static int d(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
63+
addAsBreadcrumb(tag, SentryLevel.DEBUG, msg, tr);
64+
return Log.d(tag, msg, tr);
65+
}
66+
67+
public static int i(@Nullable String tag, @Nullable String msg) {
68+
addAsBreadcrumb(tag, SentryLevel.INFO, msg);
69+
return Log.i(tag, msg);
70+
}
71+
72+
public static int i(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
73+
addAsBreadcrumb(tag, SentryLevel.INFO, msg, tr);
74+
return Log.i(tag, msg, tr);
75+
}
76+
77+
public static int w(@Nullable String tag, @Nullable String msg) {
78+
addAsBreadcrumb(tag, SentryLevel.WARNING, msg);
79+
return Log.w(tag, msg);
80+
}
81+
82+
public static int w(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
83+
addAsBreadcrumb(tag, SentryLevel.WARNING, msg, tr);
84+
return Log.w(tag, msg, tr);
85+
}
86+
87+
public static int w(@Nullable String tag, @Nullable Throwable tr) {
88+
addAsBreadcrumb(tag, SentryLevel.WARNING, tr);
89+
return Log.w(tag, tr);
90+
}
91+
92+
public static int e(@Nullable String tag, @Nullable String msg) {
93+
addAsBreadcrumb(tag, SentryLevel.ERROR, msg);
94+
return Log.e(tag, msg);
95+
}
96+
97+
public static int e(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
98+
addAsBreadcrumb(tag, SentryLevel.ERROR, msg, tr);
99+
return Log.e(tag, msg, tr);
100+
}
101+
102+
public static int wtf(@Nullable String tag, @Nullable String msg) {
103+
addAsBreadcrumb(tag, SentryLevel.ERROR, msg);
104+
return Log.wtf(tag, msg);
105+
}
106+
107+
public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
108+
addAsBreadcrumb(tag, SentryLevel.ERROR, tr);
109+
return Log.wtf(tag, tr);
110+
}
111+
112+
public static int wtf(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
113+
addAsBreadcrumb(tag, SentryLevel.ERROR, msg, tr);
114+
return Log.wtf(tag, msg, tr);
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package io.sentry.android.core
2+
3+
import android.os.Bundle
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import io.sentry.Breadcrumb
6+
import io.sentry.Sentry
7+
import io.sentry.SentryLevel
8+
import io.sentry.SentryOptions
9+
import org.junit.runner.RunWith
10+
import java.lang.RuntimeException
11+
import kotlin.test.BeforeTest
12+
import kotlin.test.Test
13+
import kotlin.test.assertEquals
14+
15+
@RunWith(AndroidJUnit4::class)
16+
class SentryLogcatAdapterTest {
17+
private val breadcrumbs = mutableListOf<Breadcrumb>()
18+
private val tag = "my-tag"
19+
private val commonMsg = "SentryLogcatAdapter"
20+
private val throwable = RuntimeException("Test Exception")
21+
22+
class Fixture {
23+
24+
fun initSut(
25+
options: Sentry.OptionsConfiguration<SentryAndroidOptions>? = null
26+
) {
27+
val metadata = Bundle().apply {
28+
putString(ManifestMetadataReader.DSN, "https://key@sentry.io/123")
29+
}
30+
val mockContext = ContextUtilsTest.mockMetaData(metaData = metadata)
31+
when {
32+
options != null -> SentryAndroid.init(mockContext, options)
33+
else -> SentryAndroid.init(mockContext)
34+
}
35+
}
36+
}
37+
38+
private val fixture = Fixture()
39+
40+
@BeforeTest
41+
fun `set up`() {
42+
Sentry.close()
43+
AppStartState.getInstance().resetInstance()
44+
breadcrumbs.clear()
45+
46+
fixture.initSut {
47+
it.beforeBreadcrumb = SentryOptions.BeforeBreadcrumbCallback { breadcrumb, _ ->
48+
breadcrumbs.add(breadcrumb)
49+
breadcrumb
50+
}
51+
}
52+
53+
SentryLogcatAdapter.v(tag, "$commonMsg verbose")
54+
SentryLogcatAdapter.i(tag, "$commonMsg info")
55+
SentryLogcatAdapter.d(tag, "$commonMsg debug")
56+
SentryLogcatAdapter.w(tag, "$commonMsg warning")
57+
SentryLogcatAdapter.e(tag, "$commonMsg error")
58+
SentryLogcatAdapter.wtf(tag, "$commonMsg wtf")
59+
}
60+
61+
@Test
62+
fun `verbose log message has expected content`() {
63+
val breadcrumb = breadcrumbs.find { it.level == SentryLevel.DEBUG && it.message?.contains("verbose") ?: false }
64+
assertEquals("Logcat", breadcrumb?.category)
65+
assertEquals(tag, breadcrumb?.data?.get("tag"))
66+
assert(breadcrumb?.message?.contains("verbose") == true)
67+
}
68+
69+
@Test
70+
fun `info log message has expected content`() {
71+
val breadcrumb = breadcrumbs.find { it.level == SentryLevel.INFO && it.message?.contains("info") ?: false }
72+
assertEquals("Logcat", breadcrumb?.category)
73+
assertEquals(tag, breadcrumb?.data?.get("tag"))
74+
assert(breadcrumb?.message?.contains("info") == true)
75+
}
76+
77+
@Test
78+
fun `debug log message has expected content`() {
79+
val breadcrumb = breadcrumbs.find { it.level == SentryLevel.DEBUG && it.message?.contains("debug") ?: false }
80+
assertEquals("Logcat", breadcrumb?.category)
81+
assertEquals(tag, breadcrumb?.data?.get("tag"))
82+
assert(breadcrumb?.message?.contains("debug") == true)
83+
}
84+
85+
@Test
86+
fun `warning log message has expected content`() {
87+
val breadcrumb = breadcrumbs.find { it.level == SentryLevel.WARNING && it.message?.contains("warning") ?: false }
88+
assertEquals("Logcat", breadcrumb?.category)
89+
assertEquals(tag, breadcrumb?.data?.get("tag"))
90+
assert(breadcrumb?.message?.contains("warning") == true)
91+
}
92+
93+
@Test
94+
fun `error log message has expected content`() {
95+
val breadcrumb = breadcrumbs.find { it.level == SentryLevel.ERROR && it.message?.contains("error") ?: false }
96+
assertEquals("Logcat", breadcrumb?.category)
97+
assertEquals(tag, breadcrumb?.data?.get("tag"))
98+
assert(breadcrumb?.message?.contains("error") == true)
99+
}
100+
101+
@Test
102+
fun `wtf log message has expected content`() {
103+
val breadcrumb = breadcrumbs.find { it.level == SentryLevel.ERROR && it.message?.contains("wtf") ?: false }
104+
assertEquals("Logcat", breadcrumb?.category)
105+
assertEquals(tag, breadcrumb?.data?.get("tag"))
106+
assert(breadcrumb?.message?.contains("wtf") == true)
107+
}
108+
109+
@Test
110+
fun `e log throwable has expected content`() {
111+
SentryLogcatAdapter.e(tag, "$commonMsg error exception", throwable)
112+
113+
val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false }
114+
assertEquals("Logcat", breadcrumb?.category)
115+
assertEquals(tag, breadcrumb?.getData("tag"))
116+
assertEquals(SentryLevel.ERROR, breadcrumb?.level)
117+
assertEquals(throwable.message, breadcrumb?.getData("throwable"))
118+
}
119+
120+
@Test
121+
fun `v log throwable has expected content`() {
122+
SentryLogcatAdapter.v(tag, "$commonMsg verbose exception", throwable)
123+
124+
val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false }
125+
assertEquals("Logcat", breadcrumb?.category)
126+
assertEquals(tag, breadcrumb?.getData("tag"))
127+
assertEquals(SentryLevel.DEBUG, breadcrumb?.level)
128+
assertEquals(throwable.message, breadcrumb?.getData("throwable"))
129+
}
130+
131+
@Test
132+
fun `i log throwable has expected content`() {
133+
SentryLogcatAdapter.i(tag, "$commonMsg info exception", throwable)
134+
135+
val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false }
136+
assertEquals("Logcat", breadcrumb?.category)
137+
assertEquals(tag, breadcrumb?.getData("tag"))
138+
assertEquals(SentryLevel.INFO, breadcrumb?.level)
139+
assertEquals(throwable.message, breadcrumb?.getData("throwable"))
140+
}
141+
142+
@Test
143+
fun `d log throwable has expected content`() {
144+
SentryLogcatAdapter.d(tag, "$commonMsg debug exception", throwable)
145+
146+
val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false }
147+
assertEquals("Logcat", breadcrumb?.category)
148+
assertEquals(tag, breadcrumb?.getData("tag"))
149+
assertEquals(SentryLevel.DEBUG, breadcrumb?.level)
150+
assertEquals(throwable.message, breadcrumb?.getData("throwable"))
151+
}
152+
153+
@Test
154+
fun `w log throwable has expected content`() {
155+
SentryLogcatAdapter.w(tag, "$commonMsg warning exception", throwable)
156+
157+
val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false }
158+
assertEquals("Logcat", breadcrumb?.category)
159+
assertEquals(tag, breadcrumb?.getData("tag"))
160+
assertEquals(SentryLevel.WARNING, breadcrumb?.level)
161+
assertEquals(throwable.message, breadcrumb?.getData("throwable"))
162+
}
163+
164+
@Test
165+
fun `wtf log throwable has expected content`() {
166+
SentryLogcatAdapter.wtf(tag, "$commonMsg wtf exception", throwable)
167+
168+
val breadcrumb = breadcrumbs.find { it.message?.contains("exception") ?: false }
169+
assertEquals("Logcat", breadcrumb?.category)
170+
assertEquals(tag, breadcrumb?.getData("tag"))
171+
assertEquals(SentryLevel.ERROR, breadcrumb?.level)
172+
assertEquals(throwable.message, breadcrumb?.getData("throwable"))
173+
}
174+
175+
@Test
176+
fun `logs add correct number of breadcrumb`() {
177+
assertEquals(
178+
6,
179+
breadcrumbs.filter {
180+
it.message?.contains("SentryLogcatAdapter") ?: false
181+
}.size
182+
)
183+
}
184+
}

0 commit comments

Comments
 (0)