Skip to content

Commit 23d5bd6

Browse files
authored
Reduce allocations for formatting exception stack traces (#178)
1 parent 4a7a6b5 commit 23d5bd6

File tree

3 files changed

+68
-9
lines changed

3 files changed

+68
-9
lines changed

ecs-logging-core/src/main/java/co/elastic/logging/EcsJsonSerializer.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.io.Writer;
2929
import java.util.List;
3030
import java.util.Map;
31+
import java.util.regex.Matcher;
3132
import java.util.regex.Pattern;
3233

3334
public class EcsJsonSerializer {
@@ -212,6 +213,10 @@ public static void serializeException(StringBuilder builder, Throwable thrown, b
212213
}
213214

214215
public static void serializeException(StringBuilder builder, String exceptionClassName, String exceptionMessage, String stackTrace, boolean stackTraceAsArray) {
216+
serializeException(builder, exceptionClassName, exceptionMessage, (CharSequence) stackTrace, stackTraceAsArray);
217+
}
218+
219+
public static void serializeException(StringBuilder builder, String exceptionClassName, CharSequence exceptionMessage, CharSequence stackTrace, boolean stackTraceAsArray) {
215220
builder.append("\"error.type\":\"");
216221
JsonUtils.quoteAsString(exceptionClassName, builder);
217222
builder.append("\",");
@@ -258,16 +263,44 @@ public void println() {
258263
removeIfEndsWith(jsonBuilder, ",");
259264
}
260265

261-
private static void formatStackTraceAsArray(StringBuilder builder, String stackTrace) {
266+
private static void formatStackTraceAsArray(StringBuilder builder, CharSequence stackTrace) {
262267
builder.append(NEW_LINE);
263-
for (String line : NEW_LINE_PATTERN.split(stackTrace)) {
264-
builder.append("\t\"");
265-
JsonUtils.quoteAsString(line, builder);
266-
builder.append("\",");
267-
builder.append(NEW_LINE);
268+
269+
// splits the stackTrace by new lines
270+
Matcher matcher = NEW_LINE_PATTERN.matcher(stackTrace);
271+
if (matcher.find()) {
272+
int index = 0;
273+
do {
274+
int start = matcher.start();
275+
int end = matcher.end();
276+
if (index == 0 && index == start && start == end) {
277+
// no empty leading substring included for zero-width match
278+
// at the beginning of the input char sequence.
279+
continue;
280+
}
281+
282+
// append non-last line
283+
appendStackTraceLine(builder, stackTrace, index, start);
284+
builder.append(',');
285+
builder.append(NEW_LINE);
286+
index = end;
287+
} while (matcher.find());
288+
289+
int length = stackTrace.length();
290+
if (index < length) {
291+
// append remaining line
292+
appendStackTraceLine(builder, stackTrace, index, length);
293+
}
294+
} else {
295+
// no newlines found, add entire stack trace as single element
296+
appendStackTraceLine(builder, stackTrace, 0, stackTrace.length());
268297
}
269-
removeIfEndsWith(builder, NEW_LINE);
270-
removeIfEndsWith(builder, ",");
298+
}
299+
300+
private static void appendStackTraceLine(StringBuilder builder, CharSequence stackTrace, int start, int end) {
301+
builder.append("\t\"");
302+
JsonUtils.quoteAsString(stackTrace, start, end, builder);
303+
builder.append("\"");
271304
}
272305

273306
public static void removeIfEndsWith(StringBuilder sb, String ending) {

ecs-logging-core/src/main/java/co/elastic/logging/JsonUtils.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,21 @@ public final class JsonUtils {
5858
}
5959

6060
public static void quoteAsString(CharSequence content, StringBuilder sb) {
61+
if (content == null) {
62+
sb.append("null");
63+
return;
64+
}
65+
quoteAsString(content, 0, content.length(), sb);
66+
}
67+
68+
public static void quoteAsString(CharSequence content, int start, int end, StringBuilder sb) {
6169
if (content == null) {
6270
sb.append("null");
6371
return;
6472
}
6573
final int[] escCodes = sOutputEscapes128;
6674
final int escLen = escCodes.length;
67-
for (int i = 0, len = content.length(); i < len; ++i) {
75+
for (int i = start; i < end; ++i) {
6876
char c = content.charAt(i);
6977
if (c >= escLen || escCodes[c] == 0) {
7078
sb.append(c);

ecs-logging-core/src/test/java/co/elastic/logging/EcsJsonSerializerTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,31 @@ void serializeExceptionWithStackTraceAsArray() throws JsonProcessingException {
148148
jsonBuilder.append('}');
149149

150150
JsonNode jsonNode = objectMapper.readTree(jsonBuilder.toString());
151+
System.out.println(jsonNode.toPrettyString());
151152
assertThat(jsonNode.get(ERROR_TYPE).textValue()).isEqualTo("className");
152153
assertThat(jsonNode.get(ERROR_STACK_TRACE).isArray()).isTrue();
154+
assertThat(jsonNode.get(ERROR_STACK_TRACE).size()).isEqualTo(2);
153155
assertThat(jsonNode.get(ERROR_STACK_TRACE).get(0).textValue()).isEqualTo("stacktrace");
154156
assertThat(jsonNode.get(ERROR_STACK_TRACE).get(1).textValue()).isEqualTo("caused by error");
155157
assertThat(jsonNode.get(ERROR_MESSAGE).textValue()).isEqualTo("message");
156158
}
157159

160+
@Test
161+
void serializeExceptionWithSingleLineStackTraceAsArray() throws JsonProcessingException {
162+
StringBuilder jsonBuilder = new StringBuilder();
163+
jsonBuilder.append('{');
164+
EcsJsonSerializer.serializeException(jsonBuilder, "className", "message", "caused by error", true);
165+
jsonBuilder.append('}');
166+
System.out.println(jsonBuilder);
167+
JsonNode jsonNode = objectMapper.readTree(jsonBuilder.toString());
168+
System.out.println(jsonNode.toPrettyString());
169+
assertThat(jsonNode.get(ERROR_TYPE).textValue()).isEqualTo("className");
170+
assertThat(jsonNode.get(ERROR_STACK_TRACE).isArray()).isTrue();
171+
assertThat(jsonNode.get(ERROR_STACK_TRACE).size()).isEqualTo(1);
172+
assertThat(jsonNode.get(ERROR_STACK_TRACE).get(0).textValue()).isEqualTo("caused by error");
173+
assertThat(jsonNode.get(ERROR_MESSAGE).textValue()).isEqualTo("message");
174+
}
175+
158176
@Test
159177
void serializeExceptionWithNullMessage() throws JsonProcessingException {
160178
StringBuilder jsonBuilder = new StringBuilder();

0 commit comments

Comments
 (0)