diff options
| author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2024-07-03 06:13:26 +0200 | 
|---|---|---|
| committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2024-07-03 06:13:26 +0200 | 
| commit | 54e813615d88956eee6c76730935282066f58f05 (patch) | |
| tree | 32ab45ae5899ed058cd005d3f8c044b008be6359 /core/src/main | |
| parent | 71ddcabc7a02c76771f7acf90559e1b1aa5ec907 (diff) | |
Generate JSON by hand.
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
Diffstat (limited to 'core/src/main')
| -rw-r--r-- | core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java | 12 | ||||
| -rw-r--r-- | core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java | 218 | 
2 files changed, 188 insertions, 42 deletions
| 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<StructuredParameterProvider> parameterProviders;    private final List<LabelProvider> labelProviders; -  private final JsonProvider json; +  private final ThreadLocal<StringBuilder> stringBuilder;    /**     * Constructs a {@link Formatter} with custom configuration. @@ -59,7 +58,7 @@ public class Formatter extends ExtFormatter {        Collection<LabelProvider> 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<String, String> 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<StructuredParameter> 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('"');    }  } | 
