diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85459fbf..2272a299 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,13 +19,13 @@ on: jobs: validation: name: Gradle wrapper validation - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: gradle/actions/wrapper-validation@v3 build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: Build with Java ${{ matrix.java }} needs: [ validation ] strategy: @@ -71,7 +71,7 @@ jobs: **/build/reports/* release: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: Release # It would be nice to run this as part of the build job, since it would be # faster and have less duplicated Yaml, it would not be possible to check diff --git a/.palantir/revapi.yml b/.palantir/revapi.yml index 1c1ab03c..24ca338e 100644 --- a/.palantir/revapi.yml +++ b/.palantir/revapi.yml @@ -33,3 +33,15 @@ acceptedBreaks: - code: "java.method.addedToInterface" new: "method int com.rollbar.notifier.config.CommonConfig::maximumTelemetryData()" justification: "This is going to be added in a major version" + "2.1.0": + com.rollbar:rollbar-java: + - code: "java.method.addedToInterface" + new: "method java.util.Map com.rollbar.notifier.wrapper.ThrowableWrapper::getAllStackTraces()" + justification: "This is a binary compatible change, which could only break custom\ + \ implementations of our config interfaces, but those interfaces are not meant\ + \ to be implemented by users" + - code: "java.method.addedToInterface" + new: "method com.rollbar.api.payload.data.body.RollbarThread com.rollbar.notifier.wrapper.ThrowableWrapper::getRollbarThread()" + justification: "This is a binary compatible change, which could only break custom\ + \ implementations of our config interfaces, but those interfaces are not meant\ + \ to be implemented by users" diff --git a/rollbar-api/src/main/java/com/rollbar/api/payload/data/TelemetryEvent.java b/rollbar-api/src/main/java/com/rollbar/api/payload/data/TelemetryEvent.java index 37209f64..b8a81b10 100644 --- a/rollbar-api/src/main/java/com/rollbar/api/payload/data/TelemetryEvent.java +++ b/rollbar-api/src/main/java/com/rollbar/api/payload/data/TelemetryEvent.java @@ -56,17 +56,12 @@ public Map asJson() { @Override public TelemetryEvent truncateStrings(int maxLength) { - Map truncatedMap = new HashMap<>(); - for (Map.Entry entry : body.entrySet()) { - String truncatedValue = TruncationHelper.truncateString(entry.getValue(), maxLength); - truncatedMap.put(entry.getKey(), truncatedValue); - } return new TelemetryEvent( this.type, this.level, this.timestamp, this.source, - truncatedMap + TruncationHelper.truncateStringsInStringMap(body, maxLength) ); } diff --git a/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/Body.java b/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/Body.java index 2ba983de..93fd471d 100755 --- a/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/Body.java +++ b/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/Body.java @@ -3,6 +3,7 @@ import com.rollbar.api.json.JsonSerializable; import com.rollbar.api.payload.data.TelemetryEvent; import com.rollbar.api.truncation.StringTruncatable; +import com.rollbar.api.truncation.TruncationHelper; import java.util.HashMap; import java.util.List; @@ -19,19 +20,41 @@ public class Body implements JsonSerializable, StringTruncatable { private final List telemetryEvents; + private final List rollbarThreads; + private Body(Builder builder) { this.bodyContent = builder.bodyContent; this.telemetryEvents = builder.telemetryEvents; + this.rollbarThreads = builder.rollbarThreads; } /** * Getter. + * * @return the contents. */ public BodyContent getContents() { return bodyContent; } + /** + * Getter. + * + * @return the rollbar threads. + */ + public List getRollbarThreads() { + return rollbarThreads; + } + + /** + * Getter. + * + * @return the list of Telemetry events. + */ + public List getTelemetryEvents() { + return telemetryEvents; + } + @Override public Object asJson() { HashMap values = new HashMap<>(); @@ -44,6 +67,10 @@ public Object asJson() { values.put("telemetry", telemetryEvents); } + if (rollbarThreads != null) { + values.put("threads", rollbarThreads); + } + return values; } @@ -51,8 +78,10 @@ public Object asJson() { public Body truncateStrings(int maxSize) { if (bodyContent != null) { return new Body.Builder(this) - .bodyContent(bodyContent.truncateStrings(maxSize)) - .build(); + .bodyContent(bodyContent.truncateStrings(maxSize)) + .telemetryEvents(TruncationHelper.truncate(telemetryEvents, maxSize)) + .rollbarThreads(TruncationHelper.truncate(rollbarThreads, maxSize)) + .build(); } else { return this; } @@ -70,20 +99,22 @@ public boolean equals(Object o) { Body body = (Body) o; return Objects.equals(bodyContent, body.bodyContent) - && Objects.equals(telemetryEvents, body.telemetryEvents); + && Objects.equals(telemetryEvents, body.telemetryEvents) + && Objects.equals(rollbarThreads, body.rollbarThreads); } @Override public int hashCode() { - return Objects.hash(bodyContent, telemetryEvents); + return Objects.hash(bodyContent, telemetryEvents, rollbarThreads); } @Override public String toString() { return "Body{" - + "bodyContent=" + bodyContent - + ", telemetry=" + telemetryEvents - + '}'; + + "bodyContent=" + bodyContent + + ", telemetry=" + telemetryEvents + + ", threads=" + rollbarThreads + + '}'; } /** @@ -95,6 +126,8 @@ public static final class Builder { private List telemetryEvents; + private List rollbarThreads; + /** * Constructor. */ @@ -108,8 +141,9 @@ public Builder() { * @param body the {@link Body body} to initialize a new builder instance. */ public Builder(Body body) { - this.bodyContent = body.bodyContent; - this.telemetryEvents = body.telemetryEvents; + bodyContent = body.bodyContent; + telemetryEvents = body.telemetryEvents; + rollbarThreads = body.rollbarThreads; } /** @@ -135,6 +169,17 @@ public Builder telemetryEvents(List telemetryEvents) { return this; } + /** + * Information from threads. + * + * @param rollbarThreads a list of threads; + * @return the builder instance. + */ + public Builder rollbarThreads(List rollbarThreads) { + this.rollbarThreads = rollbarThreads; + return this; + } + /** * Builds the {@link Body body}. * diff --git a/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/Group.java b/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/Group.java new file mode 100644 index 00000000..2808229f --- /dev/null +++ b/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/Group.java @@ -0,0 +1,63 @@ +package com.rollbar.api.payload.data.body; + +import com.rollbar.api.json.JsonSerializable; +import com.rollbar.api.truncation.StringTruncatable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Objects; + +/** + * This represent a Group of trace chains in a Thread. + * In Java we only send 1 TraceChain per Group, in other languages like Python (ExceptionGroup) or + * JS (AggregatorError), it may be more TraceChains per group. + */ +public class Group implements JsonSerializable, StringTruncatable { + private final TraceChain traceChain; + + public Group(TraceChain traceChain) { + this.traceChain = traceChain; + } + + /** + * Getter. + * + * @return the trace chain. + */ + public TraceChain getTraceChain() { + return traceChain; + } + + @Override + public Object asJson() { + HashMap values = new HashMap<>(); + values.put("trace_chain", traceChain); + ArrayList> traceChains = new ArrayList<>(); + traceChains.add(values); + return traceChains; + } + + @Override + public Group truncateStrings(int maxLength) { + return new Group(traceChain.truncateStrings(maxLength)); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Group group = (Group) o; + return Objects.equals(traceChain, group.traceChain); + } + + @Override + public int hashCode() { + return Objects.hashCode(traceChain); + } + + @Override + public String toString() { + return "Group{traceChain=" + traceChain + '}'; + } +} diff --git a/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/RollbarThread.java b/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/RollbarThread.java new file mode 100644 index 00000000..efff8f00 --- /dev/null +++ b/rollbar-api/src/main/java/com/rollbar/api/payload/data/body/RollbarThread.java @@ -0,0 +1,204 @@ +package com.rollbar.api.payload.data.body; + +import com.rollbar.api.json.JsonSerializable; +import com.rollbar.api.truncation.StringTruncatable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Represents Thread information. + */ +public class RollbarThread implements JsonSerializable, StringTruncatable { + private final String name; + private final String id; + private final String priority; + private final String state; + private final boolean isMain; + private final Group group; + + /** + * Constructor. + * @param thread the Thread. + * @param group the Group of trace chains. + */ + public RollbarThread(Thread thread, Group group) { + name = thread.getName(); + id = String.valueOf(thread.getId()); + priority = String.valueOf(thread.getPriority()); + state = thread.getState().toString(); + isMain = isMain(thread.getName()); + this.group = group; + } + + private RollbarThread( + String name, + String id, + String priority, + String state, + Group group + ) { + isMain = isMain(name); + this.name = name; + this.id = id; + this.priority = priority; + this.state = state; + this.group = group; + } + + /** + * Getter. + * + * @return the group for this Thread. + */ + public Group getGroup() { + return group; + } + + /** + * Getter. + * + * @return the state of this Thread. + */ + public String getState() { + return state; + } + + /** + * Getter. + * + * @return the priority of this Thread. + */ + public String getPriority() { + return priority; + } + + /** + * Getter. + * + * @return the id of this Thread. + */ + public String getId() { + return id; + } + + /** + * Getter. + * + * @return the name of this Thread. + */ + public String getName() { + return name; + } + + @Override + public Object asJson() { + Map values = new HashMap<>(); + values.put("name", name); + values.put("id", id); + values.put("priority", priority); + values.put("state", state); + values.put("is_main", isMain); + values.put("group", group); + return values; + } + + @Override + public RollbarThread truncateStrings(int maxLength) { + return new RollbarThread( + name, + id, + priority, + state, + group.truncateStrings(maxLength) + ); + } + + @Override + public String toString() { + return "RollbarThread{" + + "name='" + name + '\'' + + ", id='" + id + '\'' + + ", priority='" + priority + '\'' + + ", state='" + state + '\'' + + ", isMain='" + isMain + '\'' + + ", group='" + group + + '}'; + } + + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + RollbarThread that = (RollbarThread) o; + return Objects.equals(name, that.name) + && Objects.equals(id, that.id) + && Objects.equals(priority, that.priority) + && Objects.equals(state, that.state) + && Objects.equals(isMain, that.isMain) + && Objects.equals(group, that.group); + } + + @Override + public int hashCode() { + return Objects.hash(name, id, priority, state, isMain, group); + } + + private boolean isMain(String string) { + return "main".equals(string); + } + + /** + * Builder class for {@link RollbarThread RollbarThread}. + */ + public static final class Builder { + private final String name; + private final String id; + private final String priority; + private final String state; + private Group group; + + /** + * Constructor. + * + * @param rollbarThread the {@link RollbarThread rollbarThread} to initialize + * a new builder instance. + */ + public Builder(RollbarThread rollbarThread) { + name = rollbarThread.name; + id = rollbarThread.id; + priority = rollbarThread.priority; + state = rollbarThread.state; + group = rollbarThread.group; + } + + /** + * The group for this thread. + * + * @param group an updated version of group; + * @return the builder instance. + */ + public Builder group(Group group) { + this.group = group; + return this; + } + + /** + * Builds the {@link RollbarThread RollbarThread}. + * + * @return the RollbarThread. + */ + public RollbarThread build() { + return new RollbarThread( + name, + id, + priority, + state, + group + ); + } + } +} diff --git a/rollbar-api/src/main/java/com/rollbar/api/truncation/TruncationHelper.java b/rollbar-api/src/main/java/com/rollbar/api/truncation/TruncationHelper.java index b197cc7a..8463f62e 100644 --- a/rollbar-api/src/main/java/com/rollbar/api/truncation/TruncationHelper.java +++ b/rollbar-api/src/main/java/com/rollbar/api/truncation/TruncationHelper.java @@ -28,6 +28,25 @@ public static List truncateStringsInList(List values, int maxLen return result; } + /** + * Truncates all the StringTruncatable in the list to the specified maximum length. + * @param values The StringTruncatables to be truncated. + * @param maxLength Maximum length of each string. + * @return A list containing the truncated StringTruncatables. + */ + public static > List truncate(List values, int maxLength) { + if (values == null) { + return null; + } + + List result = new ArrayList<>(values.size()); + for (T value : values) { + result.add(value.truncateStrings(maxLength)); + } + + return result; + } + /** * Truncates any strings in the list to the specified maximum length. * @param values The list of objects which might contain strings to be truncated. diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/Rollbar.java b/rollbar-java/src/main/java/com/rollbar/notifier/Rollbar.java index a3214e61..e59b95e3 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/Rollbar.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/Rollbar.java @@ -553,7 +553,7 @@ public void log(String message, Map custom, Level level) { } /** - * Record an error or message with extra data at the level specified. At least ene of `error` or + * Record an error or message with extra data at the level specified. At least one of `error` or * `description` must be non-null. If error is null, `description` will be sent as a message. If * error is non-null, description will be sent as the description of the error. Custom data will * be attached to message if the error is null. Custom data will extend whatever {@link @@ -569,7 +569,7 @@ public void log(Throwable error, Map custom, String description, } /** - * Record an error or message with extra data at the level specified. At least ene of `error` or + * Record an error or message with extra data at the level specified. At least one of `error` or * `description` must be non-null. If error is null, `description` will be sent as a message. If * error is non-null, description will be sent as the description of the error. Custom data will * be attached to message if the error is null. Custom data will extend whatever {@link @@ -579,7 +579,7 @@ public void log(Throwable error, Map custom, String description, * @param custom the custom data (if any). * @param description the description of the error, or the message to send. * @param level the level to send it at. - * @param isUncaught whether or not this data comes from an uncaught exception. + * @param isUncaught whether this data comes from an uncaught exception. */ public void log(Throwable error, Map custom, String description, Level level, boolean isUncaught) { @@ -587,17 +587,42 @@ public void log(Throwable error, Map custom, String description, } /** - * Record an error or message with extra data at the level specified. At least ene of `error` or + * Record an error or message with extra data at the level specified. At least one of `error` or * `description` must be non-null. If error is null, `description` will be sent as a message. If * error is non-null, description will be sent as the description of the error. Custom data will * be attached to message if the error is null. Custom data will extend whatever {@link * Config#custom} returns. * * @param error the error (if any). + * @param thread the thread where the error happened (if any). * @param custom the custom data (if any). * @param description the description of the error, or the message to send. * @param level the level to send it at. - * @param isUncaught whether or not this data comes from an uncaught exception. + * @param isUncaught whether this data comes from an uncaught exception. + */ + public void log( + Throwable error, + Thread thread, + Map custom, + String description, + Level level, + boolean isUncaught + ) { + this.log(wrapThrowable(error, thread), custom, description, level, isUncaught); + } + + /** + * Record an error or message with extra data at the level specified. At least one of `error` or + * `description` must be non-null. If error is null, `description` will be sent as a message. If + * error is non-null, description will be sent as the description of the error. Custom data will + * be attached to message if the error is null. Custom data will extend whatever {@link + * Config#custom} returns. + * + * @param error the error (if any). + * @param custom the custom data (if any). + * @param description the description of the error, or the message to send. + * @param level the level to send it at. + * @param isUncaught whether this data comes from an uncaught exception. */ public void log(ThrowableWrapper error, Map custom, String description, Level level, boolean isUncaught) { diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java b/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java index 15f458be..cc61f94c 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/RollbarBase.java @@ -326,14 +326,20 @@ private Payload truncateIfNecessary(C config, Payload payload) { return payload; } - protected RollbarThrowableWrapper wrapThrowable(Throwable error) { - RollbarThrowableWrapper rollbarThrowableWrapper = null; + protected RollbarThrowableWrapper wrapThrowable(Throwable error, Thread thread) { + if (error != null && thread != null) { + return new RollbarThrowableWrapper(error, thread); + } else { + return wrapThrowable(error); + } + } - if (error != null) { - rollbarThrowableWrapper = new RollbarThrowableWrapper(error); + protected RollbarThrowableWrapper wrapThrowable(Throwable error) { + if (error == null) { + return null; } - return rollbarThrowableWrapper; + return new RollbarThrowableWrapper(error); } protected abstract RESULT sendPayload(C config, Payload payload); diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/truncation/FramesStrategy.java b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/FramesStrategy.java index 2a89c0f5..253f55b6 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/truncation/FramesStrategy.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/FramesStrategy.java @@ -42,7 +42,7 @@ public TruncationResult truncate(Payload payload) { return TruncationResult.none(); } - private TruncationResult truncateTraceChain(TraceChain chain) { + TruncationResult truncateTraceChain(TraceChain chain) { boolean truncated = false; ArrayList updated = new ArrayList<>(); diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/truncation/PayloadTruncator.java b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/PayloadTruncator.java index d475a23c..eb9d70b9 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/truncation/PayloadTruncator.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/PayloadTruncator.java @@ -11,7 +11,9 @@ public class PayloadTruncator { private static final Charset TRANSPORT_CHARSET = Charset.forName("UTF-8"); private static final TruncationStrategy[] STRATEGIES = { + new RollbarThreadStrategy(), new FramesStrategy(), + new TelemetryEventsStrategy(), new StringsStrategy(1024), new StringsStrategy(512), new StringsStrategy(256), diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/truncation/RollbarThreadStrategy.java b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/RollbarThreadStrategy.java new file mode 100644 index 00000000..029d6330 --- /dev/null +++ b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/RollbarThreadStrategy.java @@ -0,0 +1,69 @@ +package com.rollbar.notifier.truncation; + +import com.rollbar.api.payload.Payload; +import com.rollbar.api.payload.data.Data; +import com.rollbar.api.payload.data.body.Body; +import com.rollbar.api.payload.data.body.Group; +import com.rollbar.api.payload.data.body.RollbarThread; +import com.rollbar.api.payload.data.body.TraceChain; + +import java.util.ArrayList; +import java.util.List; + +public class RollbarThreadStrategy implements TruncationStrategy { + private static final FramesStrategy FRAMES_STRATEGY = new FramesStrategy(); + + @Override + public TruncationResult truncate(Payload payload) { + if (payload == null || payload.getData() == null || payload.getData().getBody() == null) { + return TruncationResult.none(); + } + + Body body = payload.getData().getBody(); + List rollbarThreads = body.getRollbarThreads(); + if (rollbarThreads == null) { + return TruncationResult.none(); + } + + TruncationResult> truncationResult = truncateRollbarThreads(rollbarThreads); + if (!truncationResult.wasTruncated) { + return TruncationResult.none(); + } + + Payload newPayload = new Payload.Builder(payload).data( + new Data.Builder(payload.getData()).body( + new Body.Builder(payload.getData().getBody()) + .rollbarThreads(truncationResult.value).build() + ).build() + ).build(); + + return TruncationResult.truncated(newPayload); + } + + private TruncationResult> truncateRollbarThreads( + List rollbarThreads + ) { + boolean truncated = false; + ArrayList truncatedThreads = new ArrayList<>(); + for (RollbarThread rollbarThread: rollbarThreads) { + TraceChain traceChain = rollbarThread.getGroup().getTraceChain(); + + TruncationResult result = FRAMES_STRATEGY.truncateTraceChain(traceChain); + if (result.wasTruncated) { + truncated = true; + traceChain = result.value; + } + + RollbarThread truncatedThread = new RollbarThread + .Builder(rollbarThread) + .group(new Group(traceChain)).build(); + truncatedThreads.add(truncatedThread); + } + + if (truncated) { + return TruncationResult.truncated(truncatedThreads); + } else { + return TruncationResult.none(); + } + } +} diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/truncation/TelemetryEventsStrategy.java b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/TelemetryEventsStrategy.java new file mode 100644 index 00000000..e2260acd --- /dev/null +++ b/rollbar-java/src/main/java/com/rollbar/notifier/truncation/TelemetryEventsStrategy.java @@ -0,0 +1,42 @@ +package com.rollbar.notifier.truncation; + +import com.rollbar.api.payload.Payload; +import com.rollbar.api.payload.data.Data; +import com.rollbar.api.payload.data.TelemetryEvent; +import com.rollbar.api.payload.data.body.Body; + +import java.util.ArrayList; +import java.util.List; + +public class TelemetryEventsStrategy implements TruncationStrategy { + private static final int MAX_EVENTS = 10; + + @Override + public TruncationResult truncate(Payload payload) { + if (payload == null || payload.getData() == null || payload.getData().getBody() == null) { + return TruncationResult.none(); + } + + Body body = payload.getData().getBody(); + List telemetryEvents = body.getTelemetryEvents(); + if (telemetryEvents == null || telemetryEvents.size() <= MAX_EVENTS) { + return TruncationResult.none(); + } + + ArrayList truncatedTelemetryEvents = new ArrayList<>(); + for (int i = 0; i < MAX_EVENTS; i++) { + truncatedTelemetryEvents.add(telemetryEvents.get(i)); + } + + Payload newPayload = new Payload.Builder(payload).data( + new Data.Builder(payload.getData()).body( + new Body + .Builder(payload.getData().getBody()) + .telemetryEvents(truncatedTelemetryEvents) + .build() + ).build() + ).build(); + + return TruncationResult.truncated(newPayload); + } +} diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandler.java b/rollbar-java/src/main/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandler.java index 829dbe3a..99993285 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandler.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandler.java @@ -26,7 +26,7 @@ public RollbarUncaughtExceptionHandler(Rollbar rollbar, UncaughtExceptionHandler @Override public void uncaughtException(Thread thread, Throwable throwable) { - rollbar.log(throwable, null, null, null, true); + rollbar.log(throwable, thread, null, null, null, true); if (delegate != null) { delegate.uncaughtException(thread, throwable); diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/util/BodyFactory.java b/rollbar-java/src/main/java/com/rollbar/notifier/util/BodyFactory.java index 50a847c0..103d3ed0 100755 --- a/rollbar-java/src/main/java/com/rollbar/notifier/util/BodyFactory.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/util/BodyFactory.java @@ -2,9 +2,12 @@ import com.rollbar.api.payload.data.TelemetryEvent; import com.rollbar.api.payload.data.body.Body; +import com.rollbar.api.payload.data.body.BodyContent; import com.rollbar.api.payload.data.body.ExceptionInfo; import com.rollbar.api.payload.data.body.Frame; +import com.rollbar.api.payload.data.body.Group; import com.rollbar.api.payload.data.body.Message; +import com.rollbar.api.payload.data.body.RollbarThread; import com.rollbar.api.payload.data.body.Trace; import com.rollbar.api.payload.data.body.TraceChain; import com.rollbar.jvmti.CacheFrame; @@ -68,14 +71,86 @@ public Body from( return from(throwableWrapper, description, builder); } - private Body from(ThrowableWrapper throwableWrapper, String description, Body.Builder builder) { + /** + * Builds a RollbarThread from a Thread. + * + * @param thread the thread. + * @return the RollbarThread. + */ + public RollbarThread from( + Thread thread + ) { + if (thread == null) { + return null; + } + TraceChain traceChain = traceChain(thread.getStackTrace()); + return new RollbarThread(thread, new Group(traceChain)); + } + + private Body from( + ThrowableWrapper throwableWrapper, + String description, + Body.Builder builder + ) { + return builder + .bodyContent(makeBodyContent(throwableWrapper, description)) + .rollbarThreads(makeRollbarThreads(throwableWrapper, description)) + .build(); + } + + private List makeRollbarThreads( + ThrowableWrapper throwableWrapper, + String description + ) { + if (throwableWrapper == null) { + return null; + } + Map allStackTraces = throwableWrapper.getAllStackTraces(); + if (allStackTraces == null) { + return null; + } + + RollbarThread rollbarThread = throwableWrapper.getRollbarThread(); + if (rollbarThread == null) { + return null; + } + + ArrayList rollbarThreads = new ArrayList<>(); + rollbarThreads.add(updateInitialRollbarThread(rollbarThread, throwableWrapper, description)); + return addOtherThreads(rollbarThreads, allStackTraces); + } + + private RollbarThread updateInitialRollbarThread( + RollbarThread rollbarThread, + ThrowableWrapper throwableWrapper, + String description + ) { + TraceChain traceChain = traceChain(throwableWrapper, description); + return new RollbarThread.Builder(rollbarThread).group(new Group(traceChain)).build(); + } + + private ArrayList addOtherThreads( + ArrayList rollbarThreads, + Map allStackTraces + ) { + for (Map.Entry entry : allStackTraces.entrySet()) { + TraceChain traceChain = traceChain(entry.getValue()); + RollbarThread rollbarThread = new RollbarThread(entry.getKey(), new Group(traceChain)); + rollbarThreads.add(rollbarThread); + } + return rollbarThreads; + } + + private BodyContent makeBodyContent(ThrowableWrapper throwableWrapper, String description) { if (throwableWrapper == null) { - return builder.bodyContent(message(description)).build(); + return message(description); } + if (throwableWrapper.getCause() == null) { - return builder.bodyContent(trace(throwableWrapper, description)).build(); + return trace(throwableWrapper, description); } - return builder.bodyContent(traceChain(throwableWrapper, description)).build(); + + return traceChain(throwableWrapper, description); } private static Message message(String description) { @@ -91,6 +166,14 @@ private static Trace trace(ThrowableWrapper throwableWrapper, String description .build(); } + private TraceChain traceChain(StackTraceElement[] stackTraceElements) { + List frames = frames(stackTraceElements); + Trace trace = new Trace.Builder().frames(frames).build(); + ArrayList chain = new ArrayList<>(); + chain.add(trace); + return new TraceChain.Builder().traces(chain).build(); + } + private static TraceChain traceChain(ThrowableWrapper throwableWrapper, String description) { ArrayList chain = new ArrayList<>(); do { @@ -99,8 +182,8 @@ private static TraceChain traceChain(ThrowableWrapper throwableWrapper, String d throwableWrapper = throwableWrapper.getCause(); } while (throwableWrapper != null); return new TraceChain.Builder() - .traces(chain) - .build(); + .traces(chain) + .build(); } private static List frames(ThrowableWrapper throwableWrapper) { @@ -129,14 +212,24 @@ private static List frames(ThrowableWrapper throwableWrapper) { return result; } + private static List frames(StackTraceElement[] stackTraceElements) { + + ArrayList result = new ArrayList<>(); + for (int i = stackTraceElements.length - 1; i >= 0; i--) { + result.add(frame(stackTraceElements[i], null)); + } + + return result; + } + private static ExceptionInfo info(ThrowableWrapper throwableWrapper, String description) { String className = throwableWrapper.getClassName(); String message = throwableWrapper.getMessage(); return new ExceptionInfo.Builder() - .className(className) - .message(message) - .description(description) - .build(); + .className(className) + .message(message) + .description(description) + .build(); } private static Frame frame(StackTraceElement element, Map locals) { @@ -146,11 +239,11 @@ private static Frame frame(StackTraceElement element, Map locals String className = element.getClassName(); return new Frame.Builder() - .filename(filename) - .lineNumber(lineNumber) - .method(method) - .className(className) - .locals(locals) - .build(); + .filename(filename) + .lineNumber(lineNumber) + .method(method) + .className(className) + .locals(locals) + .build(); } } diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapper.java b/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapper.java index 128049d5..7eee3ba2 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapper.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapper.java @@ -1,6 +1,11 @@ package com.rollbar.notifier.wrapper; +import com.rollbar.api.payload.data.body.RollbarThread; +import com.rollbar.notifier.util.BodyFactory; + import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** * Implementation of the {@link ThrowableWrapper throwable wrapper}. @@ -17,38 +22,128 @@ public class RollbarThrowableWrapper implements ThrowableWrapper { private final Throwable throwable; + private final RollbarThread rollbarThread; + + private final Map allStackTraces; + /** * Constructor. * * @param throwable the throwable. */ public RollbarThrowableWrapper(Throwable throwable) { - this(throwable.getClass().getName(), throwable.getMessage(), throwable.getStackTrace(), - throwable.getCause() != null ? new RollbarThrowableWrapper(throwable.getCause()) : null, - throwable); + this( + throwable.getClass().getName(), + throwable.getMessage(), + throwable.getStackTrace(), + getInnerThrowableWrapper(throwable), + throwable, + Thread.currentThread(), + captureAllStackTraces(Thread.currentThread()) + ); + } + + /** + * Constructor. + * + * @param throwable the throwable. + */ + public RollbarThrowableWrapper(Throwable throwable, Thread thread) { + this( + throwable.getClass().getName(), + throwable.getMessage(), + throwable.getStackTrace(), + getInnerThrowableWrapper(throwable), + throwable, + thread, + captureAllStackTraces(thread) + ); + } + + /** + * Private constructor used to not capture all stack traces in a trace chain + * The unused parameter is used to have a method overload. + * + * @param throwable the throwable. + * @param unusedParameter used to differentiate signatures. + */ + private RollbarThrowableWrapper(Throwable throwable, boolean unusedParameter) { + this( + throwable.getClass().getName(), + throwable.getMessage(), + throwable.getStackTrace(), + getInnerThrowableWrapper(throwable), + throwable, + null, + null + ); + } + + private static ThrowableWrapper getInnerThrowableWrapper(Throwable throwable) { + if (throwable.getCause() == null) { + return null; + } + return new RollbarThrowableWrapper(throwable.getCause(), false); + } + + private static Map captureAllStackTraces(Thread thread) { + if (thread == null) { + return null; + } + + return filter(thread, Thread.getAllStackTraces()); + } + + private static Map filter( + Thread thread, Map allStackTraces + ) { + HashMap filteredStackTraces = new HashMap<>(); + + for (Map.Entry entry : allStackTraces.entrySet()) { + Thread entryThread = entry.getKey(); + + if (!thread.equals(entryThread)) { + filteredStackTraces.put(entryThread, entry.getValue()); + } + } + + return filteredStackTraces; } /** * Constructor. * - * @param className the class name. - * @param message the message. + * @param className the class name. + * @param message the message. * @param stackTraceElements the stack trace elements. - * @param cause the cause. + * @param cause the cause. */ - public RollbarThrowableWrapper(String className, String message, + public RollbarThrowableWrapper( + String className, + String message, StackTraceElement[] stackTraceElements, - ThrowableWrapper cause) { - this(className, message, stackTraceElements, cause, null); + ThrowableWrapper cause + ) { + this(className, message, stackTraceElements, cause, null, null, null); } - private RollbarThrowableWrapper(String className, String message, - StackTraceElement[] stackTraceElements, ThrowableWrapper cause, Throwable throwable) { + private RollbarThrowableWrapper( + String className, + String message, + StackTraceElement[] stackTraceElements, + ThrowableWrapper cause, + Throwable throwable, + Thread thread, + Map allStackTraces + ) { this.className = className; this.message = message; this.stackTraceElements = stackTraceElements; this.cause = cause; this.throwable = throwable; + this.rollbarThread = new BodyFactory().from(thread); + this.allStackTraces = allStackTraces; } @Override @@ -76,15 +171,26 @@ public Throwable getThrowable() { return this.throwable; } + @Override + public RollbarThread getRollbarThread() { + return rollbarThread; + } + + @Override + public Map getAllStackTraces() { + return allStackTraces; + } + @Override public String toString() { return "RollbarThrowableWrapper{" - + "className='" + className + '\'' - + ", message='" + message + '\'' - + ", stackTraceElements=" + Arrays.toString(stackTraceElements) - + ", cause=" + cause - + ", throwable=" + throwable - + '}'; + + "className='" + className + '\'' + + ", message='" + message + '\'' + + ", stackTraceElements=" + Arrays.toString(stackTraceElements) + + ", cause=" + cause + + ", throwable=" + throwable + + ", rollbarThread=" + rollbarThread + + '}'; } @Override diff --git a/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/ThrowableWrapper.java b/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/ThrowableWrapper.java index a195c108..480de92e 100644 --- a/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/ThrowableWrapper.java +++ b/rollbar-java/src/main/java/com/rollbar/notifier/wrapper/ThrowableWrapper.java @@ -1,5 +1,9 @@ package com.rollbar.notifier.wrapper; +import com.rollbar.api.payload.data.body.RollbarThread; + +import java.util.Map; + /** * Throwable wrapper to wrap a {@link Throwable thowable} or to represent a not available one. */ @@ -39,4 +43,18 @@ public interface ThrowableWrapper { * @return the throwable. */ Throwable getThrowable(); + + /** + * Get the RollbarThread {@link RollbarThread rollbarThread}. + * + * @return the rollbarThread. + */ + RollbarThread getRollbarThread(); + + /** + * Get a map of stack traces for all live threads in the moment of the Exception. + * + * @return the map. + */ + Map getAllStackTraces(); } diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/RollbarTest.java b/rollbar-java/src/test/java/com/rollbar/notifier/RollbarTest.java index 1141f946..fe29202b 100644 --- a/rollbar-java/src/test/java/com/rollbar/notifier/RollbarTest.java +++ b/rollbar-java/src/test/java/com/rollbar/notifier/RollbarTest.java @@ -4,18 +4,13 @@ import static com.rollbar.notifier.config.ConfigBuilder.withConfig; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.Is.isA; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.hamcrest.MockitoHamcrest.argThat; import com.rollbar.api.payload.Payload; import com.rollbar.api.payload.data.Client; @@ -42,10 +37,8 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -import org.mockito.stubbing.Answer; public class RollbarTest { diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/truncation/RollbarThreadStrategyTest.java b/rollbar-java/src/test/java/com/rollbar/notifier/truncation/RollbarThreadStrategyTest.java new file mode 100644 index 00000000..8c2097d9 --- /dev/null +++ b/rollbar-java/src/test/java/com/rollbar/notifier/truncation/RollbarThreadStrategyTest.java @@ -0,0 +1,83 @@ +package com.rollbar.notifier.truncation; + +import com.rollbar.api.payload.Payload; +import com.rollbar.api.payload.data.body.Body; +import com.rollbar.notifier.truncation.TruncationStrategy.TruncationResult; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class RollbarThreadStrategyTest { + + private TestPayloadBuilder payloadBuilder; + private final RollbarThreadStrategy sut = new RollbarThreadStrategy(); + private static final int MAX_FRAMES = 20; + + @Before + public void setUp() { + payloadBuilder = new TestPayloadBuilder(); + } + + @Test + public void ifPayloadIsNullItShouldNotTruncate() { + TruncationResult result = sut.truncate(null); + + verifyNoTruncation(result); + } + + @Test + public void ifDataIsNullItShouldNotTruncate() { + Payload payload = new Payload.Builder(payloadBuilder.createTestPayload()) + .data(null) + .build(); + + TruncationResult result = sut.truncate(payload); + + verifyNoTruncation(result); + } + + @Test + public void ifBodyIsNullItShouldNotTruncate() { + Payload payload = payloadBuilder.createTestPayload((Body) null); + + TruncationResult result = sut.truncate(payload); + + verifyNoTruncation(result); + } + + @Test + public void ifRollbarThreadsIsNullItShouldNotTruncate() { + Payload payload = payloadBuilder.createTestPayload(); + + TruncationResult result = sut.truncate(payload); + + verifyNoTruncation(result); + } + + @Test + public void ifRollbarThreadsContainsFramesEqualOrLessThanMaximumItShouldNotTruncate() { + Payload payload = payloadBuilder.createTestPayloadSingleTraceWithRollbarThreads(MAX_FRAMES); + + TruncationResult result = sut.truncate(payload); + + verifyNoTruncation(result); + } + + @Test + public void ifRollbarThreadsContainsMoreFramesThanMaximumItShouldTruncate() { + Payload payload = payloadBuilder.createTestPayloadSingleTraceWithRollbarThreads(MAX_FRAMES + 1); + + TruncationResult result = sut.truncate(payload); + + assertTrue(result.wasTruncated); + assertNotNull(result.value); + assertNotEquals(payload, result.value); + } + + private void verifyNoTruncation(TruncationResult result) { + assertFalse(result.wasTruncated); + assertNull(result.value); + } + +} diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/truncation/TelemetryEventsStrategyTest.java b/rollbar-java/src/test/java/com/rollbar/notifier/truncation/TelemetryEventsStrategyTest.java new file mode 100644 index 00000000..c98797d8 --- /dev/null +++ b/rollbar-java/src/test/java/com/rollbar/notifier/truncation/TelemetryEventsStrategyTest.java @@ -0,0 +1,106 @@ +package com.rollbar.notifier.truncation; + +import com.rollbar.api.payload.Payload; +import com.rollbar.api.payload.data.Level; +import com.rollbar.api.payload.data.Source; +import com.rollbar.api.payload.data.TelemetryEvent; +import com.rollbar.api.payload.data.TelemetryType; +import com.rollbar.api.payload.data.body.Body; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.*; + +public class TelemetryEventsStrategyTest { + + private TestPayloadBuilder payloadBuilder; + private final TelemetryEventsStrategy sut = new TelemetryEventsStrategy(); + private static final int MAX_EVENTS = 10; + + @Before + public void setUp() { + payloadBuilder = new TestPayloadBuilder(); + } + + @Test + public void ifPayloadIsNullItShouldNotTruncate() { + TruncationStrategy.TruncationResult result = sut.truncate(null); + + verifyNoTruncation(result); + } + + @Test + public void ifDataIsNullItShouldNotTruncate() { + Payload payload = new Payload.Builder(payloadBuilder.createTestPayload()) + .data(null) + .build(); + + TruncationStrategy.TruncationResult result = sut.truncate(payload); + + verifyNoTruncation(result); + } + + @Test + public void ifBodyIsNullItShouldNotTruncate() { + Payload payload = payloadBuilder.createTestPayload((Body) null); + + TruncationStrategy.TruncationResult result = sut.truncate(payload); + + verifyNoTruncation(result); + } + + @Test + public void ifTelemetryEventsEqualOrLessThanMaximumItShouldTruncate() { + List telemetryEvents = createTelemetryEvents(MAX_EVENTS); + Payload payload = payloadBuilder.createTestPayloadSingleTraceWithTelemetryEvents( + 1, + telemetryEvents + ); + + TruncationStrategy.TruncationResult result = sut.truncate(payload); + + verifyNoTruncation(result); + } + + @Test + public void ifTelemetryEventsAreAboveMaximumItShouldTruncate() { + List telemetryEvents = createTelemetryEvents(MAX_EVENTS + 1); + Payload payload = payloadBuilder.createTestPayloadSingleTraceWithTelemetryEvents( + 1, + telemetryEvents + ); + + TruncationStrategy.TruncationResult result = sut.truncate(payload); + + assertTrue(result.wasTruncated); + assertNotNull(result.value); + assertNotEquals(payload, result.value); + } + + private void verifyNoTruncation(TruncationStrategy.TruncationResult result) { + assertFalse(result.wasTruncated); + assertNull(result.value); + } + + private List createTelemetryEvents(int quantity) { + ArrayList telemetryEvents = new ArrayList<>(); + for (int i = 0; i < quantity; i++) { + telemetryEvents.add(creteTelemetryEvent()); + } + return telemetryEvents; + } + + private TelemetryEvent creteTelemetryEvent() { + return new TelemetryEvent( + TelemetryType.MANUAL, + Level.DEBUG, + 1L, + Source.CLIENT, + new HashMap<>() + ); + } +} diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/truncation/TestPayloadBuilder.java b/rollbar-java/src/test/java/com/rollbar/notifier/truncation/TestPayloadBuilder.java index 7f4a0ac6..aaffc758 100644 --- a/rollbar-java/src/test/java/com/rollbar/notifier/truncation/TestPayloadBuilder.java +++ b/rollbar-java/src/test/java/com/rollbar/notifier/truncation/TestPayloadBuilder.java @@ -32,6 +32,20 @@ public Payload createTestPayloadSingleTrace(int frameCount) { return createTestPayloadSingleTrace(createFrames(frameCount)); } + public Payload createTestPayloadSingleTraceWithRollbarThreads(int frameCount) { + return createTestPayload(Collections.singletonList(createFrames(frameCount)), true, null); + } + + public Payload createTestPayloadSingleTraceWithTelemetryEvents( + int frameCount, + List telemetryEvents + ) { + return createTestPayload( + Collections.singletonList(createFrames(frameCount)), + true, + telemetryEvents); + } + public Payload createTestPayloadSingleTrace(Trace trace) { return createTestPayload(new Body.Builder().bodyContent(trace).build()); } @@ -41,16 +55,24 @@ public Payload createTestPayloadSingleTrace(List frameList) { } public Payload createTestPayload(List> frameLists) { + return createTestPayload(frameLists, false, null); + } + + public Payload createTestPayload( + List> frameLists, + boolean addRollbarThreads, + List telemetryEvents + ) { List traces = frameLists.stream().map(frameList -> new Trace.Builder() - .exception( - new ExceptionInfo.Builder() - .message(makeString("Error")) - .description(makeString("some error")) - .className(makeString("com.example.TestException")) - .build() - ) - .frames(frameList) - .build()).collect(Collectors.toList()); + .exception( + new ExceptionInfo.Builder() + .message(makeString("Error")) + .description(makeString("some error")) + .className(makeString("com.example.TestException")) + .build() + ) + .frames(frameList) + .build()).collect(Collectors.toList()); BodyContent bodyContent; if (traces.size() == 1) { @@ -59,7 +81,23 @@ public Payload createTestPayload(List> frameLists) { bodyContent = new TraceChain.Builder().traces(traces).build(); } - return createTestPayload(new Body.Builder().bodyContent(bodyContent).build()); + ArrayList rollbarThreads = null; + if (addRollbarThreads) { + rollbarThreads = new ArrayList<>(); + TraceChain traceChain = new TraceChain.Builder().traces(traces).build(); + Group group = new Group(traceChain); + RollbarThread rollbarThread = new RollbarThread(Thread.currentThread(), group); + rollbarThreads.add(rollbarThread); + } + + return createTestPayload( + new Body + .Builder() + .bodyContent(bodyContent) + .rollbarThreads(rollbarThreads) + .telemetryEvents(telemetryEvents) + .build() + ); } public Payload createTestPayload(Body body) { diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandlerTest.java b/rollbar-java/src/test/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandlerTest.java index 1c27ad69..0f7dd373 100644 --- a/rollbar-java/src/test/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandlerTest.java +++ b/rollbar-java/src/test/java/com/rollbar/notifier/uncaughtexception/RollbarUncaughtExceptionHandlerTest.java @@ -34,7 +34,7 @@ public void shouldLogThrowableToRollbar() { sut.uncaughtException(thread, throwable); - verify(rollbar).log(throwable, null, null, null, true); + verify(rollbar).log(throwable, thread, null, null, null, true); verify(uncaughtExceptionHandler, never()).uncaughtException(thread, throwable); } @@ -45,7 +45,7 @@ public void shouldLogThrowableToRollbarAndDelegate() { sut.uncaughtException(thread, throwable); - verify(rollbar).log(throwable, null, null, null, true); + verify(rollbar).log(throwable, thread, null, null, null, true); verify(uncaughtExceptionHandler).uncaughtException(thread, throwable); } diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/util/BodyFactoryTest.java b/rollbar-java/src/test/java/com/rollbar/notifier/util/BodyFactoryTest.java index 37090686..6808aab0 100644 --- a/rollbar-java/src/test/java/com/rollbar/notifier/util/BodyFactoryTest.java +++ b/rollbar-java/src/test/java/com/rollbar/notifier/util/BodyFactoryTest.java @@ -3,19 +3,18 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.mockito.Mockito.verify; - -import com.rollbar.api.payload.data.body.Body; -import com.rollbar.api.payload.data.body.ExceptionInfo; -import com.rollbar.api.payload.data.body.Frame; -import com.rollbar.api.payload.data.body.Message; -import com.rollbar.api.payload.data.body.Trace; -import com.rollbar.api.payload.data.body.TraceChain; +import static org.junit.Assert.*; + +import com.rollbar.api.payload.data.Level; +import com.rollbar.api.payload.data.Source; +import com.rollbar.api.payload.data.TelemetryEvent; +import com.rollbar.api.payload.data.TelemetryType; +import com.rollbar.api.payload.data.body.*; import com.rollbar.notifier.wrapper.RollbarThrowableWrapper; import com.rollbar.notifier.wrapper.ThrowableWrapper; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; + +import java.util.*; + import org.junit.Before; import org.junit.Test; @@ -47,6 +46,59 @@ public void shouldBuildBodyWithDescription() { assertThat(((Message) body2.getContents()).getBody(), is(DESCRIPTION)); } + @Test + public void shouldBuildBodyWithDescriptionAndTelemetryEvents() { + Body body = sut.from(null, DESCRIPTION, new ArrayList<>()); + + assertThat(body.getContents(), is(instanceOf(Message.class))); + assertThat(((Message) body.getContents()).getBody(), is(DESCRIPTION)); + HashMap map = (HashMap) body.asJson(); + assertNotNull(map.get("telemetry")); + assertNull(map.get("group")); + } + + @Test + public void shouldTruncateTelemetryEvents() { + Map telemetryBody = new HashMap<>(); + telemetryBody.put("message", "1234567890"); + TelemetryEvent telemetryEvent = makeTelemetryEvent(telemetryBody); + ArrayList telemetryEvents = new ArrayList<>(); + telemetryEvents.add(telemetryEvent); + + Body body = sut.from(null, DESCRIPTION, telemetryEvents).truncateStrings(5); + + telemetryBody.put("message", "12345"); + TelemetryEvent expected = makeTelemetryEvent(telemetryBody); + assertEquals(expected, getFirstTelemetryEvent(body)); + } + + @Test + public void shouldTruncateGroups() { + int expectedLength = 5; + Throwable throwable = buildSimpleThrowable(); + ThrowableWrapper throwableWrapper = new RollbarThrowableWrapper(throwable, Thread.currentThread()); + + Body truncatedBody = sut.from(throwableWrapper, DESCRIPTION).truncateStrings(expectedLength); + + Trace trace = getFirstTraceFromGroup(truncatedBody); + assertEquals(expectedLength, trace.getException().getMessage().length()); + assertEquals(expectedLength, trace.getException().getClassName().length()); + assertEquals(expectedLength, trace.getException().getDescription().length()); + assertEquals(expectedLength, trace.getFrames().get(0).getClassName().length()); + } + + @Test + public void shouldBuildBodyWithThreads() { + Throwable throwable = buildSimpleThrowable(); + ThrowableWrapper throwableWrapper = new RollbarThrowableWrapper(throwable, Thread.currentThread()); + Body body = sut.from(throwableWrapper, DESCRIPTION); + + assertThat(body.getContents(), is(instanceOf(Trace.class))); + HashMap map = (HashMap) body.asJson(); + assertNull(map.get("telemetry")); + assertNotNull(map.get("threads")); + } + @Test public void shouldBuildBodyWithTraceAsContent() { Throwable throwable = buildSimpleThrowable(); @@ -88,6 +140,28 @@ public void shouldBuildBodyWithTraceChainAsContentFromThrowableWrapper() { verifyTraceChain((TraceChain) body.getContents(), throwable); } + private TelemetryEvent makeTelemetryEvent( Map body) { + return new TelemetryEvent(TelemetryType.LOG, Level.DEBUG, 12L, Source.CLIENT, body); + } + + private TelemetryEvent getFirstTelemetryEvent(Body body) { + HashMap map = (HashMap) body.asJson(); + List telemetryEvents = (List) map.get("telemetry"); + return telemetryEvents.get(0); + } + + private Trace getFirstTraceFromGroup(Body body) { + HashMap bodyJson = (HashMap) body.asJson(); + List rollbarThreads = (List) bodyJson.get("threads"); + + HashMap groupJson = (HashMap) rollbarThreads.get(0).asJson(); + Group group = (Group) groupJson.get("group"); + + List> rollbarThreadJson = (List>) group.asJson(); + TraceChain traceChain = (TraceChain) rollbarThreadJson.get(0).get("trace_chain"); + return traceChain.getTraces().get(0); + } + private void verifyTrace(Trace trace, Throwable throwable, String description) { verifyFrames(trace.getFrames(), throwable); verifyExceptionInfo(trace.getException(), throwable, description); diff --git a/rollbar-java/src/test/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapperTest.java b/rollbar-java/src/test/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapperTest.java index 3456cfcd..4c752a4e 100644 --- a/rollbar-java/src/test/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapperTest.java +++ b/rollbar-java/src/test/java/com/rollbar/notifier/wrapper/RollbarThrowableWrapperTest.java @@ -2,13 +2,20 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import org.junit.Test; public class RollbarThrowableWrapperTest { + @Test + public void shouldCollectThreads() { + RollbarThrowableWrapper sut = new RollbarThrowableWrapper(new Exception("Any"), Thread.currentThread()); + + assertNotNull(sut.getAllStackTraces()); + assertNotNull(sut.getRollbarThread()); + } + @Test public void shouldBeCreatedByThrowable() { Throwable nestedThrowable = new IllegalStateException("This is the nested throwable message"); @@ -22,6 +29,8 @@ public void shouldBeCreatedByThrowable() { assertThat(sut.getStackTrace(), is(throwable.getStackTrace())); assertThat(sut.getCause(), is(nested)); assertThat(sut.getThrowable(), is(throwable)); + assertNotNull(sut.getAllStackTraces()); + assertNotNull(sut.getRollbarThread()); } @Test @@ -40,6 +49,8 @@ public void shouldBeCreatedByThrowableParams() { assertThat(sut.getStackTrace(), is(elements)); assertThat(sut.getCause(), is(cause)); assertThat(sut.getThrowable(), is(nullValue())); + assertNull(sut.getAllStackTraces()); + assertNull(sut.getRollbarThread()); } @Test @@ -49,6 +60,6 @@ public void shouldBeEqual() { RollbarThrowableWrapper sut1 = new RollbarThrowableWrapper(throwable); RollbarThrowableWrapper sut2 = new RollbarThrowableWrapper(throwable); - assertTrue(sut1.equals(sut2)); + assertEquals(sut1, sut2); } } \ No newline at end of file