Skip to content

Commit f8da227

Browse files
committed
* Improved version
* Add README
1 parent 05b9825 commit f8da227

File tree

15 files changed

+471
-61
lines changed

15 files changed

+471
-61
lines changed

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# tomcat-json-logging
2+
3+
Extension to Tomcat logging that will format the contents as JSON to be better compatible with logging environments for microservices.
4+
5+
## Example
6+
7+
If everything is configured correctly you will see output like this on our console:
8+
```json
9+
..
10+
{"when":"2018-11-07T14:40:58.540+0100","level":"INFO","source":"org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:488)","logger":"org.apache.coyote.http11.Http11AprProtocol","thread":"main","processId":22640,"message":"Starting ProtocolHandler [\"http-apr-8080\"]"}
11+
{"when":"2018-11-07T14:40:58.555+0100","level":"INFO","source":"org.apache.coyote.AbstractProtocol.start(AbstractProtocol.java:488)","logger":"org.apache.coyote.ajp.AjpAprProtocol","thread":"main","processId":22640,"message":"Starting ProtocolHandler [\"ajp-apr-8009\"]"}
12+
{"when":"2018-11-07T14:40:58.560+0100","level":"INFO","source":"org.apache.catalina.startup.Catalina.start(Catalina.java:654)","logger":"org.apache.catalina.startup.Catalina","thread":"main","processId":22640,"message":"Server startup in 859 ms"}
13+
..
14+
```
15+
16+
## Installation
17+
18+
### 1. Download
19+
20+
Go to [Release page](https://github.com/echocat/tomcat-json-logging/releases/latest) and download the matching version of `tomcat-juli.jar` for your Tomcat version. If you have Tomcat `8.5.32` running download `tomcat-juli-8.5.32.jar` and so on...
21+
22+
Replace `bin/tomcat-juli.jar` inside your Tomcat distribution directory with this file.
23+
24+
> No worries! This files is a combined version of the original Tomcat version and the new functionality.
25+
26+
### 2. Configure
27+
28+
Just replace the content of `conf/logging.properties` in your Tomcat distribution directory with the following content:
29+
```properties
30+
handlers = org.echocat.tjl.Handler
31+
```
32+
33+
This will log everything directly to the console in JSON format (by default).
34+
35+
## Customization
36+
37+
### Using system properties or environment variables
38+
39+
You can either specify a system property or environment variable to change the behavior of the logging of the Tomcat. This values always override every settings in `conf/logging.properties`.
40+
41+
##### Available properties
42+
43+
| System property | Environment property | Description |
44+
|---|---|---|
45+
| `log.format` | `LOG_FORMAT` | Forces a different formatter. See [Available formats](#available-formats). |
46+
| `log.level` | `LOG_LEVEL` | Forces a different global log level. Pleas refer [`java.util.logging.Level`](https://docs.oracle.com/javase/8/docs/api/java/util/logging/Level.html) for different levels. For example `FINE` for more output while local development. |
47+
48+
##### Available formats
49+
50+
| Value | Description |
51+
|---|---|
52+
| `json` | Will use `org.echocat.tjl.JsonFormatter` - see [Available formatter](#available-formatter) for more information. |
53+
| `text` | Will use `org.echocat.tjl.TextFormatter` - see [Available formatter](#available-formatter) for more information. |
54+
55+
### Using `conf/logging.properties`
56+
57+
##### Available properties
58+
59+
| Name | Description | Default |
60+
|------|-------------|---------|
61+
| `org.echocat.tjl.Handler.level` | Minimum level of log entries to be logged/visible. Pleas refer [`java.util.logging.Level`](https://docs.oracle.com/javase/8/docs/api/java/util/logging/Level.html) for more information. | `ALL` |
62+
| `org.echocat.tjl.Handler.formatter` | Formatter to format the message in the console. | `org.echocat.tjl.JsonFormatter` |
63+
64+
##### Available formatter
65+
66+
| Name | Description |
67+
|------|-------------|
68+
| `org.echocat.tjl.JsonFormatter` | Formats the whole output as JSON. One entry per line. |
69+
| `org.echocat.tjl.TextFormatter` | Formats every entry per line in a simple way - better for local debugging. |

core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<relativePath>../pom.xml</relativePath>
1010
<groupId>org.echocat.tjl</groupId>
1111
<artifactId>parent</artifactId>
12-
<version>0.1</version>
12+
<version>1.0</version>
1313
</parent>
1414

1515
<dependencies>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.echocat.tjl;
2+
3+
public interface Constants {
4+
String ISO_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
5+
String CONSOLE_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
6+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package org.echocat.tjl;
2+
3+
import org.echocat.tjl.util.ExtendedLogRecord;
4+
5+
import java.io.OutputStream;
6+
import java.security.PrivilegedAction;
7+
import java.util.Optional;
8+
import java.util.logging.*;
9+
10+
import static java.lang.ClassLoader.getSystemClassLoader;
11+
import static java.lang.System.getProperty;
12+
import static java.lang.System.getenv;
13+
import static java.security.AccessController.doPrivileged;
14+
import static java.util.Optional.ofNullable;
15+
import static java.util.logging.Level.ALL;
16+
import static java.util.logging.Level.parse;
17+
import static java.util.logging.LogManager.getLogManager;
18+
19+
public class Handler extends StreamHandler {
20+
21+
public Handler() {
22+
this(System.out);
23+
}
24+
25+
public Handler(OutputStream out) {
26+
super(out, new JsonFormatter());
27+
init();
28+
}
29+
30+
@Override
31+
public synchronized void publish(LogRecord record) {
32+
final ExtendedLogRecord extended = new ExtendedLogRecord(record);
33+
extended.detectAndSetSource(className -> !getClass().getName().equals(className));
34+
super.publish(extended);
35+
flush();
36+
}
37+
38+
@Override
39+
public synchronized void close() throws SecurityException {
40+
flush();
41+
}
42+
43+
protected void init() {
44+
final Formatter formatter = determineFormatter();
45+
final Level level = determineLevel();
46+
patchGlobalLevelIfRequired();
47+
48+
doPrivileged((PrivilegedAction<Void>) () -> {
49+
setLevel(level);
50+
setFormatter(formatter);
51+
return null;
52+
});
53+
}
54+
55+
protected Formatter determineFormatter() {
56+
return findSystemProperty("log.format", "LOG_FORMAT")
57+
.map(String::trim)
58+
.map(String::toUpperCase)
59+
.map(this::formatToFormatter)
60+
.orElseGet(() -> findProperty(getClass(), "formatter")
61+
.map(this::newInstanceOf)
62+
.filter(candidate -> candidate instanceof Formatter)
63+
.map(candidate -> (Formatter) candidate)
64+
.orElseGet(JsonFormatter::new)
65+
);
66+
}
67+
68+
protected Level determineLevel() {
69+
return findProperty(getClass(), "level")
70+
.map(value -> parse(value.trim()))
71+
.orElse(ALL);
72+
}
73+
74+
protected void patchGlobalLevelIfRequired() {
75+
findSystemProperty("log.level", "LOG_LEVEL")
76+
.map(String::trim)
77+
.map(String::toUpperCase)
78+
.map(Level::parse)
79+
.ifPresent(level -> getLogManager().getLogger("").setLevel(level));
80+
}
81+
82+
protected Object newInstanceOf(String className) {
83+
try {
84+
//noinspection deprecation
85+
return getSystemClassLoader().loadClass(className).newInstance();
86+
} catch (final Exception e) {
87+
return new IllegalStateException("Cannot create instance of '" + className + "'.", e);
88+
}
89+
}
90+
91+
protected Formatter formatToFormatter(String format) {
92+
if (format.equals("JSON")) {
93+
return new JsonFormatter();
94+
}
95+
if (format.equals("TEXT")) {
96+
return new TextFormatter();
97+
}
98+
throw new IllegalArgumentException("Illegal format: " + format);
99+
}
100+
101+
protected static Optional<String> findProperty(Class<?> owner, String name) {
102+
final String propertyName = owner.getName() + "." + name;
103+
return ofNullable(getLogManager().getProperty(propertyName));
104+
}
105+
106+
protected static Optional<String> findSystemProperty(String propName, String envName) {
107+
String value = getProperty(propName);
108+
if (value == null) {
109+
value = getenv(envName);
110+
}
111+
return ofNullable(value);
112+
}
113+
114+
}

core/src/main/java/org/echocat/tjl/JsonFormatter.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import java.util.logging.Formatter;
99
import java.util.logging.LogRecord;
1010

11-
import static org.echocat.tjl.LogObject.logObject;
11+
import static java.lang.System.lineSeparator;
12+
import static org.echocat.tjl.Constants.ISO_DATE_FORMAT;
13+
import static org.echocat.tjl.LogEvent.logObject;
1214

1315
@SuppressWarnings("WeakerAccess")
1416
public class JsonFormatter extends Formatter {
@@ -17,7 +19,7 @@ public class JsonFormatter extends Formatter {
1719

1820
public JsonFormatter() {
1921
this(new GsonBuilder()
20-
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
22+
.setDateFormat(ISO_DATE_FORMAT)
2123
.registerTypeAdapter(Optional.class, new OptionalTypeAdapter())
2224
.create()
2325
);
@@ -29,16 +31,16 @@ protected JsonFormatter(Gson gson) {
2931

3032
@Override
3133
public String format(LogRecord record) {
32-
return toJson(toLogObject(record)) + "\n";
34+
return toJson(toLogObject(record)) + lineSeparator();
3335
}
3436

35-
protected LogObject toLogObject(LogRecord record) {
37+
protected LogEvent toLogObject(LogRecord record) {
3638
return logObject()
3739
.basedOn(record)
3840
.build();
3941
}
4042

41-
protected String toJson(LogObject object) {
43+
protected String toJson(LogEvent object) {
4244
return gson().toJson(object);
4345
}
4446

0 commit comments

Comments
 (0)