Skip to content
This repository has been archived by the owner on Jun 29, 2023. It is now read-only.

Commit

Permalink
Replace json-simple by own JSON serializer #89
Browse files Browse the repository at this point in the history
Replace json-simple by own JSON writer and perform UTF-8 encoding within the JSON writer.

Motivation: json-simple produces a notable amount of garbage during JSON serialization. The default interchange format is String that concatenates data using StringBuilder.

The own implementation uses a facade to write bytes on an underlying stream. This reduces the garbage so data is not necessarily duplicated during serialization.
  • Loading branch information
mp911de committed Jul 13, 2016
1 parent 19b76fb commit ef2bccc
Show file tree
Hide file tree
Showing 27 changed files with 849 additions and 136 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,6 @@ Versions/Dependencies
--------------
This project is built against following dependencies/versions:

* json-simple 1.1.1
* log4j 1.2.14
* log4j2 2.5
* Java Util Logging JDK Version 1.6
Expand Down
24 changes: 10 additions & 14 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
Expand Down Expand Up @@ -58,11 +59,11 @@
<test.withRedis>false</test.withRedis>

<!-- Versions -->
<com.googlecode.json-simple.version>1.1.1</com.googlecode.json-simple.version>
<log4j.version>1.2.14</log4j.version>
<log4j2.version>2.3</log4j2.version>
<junit.version>4.11</junit.version>
<mockito.version>1.9.5</mockito.version>
<jackson.version>2.7.5</jackson.version>
<jedis.version>2.8.1</jedis.version>
<jedis-commons-pool2.version>2.4.2</jedis-commons-pool2.version>
<slf4j-api.version>1.7.13</slf4j-api.version>
Expand Down Expand Up @@ -241,18 +242,6 @@
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>${com.googlecode.json-simple.version}</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
Expand Down Expand Up @@ -336,6 +325,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>

<!-- Redis Client -->
<dependency>
<groupId>redis.clients</groupId>
Expand Down
1 change: 0 additions & 1 deletion src/main/assembly/assembly.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
<dependencySet>
<outputDirectory>/biz/paluch/logging/main</outputDirectory>
<includes>
<include>com.googlecode.json-simple:json-simple</include>
<include>redis.clients:jedis</include>
<include>org.apache.commons:commons-pool2</include>
</includes>
Expand Down
1 change: 0 additions & 1 deletion src/main/assembly/module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

<resources>
<resource-root path="${project.artifactId}-${project.version}.jar" />
<resource-root path="json-simple-${com.googlecode.json-simple.version}.jar" />
<resource-root path="jedis-${jedis.version}.jar" />
<resource-root path="commons-pool2-${jedis-commons-pool2.version}.jar" />
</resources>
Expand Down
95 changes: 67 additions & 28 deletions src/main/java/biz/paluch/logging/gelf/intern/GelfMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import java.util.Map;
import java.util.zip.GZIPOutputStream;

import org.json.simple.JSONValue;

/**
* @author https://github.com/t0xa/gelfj
* @author Mark Paluch
Expand Down Expand Up @@ -71,10 +69,11 @@ public class GelfMessage {

private static final byte[] GELF_CHUNKED_ID = new byte[] { 0x1e, 0x0f };
private static final BigDecimal TIME_DIVISOR = new BigDecimal(1000);
private static final byte[] NONE = lastFourAsciiBytes("none");

private String version = GELF_VERSION;
private String host;
private byte[] hostBytes = lastFourAsciiBytes("none");
private byte[] hostBytes = NONE;
private String shortMessage;
private String fullMessage;
private long javaTimestamp;
Expand All @@ -96,25 +95,36 @@ public GelfMessage(String shortMessage, String fullMessage, long timestamp, Stri
}

public String toJson(String additionalFieldPrefix) {
Map<String, Object> map = new HashMap<String, Object>();
return new String(toJsonByteArray(additionalFieldPrefix), Charsets.UTF8);
}

if (!isEmpty(getHost())) {
map.put(FIELD_HOST, getHost());
}
public byte[] toJsonByteArray(String additionalFieldPrefix) {

ByteArrayOutputStream buffer = new ByteArrayOutputStream();
toJson(OutputAccessor.from(buffer), additionalFieldPrefix);

return buffer.toByteArray();
}

protected void toJson(OutputAccessor out, String additionalFieldPrefix) {

boolean hasFields = false;

JsonWriter.writeObjectStart(out);

hasFields = writeIfNotEmpty(out, hasFields, FIELD_HOST, getHost());

if (!isEmpty(shortMessage)) {
map.put(FIELD_SHORT_MESSAGE, getShortMessage());
hasFields = writeIfNotEmpty(out, hasFields, FIELD_SHORT_MESSAGE, getShortMessage());
}

if (!isEmpty(getFullMessage())) {
map.put(FIELD_FULL_MESSAGE, getFullMessage());
}
hasFields = writeIfNotEmpty(out, hasFields, FIELD_FULL_MESSAGE, getFullMessage());

if (getJavaTimestamp() != 0) {
if (GELF_VERSION_1_1.equals(version)) {
map.put(FIELD_TIMESTAMP, getTimestampAsBigDecimal().doubleValue());
hasFields = writeIfNotEmpty(out, hasFields, FIELD_TIMESTAMP, getTimestampAsBigDecimal().doubleValue());
} else {
map.put(FIELD_TIMESTAMP, getTimestamp());
hasFields = writeIfNotEmpty(out, hasFields, FIELD_TIMESTAMP, getTimestampAsBigDecimal().toString());
}
}

Expand All @@ -127,14 +137,14 @@ public String toJson(String additionalFieldPrefix) {
// fallback on the default value
level = DEFAUL_LEVEL;
}
map.put(FIELD_LEVEL, level);
hasFields = writeIfNotEmpty(out, hasFields, FIELD_LEVEL, level);
} else {
map.put(FIELD_LEVEL, getLevel());
hasFields = writeIfNotEmpty(out, hasFields, FIELD_LEVEL, getLevel());
}
}

if (!isEmpty(getFacility())) {
map.put(FIELD_FACILITY, getFacility());
hasFields = writeIfNotEmpty(out, hasFields, FIELD_FACILITY, getFacility());
}

for (Map.Entry<String, String> additionalField : additonalFields.entrySet()) {
Expand All @@ -146,16 +156,47 @@ public String toJson(String additionalFieldPrefix) {
}
Object result = getAdditionalFieldValue(value, fieldType);
if (result != null) {
map.put(additionalFieldPrefix + additionalField.getKey(), result);
hasFields = writeIfNotEmpty(out, hasFields, additionalFieldPrefix + additionalField.getKey(), result);
}
}
}

return JSONValue.toJSONString(map);
JsonWriter.writeObjectEnd(out);
}

private boolean writeIfNotEmpty(OutputAccessor out, boolean hasFields, String field, Object value) {

if (value == null) {
return hasFields;
}

if (value instanceof String) {

if (isEmpty((String) value)) {
return hasFields;
}

if (hasFields) {
JsonWriter.writeKeyValueSeparator(out);
}

JsonWriter.writeMapEntry(out, field, value);

return true;
}

if (hasFields) {
JsonWriter.writeKeyValueSeparator(out);
}

JsonWriter.writeMapEntry(out, field, value);

return true;
}

/**
* Get the field value as requested data type.
*
* @param value the value as string
* @param fieldType see field types
* @return the field value in the appropriate data type or {@literal null}.
Expand Down Expand Up @@ -210,7 +251,7 @@ public String toJson() {
}

public ByteBuffer[] toUDPBuffers() {
byte[] messageBytes = gzipMessage(toJson());
byte[] messageBytes = gzipMessage(toJsonByteArray("_"));
// calculate the length of the datagrams array
int diagrams_length = messageBytes.length / maximumMessageSize;
// In case of a remainder, due to the integer division, add a extra datagram
Expand All @@ -229,16 +270,15 @@ public ByteBuffer[] toUDPBuffers() {
}

public ByteBuffer toTCPBuffer() {
byte[] messageBytes;

// Do not use GZIP, as the headers will contain \0 bytes
// graylog2-server uses \0 as a delimiter for TCP frames
// see: https://github.com/Graylog2/graylog2-server/issues/127
String json = toJson();
json += '\0';
messageBytes = Charsets.utf8(json);
byte[] messageBytes = toJsonByteArray("_");

ByteBuffer buffer = ByteBuffer.allocate(messageBytes.length);
ByteBuffer buffer = ByteBuffer.allocate(messageBytes.length + 1);
buffer.put(messageBytes);
buffer.put((byte) '\0');
buffer.flip();
return buffer;
}
Expand Down Expand Up @@ -269,14 +309,13 @@ public int getCurrentMillis() {
return (int) System.currentTimeMillis();
}

private byte[] gzipMessage(String message) {
private byte[] gzipMessage(byte[] message) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();

try {
GZIPOutputStream stream = new GZIPOutputStream(bos);
byte[] bytes = Charsets.utf8(message);

stream.write(bytes);
stream.write(message);
stream.finish();
Closer.close(stream);
byte[] zipped = bos.toByteArray();
Expand All @@ -287,7 +326,7 @@ private byte[] gzipMessage(String message) {
}
}

private byte[] lastFourAsciiBytes(String host) {
private static byte[] lastFourAsciiBytes(String host) {
final String shortHost = host.length() >= 4 ? host.substring(host.length() - 4) : host;
return Charsets.ascii(shortHost);
}
Expand Down
Loading

1 comment on commit ef2bccc

@MartinNowak
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay, less (no?) dependencies to install :).

Please sign in to comment.