From 54e813615d88956eee6c76730935282066f58f05 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Wed, 3 Jul 2024 06:13:26 +0200 Subject: Generate JSON by hand. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This improves performance further. Before: Benchmark Mode Cnt Score Error Units FormatterBenchmark.massivelyStructuredLogRecord thrpt 10 137879.001 ± 30467.644 ops/s FormatterBenchmark.simpleLogRecord thrpt 10 896085.217 ± 249890.421 ops/s FormatterBenchmark.structuredLogRecord thrpt 10 553428.807 ± 194787.754 ops/s After: Benchmark Mode Cnt Score Error Units FormatterBenchmark.massivelyStructuredLogRecord thrpt 10 220855.729 ± 56336.321 ops/s FormatterBenchmark.simpleLogRecord thrpt 10 4456647.085 ± 1041546.047 ops/s FormatterBenchmark.structuredLogRecord thrpt 10 1500896.509 ± 543358.587 ops/s There is also a new benchmark: FormatterBenchmark.nestedLogRecord thrpt 10 878620.441 ± 297024.983 ops/s Change-Id: If3e323b133f8e3e3ff29431a92d1b1e500f9b8b2 --- .../quarkus/googlecloud/jsonlogging/Formatter.java | 12 +- .../quarkus/googlecloud/jsonlogging/LogEntry.java | 218 +++++++++++++++++---- 2 files changed, 188 insertions(+), 42 deletions(-) (limited to 'core/src/main/java/eu') diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java index a7fd551..4aa8f9f 100644 --- a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java +++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java @@ -4,7 +4,6 @@ package eu.mulk.quarkus.googlecloud.jsonlogging; -import jakarta.json.spi.JsonProvider; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -43,7 +42,7 @@ public class Formatter extends ExtFormatter { private final List parameterProviders; private final List labelProviders; - private final JsonProvider json; + private final ThreadLocal stringBuilder; /** * Constructs a {@link Formatter} with custom configuration. @@ -59,7 +58,7 @@ public class Formatter extends ExtFormatter { Collection labelProviders) { this.parameterProviders = List.copyOf(parameterProviders); this.labelProviders = List.copyOf(labelProviders); - this.json = JsonProvider.provider(); + this.stringBuilder = ThreadLocal.withInitial(StringBuilder::new); } /** @@ -154,7 +153,12 @@ public class Formatter extends ExtFormatter { ndc, logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null); - return entry.json(json).build().toString() + "\n"; + var b = stringBuilder.get(); + b.delete(0, b.length()); + b.append("{"); + entry.json(b); + b.append("}\n"); + return b.toString(); } private static LogEntry.SourceLocation sourceLocationOf(ExtLogRecord logRecord) { diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java index 8bcea2b..19e6a3f 100644 --- a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java +++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java @@ -5,9 +5,8 @@ package eu.mulk.quarkus.googlecloud.jsonlogging; import io.smallrye.common.constraint.Nullable; -import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; -import jakarta.json.spi.JsonProvider; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; import java.time.Instant; import java.util.List; import java.util.Map; @@ -74,22 +73,31 @@ final class LogEntry { this.function = function; } - JsonObject json(JsonProvider json) { - var b = json.createObjectBuilder(); + void json(StringBuilder b) { + var commaNeeded = false; if (file != null) { - b.add("file", file); + b.append("\"file\":"); + appendEscapedString(b, file); + commaNeeded = true; } if (line != null) { - b.add("line", line); + if (commaNeeded) { + b.append(","); + } + b.append("\"line\":"); + appendEscapedString(b, line); + commaNeeded = true; } if (function != null) { - b.add("function", function); + if (commaNeeded) { + b.append(","); + } + b.append("\"function\":"); + appendEscapedString(b, function); } - - return b.build(); } } @@ -107,58 +115,192 @@ final class LogEntry { this(t.getEpochSecond(), t.getNano()); } - JsonObject json(JsonProvider json) { - return json.createObjectBuilder().add("seconds", seconds).add("nanos", nanos).build(); + void json(StringBuilder b) { + b.append("\"seconds\":"); + b.append(seconds); + b.append(",\"nanos\":"); + b.append(nanos); } } - JsonObjectBuilder json(JsonProvider json) { - var b = json.createObjectBuilder(); - + void json(StringBuilder b) { if (trace != null) { - b.add("logging.googleapis.com/trace", trace); + b.append("\"logging.googleapis.com/trace\":"); + appendEscapedString(b, trace); + b.append(","); } if (spanId != null) { - b.add("logging.googleapis.com/spanId", spanId); + b.append("\"logging.googleapis.com/spanId\":"); + appendEscapedString(b, spanId); + b.append(","); } if (nestedDiagnosticContext != null && !nestedDiagnosticContext.isEmpty()) { - b.add("nestedDiagnosticContext", nestedDiagnosticContext); + b.append("\"nestedDiagnosticContext\":"); + appendEscapedString(b, nestedDiagnosticContext); + b.append(","); } if (!labels.isEmpty()) { - b.add("logging.googleapis.com/labels", jsonOfStringMap(json, labels)); + b.append("\"logging.googleapis.com/labels\":{"); + + var first = true; + for (var entry : labels.entrySet()) { + if (!first) { + b.append(","); + } else { + first = false; + } + + appendEscapedString(b, entry.getKey()); + b.append(":"); + appendEscapedString(b, entry.getValue()); + } + + b.append("},"); } - if (type != null) { - b.add("@type", type); + for (var entry : mappedDiagnosticContext.entrySet()) { + appendEscapedString(b, entry.getKey()); + b.append(":"); + appendEscapedString(b, entry.getValue()); + b.append(","); + } + + for (var parameter : parameters) { + var jsonObject = parameter.json().build(); + jsonObject.forEach( + (key, value) -> { + appendEscapedString(b, key); + b.append(":"); + appendJsonObject(b, value); + b.append(","); + }); } - b.add("message", message).add("severity", severity).add("timestamp", timestamp.json(json)); + if (type != null) { + b.append("\"@type\":"); + appendEscapedString(b, type); + b.append(","); + } if (sourceLocation != null) { - b.add("logging.googleapis.com/sourceLocation", sourceLocation.json(json)); + b.append("\"logging.googleapis.com/sourceLocation\":{"); + sourceLocation.json(b); + b.append("},"); } - return b.addAll(jsonOfStringMap(json, mappedDiagnosticContext)) - .addAll(jsonOfParameterMap(json, parameters)); + b.append("\"message\":"); + appendEscapedString(b, message); + + b.append(",\"severity\":"); + appendEscapedString(b, severity); + + b.append(",\"timestamp\":{"); + timestamp.json(b); + b.append("}"); } - private JsonObjectBuilder jsonOfStringMap(JsonProvider json, Map stringMap) { - return stringMap.entrySet().stream() - .reduce( - json.createObjectBuilder(), - (acc, x) -> acc.add(x.getKey(), x.getValue()), - JsonObjectBuilder::addAll); + private void appendJsonObject(StringBuilder b, JsonValue value) { + switch (value.getValueType()) { + case ARRAY: + b.append("["); + var array = value.asJsonArray(); + for (var i = 0; i < array.size(); i++) { + if (i > 0) { + b.append(","); + } + appendJsonObject(b, array.get(i)); + } + b.append("]"); + break; + + case OBJECT: + b.append("{"); + var object = value.asJsonObject(); + var first = true; + for (var entry : object.entrySet()) { + if (!first) { + b.append(","); + } else { + first = false; + } + appendEscapedString(b, entry.getKey()); + b.append(":"); + appendJsonObject(b, entry.getValue()); + } + b.append("}"); + break; + + case STRING: + appendEscapedString(b, ((JsonString) value).getString()); + break; + + case NUMBER: + b.append(value); + break; + + case TRUE: + b.append("true"); + break; + + case FALSE: + b.append("false"); + break; + + case NULL: + b.append("null"); + break; + } } - private JsonObjectBuilder jsonOfParameterMap( - JsonProvider json, List parameters) { - return parameters.stream() - .reduce( - json.createObjectBuilder(), - (acc, p) -> acc.addAll(p.json()), - JsonObjectBuilder::addAll); + private static void appendEscapedString(StringBuilder b, String s) { + b.append('"'); + + for (var i = 0; i < s.length(); i++) { + var c = s.charAt(i); + + switch (c) { + case '"': + b.append("\\\""); + break; + + case '\\': + b.append("\\\\"); + break; + + case '\b': + b.append("\\b"); + break; + + case '\f': + b.append("\\f"); + break; + + case '\n': + b.append("\\n"); + break; + + case '\r': + b.append("\\r"); + break; + + case '\t': + b.append("\\t"); + break; + + default: + if (c < 0x20) { + b.append("\\u00"); + b.append(Character.forDigit((c >> 4) & 0xf, 16)); + b.append(Character.forDigit(c & 0xf, 16)); + } else { + b.append(c); + } + } + } + + b.append('"'); } } -- cgit v1.2.3