Skip to content

Commit ac1e895

Browse files
Attachments can be manipulated via hints (#2046)
* Store attachments in hints and allow manipulation in beforeSend and eventProcessor * Add changelog * Add tests for breadcrumbs and attachments via hints * Update CHANGELOG.md Co-authored-by: Philipp Hofmann <[email protected]> * Rename AttachmentContainer to Attachments * Use long for test * Move attachments into Hints * Fix kotlin/java interop for Hints * Convert screenshot from map entry to property * Rename hint name param * Rename clear to clearAttachments * Use kotlin short version access for getScreenshot * Move AttachmentsTest into HintsTest; add param names * Make primitiveMapping table static * Use ArrayList as there should not be a synchronization issue for hints Co-authored-by: Philipp Hofmann <[email protected]>
1 parent b0903ae commit ac1e895

File tree

10 files changed

+570
-50
lines changed

10 files changed

+570
-50
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Allow setting SDK info (name & version) in manifest ([#2016](https://github.com/getsentry/sentry-java/pull/2016))
88
- Allow setting native Android SDK name during build ([#2035](https://github.com/getsentry/sentry-java/pull/2035))
99
- Hints are now used via a Hints object and passed into beforeSend and EventProcessor as @NotNull Hints object ([#2045](https://github.com/getsentry/sentry-java/pull/2045))
10+
- Attachments can be manipulated via hints ([#2046](https://github.com/getsentry/sentry-java/pull/2046))
1011

1112
## 6.0.0-beta.3
1213

sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.sentry.android.core;
22

33
import static io.sentry.TypeCheckHint.ANDROID_ACTIVITY;
4-
import static io.sentry.TypeCheckHint.SENTRY_SCREENSHOT;
54

65
import android.annotation.SuppressLint;
76
import android.app.Activity;
@@ -93,9 +92,7 @@ public ScreenshotEventProcessor(
9392

9493
if (byteArrayOutputStream.size() > 0) {
9594
// screenshot png is around ~100-150 kb
96-
hints.set(
97-
SENTRY_SCREENSHOT,
98-
Attachment.fromScreenshot(byteArrayOutputStream.toByteArray()));
95+
hints.setScreenshot(Attachment.fromScreenshot(byteArrayOutputStream.toByteArray()));
9996
hints.set(ANDROID_ACTIVITY, activity);
10097
} else {
10198
this.options

sentry-android-core/src/test/java/io/sentry/android/core/ScreenshotEventProcessorTest.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import io.sentry.Attachment
1414
import io.sentry.MainEventProcessor
1515
import io.sentry.SentryEvent
1616
import io.sentry.TypeCheckHint.ANDROID_ACTIVITY
17-
import io.sentry.TypeCheckHint.SENTRY_SCREENSHOT
1817
import io.sentry.hints.Hints
1918
import org.junit.runner.RunWith
2019
import kotlin.test.BeforeTest
@@ -103,7 +102,7 @@ class ScreenshotEventProcessorTest {
103102
val event = fixture.mainProcessor.process(getEvent(), hints)
104103
sut.process(event, hints)
105104

106-
assertNull(hints[SENTRY_SCREENSHOT])
105+
assertNull(hints.screenshot)
107106
}
108107

109108
@Test
@@ -116,7 +115,7 @@ class ScreenshotEventProcessorTest {
116115
val event = fixture.mainProcessor.process(SentryEvent(), hints)
117116
sut.process(event, hints)
118117

119-
assertNull(hints[SENTRY_SCREENSHOT])
118+
assertNull(hints.screenshot)
120119
}
121120

122121
@Test
@@ -127,7 +126,7 @@ class ScreenshotEventProcessorTest {
127126
val event = fixture.mainProcessor.process(getEvent(), hints)
128127
sut.process(event, hints)
129128

130-
assertNull(hints[SENTRY_SCREENSHOT])
129+
assertNull(hints.screenshot)
131130
}
132131

133132
@Test
@@ -141,7 +140,7 @@ class ScreenshotEventProcessorTest {
141140
val event = fixture.mainProcessor.process(getEvent(), hints)
142141
sut.process(event, hints)
143142

144-
assertNull(hints[SENTRY_SCREENSHOT])
143+
assertNull(hints.screenshot)
145144
}
146145

147146
@Test
@@ -156,7 +155,7 @@ class ScreenshotEventProcessorTest {
156155
val event = fixture.mainProcessor.process(getEvent(), hints)
157156
sut.process(event, hints)
158157

159-
assertNull(hints[SENTRY_SCREENSHOT])
158+
assertNull(hints.screenshot)
160159
}
161160

162161
@Test
@@ -169,7 +168,7 @@ class ScreenshotEventProcessorTest {
169168
val event = fixture.mainProcessor.process(getEvent(), hints)
170169
sut.process(event, hints)
171170

172-
val screenshot = hints[SENTRY_SCREENSHOT]
171+
val screenshot = hints.screenshot
173172
assertTrue(screenshot is Attachment)
174173
assertEquals("screenshot.png", screenshot.filename)
175174
assertEquals("image/png", screenshot.contentType)
@@ -188,7 +187,7 @@ class ScreenshotEventProcessorTest {
188187
val event = fixture.mainProcessor.process(getEvent(), hints)
189188
sut.process(event, hints)
190189

191-
assertNull(hints[SENTRY_SCREENSHOT])
190+
assertNull(hints.screenshot)
192191
}
193192

194193
private fun getEvent(): SentryEvent = SentryEvent(Throwable("Throwable"))

sentry/api/sentry.api

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1638,7 +1638,6 @@ public final class io/sentry/TypeCheckHint {
16381638
public static final field OKHTTP_RESPONSE Ljava/lang/String;
16391639
public static final field OPEN_FEIGN_REQUEST Ljava/lang/String;
16401640
public static final field OPEN_FEIGN_RESPONSE Ljava/lang/String;
1641-
public static final field SENTRY_SCREENSHOT Ljava/lang/String;
16421641
public static final field SENTRY_SYNTHETIC_EXCEPTION Ljava/lang/String;
16431642
public static final field SENTRY_TYPE_CHECK_HINT Ljava/lang/String;
16441643
public static final field SERVLET_REQUEST Ljava/lang/String;
@@ -1848,9 +1847,19 @@ public abstract interface class io/sentry/hints/Flushable {
18481847

18491848
public final class io/sentry/hints/Hints {
18501849
public fun <init> ()V
1850+
public fun addAttachment (Lio/sentry/Attachment;)V
1851+
public fun addAttachments (Ljava/util/List;)V
1852+
public fun clearAttachments ()V
18511853
public fun get (Ljava/lang/String;)Ljava/lang/Object;
1854+
public fun getAs (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
1855+
public fun getAttachments ()Ljava/util/List;
1856+
public fun getScreenshot ()Lio/sentry/Attachment;
18521857
public fun remove (Ljava/lang/String;)V
1858+
public fun replaceAttachments (Ljava/util/List;)V
18531859
public fun set (Ljava/lang/String;Ljava/lang/Object;)V
1860+
public fun setScreenshot (Lio/sentry/Attachment;)V
1861+
public static fun withAttachment (Lio/sentry/Attachment;)Lio/sentry/hints/Hints;
1862+
public static fun withAttachments (Ljava/util/List;)Lio/sentry/hints/Hints;
18541863
}
18551864

18561865
public abstract interface class io/sentry/hints/Resettable {

sentry/src/main/java/io/sentry/SentryClient.java

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

3-
import static io.sentry.TypeCheckHint.SENTRY_SCREENSHOT;
4-
53
import io.sentry.clientreport.DiscardReason;
64
import io.sentry.exception.SentryEnvelopeException;
75
import io.sentry.hints.DiskFlushNotification;
@@ -80,6 +78,11 @@ private boolean shouldApplyScopeData(
8078
hints = new Hints();
8179
}
8280

81+
// TODO what does cached etc mean for devs manipulating hints in beforeSend and eventProcessor?
82+
if (shouldApplyScopeData(event, hints)) {
83+
addScopeAttachmentsToHints(scope, hints);
84+
}
85+
8386
options.getLogger().log(SentryLevel.DEBUG, "Capturing event: %s", event.getEventId());
8487

8588
if (event != null) {
@@ -173,7 +176,7 @@ private boolean shouldApplyScopeData(
173176
? scope.getTransaction().traceState()
174177
: null;
175178
final boolean shouldSendAttachments = event != null;
176-
List<Attachment> attachments = shouldSendAttachments ? getAttachments(scope, hints) : null;
179+
List<Attachment> attachments = shouldSendAttachments ? getAttachments(hints) : null;
177180
final SentryEnvelope envelope = buildEnvelope(event, attachments, session, traceState, null);
178181

179182
if (envelope != null) {
@@ -189,6 +192,12 @@ private boolean shouldApplyScopeData(
189192
return sentryId;
190193
}
191194

195+
private void addScopeAttachmentsToHints(@Nullable Scope scope, @NotNull Hints hints) {
196+
if (scope != null) {
197+
hints.addAttachments(scope.getAttachments());
198+
}
199+
}
200+
192201
private boolean shouldSendSessionUpdateForDroppedEvent(
193202
@Nullable Session sessionBeforeUpdate, @Nullable Session sessionAfterUpdate) {
194203
if (sessionAfterUpdate == null) {
@@ -215,21 +224,12 @@ private boolean shouldSendSessionUpdateForDroppedEvent(
215224
return false;
216225
}
217226

218-
private @Nullable List<Attachment> getAttachments(
219-
final @Nullable Scope scope, final @NotNull Hints hints) {
220-
List<Attachment> attachments = null;
221-
if (scope != null) {
222-
attachments = scope.getAttachments();
223-
}
224-
225-
final Object screenshotAttachment = hints.get(SENTRY_SCREENSHOT);
226-
if (screenshotAttachment instanceof Attachment) {
227-
228-
if (attachments == null) {
229-
attachments = new ArrayList<>();
230-
}
227+
private @Nullable List<Attachment> getAttachments(final @NotNull Hints hints) {
228+
@NotNull final List<Attachment> attachments = hints.getAttachments();
231229

232-
attachments.add((Attachment) screenshotAttachment);
230+
@Nullable final Attachment screenshot = hints.getScreenshot();
231+
if (screenshot != null) {
232+
attachments.add(screenshot);
233233
}
234234

235235
return attachments;
@@ -506,6 +506,10 @@ public void captureSession(final @NotNull Session session, final @Nullable Hints
506506
hints = new Hints();
507507
}
508508

509+
if (shouldApplyScopeData(transaction, hints)) {
510+
addScopeAttachmentsToHints(scope, hints);
511+
}
512+
509513
options
510514
.getLogger()
511515
.log(SentryLevel.DEBUG, "Capturing transaction: %s", transaction.getEventId());
@@ -540,7 +544,7 @@ public void captureSession(final @NotNull Session session, final @Nullable Hints
540544
final SentryEnvelope envelope =
541545
buildEnvelope(
542546
transaction,
543-
filterForTransaction(getAttachments(scope, hints)),
547+
filterForTransaction(getAttachments(hints)),
544548
null,
545549
traceState,
546550
profilingTraceData);

sentry/src/main/java/io/sentry/TypeCheckHint.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ public final class TypeCheckHint {
2525
public static final String ANDROID_VIEW = "android:view";
2626
/** Used for Fragment breadcrumbs. */
2727
public static final String ANDROID_FRAGMENT = "android:fragment";
28-
/** Used for screenshots. */
29-
public static final String SENTRY_SCREENSHOT = "sentry:screenshot";
3028

3129
/** Used for OkHttp response breadcrumbs. */
3230
public static final String OKHTTP_RESPONSE = "okHttp:response";
Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,108 @@
11
package io.sentry.hints;
22

3+
import io.sentry.Attachment;
4+
import java.util.ArrayList;
35
import java.util.HashMap;
6+
import java.util.List;
47
import java.util.Map;
58
import org.jetbrains.annotations.NotNull;
69
import org.jetbrains.annotations.Nullable;
710

811
public final class Hints {
912

13+
private static final @NotNull Map<String, Class<?>> PRIMITIVE_MAPPINGS;
14+
15+
static {
16+
PRIMITIVE_MAPPINGS = new HashMap<>();
17+
PRIMITIVE_MAPPINGS.put("boolean", Boolean.class);
18+
PRIMITIVE_MAPPINGS.put("char", Character.class);
19+
PRIMITIVE_MAPPINGS.put("byte", Byte.class);
20+
PRIMITIVE_MAPPINGS.put("short", Short.class);
21+
PRIMITIVE_MAPPINGS.put("int", Integer.class);
22+
PRIMITIVE_MAPPINGS.put("long", Long.class);
23+
PRIMITIVE_MAPPINGS.put("float", Float.class);
24+
PRIMITIVE_MAPPINGS.put("double", Double.class);
25+
}
26+
1027
private final @NotNull Map<String, Object> internalStorage = new HashMap<String, Object>();
28+
private final @NotNull List<Attachment> attachments = new ArrayList<>();
29+
private @Nullable Attachment screenshot = null;
1130

12-
public void set(@NotNull String hintType, @Nullable Object hint) {
13-
internalStorage.put(hintType, hint);
31+
public static @NotNull Hints withAttachment(@Nullable Attachment attachment) {
32+
@NotNull final Hints hints = new Hints();
33+
hints.addAttachment(attachment);
34+
return hints;
1435
}
1536

16-
public @Nullable Object get(@NotNull String hintType) {
17-
return internalStorage.get(hintType);
37+
public static @NotNull Hints withAttachments(@Nullable List<Attachment> attachments) {
38+
@NotNull final Hints hints = new Hints();
39+
hints.addAttachments(attachments);
40+
return hints;
1841
}
1942

20-
// TODO maybe not public
21-
public void remove(@NotNull String hintType) {
22-
internalStorage.remove(hintType);
43+
public void set(@NotNull String name, @Nullable Object hint) {
44+
internalStorage.put(name, hint);
2345
}
2446

25-
// TODO addAttachment(one)
26-
// TODO getAttachments(): List
27-
// TODO setAttachments(list)
28-
// TODO clearAttachments()
47+
public @Nullable Object get(@NotNull String name) {
48+
return internalStorage.get(name);
49+
}
50+
51+
@SuppressWarnings("unchecked")
52+
public <T extends Object> @Nullable T getAs(@NotNull String name, @NotNull Class<T> clazz) {
53+
Object hintValue = internalStorage.get(name);
54+
55+
if (clazz.isInstance(hintValue)) {
56+
return (T) hintValue;
57+
} else if (isCastablePrimitive(hintValue, clazz)) {
58+
return (T) hintValue;
59+
} else {
60+
return null;
61+
}
62+
}
63+
64+
public void remove(@NotNull String name) {
65+
internalStorage.remove(name);
66+
}
67+
68+
public void addAttachment(@Nullable Attachment attachment) {
69+
if (attachment != null) {
70+
attachments.add(attachment);
71+
}
72+
}
73+
74+
public void addAttachments(@Nullable List<Attachment> attachments) {
75+
if (attachments != null) {
76+
this.attachments.addAll(attachments);
77+
}
78+
}
79+
80+
public @NotNull List<Attachment> getAttachments() {
81+
return new ArrayList<>(attachments);
82+
}
83+
84+
public void replaceAttachments(@Nullable List<Attachment> attachments) {
85+
clearAttachments();
86+
addAttachments(attachments);
87+
}
88+
89+
public void clearAttachments() {
90+
attachments.clear();
91+
}
92+
93+
public void setScreenshot(@Nullable Attachment screenshot) {
94+
this.screenshot = screenshot;
95+
}
96+
97+
public @Nullable Attachment getScreenshot() {
98+
return screenshot;
99+
}
100+
101+
private boolean isCastablePrimitive(@Nullable Object hintValue, @NotNull Class<?> clazz) {
102+
Class<?> nonPrimitiveClass = PRIMITIVE_MAPPINGS.get(clazz.getCanonicalName());
103+
return hintValue != null
104+
&& clazz.isPrimitive()
105+
&& nonPrimitiveClass != null
106+
&& nonPrimitiveClass.isInstance(hintValue);
107+
}
29108
}

sentry/src/main/java/io/sentry/util/HintUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import org.jetbrains.annotations.NotNull;
1111
import org.jetbrains.annotations.Nullable;
1212

13-
/** Util class for Applying or not scope's data to an event */
13+
/** Util class dealing with Hints as not to pollute the Hints API with internal functionality */
1414
@ApiStatus.Internal
1515
public final class HintUtils {
1616

0 commit comments

Comments
 (0)