├── .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 | [](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 |
--------------------------------------------------------------------------------