From 108e46841103ba9ee488706ddcd5f83c0dc775a6 Mon Sep 17 00:00:00 2001 From: "Spindler, Justin" Date: Tue, 5 Apr 2022 10:21:41 -0400 Subject: [PATCH 1/2] Reduce allocations for formatting exception stack traces --- .../co/elastic/logging/EcsJsonSerializer.java | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index 3dbb5cf4..1a317d00 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -28,6 +28,7 @@ import java.io.Writer; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class EcsJsonSerializer { @@ -212,6 +213,10 @@ public static void serializeException(StringBuilder builder, Throwable thrown, b } public static void serializeException(StringBuilder builder, String exceptionClassName, String exceptionMessage, String stackTrace, boolean stackTraceAsArray) { + serializeException(builder, exceptionClassName, exceptionMessage, (CharSequence) stackTrace, stackTraceAsArray); + } + + public static void serializeException(StringBuilder builder, String exceptionClassName, CharSequence exceptionMessage, CharSequence stackTrace, boolean stackTraceAsArray) { builder.append("\"error.type\":\""); JsonUtils.quoteAsString(exceptionClassName, builder); builder.append("\","); @@ -258,16 +263,48 @@ public void println() { removeIfEndsWith(jsonBuilder, ","); } - private static void formatStackTraceAsArray(StringBuilder builder, String stackTrace) { + private static void formatStackTraceAsArray(StringBuilder builder, CharSequence stackTrace) { builder.append(NEW_LINE); - for (String line : NEW_LINE_PATTERN.split(stackTrace)) { + + // splits the stackTrace by new lines + Matcher matcher = NEW_LINE_PATTERN.matcher(stackTrace); + if (matcher.find()) { + int index = 0; + do { + int start = matcher.start(); + int end = matcher.end(); + if (index == 0 && index == start && start == end) { + // no empty leading substring included for zero-width match + // at the beginning of the input char sequence. + continue; + } + + // append line + CharSequence match = stackTrace.subSequence(index, matcher.start()); + builder.append("\t\""); + JsonUtils.quoteAsString(match, builder); + builder.append("\","); + builder.append(NEW_LINE); + index = end; + } while (matcher.find()); + + int length = stackTrace.length(); + if (index < length) { + // append remaining line + CharSequence remaining = stackTrace.subSequence(index, length); + builder.append("\t\""); + JsonUtils.quoteAsString(remaining, builder); + builder.append("\""); + } + + removeIfEndsWith(builder, NEW_LINE); + removeIfEndsWith(builder, ","); + } else { + // no newlines found, add entire stack trace as single element builder.append("\t\""); - JsonUtils.quoteAsString(line, builder); - builder.append("\","); - builder.append(NEW_LINE); + JsonUtils.quoteAsString(stackTrace, builder); + builder.append("\""); } - removeIfEndsWith(builder, NEW_LINE); - removeIfEndsWith(builder, ","); } public static void removeIfEndsWith(StringBuilder sb, String ending) { From 24697530f31a6ca08a9c62c2f2ad938f30546c6b Mon Sep 17 00:00:00 2001 From: eyalkoren <41850454+eyalkoren@users.noreply.github.com> Date: Tue, 26 Apr 2022 17:30:16 +0300 Subject: [PATCH 2/2] Remove more serialization allocations --- .../co/elastic/logging/EcsJsonSerializer.java | 26 ++++++++----------- .../java/co/elastic/logging/JsonUtils.java | 10 ++++++- .../logging/EcsJsonSerializerTest.java | 18 +++++++++++++ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java index 1a317d00..deb77a15 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java @@ -279,11 +279,9 @@ private static void formatStackTraceAsArray(StringBuilder builder, CharSequence continue; } - // append line - CharSequence match = stackTrace.subSequence(index, matcher.start()); - builder.append("\t\""); - JsonUtils.quoteAsString(match, builder); - builder.append("\","); + // append non-last line + appendStackTraceLine(builder, stackTrace, index, start); + builder.append(','); builder.append(NEW_LINE); index = end; } while (matcher.find()); @@ -291,22 +289,20 @@ private static void formatStackTraceAsArray(StringBuilder builder, CharSequence int length = stackTrace.length(); if (index < length) { // append remaining line - CharSequence remaining = stackTrace.subSequence(index, length); - builder.append("\t\""); - JsonUtils.quoteAsString(remaining, builder); - builder.append("\""); + appendStackTraceLine(builder, stackTrace, index, length); } - - removeIfEndsWith(builder, NEW_LINE); - removeIfEndsWith(builder, ","); } else { // no newlines found, add entire stack trace as single element - builder.append("\t\""); - JsonUtils.quoteAsString(stackTrace, builder); - builder.append("\""); + appendStackTraceLine(builder, stackTrace, 0, stackTrace.length()); } } + private static void appendStackTraceLine(StringBuilder builder, CharSequence stackTrace, int start, int end) { + builder.append("\t\""); + JsonUtils.quoteAsString(stackTrace, start, end, builder); + builder.append("\""); + } + public static void removeIfEndsWith(StringBuilder sb, String ending) { if (endsWith(sb, ending)) { sb.setLength(sb.length() - ending.length()); diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/JsonUtils.java b/ecs-logging-core/src/main/java/co/elastic/logging/JsonUtils.java index d2d696c6..fc53a8ca 100644 --- a/ecs-logging-core/src/main/java/co/elastic/logging/JsonUtils.java +++ b/ecs-logging-core/src/main/java/co/elastic/logging/JsonUtils.java @@ -58,13 +58,21 @@ public final class JsonUtils { } public static void quoteAsString(CharSequence content, StringBuilder sb) { + if (content == null) { + sb.append("null"); + return; + } + quoteAsString(content, 0, content.length(), sb); + } + + public static void quoteAsString(CharSequence content, int start, int end, StringBuilder sb) { if (content == null) { sb.append("null"); return; } final int[] escCodes = sOutputEscapes128; final int escLen = escCodes.length; - for (int i = 0, len = content.length(); i < len; ++i) { + for (int i = start; i < end; ++i) { char c = content.charAt(i); if (c >= escLen || escCodes[c] == 0) { sb.append(c); diff --git a/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java b/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java index 73f088d2..ec851e8a 100644 --- a/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java +++ b/ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java @@ -148,13 +148,31 @@ void serializeExceptionWithStackTraceAsArray() throws JsonProcessingException { jsonBuilder.append('}'); JsonNode jsonNode = objectMapper.readTree(jsonBuilder.toString()); + System.out.println(jsonNode.toPrettyString()); assertThat(jsonNode.get(ERROR_TYPE).textValue()).isEqualTo("className"); assertThat(jsonNode.get(ERROR_STACK_TRACE).isArray()).isTrue(); + assertThat(jsonNode.get(ERROR_STACK_TRACE).size()).isEqualTo(2); assertThat(jsonNode.get(ERROR_STACK_TRACE).get(0).textValue()).isEqualTo("stacktrace"); assertThat(jsonNode.get(ERROR_STACK_TRACE).get(1).textValue()).isEqualTo("caused by error"); assertThat(jsonNode.get(ERROR_MESSAGE).textValue()).isEqualTo("message"); } + @Test + void serializeExceptionWithSingleLineStackTraceAsArray() throws JsonProcessingException { + StringBuilder jsonBuilder = new StringBuilder(); + jsonBuilder.append('{'); + EcsJsonSerializer.serializeException(jsonBuilder, "className", "message", "caused by error", true); + jsonBuilder.append('}'); + System.out.println(jsonBuilder); + JsonNode jsonNode = objectMapper.readTree(jsonBuilder.toString()); + System.out.println(jsonNode.toPrettyString()); + assertThat(jsonNode.get(ERROR_TYPE).textValue()).isEqualTo("className"); + assertThat(jsonNode.get(ERROR_STACK_TRACE).isArray()).isTrue(); + assertThat(jsonNode.get(ERROR_STACK_TRACE).size()).isEqualTo(1); + assertThat(jsonNode.get(ERROR_STACK_TRACE).get(0).textValue()).isEqualTo("caused by error"); + assertThat(jsonNode.get(ERROR_MESSAGE).textValue()).isEqualTo("message"); + } + @Test void serializeExceptionWithNullMessage() throws JsonProcessingException { StringBuilder jsonBuilder = new StringBuilder();