Repository: rollbar/rollbar-java (java)

Issue:
Incorrect class names in stack traces
Clients based on ahaid/rollbar-logback send "class_name" in the stack trace elements, e.g.:

```
          {
            "class_name": "java.lang.Thread", 
            "method": "run", 
            "lineno": 745, 
            "filename": "Thread.java"
          }, 
          {
            "class_name": "org.eclipse.jetty.util.thread.QueuedThreadPool$3", 
            "method": "run", 
            "lineno": 540, 
            "filename": "QueuedThreadPool.java"
          }, 
          {
            "class_name": "org.eclipse.jetty.util.thread.QueuedThreadPool", 
            "method": "runJob", 
            "lineno": 620, 
            "filename": "QueuedThreadPool.java"
          },
```

which results in a more java-like stacktrace on the UI:

```
128 at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob (QueuedThreadPool.java:620)
129 at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run (QueuedThreadPool.java:540)
130 at java.lang.Thread.run (Thread.java:745)
```

On the other hand this library does not support _class_name_ and sends Frame objects without this field, which is rendered on the UI like this:

```
128 at runJob (org.eclipse.jetty.util.thread.QueuedThreadPool.java:620)
129 at run (org.eclipse.jetty.util.thread.QueuedThreadPool$3.java:540)
130 at main ( java.lang.Thread.java:745)
```

It's a bit odd given that the file names are incorrect (especially that ...$3.java)

Rollbar API spec does not mention _class_name_. 

Could _class_name_ be included (in the spec and in this client) to make stack traces in the UI look like regular java stacktraces - same as the ones from ahaid/rollbar-logback?


Pull request description:
add support for class_name field
This uses the very-recently-documented API field `class_name` for error reports.
The `filename` field is now filled only with the `StackTraceElement.getFileName()` value, and not the pseudo-filename from before.

I left the old constructor for backwards-compatibility; however I'm not sure it's pulling its weight and doubt many (any?) folks are using it directly. So it might be worth removing. Let me know if you'd like that.

This fixes #34.

Patch:
diff --git a/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Frame.java b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Frame.java
index cf16066..b9a4d56 100644
--- a/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Frame.java
+++ b/rollbar-payload/src/main/java/com/rollbar/payload/data/body/Frame.java
@@ -18,6 +18,7 @@ public class Frame implements JsonSerializable {
     private final Integer columnNumber;
     private final String method;
     private final String code;
+    private final String className;
     private final CodeContext context;
     private final Object[] args;
     private final LinkedHashMap<String, Object> keywordArgs;
@@ -28,7 +29,7 @@ public class Frame implements JsonSerializable {
      * @throws ArgumentNullException if filename is null
      */
     public Frame(String filename) throws ArgumentNullException {
-        this(filename, null, null, null, null, null, null, null);
+        this(filename, null, null, null, null, null, null, null, null);
     }
 
     /**
@@ -44,12 +45,30 @@ public class Frame implements JsonSerializable {
      * @throws ArgumentNullException if filename is null
      */
     public Frame(String filename, Integer lineNumber, Integer columnNumber, String method, String code, CodeContext context, Object[] args, Map<String, Object> keywordArgs) throws ArgumentNullException {
+        this(filename, lineNumber, columnNumber, method, code, null, context, args, keywordArgs);
+    }
+
+    /**
+     * Constructor
+     * @param filename the name of the file in which the error occurred
+     * @param lineNumber the line number on which the error occurred
+     * @param columnNumber the column number (if available in your language) on which the error occurred
+     * @param method the method in which the error occurred
+     * @param code the line of code that triggered the error
+     * @param className the name of the class in which the error occurred
+     * @param context extra context around the line of code that triggered the error
+     * @param args the arguments to the method from the stack frame (if available in your language)
+     * @param keywordArgs the keyword arguments to the method from the stack frame (if available in your language)
+     * @throws ArgumentNullException if filename is null
+     */
+    public Frame(String filename, Integer lineNumber, Integer columnNumber, String method, String code, String className, CodeContext context, Object[] args, Map<String, Object> keywordArgs) throws ArgumentNullException {
         Validate.isNotNullOrWhitespace(filename, "filename");
         this.filename = filename;
         this.lineNumber = lineNumber;
         this.columnNumber = columnNumber;
         this.method = method;
         this.code = code;
+        this.className = className;
         this.context = context;
         this.args = args == null ? null : args.clone();
         this.keywordArgs = keywordArgs == null ? null : new LinkedHashMap<String, Object>(keywordArgs);
@@ -79,11 +98,12 @@ public class Frame implements JsonSerializable {
      * @throws ArgumentNullException if stackTraceElement is null
      */
     public static Frame fromStackTraceElement(StackTraceElement stackTraceElement) throws ArgumentNullException {
-        String filename = stackTraceElement.getClassName() + ".java";
+        String filename = stackTraceElement.getFileName();
         Integer lineNumber = stackTraceElement.getLineNumber();
         String method = stackTraceElement.getMethodName();
+        String className = stackTraceElement.getClassName();
 
-        return new Frame(filename, lineNumber, null, method, null, null, null, null);
+        return new Frame(filename, lineNumber, null, method, null, className, null, null, null);
     }
 
     /**
@@ -100,7 +120,7 @@ public class Frame implements JsonSerializable {
     * @throws ArgumentNullException if filename is null
     */
     public Frame filename(String filename) throws ArgumentNullException {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     /**
@@ -116,7 +136,7 @@ public class Frame implements JsonSerializable {
     * @return a copy of this Frame with the lineNumber overridden
     */
     public Frame lineNumber(Integer lineNumber) {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     /**
@@ -132,7 +152,7 @@ public class Frame implements JsonSerializable {
     * @return a copy of this Frame with the columnNumber overridden
     */
     public Frame columnNumber(Integer columnNumber) {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     /**
@@ -148,7 +168,7 @@ public class Frame implements JsonSerializable {
     * @return a copy of this Frame with the method overridden
     */
     public Frame method(String method) {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     /**
@@ -164,7 +184,23 @@ public class Frame implements JsonSerializable {
     * @return a copy of this Frame with the code overridden
     */
     public Frame code(String code) {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
+    }
+
+    /**
+    * @return the name of the class in which the error occurred
+    */
+    public String className() {
+        return className;
+    }
+
+    /**
+    * Set className on a copy of this Frame
+    * @param className the name of the class in which the error occurred
+    * @return a copy of this Frame with the className overridden
+    */
+    public Frame className(String className) {
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     /**
@@ -180,7 +216,7 @@ public class Frame implements JsonSerializable {
     * @return a copy of this Frame with the context overridden
     */
     public Frame context(CodeContext context) {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     /**
@@ -196,7 +232,7 @@ public class Frame implements JsonSerializable {
     * @return a copy of this Frame with the args overridden
     */
     public Frame args(Object[] args) {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     /**
@@ -212,7 +248,7 @@ public class Frame implements JsonSerializable {
     * @return a copy of this Frame with the keywordArgs overridden
     */
     public Frame keywordArgs(Map<String, Object> keywordArgs) {
-        return new Frame(filename, lineNumber, columnNumber, method, code, context, args, keywordArgs);
+        return new Frame(filename, lineNumber, columnNumber, method, code, className, context, args, keywordArgs);
     }
 
     public Map<String, Object> asJson() {
@@ -223,6 +259,7 @@ public class Frame implements JsonSerializable {
         Integer columnNumber = columnNumber();
         String method = method();
         String code = code();
+        String className = className();
         CodeContext context = context();
         Object[] args = args();
         Map<String, Object> keywordArgs = keywordArgs();
@@ -237,6 +274,8 @@ public class Frame implements JsonSerializable {
             obj.put("method", method);
         if (code != null)
             obj.put("code", code);
+        if (className != null)
+            obj.put("class_name", className);
         if (context != null)
             obj.put("context", context);
         if (args != null)


Test patch:
diff --git a/rollbar-payload/src/test/java/com/rollbar/payload/RollbarSerializerTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/RollbarSerializerTest.java
index 39a6bb1..be1945b 100644
--- a/rollbar-payload/src/test/java/com/rollbar/payload/RollbarSerializerTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/RollbarSerializerTest.java
@@ -58,8 +58,10 @@ public class RollbarSerializerTest {
             final JsonObject lastFrame = frames.get(frames.size() - 1).getAsJsonObject();
             final JsonObject secondToLastFrame = frames.get(frames.size() - 2).getAsJsonObject();
 
-            assertEquals("com.rollbar.payload.RollbarSerializerTest.java", lastFrame.get("filename").getAsString());
-            assertEquals("com.rollbar.payload.RollbarSerializerTest.java", secondToLastFrame.get("filename").getAsString());
+            assertEquals("RollbarSerializerTest.java", lastFrame.get("filename").getAsString());
+            assertEquals("RollbarSerializerTest.java", secondToLastFrame.get("filename").getAsString());
+            assertEquals("com.rollbar.payload.RollbarSerializerTest", lastFrame.get("class_name").getAsString());
+            assertEquals("com.rollbar.payload.RollbarSerializerTest", secondToLastFrame.get("class_name").getAsString());
             assertEquals("throwException", lastFrame.get("method").getAsString());
             assertEquals("getError", secondToLastFrame.get("method").getAsString());
         } catch (ArgumentNullException e) {
diff --git a/rollbar-payload/src/test/java/com/rollbar/payload/data/body/FrameTest.java b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/FrameTest.java
index 7569507..a03eb24 100644
--- a/rollbar-payload/src/test/java/com/rollbar/payload/data/body/FrameTest.java
+++ b/rollbar-payload/src/test/java/com/rollbar/payload/data/body/FrameTest.java
@@ -92,6 +92,19 @@ public class FrameTest {
         });
     }
 
+    @Test
+    public void testClassName() throws Exception {
+        TestThat.getAndSetWorks(f, "java.lang.String", "java.lang.Integer", new GetAndSet<Frame, String>() {
+            public String get(Frame frame) {
+                return frame.className();
+            }
+
+            public Frame set(Frame frame, String val) {
+                return frame.className(val);
+            }
+        });
+    }
+
     @Test
     public void testContext() throws Exception {
         CodeContext one = new CodeContext(new String[] { "before", "the", "code" }, new String[] { "after", "the", "code" });
@@ -147,6 +160,7 @@ public class FrameTest {
         assertFalse(json.containsKey("lineno"));
         assertFalse(json.containsKey("colno"));
         assertFalse(json.containsKey("method"));
+        assertFalse(json.containsKey("class_name"));
         assertFalse(json.containsKey("code"));
         assertFalse(json.containsKey("context"));
         assertFalse(json.containsKey("args"));