Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ public final class io/sentry/android/core/UserInteractionIntegration : android/a
public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentry/EventProcessor {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
public static fun snapshotViewHierarchy (Landroid/app/Activity;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy;
public static fun snapshotViewHierarchy (Landroid/view/View;)Lio/sentry/protocol/ViewHierarchy;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.sentry.Attachment;
import io.sentry.EventProcessor;
import io.sentry.Hint;
import io.sentry.ILogger;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.android.core.internal.gestures.ViewUtils;
Expand Down Expand Up @@ -41,30 +42,43 @@ public ViewHierarchyEventProcessor(final @NotNull SentryAndroidOptions options)
}

final @Nullable Activity activity = CurrentActivityHolder.getInstance().getActivity();
final @Nullable ViewHierarchy viewHierarchy =
snapshotViewHierarchy(activity, options.getLogger());

if (viewHierarchy != null) {
hint.setViewHierarchy(Attachment.fromViewHierarchy(viewHierarchy));
}

return event;
}

@Nullable
public static ViewHierarchy snapshotViewHierarchy(
@Nullable Activity activity, @NotNull ILogger logger) {
if (activity == null) {
options.getLogger().log(SentryLevel.INFO, "Missing activity for view hierarchy snapshot.");
return event;
logger.log(SentryLevel.INFO, "Missing activity for view hierarchy snapshot.");
return null;
}

final @Nullable Window window = activity.getWindow();
if (window == null) {
options.getLogger().log(SentryLevel.INFO, "Missing window for view hierarchy snapshot.");
return event;
logger.log(SentryLevel.INFO, "Missing window for view hierarchy snapshot.");
return null;
}

final @Nullable View decorView = window.peekDecorView();
if (decorView == null) {
options.getLogger().log(SentryLevel.INFO, "Missing decor view for view hierarchy snapshot.");
return event;
logger.log(SentryLevel.INFO, "Missing decor view for view hierarchy snapshot.");
return null;
}

try {
final @NotNull ViewHierarchy viewHierarchy = snapshotViewHierarchy(decorView);
hint.setViewHierarchy(Attachment.fromViewHierarchy(viewHierarchy));
return viewHierarchy;
} catch (Throwable t) {
options.getLogger().log(SentryLevel.ERROR, "Failed to process view hierarchy.", t);
logger.log(SentryLevel.ERROR, "Failed to process view hierarchy.", t);
return null;
}
return event;
}

@NotNull
Expand Down
1 change: 1 addition & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -3674,6 +3674,7 @@ public final class io/sentry/util/HttpUtils {
public final class io/sentry/util/JsonSerializationUtils {
public fun <init> ()V
public static fun atomicIntegerArrayToList (Ljava/util/concurrent/atomic/AtomicIntegerArray;)Ljava/util/List;
public static fun bytesFrom (Lio/sentry/ISerializer;Lio/sentry/ILogger;Lio/sentry/JsonSerializable;)[B
public static fun calendarToMap (Ljava/util/Calendar;)Ljava/util/Map;
}

Expand Down
23 changes: 8 additions & 15 deletions sentry/src/main/java/io/sentry/SentryEnvelopeItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.sentry.clientreport.ClientReport;
import io.sentry.exception.SentryEnvelopeException;
import io.sentry.protocol.SentryTransaction;
import io.sentry.util.JsonSerializationUtils;
import io.sentry.util.Objects;
import io.sentry.vendor.Base64;
import java.io.BufferedInputStream;
Expand Down Expand Up @@ -179,21 +180,13 @@ public static SentryEnvelopeItem fromAttachment(
return data;
} else if (attachment.getSerializable() != null) {
final JsonSerializable serializable = attachment.getSerializable();
try {
try (final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final Writer writer =
new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) {

serializer.serialize(serializable, writer);

final byte[] data = stream.toByteArray();
ensureAttachmentSizeLimit(
data.length, maxAttachmentSize, attachment.getFilename());
return data;
}
} catch (Throwable t) {
logger.log(SentryLevel.ERROR, "Could not serialize attachment serializable", t);
throw t;
final @Nullable byte[] data =
JsonSerializationUtils.bytesFrom(serializer, logger, serializable);

if (data != null) {
ensureAttachmentSizeLimit(
data.length, maxAttachmentSize, attachment.getFilename());
return data;
}
} else if (attachment.getPathname() != null) {
return readBytesFromFile(attachment.getPathname(), maxAttachmentSize);
Expand Down
31 changes: 31 additions & 0 deletions sentry/src/main/java/io/sentry/util/JsonSerializationUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package io.sentry.util;

import io.sentry.ILogger;
import io.sentry.ISerializer;
import io.sentry.JsonSerializable;
import io.sentry.SentryLevel;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
Expand All @@ -8,10 +17,14 @@
import java.util.concurrent.atomic.AtomicIntegerArray;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public final class JsonSerializationUtils {

@SuppressWarnings("CharsetObjectCanBeUsed")
private static final Charset UTF_8 = Charset.forName("UTF-8");

public static @NotNull Map<String, Object> calendarToMap(final @NotNull Calendar calendar) {
final @NotNull Map<String, Object> map = new HashMap<>();

Expand All @@ -34,4 +47,22 @@ public final class JsonSerializationUtils {
}
return list;
}

public static @Nullable byte[] bytesFrom(
final @NotNull ISerializer serializer,
final @NotNull ILogger logger,
final @NotNull JsonSerializable serializable) {
try {
try (final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) {

serializer.serialize(serializable, writer);

return stream.toByteArray();
}
} catch (Throwable t) {
logger.log(SentryLevel.ERROR, "Could not serialize serializable", t);
return null;
}
}
}
20 changes: 20 additions & 0 deletions sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.sentry.protocol.ViewHierarchy
import io.sentry.test.injectForField
import io.sentry.vendor.Base64
import org.junit.Assert.assertArrayEquals
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
Expand All @@ -27,6 +28,11 @@ class SentryEnvelopeItemTest {
private class Fixture {
val options = SentryOptions()
val serializer = JsonSerializer(options)
val errorSerializer: JsonSerializer = mock {
on(it.serialize(any<JsonSerializable>(), any())).then {
throw Exception("Mocked exception.")
}
}
val pathname = "hello.txt"
val filename = pathname
val bytes = "hello".toByteArray()
Expand Down Expand Up @@ -250,6 +256,20 @@ class SentryEnvelopeItemTest {
)
}

@Test
fun `fromAttachment with bytesFrom serializable are null`() {
val attachment = Attachment(mock<JsonSerializable>(), "mock-file-name", null, null, false)

val item = SentryEnvelopeItem.fromAttachment(fixture.errorSerializer, fixture.options.logger, attachment, fixture.maxAttachmentSize)

assertFailsWith<SentryEnvelopeException>(
"Couldn't attach the attachment ${attachment.filename}.\n" +
"Please check that either bytes or a path is set."
) {
item.data
}
}

@Test
fun `fromProfilingTrace saves file as Base64`() {
val file = File(fixture.pathname)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package io.sentry.util

import java.util.Calendar
import io.sentry.ILogger
import io.sentry.JsonSerializable
import io.sentry.JsonSerializer
import org.mockito.invocation.InvocationOnMock
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import java.io.Writer
import java.util.*
import java.util.concurrent.atomic.AtomicIntegerArray
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertNull

class JsonSerializationUtilsTest {

Expand All @@ -30,4 +39,34 @@ class JsonSerializationUtilsTest {
val actual = JsonSerializationUtils.atomicIntegerArrayToList(AtomicIntegerArray(arrayOf(1, 2, 3).toIntArray()))
assertEquals(listOf(1, 2, 3), actual)
}

@Test
fun `returns byte array of given serializable`() {
val mockSerializer: JsonSerializer = mock {
on(it.serialize(any<JsonSerializable>(), any())).then { invocationOnMock: InvocationOnMock ->
val writer: Writer = invocationOnMock.getArgument(1)
writer.write("mock-data")
writer.flush()
}
}
val logger: ILogger = mock()
val serializable: JsonSerializable = mock()
val actualBytes = JsonSerializationUtils.bytesFrom(mockSerializer, logger, serializable)

assertContentEquals("mock-data".toByteArray(), actualBytes, "Byte array should represent the mocked input data.")
}

@Test
fun `return null on serialization error`() {
val mockSerializer: JsonSerializer = mock {
on(it.serialize(any<JsonSerializable>(), any())).then {
throw Exception("Mocked exception.")
}
}
val logger: ILogger = mock()
val serializable: JsonSerializable = mock()
val actualBytes = JsonSerializationUtils.bytesFrom(mockSerializer, logger, serializable)

assertNull(actualBytes, "Mocker error should be captured and null returned.")
}
}