Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import io.sentry.core.ILogger
import io.sentry.core.MainEventProcessor
import io.sentry.core.SentryOptions
import java.io.File
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.junit.runner.RunWith
Expand All @@ -36,7 +36,7 @@ class AndroidOptionsInitializerTest {
val loggerField = logger.get(sentryOptions)
val innerLogger = loggerField.javaClass.declaredFields.first { it.name == "logger" }
innerLogger.isAccessible = true
assertEquals(AndroidLogger::class, innerLogger.get(loggerField)::class)
assertTrue(innerLogger.get(loggerField) is AndroidLogger)
}

@Test
Expand All @@ -46,7 +46,18 @@ class AndroidOptionsInitializerTest {
val mockLogger = mock<ILogger>()

AndroidOptionsInitializer.init(sentryOptions, mockContext, mockLogger)
val actual = sentryOptions.eventProcessors.firstOrNull { it::class == DefaultAndroidEventProcessor::class }
val actual = sentryOptions.eventProcessors.any { it is DefaultAndroidEventProcessor }
assertNotNull(actual)
}

@Test
fun `MainEventProcessor added to processors list and its the 1st`() {
val sentryOptions = SentryOptions()
val mockContext = createMockContext()
val mockLogger = mock<ILogger>()

AndroidOptionsInitializer.init(sentryOptions, mockContext, mockLogger)
val actual = sentryOptions.eventProcessors.firstOrNull { it is MainEventProcessor }
assertNotNull(actual)
}

Expand Down
37 changes: 37 additions & 0 deletions sentry-core/src/main/java/io/sentry/core/MainEventProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.sentry.core;

import io.sentry.core.util.Objects;

public class MainEventProcessor implements EventProcessor {

private final SentryOptions options;
private final SentryThreadFactory sentryThreadFactory = new SentryThreadFactory();
private final SentryStackTraceFactory sentryStackTraceFactory = new SentryStackTraceFactory();
private final SentryExceptionFactory sentryExceptionFactory =
new SentryExceptionFactory(sentryStackTraceFactory);

MainEventProcessor(SentryOptions options) {
this.options = Objects.requireNonNull(options, "The SentryOptions is required.");
}

@Override
public SentryEvent process(SentryEvent event) {
if (event.getThreads() == null) {
event.setThreads(sentryThreadFactory.getCurrentThreads());
}

if (event.getRelease() == null) {
event.setRelease(options.getRelease());
}
if (event.getEnvironment() == null) {
event.setEnvironment(options.getEnvironment());
}

Throwable throwable = event.getThrowable();
if (throwable != null) {
event.setException(sentryExceptionFactory.getSentryExceptions(throwable));
}

return event;
}
}
13 changes: 0 additions & 13 deletions sentry-core/src/main/java/io/sentry/core/SentryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,6 @@ public SentryClient(SentryOptions options, @Nullable AsyncConnection connection)
public SentryId captureEvent(SentryEvent event, @Nullable Scope scope) {
log(options.getLogger(), SentryLevel.DEBUG, "Capturing event: %s", event.getEventId());

// TODO: This will go into the MainEventProcessor
if (event.getThreads() == null) {
SentryThreadFactory sentryThreadFactory = new SentryThreadFactory();
event.setThreads(sentryThreadFactory.getCurrentThreads());
}
// TODO: To be done in the main processor
if (event.getRelease() == null) {
event.setRelease(options.getRelease());
}
if (event.getEnvironment() == null) {
event.setEnvironment(options.getEnvironment());
}

for (EventProcessor processor : options.getEventProcessors()) {
processor.process(event);
}
Expand Down
18 changes: 5 additions & 13 deletions sentry-core/src/main/java/io/sentry/core/SentryEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class SentryEvent implements IUnknownPropertiesConsumer {
private String dist;
private String logger;
private SentryValues<SentryThread> threads;
private SentryValues<SentryException> exceptions;
private SentryValues<SentryException> exception;
private SentryLevel level;
private String transaction;
private String environment;
Expand Down Expand Up @@ -119,12 +119,12 @@ public void setThreads(List<SentryThread> threads) {
this.threads = new SentryValues<>(threads);
}

public List<SentryException> getExceptions() {
return exceptions.getValues();
public List<SentryException> getException() {
return exception.getValues();
}

public void setExceptions(List<SentryException> exceptions) {
this.exceptions = new SentryValues<>(exceptions);
public void setException(List<SentryException> exception) {
this.exception = new SentryValues<>(exception);
}

public void setEventId(SentryId eventId) {
Expand All @@ -139,14 +139,6 @@ public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}

public void setThreads(SentryValues<SentryThread> threads) {
this.threads = threads;
}

public void setExceptions(SentryValues<SentryException> exceptions) {
this.exceptions = exceptions;
}

public SentryLevel getLevel() {
return level;
}
Expand Down
118 changes: 118 additions & 0 deletions sentry-core/src/main/java/io/sentry/core/SentryExceptionFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package io.sentry.core;

import io.sentry.core.exception.ExceptionMechanismThrowable;
import io.sentry.core.protocol.Mechanism;
import io.sentry.core.protocol.SentryException;
import io.sentry.core.protocol.SentryStackTrace;
import io.sentry.core.util.Objects;
import io.sentry.core.util.VisibleForTesting;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/** class responsible for converting Java Throwable to SentryExceptions */
class SentryExceptionFactory {

private final SentryStackTraceFactory sentryStackTraceFactory;

public SentryExceptionFactory(SentryStackTraceFactory sentryStackTraceFactory) {
this.sentryStackTraceFactory =
Objects.requireNonNull(sentryStackTraceFactory, "The SentryStackTraceFactory is required.");
}

/**
* Creates a new instance from the given {@code throwable}.
*
* @param throwable the {@link Throwable} to build this instance from
*/
List<SentryException> getSentryExceptions(final Throwable throwable) {
return getSentryExceptions(extractExceptionQueue(throwable));
}

/**
* Creates a new instance from the given {@code exceptions}.
*
* @param exceptions a {@link Deque} of {@link SentryException} to build this instance from
*/
private List<SentryException> getSentryExceptions(final Deque<SentryException> exceptions) {
return new ArrayList<>(exceptions);
}

/**
* Creates a Sentry exception based on a Java Throwable.
*
* <p>The {@code childExceptionStackTrace} parameter is used to define the common frames with the
* child exception (Exception caused by {@code throwable}).
*
* @param throwable Java exception to send to Sentry.
* @param exceptionMechanism The optional {@link Mechanism} of the {@code throwable}. Or null if
* none exist.
*/
private SentryException getSentryException(
final Throwable throwable, final Mechanism exceptionMechanism) {

Package exceptionPackage = throwable.getClass().getPackage();
String fullClassName = throwable.getClass().getName();

SentryException exception = new SentryException();

String exceptionMessage = throwable.getMessage();

String exceptionClassName =
exceptionPackage != null
? fullClassName.replace(exceptionPackage.getName() + ".", "")
: fullClassName;

String exceptionPackageName = exceptionPackage != null ? exceptionPackage.getName() : null;

SentryStackTrace sentryStackTrace = new SentryStackTrace();
sentryStackTrace.setFrames(sentryStackTraceFactory.getStackFrames(throwable.getStackTrace()));

exception.setStacktrace(sentryStackTrace);
exception.setType(exceptionClassName);
exception.setMechanism(exceptionMechanism);
exception.setModule(exceptionPackageName);
exception.setValue(exceptionMessage);

return exception;
}

/**
* Transforms a {@link Throwable} into a Queue of {@link SentryException}.
*
* <p>Exceptions are stored in the queue from the most recent one to the oldest one.
*
* @param throwable throwable to transform in a queue of exceptions.
* @return a queue of exception with StackTrace.
*/
@VisibleForTesting
Deque<SentryException> extractExceptionQueue(final Throwable throwable) {
Deque<SentryException> exceptions = new ArrayDeque<>();
Set<Throwable> circularityDetector = new HashSet<>();
Mechanism exceptionMechanism;

Throwable currentThrowable = throwable;

// Stack the exceptions to send them in the reverse order
while (currentThrowable != null && circularityDetector.add(currentThrowable)) {
if (currentThrowable instanceof ExceptionMechanismThrowable) {
// this is for ANR I believe
ExceptionMechanismThrowable exceptionMechanismThrowable =
(ExceptionMechanismThrowable) currentThrowable;
exceptionMechanism = exceptionMechanismThrowable.getExceptionMechanism();
currentThrowable = exceptionMechanismThrowable.getThrowable();
} else {
exceptionMechanism = null;
}

SentryException exception = getSentryException(currentThrowable, exceptionMechanism);
exceptions.add(exception);
currentThrowable = currentThrowable.getCause();
}

return exceptions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public interface BeforeBreadcrumbCallback {
}

public SentryOptions() {
eventProcessors.add(new MainEventProcessor(this));
integrations.add(new UncaughtExceptionHandlerIntegration());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.sentry.core;

import io.sentry.core.protocol.SentryStackFrame;
import io.sentry.core.util.Nullable;
import java.util.ArrayList;
import java.util.List;

/** class responsible for converting Java StackTraceElements to SentryStackFrames */
class SentryStackTraceFactory {

/**
* convert an Array of Java StackTraceElements to a list of SentryStackFrames
*
* @param elements Array of Java StackTraceElements
* @return list of SentryStackFrames
*/
List<SentryStackFrame> getStackFrames(@Nullable final StackTraceElement[] elements) {
List<SentryStackFrame> sentryStackFrames = new ArrayList<>();

if (elements != null) {
for (StackTraceElement item : elements) {
SentryStackFrame sentryStackFrame = new SentryStackFrame();
sentryStackFrame.setModule(item.getClassName());
sentryStackFrame.setFunction(item.getMethodName());
sentryStackFrame.setFilename(item.getFileName());
sentryStackFrame.setLineno(item.getLineNumber());
sentryStackFrame.setNative(item.isNativeMethod());

sentryStackFrames.add(sentryStackFrame);
}
}

return sentryStackFrames;
}
}
28 changes: 7 additions & 21 deletions sentry-core/src/main/java/io/sentry/core/SentryThreadFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ List<SentryThread> getCurrentThreads() {
return getCurrentThreads(null);
}

private List<SentryThread> getCurrentThreads(@Nullable Thread crashedThread) {
private List<SentryThread> getCurrentThreads(@Nullable final Thread crashedThread) {
Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();
List<SentryThread> result = new ArrayList<>();

Expand All @@ -33,10 +33,10 @@ private List<SentryThread> getCurrentThreads(@Nullable Thread crashedThread) {
}

private SentryThread getSentryThread(
@Nullable Thread crashedThread,
Thread currentThread,
StackTraceElement[] stackFramesElements,
Thread thread) {
@Nullable final Thread crashedThread,
final Thread currentThread,
final StackTraceElement[] stackFramesElements,
final Thread thread) {
SentryThread sentryThread = new SentryThread();

sentryThread.setName(thread.getName());
Expand All @@ -49,27 +49,13 @@ private SentryThread getSentryThread(
}
sentryThread.setCurrent(thread == currentThread);

List<SentryStackFrame> frames = new ArrayList<>();
for (StackTraceElement element : stackFramesElements) {
frames.add(getSentryStackFrame(element));
}
SentryStackTraceFactory sentryStackTraceFactory = new SentryStackTraceFactory();
List<SentryStackFrame> frames = sentryStackTraceFactory.getStackFrames(stackFramesElements);

if (frames.size() > 0) {
sentryThread.setStacktrace(new SentryStackTrace(frames));
}

return sentryThread;
}

private SentryStackFrame getSentryStackFrame(StackTraceElement element) {
SentryStackFrame sentryStackFrame = new SentryStackFrame();
sentryStackFrame.setModule(element.getClassName());
sentryStackFrame.setFilename(element.getFileName());
if (element.getLineNumber() >= 0) {
sentryStackFrame.setLineno(element.getLineNumber());
}
sentryStackFrame.setFunction(element.getMethodName());
sentryStackFrame.setNative(element.isNativeMethod());
return sentryStackFrame;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.sentry.core.exception;

import io.sentry.core.protocol.Mechanism;

/**
* A throwable decorator that holds an {@link io.sentry.core.protocol.Mechanism} related to the
* decorated {@link Throwable}.
*/
@SuppressWarnings("serial")
public final class ExceptionMechanismThrowable extends Throwable {

private final Mechanism exceptionMechanism;
private final Throwable throwable;

/**
* A {@link Throwable} that decorates another with a Sentry {@link Mechanism}.
*
* @param mechanism The {@link Mechanism}.
* @param throwable The {@link java.lang.Throwable}.
*/
public ExceptionMechanismThrowable(Mechanism mechanism, Throwable throwable) {
this.exceptionMechanism = mechanism;
this.throwable = throwable;
}

public Mechanism getExceptionMechanism() {
return exceptionMechanism;
}

public Throwable getThrowable() {
return throwable;
}
}
Loading