diff --git a/.gitignore b/.gitignore index 9dbdbbfd78..bfe9da4a1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *.pyc *.DS_Store -out/* \ No newline at end of file +out/* +target/* +.idea/* \ No newline at end of file diff --git a/.idea/libraries/common.xml b/.idea/libraries/common.xml deleted file mode 100644 index 80734af5ac..0000000000 --- a/.idea/libraries/common.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.rst b/README.rst index 0521d50636..35bfacfc7f 100644 --- a/README.rst +++ b/README.rst @@ -6,11 +6,29 @@ This is a very raw project at the moment it still needs some more TLC and testin Installation ------------ -Copy the raven-java-0.2.jar file to your java classpath and then configure log4j to use the SentryAppender. +You'll need Maven 2 to build the project:: -The raven-java-0.2.jar is a self contained jar file, all dependencies are included, so this jar should be all you need. + $ cd raven-java + $ mvn package -Dmaven.test.skip -There is an example project checked into github.com where you can see an example log4j config file. +The last step will build the standalone raven-java jar file but also a jar file containing raven-java and all dependencies, which +you'll find in the target directory of the project. + +**Option 1**: add raven-java as a dependency when you're using Maven:: + + + net.kencochrane + raven-java + 1.0-SNAPSHOT + + +**Option 2**: add the plain jar and the jar files of all dependencies to your classpath + +**Option 3**: add the self contained jar file to your classpath + +Log4J configuration +------------------- +Check out src/test/java/resources/log4j_configuration.txt where you can see an example log4j config file. You will need to add the SentryAppender and the sentry_dsn properties. @@ -24,15 +42,23 @@ Log4j Config example:: log4j.appender.sentry=net.kencochrane.sentry.SentryAppender log4j.appender.sentry.sentry_dsn=http://b4935bdd78624092ac2bc70fdcdb6f5a:7a37d9ad4765428180316bfec91a27ef@localhost:8000/1 +Proxy +~~~~~ +If you need to use a proxy for HTTP transport, you can configure it as well:: + + log4j.appender.sentry.proxy=HTTP:proxyhost:proxyport Sentry Versions Supported ------------------------- This client has been tested with Sentry 2.7 and 2.8, and only very briefly. +Other +----- +If you want to generate the javadocs for this project, simply run ``mvn javadoc:javadoc`` and you'll be able to browse the +docs from the target directory of the project. + TODO ---- -- Add a ant task to build the jar files (I made this first one from intellij (10.5 community edition) File-> Project structure -> artifacts. -- Make this maven friendly. Not familiar with Maven, so maybe someone else can help with this. - Create better documentation - Add unit tests - Add more examples diff --git a/example/run.sh b/example/run.sh deleted file mode 100755 index 59502a869d..0000000000 --- a/example/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -java -classpath /Users/kcochrane/github/raven-java/out/production/raven-java:../lib/log4j-1.2.16.jar:../lib/httpmime-4.1.2.jar:../lib/commons-codec-1.4.jar:../lib/httpcore-4.1.2.jar:../lib/httpclient-4.1.2.jar:../lib/commons-logging-1.1.1.jar:../lib/json_simple-1.1.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt diff --git a/example/run2.sh b/example/run2.sh deleted file mode 100755 index 44a5d7d7bd..0000000000 --- a/example/run2.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -java -classpath ../raven-java-0.2.jar net.kencochrane.sentry.SentryExample log4j_configuration.txt diff --git a/example/src/net/kencochrane/sentry/SentryExample.java b/example/src/net/kencochrane/sentry/SentryExample.java deleted file mode 100644 index 16daedf280..0000000000 --- a/example/src/net/kencochrane/sentry/SentryExample.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.kencochrane.sentry; - -/** - * User: ken cochrane - * Date: 2/6/12 - * Time: 11:35 AM - */ - - // Import log4j classes. - import org.apache.log4j.Logger; - import org.apache.log4j.PropertyConfigurator; - -/** - * Simple example used to test out the sentry logger. - */ - public class SentryExample { - - // Define a static logger variable so that it references the - // Logger instance named "MyApp". - static final Logger logger = Logger.getLogger(SentryExample.class); - - public static void main(String[] args) { - - // PropertyConfigurator. - PropertyConfigurator.configure(args[0]); - - logger.debug("Debug example"); - logger.error("Error example"); - logger.trace("Trace Example"); - logger.fatal("Fatal Example"); - logger.info("info Example"); - logger.warn("Warn Example"); - } - } diff --git a/lib/commons-codec-1.4.jar b/lib/commons-codec-1.4.jar deleted file mode 100644 index 458d432da8..0000000000 Binary files a/lib/commons-codec-1.4.jar and /dev/null differ diff --git a/lib/commons-logging-1.1.1.jar b/lib/commons-logging-1.1.1.jar deleted file mode 100644 index 1deef144cb..0000000000 Binary files a/lib/commons-logging-1.1.1.jar and /dev/null differ diff --git a/lib/httpclient-4.1.2.jar b/lib/httpclient-4.1.2.jar deleted file mode 100644 index b3cdb4cdc8..0000000000 Binary files a/lib/httpclient-4.1.2.jar and /dev/null differ diff --git a/lib/httpcore-4.1.2.jar b/lib/httpcore-4.1.2.jar deleted file mode 100644 index 66ae90b0d1..0000000000 Binary files a/lib/httpcore-4.1.2.jar and /dev/null differ diff --git a/lib/httpmime-4.1.2.jar b/lib/httpmime-4.1.2.jar deleted file mode 100644 index eea3b3ff17..0000000000 Binary files a/lib/httpmime-4.1.2.jar and /dev/null differ diff --git a/lib/json_simple-1.1.jar b/lib/json_simple-1.1.jar deleted file mode 100644 index f395f41471..0000000000 Binary files a/lib/json_simple-1.1.jar and /dev/null differ diff --git a/lib/log4j-1.2.16.jar b/lib/log4j-1.2.16.jar deleted file mode 100644 index 3f9d847618..0000000000 Binary files a/lib/log4j-1.2.16.jar and /dev/null differ diff --git a/pom.xml b/pom.xml index d2236b46b3..d968a4b83b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,9 +23,9 @@ 1.6 - org.json - json - 20090211 + com.googlecode.json-simple + json-simple + 1.1 log4j @@ -58,6 +58,24 @@ UTF-8 + + maven-assembly-plugin + 2.3 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF deleted file mode 100644 index 87a3423ac9..0000000000 --- a/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Main-Class: net.kencochrane.sentry.SentryAppender - diff --git a/src/net/kencochrane/sentry/RavenClient.java b/src/main/java/net/kencochrane/sentry/RavenClient.java similarity index 69% rename from src/net/kencochrane/sentry/RavenClient.java rename to src/main/java/net/kencochrane/sentry/RavenClient.java index 0819ced448..7f746802ce 100644 --- a/src/net/kencochrane/sentry/RavenClient.java +++ b/src/main/java/net/kencochrane/sentry/RavenClient.java @@ -1,5 +1,6 @@ package net.kencochrane.sentry; +import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.io.IOException; @@ -58,11 +59,17 @@ public void setSentryDSN(String sentryDSN) { * @param culprit Who we think caused the problem. * @return JSON String of message body */ - private String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit) { + private String buildJSON(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { JSONObject obj = new JSONObject(); obj.put("event_id", RavenUtils.getRandomUUID()); //Hexadecimal string representing a uuid4 value. obj.put("checksum", RavenUtils.calculateChecksum(message)); - obj.put("culprit", culprit); + if (exception == null) { + obj.put("culprit", culprit); + } else { + obj.put("culprit", determineCulprit(exception)); + obj.put("sentry.interfaces.Exception", buildException(exception)); + obj.put("sentry.interfaces.Stacktrace", buildStacktrace(exception)); + } obj.put("timestamp", timestamp); obj.put("message", message); obj.put("project", getConfig().getProjectId()); @@ -72,6 +79,63 @@ private String buildJSON(String message, String timestamp, String loggerClass, i return obj.toJSONString(); } + /** + * Determines the class and method name where the root cause exception occurred. + * + * @param exception exception + * @return the culprit + */ + private String determineCulprit(Throwable exception) { + Throwable cause = exception; + String culprit = null; + while (cause != null) { + StackTraceElement[] elements = cause.getStackTrace(); + if (elements.length > 0) { + StackTraceElement trace = elements[0]; + culprit = trace.getClassName() + "." + trace.getMethodName(); + } + cause = cause.getCause(); + } + return culprit; + } + + private JSONObject buildException(Throwable exception) { + JSONObject json = new JSONObject(); + json.put("type", exception.getClass().getSimpleName()); + json.put("value", exception.getMessage()); + json.put("module", exception.getClass().getPackage().getName()); + return json; + } + + private JSONObject buildStacktrace(Throwable exception) { + JSONArray array = new JSONArray(); + Throwable cause = exception; + while (cause != null) { + StackTraceElement[] elements = cause.getStackTrace(); + for (int index = 0; index < elements.length; ++index) { + if (index == 0) { + JSONObject causedByFrame = new JSONObject(); + String msg = "Caused by: " + cause.getClass().getName(); + if (cause.getMessage() != null) { + msg += " (\"" + cause.getMessage() + "\")"; + } + causedByFrame.put("filename", msg); + causedByFrame.put("lineno", -1); + array.add(causedByFrame); + } + StackTraceElement element = elements[index]; + JSONObject frame = new JSONObject(); + frame.put("filename", element.getClassName()); + frame.put("function", element.getMethodName()); + frame.put("lineno", element.getLineNumber()); + array.add(frame); + } + cause = cause.getCause(); + } + JSONObject stacktrace = new JSONObject(); + stacktrace.put("frames", array); + return stacktrace; + } /** * Take the raw message body and get it ready for sending. Encode and compress it. @@ -98,11 +162,12 @@ private String buildMessageBody(String jsonMessage) { * @param loggerClass The class associated with the log message * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) * @param culprit Who we think caused the problem. + * @param exception exception causing the problem * @return Encode and compressed version of the JSON Message body */ - private String buildMessage(String message, String timestamp, String loggerClass, int logLevel, String culprit) { + private String buildMessage(String message, String timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { // get the json version of the body - String jsonMessage = buildJSON(message, timestamp, loggerClass, logLevel, culprit); + String jsonMessage = buildJSON(message, timestamp, loggerClass, logLevel, culprit, exception); // compress and encode the json message. return buildMessageBody(jsonMessage); @@ -183,11 +248,13 @@ private void sendMessage(String messageBody, long timestamp) { * @param loggerClass The class associated with the log message * @param logLevel int value for Log level for message (DEBUG, ERROR, INFO, etc.) * @param culprit Who we think caused the problem. + * @param exception exception that occurred */ - public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit) { + public void logMessage(String theLogMessage, long timestamp, String loggerClass, int logLevel, String culprit, Throwable exception) { String timestampDate = RavenUtils.getTimestampString(timestamp); - String message = buildMessage(theLogMessage, timestampDate, loggerClass, logLevel, culprit); + String message = buildMessage(theLogMessage, timestampDate, loggerClass, logLevel, culprit, exception); sendMessage(message, timestamp); } + } \ No newline at end of file diff --git a/src/net/kencochrane/sentry/RavenConfig.java b/src/main/java/net/kencochrane/sentry/RavenConfig.java similarity index 100% rename from src/net/kencochrane/sentry/RavenConfig.java rename to src/main/java/net/kencochrane/sentry/RavenConfig.java diff --git a/src/net/kencochrane/sentry/RavenUtils.java b/src/main/java/net/kencochrane/sentry/RavenUtils.java similarity index 94% rename from src/net/kencochrane/sentry/RavenUtils.java rename to src/main/java/net/kencochrane/sentry/RavenUtils.java index dc06b48515..049fdb5a97 100644 --- a/src/net/kencochrane/sentry/RavenUtils.java +++ b/src/main/java/net/kencochrane/sentry/RavenUtils.java @@ -1,5 +1,7 @@ package net.kencochrane.sentry; +import org.apache.commons.lang.time.DateFormatUtils; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayOutputStream; @@ -7,7 +9,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.security.SignatureException; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; import java.util.zip.CRC32; @@ -25,7 +26,6 @@ public class RavenUtils { private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - private static final SimpleDateFormat ISO8601FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); /** * Computes RFC 2104-compliant HMAC signature. @@ -83,10 +83,7 @@ public static String getHostname() { * @return ISO8601 formatted date as a String */ public static String getDateAsISO8601String(Date date) { - String result = ISO8601FORMAT.format(date); - result = result.substring(0, result.length() - 2) - + ":" + result.substring(result.length() - 2); - return result; + return DateFormatUtils.ISO_DATETIME_FORMAT.format(date); } /** @@ -95,8 +92,7 @@ public static String getDateAsISO8601String(Date date) { * @return timestamp for now as long */ public static long getTimestampLong() { - java.util.Date date = new java.util.Date(); - return date.getTime(); + return System.currentTimeMillis(); } /** diff --git a/src/net/kencochrane/sentry/SentryAppender.java b/src/main/java/net/kencochrane/sentry/SentryAppender.java similarity index 86% rename from src/net/kencochrane/sentry/SentryAppender.java rename to src/main/java/net/kencochrane/sentry/SentryAppender.java index 10dd3efc4a..a6c9184a44 100644 --- a/src/net/kencochrane/sentry/SentryAppender.java +++ b/src/main/java/net/kencochrane/sentry/SentryAppender.java @@ -2,6 +2,7 @@ import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; /** * User: ken cochrane @@ -52,7 +53,9 @@ protected void append(LoggingEvent loggingEvent) { RavenClient client = new RavenClient(getSentry_dsn(), getProxy()); // send the message to the sentry server - client.logMessage(logMessage, timestamp, loggingClass, logLevel, culprit); + ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation(); + Throwable throwable = (throwableInformation == null ? null : throwableInformation.getThrowable()); + client.logMessage(logMessage, timestamp, loggingClass, logLevel, culprit, throwable); } catch (Exception e) { System.err.println(e); diff --git a/src/test/java/net/kencochrane/sentry/SentryExample.java b/src/test/java/net/kencochrane/sentry/SentryExample.java new file mode 100644 index 0000000000..52839ad56b --- /dev/null +++ b/src/test/java/net/kencochrane/sentry/SentryExample.java @@ -0,0 +1,55 @@ +package net.kencochrane.sentry; + +/** + * User: ken cochrane + * Date: 2/6/12 + * Time: 11:35 AM + */ + +// Import log4j classes. + +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.junit.Test; + +/** + * Simple example used to test out the sentry logger. + */ +public class SentryExample { + + // Define a static logger variable so that it references the + // Logger instance named "MyApp". + static final Logger logger = Logger.getLogger(SentryExample.class); + + public void triggerRuntimeException() { + try { + triggerNullPointer(); + } catch (Exception e) { + throw new RuntimeException("Error triggering null pointer", e); + } + } + + public String triggerNullPointer() { + String c = null; + return c.toLowerCase(); + } + + @Test + public void test_simple() { + + // PropertyConfigurator. + PropertyConfigurator.configure(getClass().getResource("/log4j_configuration.txt")); + + logger.debug("Debug example"); + logger.error("Error example"); + logger.trace("Trace Example"); + logger.fatal("Fatal Example"); + logger.info("info Example"); + logger.warn("Warn Example"); + try { + triggerRuntimeException(); + } catch (RuntimeException e) { + logger.error("Error example with stacktrace", e); + } + } +} diff --git a/example/log4j_configuration.txt b/src/test/resources/log4j_configuration.txt similarity index 99% rename from example/log4j_configuration.txt rename to src/test/resources/log4j_configuration.txt index ab760c2587..7bba8f0e6c 100644 --- a/example/log4j_configuration.txt +++ b/src/test/resources/log4j_configuration.txt @@ -20,3 +20,4 @@ log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n log4j.appender.sentry=net.kencochrane.sentry.SentryAppender log4j.appender.sentry.sentry_dsn=http://b4935bdd78624092ac2bc70fdcdb6f5a:7a37d9ad4765428180316bfec91a27ef@localhost:8000/1 +