Skip to content

Attachments can be manipulated via hints #2046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Allow setting SDK info (name & version) in manifest ([#2016](https://github.com/getsentry/sentry-java/pull/2016))
- Allow setting native Android SDK name during build ([#2035](https://github.com/getsentry/sentry-java/pull/2035))
- Attachments can be manipulated via hints ([#2046](https://github.com/getsentry/sentry-java/pull/2046))

## 6.0.0-beta.3

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.sentry.android.core;

import static io.sentry.TypeCheckHint.ANDROID_ACTIVITY;
import static io.sentry.TypeCheckHint.SENTRY_SCREENSHOT;

import android.annotation.SuppressLint;
import android.app.Activity;
Expand Down Expand Up @@ -93,9 +92,7 @@ public ScreenshotEventProcessor(

if (byteArrayOutputStream.size() > 0) {
// screenshot png is around ~100-150 kb
hints.set(
SENTRY_SCREENSHOT,
Attachment.fromScreenshot(byteArrayOutputStream.toByteArray()));
hints.setScreenshot(Attachment.fromScreenshot(byteArrayOutputStream.toByteArray()));
hints.set(ANDROID_ACTIVITY, activity);
} else {
this.options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import io.sentry.Attachment
import io.sentry.MainEventProcessor
import io.sentry.SentryEvent
import io.sentry.TypeCheckHint.ANDROID_ACTIVITY
import io.sentry.TypeCheckHint.SENTRY_SCREENSHOT
import io.sentry.hints.Hints
import org.junit.runner.RunWith
import kotlin.test.BeforeTest
Expand Down Expand Up @@ -103,7 +102,7 @@ class ScreenshotEventProcessorTest {
val event = fixture.mainProcessor.process(getEvent(), hints)
sut.process(event, hints)

assertNull(hints[SENTRY_SCREENSHOT])
assertNull(hints.screenshot)
}

@Test
Expand All @@ -116,7 +115,7 @@ class ScreenshotEventProcessorTest {
val event = fixture.mainProcessor.process(SentryEvent(), hints)
sut.process(event, hints)

assertNull(hints[SENTRY_SCREENSHOT])
assertNull(hints.screenshot)
}

@Test
Expand All @@ -127,7 +126,7 @@ class ScreenshotEventProcessorTest {
val event = fixture.mainProcessor.process(getEvent(), hints)
sut.process(event, hints)

assertNull(hints[SENTRY_SCREENSHOT])
assertNull(hints.screenshot)
}

@Test
Expand All @@ -141,7 +140,7 @@ class ScreenshotEventProcessorTest {
val event = fixture.mainProcessor.process(getEvent(), hints)
sut.process(event, hints)

assertNull(hints[SENTRY_SCREENSHOT])
assertNull(hints.screenshot)
}

@Test
Expand All @@ -156,7 +155,7 @@ class ScreenshotEventProcessorTest {
val event = fixture.mainProcessor.process(getEvent(), hints)
sut.process(event, hints)

assertNull(hints[SENTRY_SCREENSHOT])
assertNull(hints.screenshot)
}

@Test
Expand All @@ -169,7 +168,7 @@ class ScreenshotEventProcessorTest {
val event = fixture.mainProcessor.process(getEvent(), hints)
sut.process(event, hints)

val screenshot = hints[SENTRY_SCREENSHOT]
val screenshot = hints.screenshot
assertTrue(screenshot is Attachment)
assertEquals("screenshot.png", screenshot.filename)
assertEquals("image/png", screenshot.contentType)
Expand All @@ -188,7 +187,7 @@ class ScreenshotEventProcessorTest {
val event = fixture.mainProcessor.process(getEvent(), hints)
sut.process(event, hints)

assertNull(hints[SENTRY_SCREENSHOT])
assertNull(hints.screenshot)
}

private fun getEvent(): SentryEvent = SentryEvent(Throwable("Throwable"))
Expand Down
11 changes: 10 additions & 1 deletion sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -1638,7 +1638,6 @@ public final class io/sentry/TypeCheckHint {
public static final field OKHTTP_RESPONSE Ljava/lang/String;
public static final field OPEN_FEIGN_REQUEST Ljava/lang/String;
public static final field OPEN_FEIGN_RESPONSE Ljava/lang/String;
public static final field SENTRY_SCREENSHOT Ljava/lang/String;
public static final field SENTRY_SYNTHETIC_EXCEPTION Ljava/lang/String;
public static final field SENTRY_TYPE_CHECK_HINT Ljava/lang/String;
public static final field SERVLET_REQUEST Ljava/lang/String;
Expand Down Expand Up @@ -1848,9 +1847,19 @@ public abstract interface class io/sentry/hints/Flushable {

public final class io/sentry/hints/Hints {
public fun <init> ()V
public fun addAttachment (Lio/sentry/Attachment;)V
public fun addAttachments (Ljava/util/List;)V
public fun clearAttachments ()V
public fun get (Ljava/lang/String;)Ljava/lang/Object;
public fun getAs (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
public fun getAttachments ()Ljava/util/List;
public fun getScreenshot ()Lio/sentry/Attachment;
public fun remove (Ljava/lang/String;)V
public fun replaceAttachments (Ljava/util/List;)V
public fun set (Ljava/lang/String;Ljava/lang/Object;)V
public fun setScreenshot (Lio/sentry/Attachment;)V
public static fun withAttachment (Lio/sentry/Attachment;)Lio/sentry/hints/Hints;
public static fun withAttachments (Ljava/util/List;)Lio/sentry/hints/Hints;
}

public abstract interface class io/sentry/hints/Resettable {
Expand Down
40 changes: 22 additions & 18 deletions sentry/src/main/java/io/sentry/SentryClient.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.sentry;

import static io.sentry.TypeCheckHint.SENTRY_SCREENSHOT;

import io.sentry.clientreport.DiscardReason;
import io.sentry.exception.SentryEnvelopeException;
import io.sentry.hints.DiskFlushNotification;
Expand Down Expand Up @@ -80,6 +78,11 @@ private boolean shouldApplyScopeData(
hints = new Hints();
}

// TODO what does cached etc mean for devs manipulating hints in beforeSend and eventProcessor?
if (shouldApplyScopeData(event, hints)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to prevent manipulation of attachments if !shouldApplyScopeData?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you did makes sense to only get the attachments from the scope into the hints with this condition.

If we're not applying the scope to the event/hint, an attachment still could show up in the hint because the user added it directly while calling capture. And that's totally fine.

addScopeAttachmentsToHints(scope, hints);
}

options.getLogger().log(SentryLevel.DEBUG, "Capturing event: %s", event.getEventId());

if (event != null) {
Expand Down Expand Up @@ -173,7 +176,7 @@ private boolean shouldApplyScopeData(
? scope.getTransaction().traceState()
: null;
final boolean shouldSendAttachments = event != null;
List<Attachment> attachments = shouldSendAttachments ? getAttachments(scope, hints) : null;
List<Attachment> attachments = shouldSendAttachments ? getAttachments(hints) : null;
final SentryEnvelope envelope = buildEnvelope(event, attachments, session, traceState, null);

if (envelope != null) {
Expand All @@ -189,6 +192,12 @@ private boolean shouldApplyScopeData(
return sentryId;
}

private void addScopeAttachmentsToHints(@Nullable Scope scope, @NotNull Hints hints) {
if (scope != null) {
hints.addAttachments(scope.getAttachments());
}
}

private boolean shouldSendSessionUpdateForDroppedEvent(
@Nullable Session sessionBeforeUpdate, @Nullable Session sessionAfterUpdate) {
if (sessionAfterUpdate == null) {
Expand All @@ -215,21 +224,12 @@ private boolean shouldSendSessionUpdateForDroppedEvent(
return false;
}

private @Nullable List<Attachment> getAttachments(
final @Nullable Scope scope, final @NotNull Hints hints) {
List<Attachment> attachments = null;
if (scope != null) {
attachments = scope.getAttachments();
}

final Object screenshotAttachment = hints.get(SENTRY_SCREENSHOT);
if (screenshotAttachment instanceof Attachment) {

if (attachments == null) {
attachments = new ArrayList<>();
}
private @Nullable List<Attachment> getAttachments(final @NotNull Hints hints) {
@NotNull final List<Attachment> attachments = hints.getAttachments();

attachments.add((Attachment) screenshotAttachment);
@Nullable final Attachment screenshot = hints.getScreenshot();
if (screenshot != null) {
attachments.add(screenshot);
}

return attachments;
Expand Down Expand Up @@ -506,6 +506,10 @@ public void captureSession(final @NotNull Session session, final @Nullable Hints
hints = new Hints();
}

if (shouldApplyScopeData(transaction, hints)) {
addScopeAttachmentsToHints(scope, hints);
}

options
.getLogger()
.log(SentryLevel.DEBUG, "Capturing transaction: %s", transaction.getEventId());
Expand Down Expand Up @@ -540,7 +544,7 @@ public void captureSession(final @NotNull Session session, final @Nullable Hints
final SentryEnvelope envelope =
buildEnvelope(
transaction,
filterForTransaction(getAttachments(scope, hints)),
filterForTransaction(getAttachments(hints)),
null,
traceState,
profilingTraceData);
Expand Down
2 changes: 0 additions & 2 deletions sentry/src/main/java/io/sentry/TypeCheckHint.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public final class TypeCheckHint {
public static final String ANDROID_VIEW = "android:view";
/** Used for Fragment breadcrumbs. */
public static final String ANDROID_FRAGMENT = "android:fragment";
/** Used for screenshots. */
public static final String SENTRY_SCREENSHOT = "sentry:screenshot";

/** Used for OkHttp response breadcrumbs. */
public static final String OKHTTP_RESPONSE = "okHttp:response";
Expand Down
101 changes: 90 additions & 11 deletions sentry/src/main/java/io/sentry/hints/Hints.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,108 @@
package io.sentry.hints;

import io.sentry.Attachment;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class Hints {

private final @NotNull Map<String, Object> internalStorage = new HashMap<String, Object>();
private final @NotNull List<Attachment> attachments = new CopyOnWriteArrayList<>();
private @Nullable Attachment screenshot = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need some kind of synchronization around this field (looking at attachments - it's CopyOnWriteArrayList)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah no, I don't think it needs to be synchronized, can replace with a different implementation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


public void set(@NotNull String hintType, @Nullable Object hint) {
internalStorage.put(hintType, hint);
public static @NotNull Hints withAttachment(@Nullable Attachment attachment) {
@NotNull final Hints hints = new Hints();
hints.addAttachment(attachment);
return hints;
}

public @Nullable Object get(@NotNull String hintType) {
return internalStorage.get(hintType);
public static @NotNull Hints withAttachments(@Nullable List<Attachment> attachments) {
@NotNull final Hints hints = new Hints();
hints.addAttachments(attachments);
return hints;
}

// TODO maybe not public
public void remove(@NotNull String hintType) {
internalStorage.remove(hintType);
public Hints() {
primitiveMappings = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be done in a static block (and can it be a static map)? Not sure how many instances of HInts we'll have during the lifetime of an app.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

primitiveMappings.put("boolean", Boolean.class);
primitiveMappings.put("char", Character.class);
primitiveMappings.put("byte", Byte.class);
primitiveMappings.put("short", Short.class);
primitiveMappings.put("int", Integer.class);
primitiveMappings.put("long", Long.class);
primitiveMappings.put("float", Float.class);
primitiveMappings.put("double", Double.class);
}

// TODO addAttachment(one)
// TODO getAttachments(): List
// TODO setAttachments(list)
// TODO clearAttachments()
public void set(@NotNull String name, @Nullable Object hint) {
internalStorage.put(name, hint);
}

public @Nullable Object get(@NotNull String name) {
return internalStorage.get(name);
}

@SuppressWarnings("unchecked")
public <T extends Object> @Nullable T getAs(@NotNull String name, @NotNull Class<T> clazz) {
Object hintValue = internalStorage.get(name);

if (clazz.isInstance(hintValue)) {
return (T) hintValue;
} else if (isCastablePrimitive(hintValue, clazz)) {
return (T) hintValue;
} else {
return null;
}
}

public void remove(@NotNull String name) {
internalStorage.remove(name);
}

public void addAttachment(@Nullable Attachment attachment) {
if (attachment != null) {
attachments.add(attachment);
}
}

public void addAttachments(@Nullable List<Attachment> attachments) {
if (attachments != null) {
this.attachments.addAll(attachments);
}
}

public @NotNull List<Attachment> getAttachments() {
return new CopyOnWriteArrayList<>(attachments);
}

public void replaceAttachments(@Nullable List<Attachment> attachments) {
clearAttachments();
addAttachments(attachments);
}

public void clearAttachments() {
attachments.clear();
}

public void setScreenshot(@Nullable Attachment screenshot) {
this.screenshot = screenshot;
}

public @Nullable Attachment getScreenshot() {
return screenshot;
}

private final Map<String, Class<?>> primitiveMappings;

private boolean isCastablePrimitive(@Nullable Object hintValue, @NotNull Class<?> clazz) {
Class<?> nonPrimitiveClass = primitiveMappings.get(clazz.getCanonicalName());
return hintValue != null
&& clazz.isPrimitive()
&& nonPrimitiveClass != null
&& nonPrimitiveClass.isInstance(hintValue);
}
}
2 changes: 1 addition & 1 deletion sentry/src/main/java/io/sentry/util/HintUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

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

Expand Down
Loading