From c7c25e3cc18a7f36cb216aa419f7a04714ca0d65 Mon Sep 17 00:00:00 2001 From: "Spindler, Justin" Date: Wed, 11 Nov 2020 11:00:20 -0500 Subject: [PATCH] Refactor throwable serialization into implementation of interface --- .../logging/DefaultThrowableSerializer.java | 162 ++++++++++++++++++ .../co/elastic/logging/EcsJsonSerializer.java | 136 +-------------- .../elastic/logging/StringBuilderWriter.java | 84 +++++++++ .../elastic/logging/ThrowableSerializer.java | 30 ++++ 4 files changed, 284 insertions(+), 128 deletions(-) create mode 100644 ecs-logging-core/src/main/java/co/elastic/logging/DefaultThrowableSerializer.java create mode 100644 ecs-logging-core/src/main/java/co/elastic/logging/StringBuilderWriter.java create mode 100644 ecs-logging-core/src/main/java/co/elastic/logging/ThrowableSerializer.java diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/DefaultThrowableSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/DefaultThrowableSerializer.java new file mode 100644 index 00000000..e51cce26 --- /dev/null +++ b/ecs-logging-core/src/main/java/co/elastic/logging/DefaultThrowableSerializer.java @@ -0,0 +1,162 @@ +/*- + * #%L + * Java ECS logging + * %% + * Copyright (C) 2019 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * #L% + */ +package co.elastic.logging; + +import java.io.PrintWriter; +import java.util.regex.Pattern; + +import static co.elastic.logging.EcsJsonSerializer.getMessageStringBuilder; + +public class DefaultThrowableSerializer implements ThrowableSerializer { + static final String NEW_LINE = System.getProperty("line.separator"); + static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\n"); + + private static final ThrowableSerializer instance = new DefaultThrowableSerializer(); + + public static ThrowableSerializer getInstance() { + return instance; + } + + @Override + public void serialize(StringBuilder builder, Throwable thrown, boolean stackTraceAsArray) { + serializeErrorType(builder, thrown); + serializeErrorMessage(builder, thrown); + serializeStackTrace(builder, thrown, stackTraceAsArray); + } + + @Override + public void serialize(StringBuilder builder, String exceptionClassName, String exceptionMessage, String stackTrace, boolean stackTraceAsArray) { + serializeErrorType(builder, exceptionClassName); + serializeErrorMessage(builder, exceptionMessage); + serializeStackTrace(builder, stackTrace, stackTraceAsArray); + } + + protected void serializeErrorType(StringBuilder builder, Throwable thrown) { + serializeErrorType(builder, formatErrorType(thrown)); + } + + protected String formatErrorType(Throwable thrown) { + return thrown.getClass().getName(); + } + + protected void serializeErrorType(StringBuilder builder, String errorType) { + builder.append("\"error.type\":\""); + JsonUtils.quoteAsString(errorType, builder); + builder.append("\","); + } + + protected void serializeErrorMessage(StringBuilder builder, Throwable thrown) { + serializeErrorMessage(builder, formatErrorMessage(thrown)); + } + + protected String formatErrorMessage(Throwable thrown) { + return thrown.getMessage(); + } + + protected void serializeErrorMessage(StringBuilder builder, String message) { + if (message != null) { + builder.append("\"error.message\":\""); + JsonUtils.quoteAsString(message, builder); + builder.append("\","); + } + } + + protected void serializeStackTrace(StringBuilder builder, Throwable thrown, boolean stackTraceAsArray) { + builder.append("\"error.stack_trace\":"); + if (stackTraceAsArray) { + serializeStackTraceAsArray(builder, thrown); + } else { + serializeStackTraceAsString(builder, thrown); + } + } + + protected void serializeStackTraceAsArray(final StringBuilder builder, Throwable thrown) { + builder.append('['); + final StringBuilder buffer = getMessageStringBuilder(); + final PrintWriter pw = new PrintWriter(new StringBuilderWriter(buffer), true) { + boolean firstElement = true; + @Override + public void println() { + flush(); + if (firstElement) { + firstElement = false; + } else { + builder.append(','); + } + builder.append('\"'); + JsonUtils.quoteAsString(buffer, builder); + builder.append('\"'); + buffer.setLength(0); + } + }; + thrown.printStackTrace(pw); + builder.append(']'); + } + + protected void serializeStackTraceAsString(StringBuilder builder, Throwable thrown) { + builder.append('\"'); + JsonUtils.quoteAsString(formatStackTrace(thrown), builder); + builder.append('\"'); + } + + protected CharSequence formatStackTrace(Throwable thrown) { + StringBuilder buffer = getMessageStringBuilder(); + final PrintWriter pw = new PrintWriter(new StringBuilderWriter(buffer)); + thrown.printStackTrace(pw); + pw.flush(); + return buffer; + } + + protected void serializeStackTrace(StringBuilder builder, String stackTrace, boolean stackTraceAsArray) { + builder.append("\"error.stack_trace\":"); + if (stackTraceAsArray) { + serializeStackTraceAsArray(builder, stackTrace); + } else { + serializeStackTraceAsString(builder, stackTrace); + } + } + + protected void serializeStackTraceAsString(StringBuilder builder, String stackTrace) { + builder.append('\"'); + JsonUtils.quoteAsString(stackTrace, builder); + builder.append('\"'); + } + + protected void serializeStackTraceAsArray(StringBuilder builder, String stackTrace) { + builder.append('['); + boolean firstElement = true; + for (String line : NEW_LINE_PATTERN.split(stackTrace)) { + if (firstElement) { + firstElement = false; + } else { + builder.append(','); + } + builder.append('\"'); + JsonUtils.quoteAsString(line, builder); + builder.append('\"'); + } + builder.append(']'); + } +} 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 850dd311..e9eed45b 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 @@ -24,17 +24,12 @@ */ package co.elastic.logging; -import java.io.PrintWriter; -import java.io.Writer; import java.util.Map; -import java.util.regex.Pattern; public class EcsJsonSerializer { private static final TimestampSerializer TIMESTAMP_SERIALIZER = new TimestampSerializer(); private static final ThreadLocal messageStringBuilder = new ThreadLocal(); - private static final String NEW_LINE = System.getProperty("line.separator"); - private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\\n"); public static CharSequence toNullSafeString(final CharSequence s) { return s == null ? "" : s; @@ -166,79 +161,21 @@ public static void serializeMDC(StringBuilder builder, Map properties } public static void serializeException(StringBuilder builder, Throwable thrown, boolean stackTraceAsArray) { - if (thrown != null) { - builder.append("\"error.type\":\""); - JsonUtils.quoteAsString(thrown.getClass().getName(), builder); - builder.append("\","); - - String message = thrown.getMessage(); - if (message != null) { - builder.append("\"error.message\":\""); - JsonUtils.quoteAsString(message, builder); - builder.append("\","); - } - if (stackTraceAsArray) { - builder.append("\"error.stack_trace\":[").append(NEW_LINE); - formatThrowableAsArray(builder, thrown); - builder.append("]"); - } else { - builder.append("\"error.stack_trace\":\""); - JsonUtils.quoteAsString(formatThrowable(thrown), builder); - builder.append("\""); - } - } + serializeException(builder, thrown, stackTraceAsArray, DefaultThrowableSerializer.getInstance()); } - public static void serializeException(StringBuilder builder, String exceptionClassName, String exceptionMessage, String stackTrace, boolean stackTraceAsArray) { - builder.append("\"error.type\":\""); - JsonUtils.quoteAsString(exceptionClassName, builder); - builder.append("\","); - builder.append("\"error.message\":\""); - JsonUtils.quoteAsString(exceptionMessage, builder); - builder.append("\","); - if (stackTraceAsArray) { - builder.append("\"error.stack_trace\":[").append(NEW_LINE); - for (String line : NEW_LINE_PATTERN.split(stackTrace)) { - appendQuoted(builder, line); - } - builder.append("]"); - } else { - builder.append("\"error.stack_trace\":\""); - JsonUtils.quoteAsString(stackTrace, builder); - builder.append("\""); + public static void serializeException(StringBuilder builder, Throwable thrown, boolean stackTraceAsArray, ThrowableSerializer throwableSerializer) { + if (thrown != null) { + throwableSerializer.serialize(builder, thrown, stackTraceAsArray); } } - private static void appendQuoted(StringBuilder builder, CharSequence content) { - builder.append('"'); - JsonUtils.quoteAsString(content, builder); - builder.append('"'); - } - - private static CharSequence formatThrowable(final Throwable throwable) { - StringBuilder buffer = getMessageStringBuilder(); - final PrintWriter pw = new PrintWriter(new StringBuilderWriter(buffer)); - throwable.printStackTrace(pw); - pw.flush(); - return buffer; + public static void serializeException(StringBuilder builder, String exceptionClassName, String exceptionMessage, String stackTrace, boolean stackTraceAsArray) { + serializeException(builder, exceptionClassName, exceptionMessage, stackTrace, stackTraceAsArray, DefaultThrowableSerializer.getInstance()); } - private static void formatThrowableAsArray(final StringBuilder jsonBuilder, final Throwable throwable) { - final StringBuilder buffer = getMessageStringBuilder(); - final PrintWriter pw = new PrintWriter(new StringBuilderWriter(buffer), true) { - @Override - public void println() { - flush(); - jsonBuilder.append("\t\""); - JsonUtils.quoteAsString(buffer, jsonBuilder); - jsonBuilder.append("\","); - jsonBuilder.append(NEW_LINE); - buffer.setLength(0); - } - }; - throwable.printStackTrace(pw); - removeIfEndsWith(jsonBuilder, NEW_LINE); - removeIfEndsWith(jsonBuilder, ","); + public static void serializeException(StringBuilder builder, String exceptionClassName, String exceptionMessage, String stackTrace, boolean stackTraceAsArray, ThrowableSerializer throwableSerializer) { + throwableSerializer.serialize(builder, exceptionClassName, exceptionMessage, stackTrace, stackTraceAsArray); } public static void removeIfEndsWith(StringBuilder sb, String ending) { @@ -277,61 +214,4 @@ public static String computeEventDataset(String eventDataset, String serviceName } return eventDataset; } - - private static class StringBuilderWriter extends Writer { - - private final StringBuilder buffer; - - StringBuilderWriter(StringBuilder buffer) { - this.buffer = buffer; - } - - @Override - public Writer append(CharSequence csq) { - buffer.append(csq); - return this; - } - - @Override - public void write(String str) { - buffer.append(str); - } - - @Override - public void write(String str, int off, int len) { - buffer.append(str, off, len); - } - - @Override - public Writer append(CharSequence csq, int start, int end) { - buffer.append(csq, start, end); - return this; - } - - @Override - public Writer append(char c) { - buffer.append(c); - return this; - } - - @Override - public void write(int c) { - buffer.append((char) c); - } - - @Override - public void write(char[] cbuf, int off, int len) { - buffer.append(cbuf, off, len); - } - - @Override - public void flush() { - - } - - @Override - public void close() { - - } - } } diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/StringBuilderWriter.java b/ecs-logging-core/src/main/java/co/elastic/logging/StringBuilderWriter.java new file mode 100644 index 00000000..999d1a39 --- /dev/null +++ b/ecs-logging-core/src/main/java/co/elastic/logging/StringBuilderWriter.java @@ -0,0 +1,84 @@ +/*- + * #%L + * Java ECS logging + * %% + * Copyright (C) 2019 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * #L% + */ +package co.elastic.logging; + +import java.io.Writer; + +final class StringBuilderWriter extends Writer { + + private final StringBuilder buffer; + + StringBuilderWriter(StringBuilder buffer) { + this.buffer = buffer; + } + + @Override + public Writer append(CharSequence csq) { + buffer.append(csq); + return this; + } + + @Override + public void write(String str) { + buffer.append(str); + } + + @Override + public void write(String str, int off, int len) { + buffer.append(str, off, len); + } + + @Override + public Writer append(CharSequence csq, int start, int end) { + buffer.append(csq, start, end); + return this; + } + + @Override + public Writer append(char c) { + buffer.append(c); + return this; + } + + @Override + public void write(int c) { + buffer.append((char) c); + } + + @Override + public void write(char[] cbuf, int off, int len) { + buffer.append(cbuf, off, len); + } + + @Override + public void flush() { + + } + + @Override + public void close() { + + } +} diff --git a/ecs-logging-core/src/main/java/co/elastic/logging/ThrowableSerializer.java b/ecs-logging-core/src/main/java/co/elastic/logging/ThrowableSerializer.java new file mode 100644 index 00000000..8f47d760 --- /dev/null +++ b/ecs-logging-core/src/main/java/co/elastic/logging/ThrowableSerializer.java @@ -0,0 +1,30 @@ +/*- + * #%L + * Java ECS logging + * %% + * Copyright (C) 2019 - 2020 Elastic and contributors + * %% + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * #L% + */ +package co.elastic.logging; + +public interface ThrowableSerializer { + void serialize(StringBuilder builder, Throwable thrown, boolean stackTraceAsArray); + void serialize(StringBuilder builder, String exceptionClassName, String exceptionMessage, String stackTrace, boolean stackTraceAsArray); +}