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 1 commit
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
Prev Previous commit
Next Next commit
parsing stacktraces
  • Loading branch information
Manoel Aranda Neto committed Oct 24, 2019
commit affa3cbd9c80e3ffbc30b8579fd624bc11c0f30a
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.core;

import io.sentry.core.exception.SentryExceptionReader;
import io.sentry.core.protocol.Message;
import io.sentry.core.protocol.SentryException;
import io.sentry.core.protocol.SentryStackFrame;
Expand Down Expand Up @@ -28,6 +29,7 @@ public SentryEvent process(SentryEvent event) {
// List<SentryException> exceptions = new ArrayList<>();
// exceptions.add(extractExceptionQueue(event.getThrowable()));
// event.setException(exceptions);
event.setException(SentryExceptionReader.sentryExceptionReader(throwable));
}

return event;
Expand All @@ -48,7 +50,7 @@ private SentryException extractExceptionQueue(Throwable throwable) {

for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
SentryStackFrame stackFrame = new SentryStackFrame();
stackFrame.setRawFunction(stackTraceElement.getMethodName());
stackFrame.setFunction(stackTraceElement.getMethodName());
stackFrame.setModule(stackTraceElement.getClassName());
stackFrame.setFilename(stackTraceElement.getFileName());
stackFrame.setLineno(stackTraceElement.getLineNumber());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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}.
*/
public final class ExceptionMechanismThrowable extends Throwable {
private static final long serialVersionUID = 100L;

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;
}
}
111 changes: 111 additions & 0 deletions sentry-core/src/main/java/io/sentry/core/exception/FrameCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.sentry.core.exception;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

/**
* Utility class used by the Sentry Java Agent to store per-frame local variable
* information for the last thrown exception.
*/
public final class FrameCache {
private static Set<String> appPackages = new HashSet<>();

private static ThreadLocal<WeakHashMap<Throwable, SentryFrame[]>> cache =
new ThreadLocal<WeakHashMap<Throwable, SentryFrame[]>>() {
@Override
protected WeakHashMap<Throwable, SentryFrame[]> initialValue() {
return new WeakHashMap<>();
}
};

/**
* Utility class, no public ctor.
*/
private FrameCache() {

}

/**
* Store the per-frame local variable information for the last exception thrown on this thread.
*
* @param throwable Throwable that the provided {@link SentryFrame}s represent.
* @param frames Array of {@link SentryFrame}s to store
*/
public static void add(Throwable throwable, SentryFrame[] frames) {
Map<Throwable, SentryFrame[]> weakMap = cache.get();
weakMap.put(throwable, frames);
}

/**
* Retrieve the per-frame local variable information for the last exception thrown on this thread.
*
* @param throwable Throwable to look up cached {@link SentryFrame}s for.
* @return Array of {@link SentryFrame}s
*/
public static SentryFrame[] get(Throwable throwable) {
Map<Throwable, SentryFrame[]> weakMap = cache.get();
return weakMap.get(throwable);
}

/**
* Check whether the provided {@link Throwable} should be cached or not. Called by
* the native agent code so that the Java side (this code) can check the existing
* cache and user configuration, such as which packages are "in app".
*
* @param throwable Throwable to be checked
* @param numFrames Number of frames in the Throwable's stacktrace
* @return true if the Throwable should be processed and cached
*/
public static boolean shouldCacheThrowable(Throwable throwable, int numFrames) {
// only cache frames when 'in app' packages are provided
if (appPackages.isEmpty()) {
return false;
}

// many libraries/frameworks seem to rethrow the same object with trimmed
// stacktraces, which means later ("smaller") throws would overwrite the existing
// object in cache. for this reason we prefer the throw with the greatest stack
// length...
Map<Throwable, SentryFrame[]> weakMap = cache.get();
SentryFrame[] existing = weakMap.get(throwable);
if (existing != null && numFrames <= existing.length) {
return false;
}

// check each frame against all "in app" package prefixes
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
for (String appFrame : appPackages) {
if (stackTraceElement.getClassName().startsWith(appFrame)) {
return true;
}
}
}

return false;
}

/**
* Add an "in app" package prefix to the set of packages for which exception
* local variables will be cached.
*
* When an exception is thrown it must contain at least one frame that originates
* from a package in this set, otherwise local variable information will not be
* cached. See {@link FrameCache#shouldCacheThrowable(Throwable, int)}.
*
* @param newAppPackage package prefix to add to the set
*/
public static void addAppPackage(String newAppPackage) {
appPackages.add(newAppPackage);
}

/**
* This method is meant for cleaner testing. Don't attempt to use it in production.
*/
// visible for testing
static void reset() {
cache.get().clear();
appPackages.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.sentry.core.exception;

import io.sentry.core.protocol.Mechanism;
import io.sentry.core.protocol.SentryException;
import io.sentry.core.protocol.SentryStackTrace;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class SentryExceptionReader {

/**
* Creates a new instance from the given {@code throwable}.
*
* @param throwable the {@link Throwable} to build this instance from
*/
public static List<SentryException> sentryExceptionReader(final Throwable throwable) {
return sentryExceptionReader(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 static List<SentryException> sentryExceptionReader(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 childExceptionStackTrace StackTrace of the exception caused by {@code throwable}.
* @param exceptionMechanism The optional {@link Mechanism} of the {@code throwable}.
* Or null if none exist.
*/
private static SentryException sentryExceptionReader(
Throwable throwable,
StackTraceElement[] childExceptionStackTrace,
Mechanism exceptionMechanism) {

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

SentryException exception = new SentryException();

// this.exceptionMessage = throwable.getMessage();
String exceptionClassName = exceptionPackage != null
? fullClassName.replace(exceptionPackage.getName() + ".", "")
: fullClassName;

String exceptionPackageName = exceptionPackage != null
? exceptionPackage.getName()
: null;
// TODO: whats about those missing fields? message, classname, packagename, ...

// StackTraceReader stackTraceInterface = new StackTraceReader(
// throwable.getStackTrace(),
// childExceptionStackTrace,
// FrameCache.get(throwable));
SentryStackTrace sentryStackTrace = new SentryStackTrace();
sentryStackTrace.setFrames(
Arrays.asList(SentryStackFrameReader.fromStackTraceElements(
throwable.getStackTrace(), null))); // TODO: cached frames

// SentryStackTrace sentryStackTrace = new SentryStackTrace();
exception.setStacktrace(sentryStackTrace);
exception.setType("ValueError"); // TODO ?

exception.setMechanism(exceptionMechanism);
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.
*/
private static Deque<SentryException> extractExceptionQueue(Throwable throwable) {
Deque<SentryException> exceptions = new ArrayDeque<>();
Set<Throwable> circularityDetector = new HashSet<>();
StackTraceElement[] childExceptionStackTrace = new StackTraceElement[0];
Mechanism exceptionMechanism = null;

//Stack the exceptions to send them in the reverse order
while (throwable != null && circularityDetector.add(throwable)) {
if (throwable instanceof ExceptionMechanismThrowable) {
ExceptionMechanismThrowable exceptionMechanismThrowable = (ExceptionMechanismThrowable) throwable;
exceptionMechanism = exceptionMechanismThrowable.getExceptionMechanism();
throwable = exceptionMechanismThrowable.getThrowable();
} else {
exceptionMechanism = null;
}

SentryException exception = sentryExceptionReader(throwable, childExceptionStackTrace, exceptionMechanism);
exceptions.add(exception);
childExceptionStackTrace = throwable.getStackTrace();
throwable = throwable.getCause();
}

return exceptions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.sentry.core.exception;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class SentryFrame {
/**
* Method that this frame originated in.
*/
private Method method;
/**
* Local variable information for this frame.
*/
private final LocalVariable[] locals;

/**
* Construct a {@link SentryFrame}.
*
* @param method Method that this frame originated in.
* @param locals Local variable information for this frame.
*/
public SentryFrame(Method method, LocalVariable[] locals) {
this.method = method;
this.locals = locals;
}

public Method getMethod() {
return method;
}

/**
* Converts the locals array to a Map of variable-name -> variable-value.
*
* @return Map of variable-name -> variable-value.
*/
public Map<String, Object> getLocals() {
if (locals == null || locals.length == 0) {
return Collections.emptyMap();
}

Map<String, Object> localsMap = new HashMap<>();
for (SentryFrame.LocalVariable localVariable : locals) {
if (localVariable != null) {
localsMap.put(localVariable.getName(), localVariable.getValue());
}
}

return localsMap;
}

/**
* Class representing a single local variable.
*/
public static final class LocalVariable {
/**
* Variable name.
*/
final String name;
/**
* Variable value.
*/
final Object value;

/**
* Construct a {@link LocalVariable} for a live object.
*
* @param name Variable name.
* @param value Variable value.
*/
public LocalVariable(String name, Object value) {
this.name = name;
this.value = value;
}

public String getName() {
return name;
}

public Object getValue() {
return value;
}
}
}
Loading