diff options
Diffstat (limited to 'core')
8 files changed, 402 insertions, 85 deletions
diff --git a/core/pom.xml b/core/pom.xml index 4a70b51..b8aaf94 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -14,7 +14,7 @@ SPDX-License-Identifier: LGPL-3.0-or-later <parent> <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId> <artifactId>quarkus-googlecloud-jsonlogging-parent</artifactId> - <version>6.3.0</version> + <version>6.5.1-SNAPSHOT</version> </parent> <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId> @@ -33,10 +33,17 @@ SPDX-License-Identifier: LGPL-3.0-or-later <optional>true</optional> </dependency> <dependency> + <groupId>jakarta.json</groupId> + <artifactId>jakarta.json-api</artifactId> + <version>2.1.3</version> + </dependency> + <dependency> <groupId>io.smallrye.common</groupId> <artifactId>smallrye-common-constraint</artifactId> <version>2.4.0</version> </dependency> + + <!-- Include Parsson for backwards-compatibility. --> <dependency> <groupId>org.eclipse.parsson</groupId> <artifactId>parsson</artifactId> @@ -57,16 +64,16 @@ SPDX-License-Identifier: LGPL-3.0-or-later </dependency> <dependency> - <groupId>org.openjdk.jmh</groupId> - <artifactId>jmh-core</artifactId> - <version>1.35</version> - <scope>test</scope> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-core</artifactId> + <version>1.35</version> + <scope>test</scope> </dependency> <dependency> - <groupId>org.openjdk.jmh</groupId> - <artifactId>jmh-generator-annprocess</artifactId> - <version>1.35</version> - <scope>test</scope> + <groupId>org.openjdk.jmh</groupId> + <artifactId>jmh-generator-annprocess</artifactId> + <version>1.35</version> + <scope>test</scope> </dependency> </dependencies> 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..0b2003d 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); } /** @@ -124,6 +123,8 @@ public class Formatter extends ExtFormatter { } } + String insertId = null; + if (logRecord.getParameters() != null) { for (var parameter : logRecord.getParameters()) { if (parameter instanceof StructuredParameter) { @@ -131,6 +132,8 @@ public class Formatter extends ExtFormatter { } else if (parameter instanceof Label) { var label = (Label) parameter; labels.put(label.key(), label.value()); + } else if (parameter instanceof InsertId) { + insertId = ((InsertId) parameter).value(); } } } @@ -152,9 +155,15 @@ public class Formatter extends ExtFormatter { parameters, mdc, ndc, - logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null); - - return entry.json(json).build().toString() + "\n"; + logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null, + insertId); + + 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/InsertId.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/InsertId.java new file mode 100644 index 0000000..b55cf78 --- /dev/null +++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/InsertId.java @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: © 2024 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +package eu.mulk.quarkus.googlecloud.jsonlogging; + +import java.util.Objects; + +/** + * A unique identifier for a log entry. + * + * <p>Prevents the duplicate insertion of log entries. Also serves as a discriminator to order log entries that carry + * the same time stamp. + * + * <p>Will be generated by Google Cloud Logging if not provided. + * + * <p>Instances of {@link InsertId} can be passed as log parameters to the {@code *f} family of logging + * functions on {@link org.jboss.logging.Logger}. + * + * <p><strong>Example:</strong> + * + * {@snippet : + * logger.logf("Request rejected: unauthorized.", InsertId.of("123")); + * } + * + * <p>Result: + * + * {@snippet lang="json" : + * { + * "textPayload": "Request rejected: unauthorized.", + * "logging.googleapis.com/insertId": "123" + * } + * } + * + * @see Label + * @see StructuredParameter + */ +public final class InsertId { + + private final String value; + + private InsertId(String value) { + this.value = value; + } + + /** + * Constructs an {@link InsertId} from a string. + * + * @param value the value of the insertion ID. + * @return the newly constructed {@link InsertId}, ready to be passed to a logging function. + */ + public static InsertId of(String value) { + return new InsertId(value); + } + + /** + * The value of the label. + * + * @return the value of the label. + */ + public String value() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (InsertId) obj; + return Objects.equals(this.value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "InsertId[value=" + value + ']'; + } +} diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/KeyValueParameter.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/KeyValueParameter.java index 2ea4521..a2c468b 100644 --- a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/KeyValueParameter.java +++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/KeyValueParameter.java @@ -4,9 +4,9 @@ package eu.mulk.quarkus.googlecloud.jsonlogging; -import jakarta.json.Json; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonValue; +import jakarta.json.spi.JsonProvider; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Objects; @@ -40,6 +40,8 @@ import java.util.Objects; */ public final class KeyValueParameter implements StructuredParameter { + private static final JsonProvider JSON = JsonProvider.provider(); + private final String key; private final JsonValue value; @@ -58,7 +60,7 @@ public final class KeyValueParameter implements StructuredParameter { * @return the newly constructed parameter, ready to be passed to a logging function. */ public static KeyValueParameter of(String key, String value) { - return new KeyValueParameter(key, Json.createValue(value)); + return new KeyValueParameter(key, JSON.createValue(value)); } /** @@ -71,7 +73,7 @@ public final class KeyValueParameter implements StructuredParameter { * @return the newly constructed parameter, ready to be passed to a logging function. */ public static KeyValueParameter of(String key, int value) { - return new KeyValueParameter(key, Json.createValue(value)); + return new KeyValueParameter(key, JSON.createValue(value)); } /** @@ -84,7 +86,7 @@ public final class KeyValueParameter implements StructuredParameter { * @return the newly constructed parameter, ready to be passed to a logging function. */ public static KeyValueParameter of(String key, long value) { - return new KeyValueParameter(key, Json.createValue(value)); + return new KeyValueParameter(key, JSON.createValue(value)); } /** @@ -97,7 +99,7 @@ public final class KeyValueParameter implements StructuredParameter { * @return the newly constructed parameter, ready to be passed to a logging function. */ public static KeyValueParameter of(String key, double value) { - return new KeyValueParameter(key, Json.createValue(value)); + return new KeyValueParameter(key, JSON.createValue(value)); } /** @@ -110,7 +112,7 @@ public final class KeyValueParameter implements StructuredParameter { * @return the newly constructed parameter, ready to be passed to a logging function. */ public static KeyValueParameter of(String key, BigDecimal value) { - return new KeyValueParameter(key, Json.createValue(value)); + return new KeyValueParameter(key, JSON.createValue(value)); } /** @@ -123,7 +125,7 @@ public final class KeyValueParameter implements StructuredParameter { * @return the newly constructed parameter, ready to be passed to a logging function. */ public static KeyValueParameter of(String key, BigInteger value) { - return new KeyValueParameter(key, Json.createValue(value)); + return new KeyValueParameter(key, JSON.createValue(value)); } /** @@ -141,7 +143,7 @@ public final class KeyValueParameter implements StructuredParameter { @Override public JsonObjectBuilder json() { - return Json.createObjectBuilder().add(key, value); + return JSON.createObjectBuilder().add(key, value); } /** 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..2d08c29 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; @@ -36,6 +35,7 @@ final class LogEntry { private final Map<String, String> mappedDiagnosticContext; @Nullable private final String nestedDiagnosticContext; @Nullable private final String type; + @Nullable private final String insertId; LogEntry( String message, @@ -48,7 +48,8 @@ final class LogEntry { List<StructuredParameter> parameters, Map<String, String> mappedDiagnosticContext, @Nullable String nestedDiagnosticContext, - @Nullable String type) { + @Nullable String type, + @Nullable String insertId) { this.message = message; this.severity = severity; this.timestamp = timestamp; @@ -60,6 +61,7 @@ final class LogEntry { this.mappedDiagnosticContext = mappedDiagnosticContext; this.nestedDiagnosticContext = nestedDiagnosticContext; this.type = type; + this.insertId = insertId; } static final class SourceLocation { @@ -74,22 +76,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\":"); + appendJsonString(b, file); + commaNeeded = true; } if (line != null) { - b.add("line", line); + if (commaNeeded) { + b.append(","); + } + b.append("\"line\":"); + appendJsonString(b, line); + commaNeeded = true; } if (function != null) { - b.add("function", function); + if (commaNeeded) { + b.append(","); + } + b.append("\"function\":"); + appendJsonString(b, function); } - - return b.build(); } } @@ -107,58 +118,199 @@ 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 (insertId != null) { + b.append("\"logging.googleapis.com/insertId\":"); + appendJsonString(b, insertId); + b.append(","); + } if (trace != null) { - b.add("logging.googleapis.com/trace", trace); + b.append("\"logging.googleapis.com/trace\":"); + appendJsonString(b, trace); + b.append(","); } if (spanId != null) { - b.add("logging.googleapis.com/spanId", spanId); + b.append("\"logging.googleapis.com/spanId\":"); + appendJsonString(b, spanId); + b.append(","); } if (nestedDiagnosticContext != null && !nestedDiagnosticContext.isEmpty()) { - b.add("nestedDiagnosticContext", nestedDiagnosticContext); + b.append("\"nestedDiagnosticContext\":"); + appendJsonString(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; + } + + appendJsonString(b, entry.getKey()); + b.append(":"); + appendJsonString(b, entry.getValue()); + } + + b.append("},"); } - if (type != null) { - b.add("@type", type); + for (var entry : mappedDiagnosticContext.entrySet()) { + appendJsonString(b, entry.getKey()); + b.append(":"); + appendJsonString(b, entry.getValue()); + b.append(","); } - b.add("message", message).add("severity", severity).add("timestamp", timestamp.json(json)); + for (var parameter : parameters) { + var jsonObject = parameter.json().build(); + jsonObject.forEach( + (key, value) -> { + appendJsonString(b, key); + b.append(":"); + appendJsonObject(b, value); + b.append(","); + }); + } + + if (type != null) { + b.append("\"@type\":"); + appendJsonString(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\":"); + appendJsonString(b, message); + + b.append(",\"severity\":"); + appendJsonString(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 static 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; + } + appendJsonString(b, entry.getKey()); + b.append(":"); + appendJsonObject(b, entry.getValue()); + } + b.append("}"); + break; + + case STRING: + appendJsonString(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 appendJsonString(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('"'); } } diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java index ec1e0f6..a84f1fc 100644 --- a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java +++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java @@ -43,7 +43,7 @@ * <dependency> * <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId> * <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId> - * <version>6.3.0</version> + * <version>6.5.0</version> * </dependency> * * ... @@ -59,7 +59,7 @@ * dependencies { * // ... * - * implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:6.3.0") + * implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:6.5.0") * * // ... * } diff --git a/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterBenchmark.java b/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterBenchmark.java index 4900a01..c330dfe 100644 --- a/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterBenchmark.java +++ b/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterBenchmark.java @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: © 2024 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: LGPL-3.0-or-later + package eu.mulk.quarkus.googlecloud.jsonlogging; import java.util.List; @@ -14,6 +18,7 @@ public class FormatterBenchmark { private ExtLogRecord simpleLogRecord; private ExtLogRecord structuredLogRecord; private ExtLogRecord massivelyStructuredLogRecord; + private ExtLogRecord nestedLogRecord; private Formatter formatter; @Setup @@ -21,6 +26,7 @@ public class FormatterBenchmark { simpleLogRecord = FormatterTest.makeSimpleRecord(); structuredLogRecord = FormatterTest.makeStructuredRecord(); massivelyStructuredLogRecord = FormatterTest.makeMassivelyStructuredRecord(); + nestedLogRecord = FormatterTest.makeNestedRecord(); formatter = new Formatter(List.of(), List.of()); } @@ -39,4 +45,10 @@ public class FormatterBenchmark { var f = formatter.format(massivelyStructuredLogRecord); blackhole.consume(f); } + + @Benchmark + public void nestedLogRecord(Blackhole blackhole) { + var f = formatter.format(nestedLogRecord); + blackhole.consume(f); + } } diff --git a/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java b/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java index 6d40b15..33df302 100644 --- a/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java +++ b/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: © 2024 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: LGPL-3.0-or-later + package eu.mulk.quarkus.googlecloud.jsonlogging; import static org.junit.jupiter.api.Assertions.assertLinesMatch; @@ -23,14 +27,14 @@ class FormatterTest { assertLinesMatch( List.of( "\\{" - + "\"message\":\"Hello, world!\"," - + "\"severity\":\"INFO\"," - + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}," + "\"logging.googleapis.com/sourceLocation\":" + "\\{\"file\":\"ReflectionUtils.java\"," + "\"line\":\"\\d+\"," + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\"" - + "\\}" + + "\\}," + + "\"message\":\"Hello, world!\"," + + "\"severity\":\"INFO\"," + + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}" + "\\}\n"), List.of(formattingResult)); } @@ -67,20 +71,21 @@ class FormatterTest { assertLinesMatch( List.of( "\\{" + + "\"logging.googleapis.com/insertId\":\"123-456-789\"," + "\"logging.googleapis.com/labels\":\\{\"a\":\"b\",\"requestId\":\"123\"\\}," - + "\"message\":\"Hello, world!\"," - + "\"severity\":\"INFO\"," - + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}," + + "\"traceId\":\"39f9a49a9567a8bd7087b708f8932550\"," + + "\"spanId\":\"c7431b14630b633d\"," + + "\"one\":1," + + "\"two\":2.0," + + "\"yes\":true," + "\"logging.googleapis.com/sourceLocation\":" + "\\{\"file\":\"ReflectionUtils.java\"," + "\"line\":\"\\d+\"," + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\"" + "\\}," - + "\"traceId\":\"39f9a49a9567a8bd7087b708f8932550\"," - + "\"spanId\":\"c7431b14630b633d\"," - + "\"one\":1," - + "\"two\":2.0," - + "\"yes\":true" + + "\"message\":\"Hello, world!\"," + + "\"severity\":\"INFO\"," + + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}" + "\\}\n"), List.of(formattingResult)); } @@ -91,7 +96,8 @@ class FormatterTest { new Object[] { (StructuredParameter) () -> JSON.createObjectBuilder().add("one", 1).add("two", 2.0).add("yes", true), - Label.of("a", "b") + Label.of("a", "b"), + InsertId.of("123-456-789"), }); return logRecord; } @@ -105,21 +111,21 @@ class FormatterTest { assertLinesMatch( List.of( "\\{" - + "\"message\":\"Hello, world!\"," - + "\"severity\":\"INFO\"," - + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}," - + "\"logging.googleapis.com/sourceLocation\":" - + "\\{\"file\":\"ReflectionUtils.java\"," - + "\"line\":\"\\d+\"," - + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\"" - + "\\}," + "\"int-0\":0,\"int-1\":1,\"int-2\":2,\"int-3\":3,\"int-4\":4,\"int-5\":5,\"int-6\":6,\"int-7\":7,\"int-8\":8,\"int-9\":9," + "\"double-10\":10.0,\"double-11\":11.0,\"double-12\":12.0,\"double-13\":13.0,\"double-14\":14.0," + "\"double-15\":15.0,\"double-16\":16.0,\"double-17\":17.0,\"double-18\":18.0,\"double-19\":19.0," + "\"boolean-20\":true,\"boolean-21\":false,\"boolean-22\":true,\"boolean-23\":false,\"boolean-24\":true," + "\"boolean-25\":false,\"boolean-26\":true,\"boolean-27\":false,\"boolean-28\":true,\"boolean-29\":false," + "\"string-30\":\"30\",\"string-31\":\"31\",\"string-32\":\"32\",\"string-33\":\"33\",\"string-34\":\"34\"," - + "\"string-35\":\"35\",\"string-36\":\"36\",\"string-37\":\"37\",\"string-38\":\"38\",\"string-39\":\"39\"" + + "\"string-35\":\"35\",\"string-36\":\"36\",\"string-37\":\"37\",\"string-38\":\"38\",\"string-39\":\"39\"," + + "\"logging.googleapis.com/sourceLocation\":" + + "\\{\"file\":\"ReflectionUtils.java\"," + + "\"line\":\"\\d+\"," + + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\"" + + "\\}," + + "\"message\":\"Hello, world!\"," + + "\"severity\":\"INFO\"," + + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}" + "\\}\n"), List.of(formattingResult)); } @@ -148,4 +154,51 @@ class FormatterTest { }); return logRecord; } + + @Test + void nestedRecord() { + var logRecord = makeNestedRecord(); + + var formatter = new Formatter(List.of(), List.of()); + var formattingResult = formatter.format(logRecord); + assertLinesMatch( + List.of( + "\\{" + + "\"anObject\":\\{\"a\":\"b\\\\nc\\\\nd\\\\u0000\\\\u0001\\\\u0002e\"\\}," + + "\"anArray\":\\[1,2,3\\]," + + "\"anArrayOfObjects\":\\[" + + "\\{\"a\":1,\"b\":2\\}," + + "\\{\"b\":2,\"c\":3\\}," + + "\\{\"c\":3,\"d\":4\\}" + + "\\]," + + "\"logging.googleapis.com/sourceLocation\":" + + "\\{\"file\":\"ReflectionUtils.java\"," + + "\"line\":\"\\d+\"," + + "\"function\":\"org.junit.platform.commons.util.ReflectionUtils.invokeMethod\"" + + "\\}," + + "\"message\":\"Hello, world!\"," + + "\"severity\":\"INFO\"," + + "\"timestamp\":\\{\"seconds\":\\d+,\"nanos\":\\d+\\}" + + "\\}\n"), + List.of(formattingResult)); + } + + static ExtLogRecord makeNestedRecord() { + var logRecord = makeSimpleRecord(); + logRecord.setParameters( + new Object[] { + (StructuredParameter) + () -> + JSON.createObjectBuilder() + .add("anObject", JSON.createObjectBuilder().add("a", "b\nc\nd\0\1\2e")) + .add("anArray", JSON.createArrayBuilder().add(1).add(2).add(3)) + .add( + "anArrayOfObjects", + JSON.createArrayBuilder() + .add(JSON.createObjectBuilder().add("a", 1).add("b", 2)) + .add(JSON.createObjectBuilder().add("b", 2).add("c", 3)) + .add(JSON.createObjectBuilder().add("c", 3).add("d", 4))) + }); + return logRecord; + } } |