├── .gitignore ├── .travis.yml ├── Contributors.md ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── net │ │ └── logstash │ │ └── log4j │ │ ├── JSONEventLayoutV0.java │ │ ├── JSONEventLayoutV1.java │ │ └── data │ │ └── HostData.java └── resources │ ├── log4j.locinfo.properties.example │ ├── log4j.v0.properties.example │ └── log4j.v1.properties.example └── test └── java └── net └── logstash └── log4j ├── JSONEventLayoutV0Test.java ├── JSONEventLayoutV1Test.java ├── MockAppenderV0.java └── MockAppenderV1.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | target/* 3 | .idea 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk6 4 | - openjdk7 5 | -------------------------------------------------------------------------------- /Contributors.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | - @pyr (Pierre-Yves Ritschard) 4 | - @StFS (Stefán Freyr Stefánsson) 5 | - @looztra (Christophe Furmaniak) 6 | - @astrochoupe 7 | - @bfritz (Brad Fritz) 8 | - @vrivellino (Vincent R.) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012 John E. Vincent and contributors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logstash `json_event` pattern for log4j 2 | 3 | [![Build Status](https://travis-ci.org/lusis/log4j-jsonevent-layout.png)](https://travis-ci.org/lusis/log4j-jsonevent-layout) 4 | 5 | ## What is it? 6 | If you've used log4j, you know that certain appenders support things called _"Layouts"_. These are basically ways for you to control the formatting/rendering of a log event. 7 | 8 | There's a full list of formatting macros [here](http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html) if you're curious. 9 | 10 | The PatternLayout an organization uses is pretty much begging to be bikeshedded. This library is essentially a "preformatted" layout that just happens to be the exact format that Logstash uses for `json_event`. 11 | 12 | ## JSONEventLayout 13 | I recently came to a situation where I needed consider parsing some log4j-generated log events with Logstash. I had [another appender already](https://github.com/lusis/zmq-appender) but unfortunately the external dependency on ZeroMQ itself was a no go. So there were two options: 14 | 15 | - The experimental log4j input 16 | - file input 17 | 18 | The `log4j` input is actually pretty cool but needs to be refactored a little bit. It's based around log4j's SocketAppender which actually sends a serialized version of the LoggingEvent object over the wire. This has a few issues that are easily worked out but it's also experimental. I needed something a bit more stable. I also wanted to avoid any filtering if at all possible. 19 | 20 | Then I remembered the "hack" that would let you dump your [apache](http://cookbook.logstash.net/recipes/apache-json-logs/) or [nginx](http://blog.pkhamre.com/2012/08/23/logging-to-logstash-json-format-in-nginx/) logs in json_event format. 21 | 22 | I probably could have pulled this off using a complicated PatternLayout but decided I wanted a turnkey "solution" that did the work for you. That's what this library is. 23 | 24 | # Usage 25 | This is just a quick snippit of a `log4j.properties` file: 26 | 27 | ``` 28 | log4j.rootCategory=WARN, RollingLog 29 | log4j.appender.RollingLog=org.apache.log4j.DailyRollingFileAppender 30 | log4j.appender.RollingLog.Threshold=TRACE 31 | log4j.appender.RollingLog.File=api.log 32 | log4j.appender.RollingLog.DatePattern=.yyyy-MM-dd 33 | log4j.appender.RollingLog.layout=net.logstash.log4j.JSONEventLayoutV1 34 | ``` 35 | 36 | If you use this, your logfile will now have one line per event and it will look something like this: 37 | 38 | ```json 39 | { 40 | "mdc":{}, 41 | "line_number":"29", 42 | "class":"org.eclipse.jetty.examples.logging.EchoFormServlet", 43 | "@version":1, 44 | "source_host":"jvstratusmbp.local", 45 | "thread_name":"qtp513694835-14", 46 | "message":"Got request from 0:0:0:0:0:0:0:1%0 using Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/32.0.1700.77 Safari\/537.36", 47 | "@timestamp":"2014-01-27T19:52:35.738Z", 48 | "level":"INFO", 49 | "file":"EchoFormServlet.java", 50 | "method":"doPost", 51 | "logger_name":"org.eclipse.jetty.examples.logging.EchoFormServlet" 52 | } 53 | ``` 54 | 55 | If you point logstash to this file and set the format to `json`, you'll basically get the same thing in your output because no filtering was needed to get this data into the right places. 56 | 57 | Nothing really groundbreaking here. However you can now use this same prefab PatternLayout with ANY appender that supports layouts. If that appender matches up with a logstash input, you've now got flexibility in your transport with reduced filtering impact (since you don't need to parse the logs as much). In fact, if you want to use RabbitMQ, you could use this layout with [@jbrisbin's amqp-appender](https://github.com/jbrisbin/vcloud/tree/master/amqp-appender) or [Ryan Tenney's redis appender](https://github.com/ryantenney/log4j-redis-appender) 58 | 59 | # Exceptions 60 | 61 | If there is throwable information available in your event, it will be populated under the key `exception` like so: 62 | 63 | ```json 64 | { 65 | "mdc":{}, 66 | "exception":{ 67 | "exception_class":"java.lang.IllegalArgumentException", 68 | "exception_message":"Something broke!", 69 | "stacktrace":"java.lang.IllegalArgumentException: Something broke!\n\tat org.eclipse.jetty.examples.logging.EchoFormServlet.doPost(EchoFormServlet.java:64)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:755)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:848)\n\tat org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:684)\n\tat org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:501)\n\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:137)\n\tat org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:533)\n\tat org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:231)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1086)\n\tat org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:428)\n\tat org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193)\n\tat org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)\n\tat org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)\n\tat org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:255)\n\tat org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:154)\n\tat org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)\n\tat org.eclipse.jetty.server.Server.handle(Server.java:370)\n\tat org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:494)\n\tat org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:982)\n\tat org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:1043)\n\tat org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:865)\n\tat org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240)\n\tat org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)\n\tat org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:667)\n\tat org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)\n\tat org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)\n\tat java.lang.Thread.run(Thread.java:695)" 70 | }, 71 | "line_number":"64", 72 | "class":"org.eclipse.jetty.examples.logging.EchoFormServlet", 73 | "@version":1, 74 | "source_host":"jvstratusmbp.local", 75 | "message":"[exception] description = \"kaboom\"", 76 | "thread_name":"qtp1787577195-18", 77 | "@timestamp":"2014-01-27T20:11:36.006Z", 78 | "level":"FATAL", 79 | "file":"EchoFormServlet.java", 80 | "method":"doPost", 81 | "logger_name":"org.eclipse.jetty.examples.logging.EchoFormServlet" 82 | } 83 | ``` 84 | 85 | Easy access to the exception class and exception message let's you work with those....easier. 86 | 87 | # Sample XML configuration 88 | If you use the XML format for your log4j configuration (and there are valid reasons thanks to AsyncAppender - fml), changing your layout class for your appender would look like this 89 | 90 | ## Old 91 | ```xml 92 | 93 | 94 | 95 | 96 | 97 | 98 | ``` 99 | 100 | ## New 101 | ```xml 102 | 103 | 104 | 105 | 106 | ``` 107 | 108 | Any appender that supports defining the layout can use this. 109 | 110 | # V0/V1/Vnothing 111 | Originally the layout class was called `JSONEventLayout`. This was originally written back when there was no versioned event format for logstash. As of Logstash 1.2 and forward, the event format is now versioned. The current version is `1` and defined the following required fields: 112 | 113 | - `@version` 114 | - `@timestamp` (optional - will be inferred from event receipt time 115 | 116 | Because of this, when adding support for the new format, `JSONEventLayoutV1` was used to allow backwards compatibility. As of `1.6` of the jsonevent-layout library, we've now gone to fully versioned appenders. There is no longer a `JSONEventLayout`. Instead there is: 117 | 118 | - `JSONEventLayoutV0` 119 | - `JSONEventLayoutV1` 120 | 121 | Work has stopped on V0 but it won't be removed. No new features are added to V0 (custom UserFields for instance). 122 | 123 | # Custom User Fields 124 | As of version 1.6, you can now add your own metadata to the event in the form of comma-separated key:value pairs. This can be set in either the log4jconfig OR set on the java command-line: 125 | 126 | ## log4j config 127 | ```xml 128 | 129 | 130 | 131 | ``` 132 | 133 | or 134 | 135 | ``` 136 | log4j.appender.RollingLog.layout=net.logstash.log4j.JSONEventLayoutV1 137 | log4j.appender.RollingLog.layout.UserFields=foo:bar,baz:qux 138 | ``` 139 | 140 | ## Command-line 141 | *Note that the command-line version will OVERRIDE any values specified in the config file should there be a key conflict!* 142 | 143 | `java -Dnet.logstash.log4j.JSONEventLayoutV1.UserFields="field3:prop3,field4:prop4" -jar .....` 144 | 145 | A warning will be logged should you attempt to set values in both places. 146 | 147 | # Pull Requests 148 | Pull requests are welcome for any and all things - documentation, bug fixes...whatever. 149 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | net.logstash.log4j 4 | jsonevent-layout 5 | jar 6 | 1.8-SNAPSHOT 7 | jsonevent-layout 8 | Log4j pattern layout that conforms to the logstash json_event format 9 | http://logstash.net 10 | 11 | 12 | The Apache Software License, Version 2.0 13 | http://www.apache.org/licenses/LICENSE-2.0.txt 14 | repo 15 | 16 | 17 | 18 | 19 | lusis 20 | John E. Vincent 21 | lusis.org+github.com@gmail.com 22 | 23 | 24 | pyr 25 | Pierre-Yves Ritschard 26 | pyr@spootnik.org 27 | 28 | 29 | 30 | UTF-8 31 | UTF-8 32 | 33 | 34 | org.sonatype.oss 35 | oss-parent 36 | 7 37 | 38 | 39 | scm:git:git@github.com:git@github.com:logstash/log4j-jsonevent-layout.git 40 | scm:git:git@github.com:logstash/log4j-jsonevent-layout.git 41 | git@github.com:logstash/log4j-jsonevent-layout.git 42 | 43 | 44 | 45 | 46 | 47 | maven-assembly-plugin 48 | 2.2.2 49 | 50 | 51 | uberjar 52 | package 53 | 54 | single 55 | 56 | 57 | 58 | 59 | 60 | jar-with-dependencies 61 | 62 | 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-compiler-plugin 67 | 2.3.2 68 | 69 | 1.5 70 | 1.5 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-source-plugin 76 | 77 | 78 | attach-sources 79 | 80 | jar 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-javadoc-plugin 88 | 89 | 90 | attach-javadocs 91 | 92 | jar 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | net.minidev 103 | json-smart 104 | 1.1.1 105 | 106 | 107 | log4j 108 | log4j 109 | 1.2.16 110 | provided 111 | 112 | 113 | commons-lang 114 | commons-lang 115 | 2.6 116 | 117 | 118 | junit 119 | junit 120 | 4.8.1 121 | test 122 | 123 | 124 | 125 | 126 | bundle 127 | 128 | 129 | 130 | org.apache.felix 131 | maven-bundle-plugin 132 | 2.3.7 133 | true 134 | 135 | 136 | ${project.groupId}.${project.artifactId} 137 | ${project.groupId}.${project.artifactId} 138 | !* 139 | org.ops4j.pax.logging.pax-logging-service;bundle-version="[1.6,1.7)" 140 | *;scope=compile|runtime;inline=true 141 | ${project.version} 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | release-sign-artifacts 150 | 151 | 152 | performRelease 153 | true 154 | 155 | 156 | 157 | 158 | 159 | org.apache.maven.plugins 160 | maven-gpg-plugin 161 | 162 | 163 | sign-artifacts 164 | verify 165 | 166 | sign 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | ossrh 178 | https://oss.sonatype.org/content/repositories/snapshots 179 | 180 | 181 | ossrh 182 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/main/java/net/logstash/log4j/JSONEventLayoutV0.java: -------------------------------------------------------------------------------- 1 | package net.logstash.log4j; 2 | 3 | import net.logstash.log4j.data.HostData; 4 | import net.minidev.json.JSONObject; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.apache.commons.lang.time.FastDateFormat; 7 | import org.apache.log4j.Layout; 8 | import org.apache.log4j.spi.LocationInfo; 9 | import org.apache.log4j.spi.LoggingEvent; 10 | import org.apache.log4j.spi.ThrowableInformation; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.TimeZone; 15 | 16 | public class JSONEventLayoutV0 extends Layout { 17 | 18 | private boolean locationInfo = false; 19 | 20 | private String tags; 21 | private boolean ignoreThrowable = false; 22 | 23 | private boolean activeIgnoreThrowable = ignoreThrowable; 24 | private String hostname = new HostData().getHostName(); 25 | private String threadName; 26 | private long timestamp; 27 | private String ndc; 28 | private Map mdc; 29 | private LocationInfo info; 30 | private HashMap fieldData; 31 | private HashMap exceptionInformation; 32 | 33 | private JSONObject logstashEvent; 34 | 35 | public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); 36 | public static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", UTC); 37 | 38 | public static String dateFormat(long timestamp) { 39 | return ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(timestamp); 40 | } 41 | 42 | /** 43 | * For backwards compatibility, the default is to generate location information 44 | * in the log messages. 45 | */ 46 | public JSONEventLayoutV0() { 47 | this(true); 48 | } 49 | 50 | /** 51 | * Creates a layout that optionally inserts location information into log messages. 52 | * 53 | * @param locationInfo whether or not to include location information in the log messages. 54 | */ 55 | public JSONEventLayoutV0(boolean locationInfo) { 56 | this.locationInfo = locationInfo; 57 | } 58 | 59 | public String format(LoggingEvent loggingEvent) { 60 | threadName = loggingEvent.getThreadName(); 61 | timestamp = loggingEvent.getTimeStamp(); 62 | fieldData = new HashMap(); 63 | exceptionInformation = new HashMap(); 64 | mdc = loggingEvent.getProperties(); 65 | ndc = loggingEvent.getNDC(); 66 | 67 | logstashEvent = new JSONObject(); 68 | 69 | logstashEvent.put("@source_host", hostname); 70 | logstashEvent.put("@message", loggingEvent.getRenderedMessage()); 71 | logstashEvent.put("@timestamp", dateFormat(timestamp)); 72 | 73 | if (loggingEvent.getThrowableInformation() != null) { 74 | final ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation(); 75 | if (throwableInformation.getThrowable().getClass().getCanonicalName() != null) { 76 | exceptionInformation.put("exception_class", throwableInformation.getThrowable().getClass().getCanonicalName()); 77 | } 78 | if (throwableInformation.getThrowable().getMessage() != null) { 79 | exceptionInformation.put("exception_message", throwableInformation.getThrowable().getMessage()); 80 | } 81 | if (throwableInformation.getThrowableStrRep() != null) { 82 | String stackTrace = StringUtils.join(throwableInformation.getThrowableStrRep(), "\n"); 83 | exceptionInformation.put("stacktrace", stackTrace); 84 | } 85 | addFieldData("exception", exceptionInformation); 86 | } 87 | 88 | if (locationInfo) { 89 | info = loggingEvent.getLocationInformation(); 90 | addFieldData("file", info.getFileName()); 91 | addFieldData("line_number", info.getLineNumber()); 92 | addFieldData("class", info.getClassName()); 93 | addFieldData("method", info.getMethodName()); 94 | } 95 | 96 | addFieldData("loggerName", loggingEvent.getLoggerName()); 97 | addFieldData("mdc", mdc); 98 | addFieldData("ndc", ndc); 99 | addFieldData("level", loggingEvent.getLevel().toString()); 100 | addFieldData("threadName", threadName); 101 | 102 | logstashEvent.put("@fields", fieldData); 103 | return logstashEvent.toString() + "\n"; 104 | } 105 | 106 | public boolean ignoresThrowable() { 107 | return ignoreThrowable; 108 | } 109 | 110 | /** 111 | * Query whether log messages include location information. 112 | * 113 | * @return true if location information is included in log messages, false otherwise. 114 | */ 115 | public boolean getLocationInfo() { 116 | return locationInfo; 117 | } 118 | 119 | /** 120 | * Set whether log messages should include location information. 121 | * 122 | * @param locationInfo true if location information should be included, false otherwise. 123 | */ 124 | public void setLocationInfo(boolean locationInfo) { 125 | this.locationInfo = locationInfo; 126 | } 127 | 128 | public void activateOptions() { 129 | activeIgnoreThrowable = ignoreThrowable; 130 | } 131 | 132 | private void addFieldData(String keyname, Object keyval) { 133 | if (null != keyval) { 134 | fieldData.put(keyname, keyval); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/net/logstash/log4j/JSONEventLayoutV1.java: -------------------------------------------------------------------------------- 1 | package net.logstash.log4j; 2 | 3 | import net.logstash.log4j.data.HostData; 4 | import net.minidev.json.JSONObject; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.apache.commons.lang.time.FastDateFormat; 7 | import org.apache.log4j.Layout; 8 | import org.apache.log4j.helpers.LogLog; 9 | import org.apache.log4j.spi.LocationInfo; 10 | import org.apache.log4j.spi.LoggingEvent; 11 | import org.apache.log4j.spi.ThrowableInformation; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.TimeZone; 16 | 17 | public class JSONEventLayoutV1 extends Layout { 18 | 19 | private boolean locationInfo = false; 20 | private String customUserFields; 21 | 22 | private boolean ignoreThrowable = false; 23 | 24 | private boolean activeIgnoreThrowable = ignoreThrowable; 25 | private String hostname = new HostData().getHostName(); 26 | private String threadName; 27 | private long timestamp; 28 | private String ndc; 29 | private Map mdc; 30 | private LocationInfo info; 31 | private HashMap exceptionInformation; 32 | private static Integer version = 1; 33 | 34 | 35 | private JSONObject logstashEvent; 36 | 37 | public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); 38 | public static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", UTC); 39 | public static final String ADDITIONAL_DATA_PROPERTY = "net.logstash.log4j.JSONEventLayoutV1.UserFields"; 40 | 41 | public static String dateFormat(long timestamp) { 42 | return ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS.format(timestamp); 43 | } 44 | 45 | /** 46 | * For backwards compatibility, the default is to generate location information 47 | * in the log messages. 48 | */ 49 | public JSONEventLayoutV1() { 50 | this(true); 51 | } 52 | 53 | /** 54 | * Creates a layout that optionally inserts location information into log messages. 55 | * 56 | * @param locationInfo whether or not to include location information in the log messages. 57 | */ 58 | public JSONEventLayoutV1(boolean locationInfo) { 59 | this.locationInfo = locationInfo; 60 | } 61 | 62 | public String format(LoggingEvent loggingEvent) { 63 | threadName = loggingEvent.getThreadName(); 64 | timestamp = loggingEvent.getTimeStamp(); 65 | exceptionInformation = new HashMap(); 66 | mdc = loggingEvent.getProperties(); 67 | ndc = loggingEvent.getNDC(); 68 | 69 | logstashEvent = new JSONObject(); 70 | String whoami = this.getClass().getSimpleName(); 71 | 72 | /** 73 | * All v1 of the event format requires is 74 | * "@timestamp" and "@version" 75 | * Every other field is arbitrary 76 | */ 77 | logstashEvent.put("@version", version); 78 | logstashEvent.put("@timestamp", dateFormat(timestamp)); 79 | 80 | /** 81 | * Extract and add fields from log4j config, if defined 82 | */ 83 | if (getUserFields() != null) { 84 | String userFlds = getUserFields(); 85 | LogLog.debug("["+whoami+"] Got user data from log4j property: "+ userFlds); 86 | addUserFields(userFlds); 87 | } 88 | 89 | /** 90 | * Extract fields from system properties, if defined 91 | * Note that CLI props will override conflicts with log4j config 92 | */ 93 | if (System.getProperty(ADDITIONAL_DATA_PROPERTY) != null) { 94 | if (getUserFields() != null) { 95 | LogLog.warn("["+whoami+"] Loading UserFields from command-line. This will override any UserFields set in the log4j configuration file"); 96 | } 97 | String userFieldsProperty = System.getProperty(ADDITIONAL_DATA_PROPERTY); 98 | LogLog.debug("["+whoami+"] Got user data from system property: " + userFieldsProperty); 99 | addUserFields(userFieldsProperty); 100 | } 101 | 102 | /** 103 | * Now we start injecting our own stuff. 104 | */ 105 | logstashEvent.put("source_host", hostname); 106 | logstashEvent.put("message", loggingEvent.getRenderedMessage()); 107 | 108 | if (loggingEvent.getThrowableInformation() != null) { 109 | final ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation(); 110 | if (throwableInformation.getThrowable().getClass().getCanonicalName() != null) { 111 | exceptionInformation.put("exception_class", throwableInformation.getThrowable().getClass().getCanonicalName()); 112 | } 113 | if (throwableInformation.getThrowable().getMessage() != null) { 114 | exceptionInformation.put("exception_message", throwableInformation.getThrowable().getMessage()); 115 | } 116 | if (throwableInformation.getThrowableStrRep() != null) { 117 | String stackTrace = StringUtils.join(throwableInformation.getThrowableStrRep(), "\n"); 118 | exceptionInformation.put("stacktrace", stackTrace); 119 | } 120 | addEventData("exception", exceptionInformation); 121 | } 122 | 123 | if (locationInfo) { 124 | info = loggingEvent.getLocationInformation(); 125 | addEventData("file", info.getFileName()); 126 | addEventData("line_number", info.getLineNumber()); 127 | addEventData("class", info.getClassName()); 128 | addEventData("method", info.getMethodName()); 129 | } 130 | 131 | addEventData("logger_name", loggingEvent.getLoggerName()); 132 | addEventData("mdc", mdc); 133 | addEventData("ndc", ndc); 134 | addEventData("level", loggingEvent.getLevel().toString()); 135 | addEventData("thread_name", threadName); 136 | 137 | return logstashEvent.toString() + "\n"; 138 | } 139 | 140 | public boolean ignoresThrowable() { 141 | return ignoreThrowable; 142 | } 143 | 144 | /** 145 | * Query whether log messages include location information. 146 | * 147 | * @return true if location information is included in log messages, false otherwise. 148 | */ 149 | public boolean getLocationInfo() { 150 | return locationInfo; 151 | } 152 | 153 | /** 154 | * Set whether log messages should include location information. 155 | * 156 | * @param locationInfo true if location information should be included, false otherwise. 157 | */ 158 | public void setLocationInfo(boolean locationInfo) { 159 | this.locationInfo = locationInfo; 160 | } 161 | 162 | public String getUserFields() { return customUserFields; } 163 | public void setUserFields(String userFields) { this.customUserFields = userFields; } 164 | 165 | public void activateOptions() { 166 | activeIgnoreThrowable = ignoreThrowable; 167 | } 168 | 169 | private void addUserFields(String data) { 170 | if (null != data) { 171 | String[] pairs = data.split(","); 172 | for (String pair : pairs) { 173 | String[] userField = pair.split(":", 2); 174 | if (userField[0] != null) { 175 | String key = userField[0]; 176 | String val = userField[1]; 177 | addEventData(key, val); 178 | } 179 | } 180 | } 181 | } 182 | private void addEventData(String keyname, Object keyval) { 183 | if (null != keyval) { 184 | logstashEvent.put(keyname, keyval); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/net/logstash/log4j/data/HostData.java: -------------------------------------------------------------------------------- 1 | package net.logstash.log4j.data; 2 | 3 | import java.net.UnknownHostException; 4 | 5 | public class HostData { 6 | 7 | public String hostName; 8 | 9 | public String getHostName() { 10 | return hostName; 11 | } 12 | 13 | public void setHostName(String hostName) { 14 | this.hostName = hostName; 15 | } 16 | 17 | public HostData() { 18 | try { 19 | this.hostName = java.net.InetAddress.getLocalHost().getHostName(); 20 | } catch (UnknownHostException e) { 21 | setHostName("unknown-host"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/resources/log4j.locinfo.properties.example: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=TRACE, stdout 2 | 3 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.Threshold=TRACE 5 | log4j.appender.stdout.layout=net.logstash.log4j.JSONEventLayout 6 | log4j.appender.stdout.layout.locationInfo=true 7 | -------------------------------------------------------------------------------- /src/main/resources/log4j.v0.properties.example: -------------------------------------------------------------------------------- 1 | log4j.RootCategory=TRACE, Console 2 | 3 | log4j.appender.Console=org.apache.log4j.ConsoleAppender 4 | log4j.appender.Console.Threshold=TRACE 5 | log4j.appender.Console.layout=net.logstash.log4j.JSONEventLayout 6 | -------------------------------------------------------------------------------- /src/main/resources/log4j.v1.properties.example: -------------------------------------------------------------------------------- 1 | log4j.RootCategory=TRACE, Console 2 | 3 | log4j.appender.Console=org.apache.log4j.ConsoleAppender 4 | log4j.appender.Console.Threshold=TRACE 5 | log4j.appender.Console.layout=net.logstash.log4j.JSONEventLayoutV1 6 | -------------------------------------------------------------------------------- /src/test/java/net/logstash/log4j/JSONEventLayoutV0Test.java: -------------------------------------------------------------------------------- 1 | package net.logstash.log4j; 2 | 3 | import junit.framework.Assert; 4 | import net.minidev.json.JSONObject; 5 | import net.minidev.json.JSONValue; 6 | import org.apache.log4j.Level; 7 | import org.apache.log4j.Logger; 8 | import org.apache.log4j.NDC; 9 | import org.apache.log4j.MDC; 10 | import org.junit.After; 11 | import org.junit.BeforeClass; 12 | import org.junit.Ignore; 13 | import org.junit.Test; 14 | 15 | /** 16 | * Created with IntelliJ IDEA. 17 | * User: jvincent 18 | * Date: 12/5/12 19 | * Time: 12:07 AM 20 | * To change this template use File | Settings | File Templates. 21 | */ 22 | public class JSONEventLayoutV0Test { 23 | static Logger logger; 24 | static MockAppenderV0 appender; 25 | static final String[] logstashFields = new String[]{ 26 | "@message", 27 | "@source_host", 28 | "@fields", 29 | "@timestamp" 30 | }; 31 | 32 | @BeforeClass 33 | public static void setupTestAppender() { 34 | appender = new MockAppenderV0(new JSONEventLayoutV0()); 35 | logger = Logger.getRootLogger(); 36 | appender.setThreshold(Level.TRACE); 37 | appender.setName("mockappender"); 38 | appender.activateOptions(); 39 | logger.addAppender(appender); 40 | } 41 | 42 | @After 43 | public void clearTestAppender() { 44 | NDC.clear(); 45 | appender.clear(); 46 | appender.close(); 47 | } 48 | 49 | @Test 50 | public void testJSONEventLayoutIsJSON() { 51 | logger.info("this is an info message"); 52 | String message = appender.getMessages()[0]; 53 | Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message)); 54 | } 55 | 56 | @Test 57 | public void testJSONEventLayoutHasKeys() { 58 | logger.info("this is a test message"); 59 | String message = appender.getMessages()[0]; 60 | Object obj = JSONValue.parse(message); 61 | JSONObject jsonObject = (JSONObject) obj; 62 | 63 | for (String fieldName : logstashFields) { 64 | Assert.assertTrue("Event does not contain field: " + fieldName, jsonObject.containsKey(fieldName)); 65 | } 66 | } 67 | 68 | @Test 69 | public void testJSONEventLayoutHasFieldLevel() { 70 | logger.fatal("this is a new test message"); 71 | String message = appender.getMessages()[0]; 72 | Object obj = JSONValue.parse(message); 73 | JSONObject jsonObject = (JSONObject) obj; 74 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 75 | 76 | Assert.assertEquals("Log level is wrong", "FATAL", atFields.get("level")); 77 | } 78 | 79 | @Test 80 | public void testJSONEventLayoutHasNDC() { 81 | String ndcData = new String("json-layout-test"); 82 | NDC.push(ndcData); 83 | logger.warn("I should have NDC data in my log"); 84 | String message = appender.getMessages()[0]; 85 | Object obj = JSONValue.parse(message); 86 | JSONObject jsonObject = (JSONObject) obj; 87 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 88 | 89 | Assert.assertEquals("NDC is wrong", ndcData, atFields.get("ndc")); 90 | } 91 | 92 | @Test 93 | public void testJSONEventLayoutHasMDC() { 94 | MDC.put("foo","bar"); 95 | logger.warn("I should have MDC data in my log"); 96 | String message = appender.getMessages()[0]; 97 | Object obj = JSONValue.parse(message); 98 | JSONObject jsonObject = (JSONObject) obj; 99 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 100 | JSONObject mdcData = (JSONObject) atFields.get("mdc"); 101 | 102 | Assert.assertEquals("MDC is wrong","bar", mdcData.get("foo")); 103 | } 104 | 105 | @Test 106 | public void testJSONEventLayoutExceptions() { 107 | String exceptionMessage = new String("shits on fire, yo"); 108 | logger.fatal("uh-oh", new IllegalArgumentException(exceptionMessage)); 109 | String message = appender.getMessages()[0]; 110 | Object obj = JSONValue.parse(message); 111 | JSONObject jsonObject = (JSONObject) obj; 112 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 113 | JSONObject exceptionInformation = (JSONObject) atFields.get("exception"); 114 | 115 | Assert.assertEquals("Exception class missing", "java.lang.IllegalArgumentException", exceptionInformation.get("exception_class")); 116 | Assert.assertEquals("Exception exception message", exceptionMessage, exceptionInformation.get("exception_message")); 117 | } 118 | 119 | @Test 120 | public void testJSONEventLayoutHasClassName() { 121 | logger.warn("warning dawg"); 122 | String message = appender.getMessages()[0]; 123 | Object obj = JSONValue.parse(message); 124 | JSONObject jsonObject = (JSONObject) obj; 125 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 126 | 127 | Assert.assertEquals("Logged class does not match", this.getClass().getCanonicalName().toString(), atFields.get("class")); 128 | } 129 | 130 | @Test 131 | public void testJSONEventHasFileName() { 132 | logger.warn("whoami"); 133 | String message = appender.getMessages()[0]; 134 | Object obj = JSONValue.parse(message); 135 | JSONObject jsonObject = (JSONObject) obj; 136 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 137 | 138 | Assert.assertNotNull("File value is missing", atFields.get("file")); 139 | } 140 | 141 | @Test 142 | public void testJSONEventHasLoggerName() { 143 | logger.warn("whoami"); 144 | String message = appender.getMessages()[0]; 145 | Object obj = JSONValue.parse(message); 146 | JSONObject jsonObject = (JSONObject) obj; 147 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 148 | Assert.assertNotNull("LoggerName value is missing", atFields.get("loggerName")); 149 | } 150 | 151 | @Test 152 | public void testJSONEventHasThreadName() { 153 | logger.warn("whoami"); 154 | String message = appender.getMessages()[0]; 155 | Object obj = JSONValue.parse(message); 156 | JSONObject jsonObject = (JSONObject) obj; 157 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 158 | Assert.assertNotNull("ThreadName value is missing", atFields.get("threadName")); 159 | } 160 | 161 | @Test 162 | public void testJSONEventLayoutNoLocationInfo() { 163 | JSONEventLayoutV0 layout = (JSONEventLayoutV0) appender.getLayout(); 164 | boolean prevLocationInfo = layout.getLocationInfo(); 165 | 166 | layout.setLocationInfo(false); 167 | 168 | logger.warn("warning dawg"); 169 | String message = appender.getMessages()[0]; 170 | Object obj = JSONValue.parse(message); 171 | JSONObject jsonObject = (JSONObject) obj; 172 | JSONObject atFields = (JSONObject) jsonObject.get("@fields"); 173 | 174 | Assert.assertFalse("atFields contains file value", atFields.containsKey("file")); 175 | Assert.assertFalse("atFields contains line_number value", atFields.containsKey("line_number")); 176 | Assert.assertFalse("atFields contains class value", atFields.containsKey("class")); 177 | Assert.assertFalse("atFields contains method value", atFields.containsKey("method")); 178 | 179 | // Revert the change to the layout to leave it as we found it. 180 | layout.setLocationInfo(prevLocationInfo); 181 | } 182 | 183 | @Test 184 | @Ignore 185 | public void measureJSONEventLayoutLocationInfoPerformance() { 186 | JSONEventLayoutV0 layout = (JSONEventLayoutV0) appender.getLayout(); 187 | boolean locationInfo = layout.getLocationInfo(); 188 | int iterations = 100000; 189 | long start, stop; 190 | 191 | start = System.currentTimeMillis(); 192 | for (int i = 0; i < iterations; i++) { 193 | logger.warn("warning dawg"); 194 | } 195 | stop = System.currentTimeMillis(); 196 | long firstMeasurement = stop - start; 197 | 198 | layout.setLocationInfo(!locationInfo); 199 | start = System.currentTimeMillis(); 200 | for (int i = 0; i < iterations; i++) { 201 | logger.warn("warning dawg"); 202 | } 203 | stop = System.currentTimeMillis(); 204 | long secondMeasurement = stop - start; 205 | 206 | System.out.println("First Measurement (locationInfo: " + locationInfo + "): " + firstMeasurement); 207 | System.out.println("Second Measurement (locationInfo: " + !locationInfo + "): " + secondMeasurement); 208 | 209 | // Clean up 210 | layout.setLocationInfo(!locationInfo); 211 | } 212 | 213 | @Test 214 | public void testDateFormat() { 215 | long timestamp = 1364844991207L; 216 | Assert.assertEquals("format does not produce expected output", "2013-04-01T19:36:31.207Z", JSONEventLayoutV0.dateFormat(timestamp)); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/test/java/net/logstash/log4j/JSONEventLayoutV1Test.java: -------------------------------------------------------------------------------- 1 | package net.logstash.log4j; 2 | 3 | import junit.framework.Assert; 4 | import net.minidev.json.JSONObject; 5 | import net.minidev.json.JSONValue; 6 | import org.apache.log4j.*; 7 | import org.apache.log4j.or.ObjectRenderer; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.AfterClass; 11 | import org.junit.BeforeClass; 12 | import org.junit.Ignore; 13 | import org.junit.Test; 14 | 15 | import java.util.HashMap; 16 | 17 | /** 18 | * Created with IntelliJ IDEA. 19 | * User: jvincent 20 | * Date: 12/5/12 21 | * Time: 12:07 AM 22 | * To change this template use File | Settings | File Templates. 23 | */ 24 | public class JSONEventLayoutV1Test { 25 | static Logger logger; 26 | static MockAppenderV1 appender; 27 | static MockAppenderV1 userFieldsAppender; 28 | static JSONEventLayoutV1 userFieldsLayout; 29 | static final String userFieldsSingle = new String("field1:value1"); 30 | static final String userFieldsMulti = new String("field2:value2,field3:value3"); 31 | static final String userFieldsSingleProperty = new String("field1:propval1"); 32 | 33 | static final String[] logstashFields = new String[]{ 34 | "message", 35 | "source_host", 36 | "@timestamp", 37 | "@version" 38 | }; 39 | 40 | @BeforeClass 41 | public static void setupTestAppender() { 42 | appender = new MockAppenderV1(new JSONEventLayoutV1()); 43 | logger = Logger.getRootLogger(); 44 | appender.setThreshold(Level.TRACE); 45 | appender.setName("mockappenderv1"); 46 | appender.activateOptions(); 47 | logger.addAppender(appender); 48 | } 49 | 50 | @After 51 | public void clearTestAppender() { 52 | NDC.clear(); 53 | appender.clear(); 54 | appender.close(); 55 | } 56 | 57 | @Test 58 | public void testJSONEventLayoutIsJSON() { 59 | logger.info("this is an info message"); 60 | String message = appender.getMessages()[0]; 61 | Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message)); 62 | } 63 | 64 | @Test 65 | public void testJSONEventLayoutHasUserFieldsFromProps() { 66 | System.setProperty(JSONEventLayoutV1.ADDITIONAL_DATA_PROPERTY, userFieldsSingleProperty); 67 | logger.info("this is an info message with user fields"); 68 | String message = appender.getMessages()[0]; 69 | Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message)); 70 | Object obj = JSONValue.parse(message); 71 | JSONObject jsonObject = (JSONObject) obj; 72 | Assert.assertTrue("Event does not contain field 'field1'" , jsonObject.containsKey("field1")); 73 | Assert.assertEquals("Event does not contain value 'value1'", "propval1", jsonObject.get("field1")); 74 | System.clearProperty(JSONEventLayoutV1.ADDITIONAL_DATA_PROPERTY); 75 | } 76 | 77 | @Test 78 | public void testJSONEventLayoutHasUserFieldsFromConfig() { 79 | JSONEventLayoutV1 layout = (JSONEventLayoutV1) appender.getLayout(); 80 | String prevUserData = layout.getUserFields(); 81 | layout.setUserFields(userFieldsSingle); 82 | 83 | logger.info("this is an info message with user fields"); 84 | String message = appender.getMessages()[0]; 85 | Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message)); 86 | Object obj = JSONValue.parse(message); 87 | JSONObject jsonObject = (JSONObject) obj; 88 | Assert.assertTrue("Event does not contain field 'field1'" , jsonObject.containsKey("field1")); 89 | Assert.assertEquals("Event does not contain value 'value1'", "value1", jsonObject.get("field1")); 90 | 91 | layout.setUserFields(prevUserData); 92 | } 93 | 94 | @Test 95 | public void testJSONEventLayoutUserFieldsMulti() { 96 | JSONEventLayoutV1 layout = (JSONEventLayoutV1) appender.getLayout(); 97 | String prevUserData = layout.getUserFields(); 98 | layout.setUserFields(userFieldsMulti); 99 | 100 | logger.info("this is an info message with user fields"); 101 | String message = appender.getMessages()[0]; 102 | Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message)); 103 | Object obj = JSONValue.parse(message); 104 | JSONObject jsonObject = (JSONObject) obj; 105 | Assert.assertTrue("Event does not contain field 'field2'" , jsonObject.containsKey("field2")); 106 | Assert.assertEquals("Event does not contain value 'value2'", "value2", jsonObject.get("field2")); 107 | Assert.assertTrue("Event does not contain field 'field3'" , jsonObject.containsKey("field3")); 108 | Assert.assertEquals("Event does not contain value 'value3'", "value3", jsonObject.get("field3")); 109 | 110 | layout.setUserFields(prevUserData); 111 | } 112 | 113 | @Test 114 | public void testJSONEventLayoutUserFieldsPropOverride() { 115 | // set the property first 116 | System.setProperty(JSONEventLayoutV1.ADDITIONAL_DATA_PROPERTY, userFieldsSingleProperty); 117 | 118 | // set the config values 119 | JSONEventLayoutV1 layout = (JSONEventLayoutV1) appender.getLayout(); 120 | String prevUserData = layout.getUserFields(); 121 | layout.setUserFields(userFieldsSingle); 122 | 123 | logger.info("this is an info message with user fields"); 124 | String message = appender.getMessages()[0]; 125 | Assert.assertTrue("Event is not valid JSON", JSONValue.isValidJsonStrict(message)); 126 | Object obj = JSONValue.parse(message); 127 | JSONObject jsonObject = (JSONObject) obj; 128 | Assert.assertTrue("Event does not contain field 'field1'" , jsonObject.containsKey("field1")); 129 | Assert.assertEquals("Event does not contain value 'propval1'", "propval1", jsonObject.get("field1")); 130 | 131 | layout.setUserFields(prevUserData); 132 | System.clearProperty(JSONEventLayoutV1.ADDITIONAL_DATA_PROPERTY); 133 | 134 | } 135 | 136 | @Test 137 | public void testJSONEventLayoutHasKeys() { 138 | logger.info("this is a test message"); 139 | String message = appender.getMessages()[0]; 140 | Object obj = JSONValue.parse(message); 141 | JSONObject jsonObject = (JSONObject) obj; 142 | for (String fieldName : logstashFields) { 143 | Assert.assertTrue("Event does not contain field: " + fieldName, jsonObject.containsKey(fieldName)); 144 | } 145 | } 146 | 147 | @Test 148 | public void testJSONEventLayoutHasNDC() { 149 | String ndcData = new String("json-layout-test"); 150 | NDC.push(ndcData); 151 | logger.warn("I should have NDC data in my log"); 152 | String message = appender.getMessages()[0]; 153 | Object obj = JSONValue.parse(message); 154 | JSONObject jsonObject = (JSONObject) obj; 155 | 156 | Assert.assertEquals("NDC is wrong", ndcData, jsonObject.get("ndc")); 157 | } 158 | 159 | @Test 160 | public void testJSONEventLayoutHasMDC() { 161 | MDC.put("foo", "bar"); 162 | logger.warn("I should have MDC data in my log"); 163 | String message = appender.getMessages()[0]; 164 | Object obj = JSONValue.parse(message); 165 | JSONObject jsonObject = (JSONObject) obj; 166 | JSONObject mdc = (JSONObject) jsonObject.get("mdc"); 167 | 168 | Assert.assertEquals("MDC is wrong","bar", mdc.get("foo")); 169 | } 170 | 171 | @Test 172 | public void testJSONEventLayoutHasNestedMDC() { 173 | HashMap nestedMdc = new HashMap(); 174 | nestedMdc.put("bar","baz"); 175 | MDC.put("foo",nestedMdc); 176 | logger.warn("I should have nested MDC data in my log"); 177 | String message = appender.getMessages()[0]; 178 | Object obj = JSONValue.parse(message); 179 | JSONObject jsonObject = (JSONObject) obj; 180 | JSONObject mdc = (JSONObject) jsonObject.get("mdc"); 181 | JSONObject nested = (JSONObject) mdc.get("foo"); 182 | 183 | Assert.assertTrue("Event is missing foo key", mdc.containsKey("foo")); 184 | Assert.assertEquals("Nested MDC data is wrong", "baz", nested.get("bar")); 185 | } 186 | 187 | @Test 188 | public void testJSONEventLayoutExceptions() { 189 | String exceptionMessage = new String("shits on fire, yo"); 190 | logger.fatal("uh-oh", new IllegalArgumentException(exceptionMessage)); 191 | String message = appender.getMessages()[0]; 192 | Object obj = JSONValue.parse(message); 193 | JSONObject jsonObject = (JSONObject) obj; 194 | JSONObject exceptionInformation = (JSONObject) jsonObject.get("exception"); 195 | 196 | Assert.assertEquals("Exception class missing", "java.lang.IllegalArgumentException", exceptionInformation.get("exception_class")); 197 | Assert.assertEquals("Exception exception message", exceptionMessage, exceptionInformation.get("exception_message")); 198 | } 199 | 200 | @Test 201 | public void testJSONEventLayoutHasClassName() { 202 | logger.warn("warning dawg"); 203 | String message = appender.getMessages()[0]; 204 | Object obj = JSONValue.parse(message); 205 | JSONObject jsonObject = (JSONObject) obj; 206 | 207 | Assert.assertEquals("Logged class does not match", this.getClass().getCanonicalName().toString(), jsonObject.get("class")); 208 | } 209 | 210 | @Test 211 | public void testJSONEventHasFileName() { 212 | logger.warn("whoami"); 213 | String message = appender.getMessages()[0]; 214 | Object obj = JSONValue.parse(message); 215 | JSONObject jsonObject = (JSONObject) obj; 216 | 217 | Assert.assertNotNull("File value is missing", jsonObject.get("file")); 218 | } 219 | 220 | @Test 221 | public void testJSONEventHasLoggerName() { 222 | logger.warn("whoami"); 223 | String message = appender.getMessages()[0]; 224 | Object obj = JSONValue.parse(message); 225 | JSONObject jsonObject = (JSONObject) obj; 226 | Assert.assertNotNull("LoggerName value is missing", jsonObject.get("logger_name")); 227 | } 228 | 229 | @Test 230 | public void testJSONEventHasThreadName() { 231 | logger.warn("whoami"); 232 | String message = appender.getMessages()[0]; 233 | Object obj = JSONValue.parse(message); 234 | JSONObject jsonObject = (JSONObject) obj; 235 | Assert.assertNotNull("ThreadName value is missing", jsonObject.get("thread_name")); 236 | } 237 | 238 | @Test 239 | public void testJSONEventLayoutNoLocationInfo() { 240 | JSONEventLayoutV1 layout = (JSONEventLayoutV1) appender.getLayout(); 241 | boolean prevLocationInfo = layout.getLocationInfo(); 242 | 243 | layout.setLocationInfo(false); 244 | 245 | logger.warn("warning dawg"); 246 | String message = appender.getMessages()[0]; 247 | Object obj = JSONValue.parse(message); 248 | JSONObject jsonObject = (JSONObject) obj; 249 | 250 | Assert.assertFalse("atFields contains file value", jsonObject.containsKey("file")); 251 | Assert.assertFalse("atFields contains line_number value", jsonObject.containsKey("line_number")); 252 | Assert.assertFalse("atFields contains class value", jsonObject.containsKey("class")); 253 | Assert.assertFalse("atFields contains method value", jsonObject.containsKey("method")); 254 | 255 | // Revert the change to the layout to leave it as we found it. 256 | layout.setLocationInfo(prevLocationInfo); 257 | } 258 | 259 | @Test 260 | @Ignore 261 | public void measureJSONEventLayoutLocationInfoPerformance() { 262 | JSONEventLayoutV1 layout = (JSONEventLayoutV1) appender.getLayout(); 263 | boolean locationInfo = layout.getLocationInfo(); 264 | int iterations = 100000; 265 | long start, stop; 266 | 267 | start = System.currentTimeMillis(); 268 | for (int i = 0; i < iterations; i++) { 269 | logger.warn("warning dawg"); 270 | } 271 | stop = System.currentTimeMillis(); 272 | long firstMeasurement = stop - start; 273 | 274 | layout.setLocationInfo(!locationInfo); 275 | start = System.currentTimeMillis(); 276 | for (int i = 0; i < iterations; i++) { 277 | logger.warn("warning dawg"); 278 | } 279 | stop = System.currentTimeMillis(); 280 | long secondMeasurement = stop - start; 281 | 282 | System.out.println("First Measurement (locationInfo: " + locationInfo + "): " + firstMeasurement); 283 | System.out.println("Second Measurement (locationInfo: " + !locationInfo + "): " + secondMeasurement); 284 | 285 | // Clean up 286 | layout.setLocationInfo(!locationInfo); 287 | } 288 | 289 | @Test 290 | public void testDateFormat() { 291 | long timestamp = 1364844991207L; 292 | Assert.assertEquals("format does not produce expected output", "2013-04-01T19:36:31.207Z", JSONEventLayoutV1.dateFormat(timestamp)); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/test/java/net/logstash/log4j/MockAppenderV0.java: -------------------------------------------------------------------------------- 1 | package net.logstash.log4j; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.apache.log4j.AppenderSkeleton; 7 | import org.apache.log4j.spi.LoggingEvent; 8 | import org.apache.log4j.Layout; 9 | 10 | public class MockAppenderV0 extends AppenderSkeleton { 11 | 12 | private static List messages = new ArrayList(); 13 | 14 | public MockAppenderV0(Layout layout){ 15 | this.layout = layout; 16 | } 17 | @Override 18 | protected void append(LoggingEvent event){ 19 | messages.add(layout.format(event)); 20 | } 21 | 22 | public void close(){ 23 | messages.clear(); 24 | } 25 | 26 | public boolean requiresLayout(){ 27 | return true; 28 | } 29 | 30 | public static String[] getMessages() { 31 | return (String[]) messages.toArray(new String[messages.size()]); 32 | } 33 | 34 | public void clear() { 35 | messages.clear(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/net/logstash/log4j/MockAppenderV1.java: -------------------------------------------------------------------------------- 1 | package net.logstash.log4j; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.apache.log4j.AppenderSkeleton; 7 | import org.apache.log4j.spi.LoggingEvent; 8 | import org.apache.log4j.Layout; 9 | 10 | public class MockAppenderV1 extends AppenderSkeleton { 11 | 12 | private static List messages = new ArrayList(); 13 | 14 | public MockAppenderV1(Layout layout){ 15 | this.layout = layout; 16 | } 17 | @Override 18 | protected void append(LoggingEvent event){ 19 | messages.add(layout.format(event)); 20 | } 21 | 22 | public void close(){ 23 | messages.clear(); 24 | } 25 | 26 | public boolean requiresLayout(){ 27 | return true; 28 | } 29 | 30 | public static String[] getMessages() { 31 | return (String[]) messages.toArray(new String[messages.size()]); 32 | } 33 | 34 | public void clear() { 35 | messages.clear(); 36 | } 37 | } 38 | --------------------------------------------------------------------------------