diff --git a/README.md b/README.md index f627e046..00a9b059 100644 --- a/README.md +++ b/README.md @@ -5,19 +5,31 @@ ## Summary -This is a collection of support libraries for Java applications (Java 8 and above) running on Cloud Foundry that serves three main purposes: It provides (a) means to emit *structured application log messages*, (b) instrument parts of your application stack to *collect request metrics* and (c) allow production of *custom metrics*. +This is a collection of support libraries for Java applications (Java 8 and above) that serves three main purposes: -When we say structured, we actually mean in JSON format. In that sense, it shares ideas with [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) (and a first internal version was actually based on it), but takes a simpler approach as we want to ensure that these structured messages adhere to standardized formats. With such standardized formats in place, it becomes much easier to ingest, process and search such messages in log analysis stacks such as [ELK](https://www.elastic.co/webinars/introduction-elk-stack). +1. Provide means to emit *structured application log messages* +2. Instrument parts of your application stack to *collect request metrics* +3. Allow production of *custom metrics*. + +The libraries started out to support applications running on Cloud Foundry. +This integration has become optional. +The library can be used in any runtime environment such as Kubernetes or Kyma. + +When we say structured, we actually mean in JSON format. +In that sense, it shares ideas with [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder), but takes a simpler approach as we want to ensure that these structured messages adhere to standardized formats. +With such standardized formats in place, it becomes much easier to ingest, process and search such messages in log analysis stacks such as [ELK](https://www.elastic.co/webinars/introduction-elk-stack). If you're interested in the specifications of these standardized formats, you may want to have a closer look at the `fields.yml` files in the [beats folder](./cf-java-logging-support-core/beats). While [logstash-logback-encoder](https://github.com/logstash/logstash-logback-encoder) is tied to [logback](http://logback.qos.ch/), we've tried to keep implementation neutral and have implemented the core functionality on top of [slf4j](http://www.slf4j.org/), but provided implementations for both [logback](http://logback.qos.ch/) and [log4j2](http://logging.apache.org/log4j/2.x/) (and we're open to contributions that would support other implementations). -The instrumentation part is currently focusing on providing [request filters for Java Servlets](http://www.oracle.com/technetwork/java/filters-137243.html) and [client and server filters for Jersey](https://jersey.java.net/documentation/latest/filters-and-interceptors.html), but again, we're open to contributions for other APIs and frameworks. +The instrumentation part is currently focusing on providing [request filters for Java Servlets](http://www.oracle.com/technetwork/java/filters-137243.html), but again, we're open to contributions for other APIs and frameworks. -The custom metrics instrumentation allows users to easily define and emit custom metrics. The different modules configure all necessary components and make it possible to define custom metrics with minimal code change. +The custom metrics instrumentation allows users to easily define and emit custom metrics. +The different modules configure all necessary components and make it possible to define custom metrics with minimal code change. +Once collected, custom metrics are sent as special log message. -Lastly, there are also two sibling projects on [node.js logging support](https://github.com/SAP/cf-nodejs-logging-support) and [python logging support](https://github.com/SAP/cf-python-logging-support). +Lastly, there is also a project on [node.js logging support](https://github.com/SAP/cf-nodejs-logging-support). ## Features and dependencies @@ -95,7 +107,7 @@ Again, we don't include dependencies to those implementation backends ourselves, ch.qos.logback logback-classic - 1.2.10 + 1.2.11 ``` @@ -110,12 +122,12 @@ Again, we don't include dependencies to those implementation backends ourselves, org.apache.logging.log4j log4j-slf4j-impl - 2.17.1 + 2.17.2 org.apache.logging.log4j log4j-core - 2.17.1 + 2.17.2 ``` diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultCustomFieldsConverter.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultCustomFieldsConverter.java deleted file mode 100644 index 15a92a63..00000000 --- a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultCustomFieldsConverter.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.jr.ob.JSON; -import com.fasterxml.jackson.jr.ob.JSONComposer; -import com.fasterxml.jackson.jr.ob.JSONObjectException; -import com.fasterxml.jackson.jr.ob.comp.ArrayComposer; -import com.fasterxml.jackson.jr.ob.comp.ObjectComposer; -import com.sap.hcp.cf.logging.common.customfields.CustomField; - -public class DefaultCustomFieldsConverter { - - private String fieldName = null; - private boolean embed = true; - private List customFieldKeyNames; - - public void setFieldName(String fieldName) { - if (fieldName != null) { - this.fieldName = fieldName; - embed = false; - } - } - - public void setCustomFieldKeyNames(List customFieldKeyNames) { - this.customFieldKeyNames = customFieldKeyNames; - } - - private static class LoggerHolder { - static final Logger LOG = LoggerFactory.getLogger(LoggerHolder.class.getEnclosingClass()); - } - - public void convert(StringBuilder appendTo, Map mdcPropertiesMap, Object... arguments) { - if (customFieldKeyNames.isEmpty()) { - return; - } - Map customFields = getRegisteredCustomFields(arguments); - Map mdcCustomFields = getRegisteredMdcCustomFields(mdcPropertiesMap); - if (!customFields.isEmpty() || !mdcCustomFields.isEmpty()) { - try { - ArrayComposer>> oc = startJson(appendTo); - addCustomFields(oc, customFields, mdcCustomFields); - finishJson(oc, appendTo); - } catch (Exception ex) { - /* -- avoids substitute logger warnings on startup -- */ - LoggerHolder.LOG.error("Conversion failed ", ex); - } - } - } - - private Map getRegisteredMdcCustomFields(Map mdcPropertiesMap) { - if (mdcPropertiesMap.isEmpty()) { - return Collections.emptyMap(); - } - Map mdcCustomFields = new HashMap<>(mdcPropertiesMap.size()); - for (Map.Entry current : mdcPropertiesMap.entrySet()) { - if (customFieldKeyNames.contains(current.getKey())) { - mdcCustomFields.put(current.getKey(), current.getValue()); - } - } - return mdcCustomFields; - } - - private Map getRegisteredCustomFields(Object... arguments) { - if (arguments == null) { - return Collections.emptyMap(); - } - Map result = new HashMap<>(); - for (Object current : arguments) { - if (current instanceof CustomField) { - CustomField field = (CustomField) current; - if (customFieldKeyNames.contains(field.getKey())) { - result.put(field.getKey(), field); - } - } - } - return result; - } - - private ArrayComposer>> startJson(StringBuilder appendTo) - throws IOException, JSONObjectException, JsonProcessingException { - if (!embed) { - appendTo.append(JSON.std.asString(fieldName)).append(":"); - } - /* - * -- no matter whether we embed or not, it seems easier to compose -- a JSON - * object from the key/value pairs. -- if we embed that object, we simply chop - * off the outermost curly braces. - */ - return JSON.std.composeString().startObject().startArrayField("string"); - } - - private void addCustomFields(ArrayComposer>> oc, - Map customFields, Map mdcCustomFields) - throws IOException, JsonProcessingException { - for (int i = 0; i < customFieldKeyNames.size(); i++) { - String key = customFieldKeyNames.get(i); - String value = mdcCustomFields.get(key); - // Let argument CustomField take precedence over MDC - CustomField field = customFields.get(key); - if (field != null) { - value = String.valueOf(field.getValue()); - } - if (value != null) { - oc.startObject().put("k", key).put("v", value).put("i", i).end(); - } - } - } - - private void finishJson(ArrayComposer>> oc, StringBuilder appendTo) - throws IOException, JsonProcessingException { - ObjectComposer> end = oc.end(); - String result = end.end().finish().trim(); - if (embed) { - /* -- chop off curly braces -- */ - appendTo.append(result.substring(1, result.length() - 1)); - } else { - appendTo.append(result); - } - } -} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultMessageConverter.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultMessageConverter.java deleted file mode 100644 index e673437a..00000000 --- a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultMessageConverter.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.jr.ob.JSON; - -public class DefaultMessageConverter { - - private boolean flatten = false; - private boolean escape = false; - private static final String LBRACE = "{"; - private static final String RBRACE = "}"; - private static final String LBRACKET = "["; - private static final String RBRACKET = "]"; - - public boolean isFlatten() { - return flatten; - } - - public void setFlatten(boolean flatten) { - this.flatten = flatten; - } - - public boolean isEscape() { - return escape; - } - - public void setEscape(boolean escape) { - this.escape = escape; - } - - private static class LoggerHolder { - static final Logger LOG = LoggerFactory.getLogger(LoggerHolder.class.getEnclosingClass()); - } - - public void convert(String message, StringBuilder appendTo) { - if (message != null) { - String result; - if (flatten) { - result = flattenMsg(message); - } else { - result = message; - } - if (escape) { - try { - appendTo.append(JSON.std.asString(result)); - } catch (Exception ex) { - /* -- avoids substitute logger warnings on startup -- */ - LoggerHolder.LOG.error("Conversion failed ", ex); - appendTo.append(result); - } - } else { - appendTo.append(result); - } - } else { - appendTo.append("null"); - } - - } - - private String flattenMsg(String msg) { - String trimmedMsg = msg.trim(); - - if (trimmedMsg.indexOf(LBRACE) == 0 && trimmedMsg.lastIndexOf(RBRACE) == trimmedMsg.length() - 1) { - return trimmedMsg.substring(1, trimmedMsg.length() - 1); - } - if (trimmedMsg.indexOf(LBRACKET) == 0 && trimmedMsg.lastIndexOf(RBRACKET) == trimmedMsg.length() - 1) { - return trimmedMsg.substring(1, trimmedMsg.length() - 1); - } - return msg; - } - -} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultPropertiesConverter.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultPropertiesConverter.java deleted file mode 100644 index 6f69f8c2..00000000 --- a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultPropertiesConverter.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; - -import com.fasterxml.jackson.jr.ob.JSON; -import com.fasterxml.jackson.jr.ob.JSONComposer; -import com.fasterxml.jackson.jr.ob.comp.ObjectComposer; -import com.sap.hcp.cf.logging.common.Defaults; -import com.sap.hcp.cf.logging.common.LogContext; - -public class DefaultPropertiesConverter { - - private final Set exclusions = new HashSet(); - private boolean sendDefaultValues = false; - - public DefaultPropertiesConverter() { - } - - public void setExclusions(List exclusionList) { - if (exclusionList != null) { - for (String exclusion: exclusionList) { - exclusions.add(exclusion); - } - } - } - - public void setSendDefaultValues(boolean sendDefaultValues) { - this.sendDefaultValues = sendDefaultValues; - } - - private static class LoggerHolder { - static final Logger LOG = LoggerFactory.getLogger(LoggerHolder.class.getEnclosingClass()); - } - - public void convert(StringBuilder appendTo, Map eventProperties) { - Map properties = mergeContextMaps(eventProperties); - if (properties != null && !properties.isEmpty()) { - try { - /* - * -- let's compose an JSON object, then chop off the outermost - * curly braces - */ - ObjectComposer> oc = JSON.std.composeString().startObject(); - for (Entry p: properties.entrySet()) { - if (!exclusions.contains(p.getKey())) { - if (sendDefaultValues) { - oc.put(p.getKey(), p.getValue()); - } else { - String defaultValue = getDefaultValue(p.getKey()); - if (!defaultValue.equals(p.getValue())) { - oc.put(p.getKey(), p.getValue()); - } - } - } - } - String result = oc.end().finish().trim(); - appendTo.append(result.substring(1, result.length() - 1)); - } catch (Exception ex) { - /* -- avoids substitute logger warnings on startup -- */ - LoggerHolder.LOG.error("Conversion failed ", ex); - } - } - } - - private Map mergeContextMaps(Map eventMap) { - Map result = new HashMap(); - if (eventMap != null) { - result.putAll(eventMap); - } - LogContext.loadContextFields(); - if (MDC.getCopyOfContextMap() != null) { - for (Entry e: MDC.getCopyOfContextMap().entrySet()) { - if (!result.containsKey(e.getKey())) { - result.put(e.getKey(), e.getValue()); - } - } - } - return result; - } - - private String getDefaultValue(String key) { - String defaultValue = LogContext.getDefault(key); - return defaultValue == null ? Defaults.UNKNOWN : defaultValue; - } -} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultStacktraceConverter.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultStacktraceConverter.java deleted file mode 100644 index 29a67a4d..00000000 --- a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultStacktraceConverter.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import java.io.PrintWriter; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.jr.ob.JSON; -import com.fasterxml.jackson.jr.ob.JSONComposer; -import com.fasterxml.jackson.jr.ob.comp.ArrayComposer; - -public class DefaultStacktraceConverter extends StacktraceConverter { - public static final int MAX_SIZE = 55 * 1024; - private int maxSize; - - public DefaultStacktraceConverter() { - this(MAX_SIZE); - } - - DefaultStacktraceConverter(int maxSize) { - this.maxSize = maxSize; - } - - private static class LoggerHolder { - static final Logger LOG = LoggerFactory.getLogger(LoggerHolder.class.getEnclosingClass()); - } - - @Override - public void convert(Throwable t, StringBuilder appendTo) { - if (t == null) { - return; - } - try { - LineWriter lw = new LineWriter(); - t.printStackTrace(new PrintWriter(lw)); - List lines = lw.getLines(); - StacktraceLines stacktraceLines = new StacktraceLines(lines); - - ArrayComposer> ac = JSON.std.composeString().startArray(); - if (stacktraceLines.getTotalLineLength() <= maxSize) { - for (String line: stacktraceLines.getLines()) { - ac.add(line); - } - } else { - ac.add("-------- STACK TRACE TRUNCATED --------"); - for (String line: stacktraceLines.getFirstLines(maxSize / 3)) { - ac.add(line); - } - ac.add("-------- OMITTED --------"); - for (String line: stacktraceLines.getLastLines((maxSize / 3) * 2)) { - ac.add(line); - } - } - appendTo.append(ac.end().finish()); - } catch (Exception ex) { - /* -- avoids substitute logger warnings on startup -- */ - LoggerHolder.LOG.error("Conversion failed ", ex); - } - } -} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/StacktraceConverter.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/StacktraceConverter.java deleted file mode 100644 index d9b42c9e..00000000 --- a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/StacktraceConverter.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -public abstract class StacktraceConverter { - public abstract void convert(Throwable t, StringBuilder appendTo); - - public final static StacktraceConverter CONVERTER = new DefaultStacktraceConverter(); -} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/AbstractContextFieldSupplier.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/AbstractContextFieldSupplier.java new file mode 100644 index 00000000..a2f7505f --- /dev/null +++ b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/AbstractContextFieldSupplier.java @@ -0,0 +1,35 @@ +package com.sap.hcp.cf.logging.common.serialization; + +import java.util.HashMap; +import java.util.Map; + +import com.sap.hcp.cf.logging.common.customfields.CustomField; + +public abstract class AbstractContextFieldSupplier implements EventContextFieldSupplier { + + public AbstractContextFieldSupplier() { + super(); + } + + @Override + public Map map(T event) { + Map result = new HashMap<>(); + result.putAll(getContextMap(event)); + Object[] parameters = getParameterArray(event); + if (parameters == null) { + return result; + } + for (Object parameter: parameters) { + if (parameter instanceof CustomField) { + CustomField customField = (CustomField) parameter; + result.put(customField.getKey(), customField.getValue()); + } + } + return result; + } + + protected abstract Object[] getParameterArray(T event); + + protected abstract Map getContextMap(T event); + +} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/AbstractRequestRecordFieldSupplier.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/AbstractRequestRecordFieldSupplier.java new file mode 100644 index 00000000..ba06a8cc --- /dev/null +++ b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/AbstractRequestRecordFieldSupplier.java @@ -0,0 +1,53 @@ +package com.sap.hcp.cf.logging.common.serialization; + +import static java.util.stream.Collectors.toMap; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import com.fasterxml.jackson.jr.ob.JSON; +import com.sap.hcp.cf.logging.common.request.RequestRecord; + +public abstract class AbstractRequestRecordFieldSupplier implements EventContextFieldSupplier { + + public AbstractRequestRecordFieldSupplier() { + super(); + } + + @Override + public Map map(T event) { + if (!isRequestLog(event)) { + return Collections.emptyMap(); + } + Object[] parameterArray = getParameterArray(event); + if (parameterArray == null || parameterArray.length == 0) { + try { + Map parsed = JSON.std.mapFrom(getFormattedMessage(event)); + return parsed != null ? parsed : Collections.emptyMap(); + } catch (IOException ignored) { + return Collections.emptyMap(); + } + } + Optional requestRecord = findRequestRecord(parameterArray); + if (requestRecord.isPresent()) { + RequestRecord record = requestRecord.get(); + return record.getFields().entrySet().stream().collect(toMap(e -> e.getKey(), e -> e.getValue().getValue())); + } + return Collections.emptyMap(); + } + + private Optional findRequestRecord(Object[] parameterArray) { + return Stream.of(parameterArray).filter(o -> o instanceof RequestRecord).map(o -> (RequestRecord) o) + .findFirst(); + } + + protected abstract boolean isRequestLog(T event); + + protected abstract String getFormattedMessage(T event); + + protected abstract Object[] getParameterArray(T event); + +} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/ContextFieldConverter.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/ContextFieldConverter.java new file mode 100644 index 00000000..d73a9af0 --- /dev/null +++ b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/ContextFieldConverter.java @@ -0,0 +1,100 @@ +package com.sap.hcp.cf.logging.common.serialization; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.jr.ob.comp.ArrayComposer; +import com.fasterxml.jackson.jr.ob.comp.ComposerBase; +import com.fasterxml.jackson.jr.ob.comp.ObjectComposer; +import com.sap.hcp.cf.logging.common.Defaults; +import com.sap.hcp.cf.logging.common.Fields; +import com.sap.hcp.cf.logging.common.LogContext; + +public class ContextFieldConverter { + + private final boolean sendDefaultValues; + private final List customFieldMdcKeyNames; + private final List retainFieldMdcKeyNames; + + public ContextFieldConverter(boolean sendDefaultValues, List customFieldMdcKeyNames, + List retainFieldMdcKeyNames) { + this.sendDefaultValues = sendDefaultValues; + this.customFieldMdcKeyNames = customFieldMdcKeyNames; + this.retainFieldMdcKeyNames = retainFieldMdcKeyNames; + } + + public

void addContextFields(ObjectComposer

oc, Map contextFields) { + contextFields.keySet().stream().filter(this::isContextField).forEach(n -> addContextField(oc, n, contextFields + .get(n))); + ; + } + + private boolean isContextField(String name) { + return retainFieldMdcKeyNames.contains(name) || !customFieldMdcKeyNames.contains(name); + } + + private

void addContextField(ObjectComposer

oc, String name, Object value) { + try { + if (sendDefaultValues) { + put(oc, name, value); + } else { + String defaultValue = getDefaultValue(name); + if (!defaultValue.equals(value)) { + put(oc, name, value); + } + } + } catch (IOException ignored) { + try { + oc.put(name, "invalid value"); + } catch (IOException cause) { + throw new JsonSerializationException("Cannot create field \"" + name + "\".", cause); + } + } + } + + private

void put(ObjectComposer

oc, String name, Object value) throws IOException, + JsonProcessingException { + if (value instanceof String) { + oc.put(name, (String) value); + } else if (value instanceof Long) { + oc.put(name, ((Long) value).longValue()); + } else if (value instanceof Double) { + oc.put(name, ((Double) value).doubleValue()); + } else if (value instanceof Boolean) { + oc.put(name, ((Boolean) value).booleanValue()); + } else if (value instanceof Integer) { + oc.put(name, ((Integer) value).intValue()); + } else if (value instanceof Float) { + oc.put(name, ((Float) value).floatValue()); + } else { + oc.put(name, String.valueOf(value)); + } + } + + private String getDefaultValue(String key) { + String defaultValue = LogContext.getDefault(key); + return defaultValue == null ? Defaults.UNKNOWN : defaultValue; + } + + public

void addCustomFields(ObjectComposer

oc, Map contextFields) + throws IOException, + JsonProcessingException { + ArrayComposer>> customFieldComposer = null; + for (int i = 0; i < customFieldMdcKeyNames.size(); i++) { + String key = customFieldMdcKeyNames.get(i); + Object value = contextFields.get(key); + if (value != null) { + if (customFieldComposer == null) { + customFieldComposer = oc.startObjectField(Fields.CUSTOM_FIELDS).startArrayField("string"); + } + customFieldComposer.startObject().put("k", key).put("v", String.valueOf(value)).put("i", i).end(); + } + } + if (customFieldComposer != null) { + customFieldComposer.end().end(); + } + } + +} diff --git a/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/EventContextFieldSupplier.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/EventContextFieldSupplier.java new file mode 100644 index 00000000..940828dc --- /dev/null +++ b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/EventContextFieldSupplier.java @@ -0,0 +1,13 @@ +package com.sap.hcp.cf.logging.common.serialization; + +import java.util.Map; + +@FunctionalInterface +public interface EventContextFieldSupplier extends ContextFieldSupplier { + + Map map(T event); + + default Map get() { + return map(null); + } +} diff --git a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/JsonSerializationException.java b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/JsonSerializationException.java similarity index 59% rename from cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/JsonSerializationException.java rename to cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/JsonSerializationException.java index 17d37026..ee811b67 100644 --- a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/JsonSerializationException.java +++ b/cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/serialization/JsonSerializationException.java @@ -1,8 +1,8 @@ -package com.sap.hcp.cf.logback.encoder; +package com.sap.hcp.cf.logging.common.serialization; public class JsonSerializationException extends RuntimeException { - private static final long serialVersionUID = 8365183525285128131L; + private static final long serialVersionUID = 6529980121185308722L; public JsonSerializationException(String message, Throwable cause) { super(message, cause); diff --git a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/AbstractConverterTest.java b/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/AbstractConverterTest.java deleted file mode 100644 index 248e8d75..00000000 --- a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/AbstractConverterTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import java.io.IOException; - -import com.fasterxml.jackson.jr.ob.JSON; -import com.fasterxml.jackson.jr.ob.JSONObjectException; - -public abstract class AbstractConverterTest { - protected static final String EMPTY = ""; - protected static final String STRANGE_SEQ = "}{:\",\""; - protected static final String TEST_MSG_NO_ARGS = "This is a test "; - - protected String formatMsg(DefaultMessageConverter mc, String msg) { - StringBuilder sb = new StringBuilder(); - mc.convert(msg, sb); - return sb.toString(); - } - - protected String formatStacktrace(DefaultStacktraceConverter dstc, Throwable t) { - StringBuilder sb = new StringBuilder(); - dstc.convert(t, sb); - return sb.toString(); - } - - protected Object arrayElem(String serialized, int i) throws JSONObjectException, IOException { - return arrayFrom(serialized)[i]; - } - - protected Object[] arrayFrom(String serialized) throws JSONObjectException, IOException { - return JSON.std.arrayFrom(serialized); - } -} diff --git a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultCustomFieldsConverterTest.java b/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultCustomFieldsConverterTest.java deleted file mode 100644 index 76c4e799..00000000 --- a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultCustomFieldsConverterTest.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import static com.sap.hcp.cf.logging.common.converter.CustomFieldMatchers.hasCustomField; -import static com.sap.hcp.cf.logging.common.converter.UnmarshallUtilities.unmarshalCustomFields; -import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasToString; -import static org.hamcrest.Matchers.isEmptyString; -import static org.junit.Assert.assertThat; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; - -public class DefaultCustomFieldsConverterTest { - - private static final String CUSTOM_KEY_0 = "custom_key_0"; - private static final String CUSTOM_VALUE_0 = "custom_value_0"; - private static final String CUSTOM_KEY_1 = "custom_key_1"; - private static final String CUSTOM_VALUE_1 = "custom_value_1"; - private static final String UNREGISTERED_KEY = "unregistered_key"; - private static final String UNREGISTERED_VALUE = "unregistered_value"; - private static final String HACK_ATTEMPT = "}{:\",\""; - private DefaultCustomFieldsConverter converter; - - @Before - public void initConverter() { - this.converter = new DefaultCustomFieldsConverter(); - converter.setCustomFieldKeyNames(Arrays.asList(CUSTOM_KEY_0, CUSTOM_KEY_1)); - } - - @Test - public void emptyMdcAndArguments() { - StringBuilder sb = new StringBuilder(); - - converter.convert(sb, Collections.emptyMap()); - - assertThat(sb, hasToString("")); - } - - @Test - public void standardArgument() throws Exception { - StringBuilder sb = new StringBuilder(); - converter.convert(sb, Collections.emptyMap(), "an_argument"); - assertThat(sb, hasToString("")); - } - - @Test - public void singleCustomFieldArgumentEmbedded() throws Exception { - StringBuilder sb = new StringBuilder(); - - converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0)); - - assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0))); - } - - @SuppressWarnings("unchecked") - @Test - public void multipleCustomFieldArgumentEmbedded() throws Exception { - StringBuilder sb = new StringBuilder(); - - converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_1, CUSTOM_VALUE_1), - customField(UNREGISTERED_KEY, UNREGISTERED_VALUE), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0)); - - assertThat(unmarshalCustomFields(sb), containsInAnyOrder(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0), - hasCustomField(CUSTOM_KEY_1, CUSTOM_VALUE_1, 1))); - } - - @Test - public void singleCustomFieldArgumentPrefix() throws Exception { - converter.setFieldName("prefix"); - StringBuilder sb = new StringBuilder(); - - converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0)); - - assertThat(unmarshalCustomFields(sb, "prefix"), contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0))); - } - - @Test - public void singleMdcField() throws Exception { - StringBuilder sb = new StringBuilder(); - - @SuppressWarnings("serial") - Map mdcFields = new HashMap() { - { - put(CUSTOM_KEY_0, CUSTOM_VALUE_0); - } - }; - - converter.convert(sb, mdcFields); - - assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0))); - } - - @SuppressWarnings("unchecked") - @Test - public void multipleMdcFields() throws Exception { - StringBuilder sb = new StringBuilder(); - - @SuppressWarnings("serial") - Map mdcFields = new HashMap() { - { - put(CUSTOM_KEY_1, CUSTOM_VALUE_1); - put(UNREGISTERED_KEY, UNREGISTERED_VALUE); - put(CUSTOM_KEY_0, CUSTOM_VALUE_0); - } - }; - - converter.convert(sb, mdcFields); - - assertThat(unmarshalCustomFields(sb), containsInAnyOrder(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0), - hasCustomField(CUSTOM_KEY_1, CUSTOM_VALUE_1, 1))); - } - - @SuppressWarnings("unchecked") - @Test - public void argumentsTakePrecendenceOverMdc() throws Exception { - StringBuilder sb = new StringBuilder(); - - @SuppressWarnings("serial") - Map mdcFields = new HashMap() { - { - put(CUSTOM_KEY_0, CUSTOM_VALUE_0); - put(CUSTOM_KEY_1, CUSTOM_VALUE_1); - } - }; - - converter.convert(sb, mdcFields, customField(CUSTOM_KEY_0, "preferred value")); - - assertThat(unmarshalCustomFields(sb), containsInAnyOrder(hasCustomField(CUSTOM_KEY_0, "preferred value", 0), - hasCustomField(CUSTOM_KEY_1, CUSTOM_VALUE_1, 1))); - } - - @Test - public void doesNotWriteJsonWhenNoFieldKeysAreConfigured() throws Exception { - StringBuilder sb = new StringBuilder(); - - converter.setCustomFieldKeyNames(Collections.emptyList()); - converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0)); - - assertThat(sb.toString(), isEmptyString()); - } - - @Test - public void properlyEscapesValues() throws Exception { - StringBuilder sb = new StringBuilder(); - - converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, HACK_ATTEMPT)); - - assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, HACK_ATTEMPT, 0))); - } - - @Test - public void properlyEscapesMdcFields() throws Exception { - StringBuilder sb = new StringBuilder(); - - @SuppressWarnings("serial") - Map mdcFields = new HashMap() { - { - put(CUSTOM_KEY_0, HACK_ATTEMPT); - } - }; - - converter.convert(sb, mdcFields); - - assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, HACK_ATTEMPT, 0))); - - } - - @Test - public void properlyEscapesFieldNames() throws Exception { - converter.setFieldName(HACK_ATTEMPT); - StringBuilder sb = new StringBuilder(); - - converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0)); - - assertThat(unmarshalCustomFields(sb, HACK_ATTEMPT), - contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0))); - } - -} diff --git a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultPropertiesConverterTest.java b/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultPropertiesConverterTest.java deleted file mode 100644 index d7b7a048..00000000 --- a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultPropertiesConverterTest.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import static com.sap.hcp.cf.logging.common.converter.UnmarshallUtilities.unmarshal; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.hamcrest.Matcher; -import org.junit.Before; -import org.junit.Test; -import org.slf4j.MDC; - -import com.sap.hcp.cf.logging.common.Defaults; -import com.sap.hcp.cf.logging.common.Fields; - -public class DefaultPropertiesConverterTest { - - private static final String HACK_ATTEMPT = "}{:\",\""; - - private DefaultPropertiesConverter converter; - - @Before - public void initConverter() { - this.converter = new DefaultPropertiesConverter(); - } - - @Before - public void cleadMdc() { - MDC.clear(); - } - - @Test - public void emptyProperties() throws Exception { - StringBuilder sb = new StringBuilder(); - - converter.convert(sb, Collections.emptyMap()); - - assertTrue("Should have empty properties by default", unmarshal(sb).isEmpty()); - } - - @Test - public void singleMdcEntry() throws Exception { - StringBuilder sb = new StringBuilder(); - MDC.put("some key", "some value"); - - converter.convert(sb, Collections.emptyMap()); - - assertThat(unmarshal(sb), hasEntry("some key", "some value")); - } - - @Test - public void twoMdcEntries() throws Exception { - StringBuilder sb = new StringBuilder(); - MDC.put("some key", "some value"); - MDC.put("other key", "other value"); - - converter.convert(sb, Collections.emptyMap()); - - assertThat(unmarshal(sb), allOf(hasEntry("some key", "some value"), hasEntry("other key", "other value"))); - } - - @Test - public void singleExplicitEntry() throws Exception { - StringBuilder sb = new StringBuilder(); - @SuppressWarnings("serial") - Map explicitFields = new HashMap() { - { - put("explicit key", "explicit value"); - } - }; - - converter.convert(sb, explicitFields); - - assertThat(unmarshal(sb), hasEntry("explicit key", "explicit value")); - } - - @Test - public void mergesDifferentMdcAndExplicitEntries() throws Exception { - StringBuilder sb = new StringBuilder(); - @SuppressWarnings("serial") - Map explicitFields = new HashMap() { - { - put("explicit key", "explicit value"); - } - }; - MDC.put("some key", "some value"); - - converter.convert(sb, explicitFields); - - assertThat(unmarshal(sb), allOf(hasEntry("some key", "some value"), hasEntry("explicit key", - "explicit value"))); - } - - @Test - public void explicitValuesOverwritesMdc() throws Exception { - StringBuilder sb = new StringBuilder(); - @SuppressWarnings("serial") - Map explicitFields = new HashMap() { - { - put("some key", "explicit value"); - } - }; - MDC.put("some key", "some value"); - - converter.convert(sb, explicitFields); - - assertThat(unmarshal(sb), hasEntry("some key", "explicit value")); - } - - @Test - public void dropsExclusions() throws Exception { - StringBuilder sb = new StringBuilder(); - @SuppressWarnings("serial") - Map explicitFields = new HashMap() { - { - put("excluded explicit key", "excluded explicit value"); - put("retained explicit key", "retained explicit value"); - } - }; - MDC.put("retained mdc key", "retained mdc value"); - MDC.put("excluded mdc key", "excluded mdc value"); - - converter.setExclusions(Arrays.asList("excluded explicit key", "excluded mdc key")); - converter.convert(sb, explicitFields); - - assertThat(unmarshal(sb), allOf(hasEntry("retained mdc key", "retained mdc value"), not(hasEntry( - "excluded mdc key", - "excluded mdc value")), - hasEntry("retained explicit key", "retained explicit value"), not(hasEntry( - "excluded explicit key", - "excluded explicit value")))); - - } - - @Test - public void properlyEscapesKeys() throws Exception { - StringBuilder sb = new StringBuilder(); - @SuppressWarnings("serial") - Map explicitFields = new HashMap() { - { - put("explicit" + HACK_ATTEMPT, "explicit value"); - } - }; - MDC.put("mdc" + HACK_ATTEMPT, "mdc value"); - - converter.convert(sb, explicitFields); - - assertThat(unmarshal(sb), allOf(hasEntry("mdc" + HACK_ATTEMPT, "mdc value"), hasEntry("explicit" + HACK_ATTEMPT, - "explicit value"))); - } - - @Test - public void properlyEscapesValues() throws Exception { - StringBuilder sb = new StringBuilder(); - @SuppressWarnings("serial") - Map explicitFields = new HashMap() { - { - put("explicit key", "explicit" + HACK_ATTEMPT); - } - }; - MDC.put("mdc key", "mdc" + HACK_ATTEMPT); - - converter.convert(sb, explicitFields); - - assertThat(unmarshal(sb), allOf(hasEntry("mdc key", "mdc" + HACK_ATTEMPT), hasEntry("explicit key", "explicit" + - HACK_ATTEMPT))); - } - - @Test - public void properlyEscapesExclusions() throws Exception { - StringBuilder sb = new StringBuilder(); - @SuppressWarnings("serial") - Map explicitFields = new HashMap() { - { - put("explicit" + HACK_ATTEMPT, "explicit value"); - } - }; - MDC.put("mdc" + HACK_ATTEMPT, "mdc value"); - - converter.setExclusions(Arrays.asList("explicit" + HACK_ATTEMPT, "mdc" + HACK_ATTEMPT)); - converter.convert(sb, explicitFields); - - assertThat(unmarshal(sb), allOf(not(hasEntry("mdc" + HACK_ATTEMPT, "mdc value")), not(hasEntry("explicit" + - HACK_ATTEMPT, - "explicit value")))); - } - - @Test - public void fullDefaultPropertiesIfConfigured() throws Exception { - StringBuilder sb = new StringBuilder(); - - converter.setSendDefaultValues(true); - converter.convert(sb, Collections.emptyMap()); - - assertThat(unmarshal(sb), hasDefaultProperties()); - - } - - @SuppressWarnings("unchecked") - private static Matcher> hasDefaultProperties() { - return allOf(hasEntry(Fields.CORRELATION_ID, Defaults.UNKNOWN), // - hasEntry(Fields.TENANT_ID, Defaults.UNKNOWN), // - hasEntry(Fields.COMPONENT_ID, Defaults.UNKNOWN), // - hasEntry(Fields.COMPONENT_NAME, Defaults.UNKNOWN), // - hasEntry(Fields.COMPONENT_TYPE, Defaults.COMPONENT_TYPE), // - hasEntry(Fields.COMPONENT_INSTANCE, Defaults.COMPONENT_INDEX), // - hasEntry(Fields.CONTAINER_ID, Defaults.UNKNOWN), // - hasEntry(Fields.ORGANIZATION_ID, Defaults.UNKNOWN), // - hasEntry(Fields.ORGANIZATION_NAME, Defaults.UNKNOWN), // - hasEntry(Fields.SPACE_ID, Defaults.UNKNOWN), // - hasEntry(Fields.SPACE_NAME, Defaults.UNKNOWN), // - not(hasKey(Fields.REQUEST_ID))); - } -} diff --git a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultStacktraceConverterTest.java b/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultStacktraceConverterTest.java deleted file mode 100644 index 5f644d8e..00000000 --- a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/DefaultStacktraceConverterTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.assertThat; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.Reader; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.mockito.Matchers; -import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import com.sap.hcp.cf.logging.common.helper.StacktraceGenerator; - -public class DefaultStacktraceConverterTest extends AbstractConverterTest { - - private String expectedString; - StringBuilder stringBuilder = new StringBuilder(); - - @Test - public void testNull() { - DefaultStacktraceConverter dstc = new DefaultStacktraceConverter(); - assertThat(formatStacktrace(dstc, null), is(EMPTY)); - } - - @Test - public void testSynthetic() throws Exception { - DefaultStacktraceConverter dstc = new DefaultStacktraceConverter(); - String actual = formatStacktrace(dstc, new NullPointerException()); - assertThat(actual, not(is(EMPTY))); - assertThat(arrayElem(actual, 0).toString(), is(NullPointerException.class.getName())); - } - - @Test - public void testReal() throws Exception { - DefaultStacktraceConverter dstc = new DefaultStacktraceConverter(); - - try { - throw new ArrayIndexOutOfBoundsException(); - } catch (ArrayIndexOutOfBoundsException ex) { - String actual = formatStacktrace(dstc, ex); - assertThat(actual, not(is(EMPTY))); - assertThat(arrayElem(actual, 0).toString(), containsString(ex.getClass().getName())); - } - } - - @Test - public void compareCompleteStacktraceWithExpectation() throws InterruptedException { - Thread thread = new Thread(new Runnable() { - - @Override - public void run() { - DefaultStacktraceConverter converter = new DefaultStacktraceConverter(4000); - StacktraceGenerator generator = new StacktraceGenerator(5, 10, 5); - converter.convert(generator.generateException(), stringBuilder); - } - }); - thread.start(); - thread.join(); - expectedString = "[\"java.lang.IllegalArgumentException: too long\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.generateException(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.converter.DefaultStacktraceConverterTest$X.run(DefaultStacktraceConverterTest.java:X)\"," + // - "\"\\tat " + getThreadClassName() + ".run(Thread.java:X)\"]"; - Assert.assertEquals(expectedString, stringBuilder.toString().replaceAll(":\\d+\\)", ":X)").// - replaceAll("\\$\\d", "\\$X")); - } - - @Test - public void compareTruncatedStacktraceWithExpectation() throws InterruptedException { - Thread thread = new Thread(new Runnable() { - - @Override - public void run() { - DefaultStacktraceConverter converter = new DefaultStacktraceConverter(2000); - StacktraceGenerator generator = new StacktraceGenerator(5, 20, 5); - converter.convert(generator.generateException(), stringBuilder); - } - }); - thread.start(); - thread.join(); - expectedString = "[\"-------- STACK TRACE TRUNCATED --------\"," + // - "\"java.lang.IllegalArgumentException: too long\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"-------- OMITTED --------\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.helper.StacktraceGenerator.generateException(StacktraceGenerator.java:X)\"," + // - "\"\\tat com.sap.hcp.cf.logging.common.converter.DefaultStacktraceConverterTest$X.run(DefaultStacktraceConverterTest.java:X)\"," + // - "\"\\tat " + getThreadClassName() + ".run(Thread.java:X)\"]"; - Assert.assertEquals(expectedString, stringBuilder.toString().replaceAll(":\\d+\\)", ":X)").// - replaceAll("\\$\\d", "\\$X")); - } - - // This method is required to account for the module path in stacktraces - // after Java 9 - private String getThreadClassName() { - String stackTraceElement = Thread.currentThread().getStackTrace()[0].toString(); - int i = stackTraceElement.indexOf("Thread"); - return stackTraceElement.subSequence(0, i) + Thread.class.getSimpleName(); - } - - /** - * This test case can be used for failure analysis. Paste a custom stacktrace - * into src/test/resources/com/sap/hcp/cf/logging/converter/stacktrace.txt and - * unignore this test. It will print the formatted stacktrace to the console. - * - * @throws Exception - */ - @Test - @Ignore("Use this test for failure analysis of custom stacktraces.") - public void customStacktrace() throws Exception { - try (InputStream input = getClass().getResourceAsStream("stacktrace.txt"); - Reader reader = new InputStreamReader(input); - BufferedReader stacktrace = new BufferedReader(reader)) { - Throwable exception = Mockito.mock(Throwable.class); - Mockito.when(exception.getMessage()).thenReturn("Test-Message"); - Mockito.doAnswer(new Answer() { - - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - PrintWriter writer = (PrintWriter) invocation.getArguments()[0]; - stacktrace.lines().forEach(l -> writer.println(l)); - return null; - } - }).when(exception).printStackTrace(Matchers.any(PrintWriter.class)); - DefaultStacktraceConverter converter = new DefaultStacktraceConverter(); - StringBuilder sb = new StringBuilder(); - converter.convert(exception, sb); - System.out.print(sb.toString()); - } - } - -} diff --git a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/TestJsonMessageConverter.java b/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/TestJsonMessageConverter.java deleted file mode 100644 index 0a0d743e..00000000 --- a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/TestJsonMessageConverter.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.sap.hcp.cf.logging.common.converter; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -import org.junit.Test; - -import com.sap.hcp.cf.logging.common.request.RequestRecord; - -public class TestJsonMessageConverter extends AbstractConverterTest { - - private static final String ARRAY_MSG = "[1, 2, 3, 4]"; - private static final String OBJ_MSG = "{\"foo\":\"bar\", \"baz\":1}"; - private static final String LOG_PROVIDER = "test"; - - @Test - public void testSimple() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - assertThat(formatMsg(jmc, TEST_MSG_NO_ARGS), is(TEST_MSG_NO_ARGS)); - } - - @Test - public void testSimpleQuoted() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - String quotedMsg = TEST_MSG_NO_ARGS + " with a \"quote\""; - assertThat(formatMsg(jmc, quotedMsg), is(quotedMsg)); - } - - @Test - public void testArrayMsgNotFlattened() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - assertThat(formatMsg(jmc, ARRAY_MSG), is(ARRAY_MSG)); - } - - @Test - public void testArrayMsgFlattened() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - jmc.setFlatten(true); - assertThat(formatMsg(jmc, ARRAY_MSG), is(ARRAY_MSG.substring(1, ARRAY_MSG.length() - 1))); - } - - @Test - public void testObjMsgNotFlattened() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - assertThat(formatMsg(jmc, OBJ_MSG), is(OBJ_MSG)); - } - - @Test - public void testObjMsgFlattened() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - jmc.setFlatten(true); - assertThat(formatMsg(jmc, OBJ_MSG), is(OBJ_MSG.substring(1, OBJ_MSG.length() - 1))); - } - - @Test - public void testLogRecordMsgNotFlattened() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - RequestRecord lrec = new RequestRecord(LOG_PROVIDER); - String lmsg = lrec.toString(); - assertThat(formatMsg(jmc, lmsg), is(lmsg)); - } - - @Test - public void testLogRecordMsgFlattened() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - jmc.setFlatten(true); - RequestRecord lrec = new RequestRecord(LOG_PROVIDER); - String lmsg = lrec.toString(); - assertThat(formatMsg(jmc, lmsg), is(lmsg.substring(1, lmsg.length() - 1))); - } - - @Test - public void testEscapedMessage() { - DefaultMessageConverter jmc = new DefaultMessageConverter(); - String strangeMsg = TEST_MSG_NO_ARGS + STRANGE_SEQ; - assertThat(formatMsg(jmc, strangeMsg), is(strangeMsg)); - } - - @Test - public void testObjNestedBraces() { - String nestedBracesMsg = "\"request\": \"/Foo(${bar})\""; - String logMsg = " {" + nestedBracesMsg + "} "; - DefaultMessageConverter jmc = new DefaultMessageConverter(); - jmc.setFlatten(true); - assertThat(formatMsg(jmc, logMsg), is(nestedBracesMsg)); - } - - @Test - public void testObjIncompleteNestedBraces() { - String nestedBracesMsg = "\"request\": \"/Foo(${bar)\""; - String logMsg = " {" + nestedBracesMsg + "} "; - DefaultMessageConverter jmc = new DefaultMessageConverter(); - jmc.setFlatten(true); - assertThat(formatMsg(jmc, logMsg), is(nestedBracesMsg)); - } - - @Test - public void testInvalidObjNestedBraces() { - String nestedBracesMsg = "\"request\": \"/Foo(${bar)\""; - String logMsg = " {" + nestedBracesMsg; - DefaultMessageConverter jmc = new DefaultMessageConverter(); - jmc.setFlatten(true); - assertThat(formatMsg(jmc, logMsg), is(logMsg)); - } - - @Test - public void testObjNestedBrackets() { - String nestedBracketsMsg = "\"request\", \"/Foo($[bar])\""; - String logMsg = " [" + nestedBracketsMsg + "] "; - DefaultMessageConverter jmc = new DefaultMessageConverter(); - jmc.setFlatten(true); - assertThat(formatMsg(jmc, logMsg), is(nestedBracketsMsg)); - } - - @Test - public void testNullMessage() { - String logMsg = null; - DefaultMessageConverter jmc = new DefaultMessageConverter(); - assertThat(formatMsg(jmc, logMsg), is("null")); - } - -} diff --git a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/UnmarshallUtilities.java b/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/UnmarshallUtilities.java index 23ce5433..ee685a55 100644 --- a/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/UnmarshallUtilities.java +++ b/cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/UnmarshallUtilities.java @@ -7,29 +7,23 @@ public final class UnmarshallUtilities { - private UnmarshallUtilities() { - } - - public static Map unmarshal(CharSequence sb) throws Exception { - String source = sb.toString().trim(); - String json = isEnclosedInBrackets(source) ? source : "{" + source + "}"; - return JSON.std.mapFrom(json); - } - - private static boolean isEnclosedInBrackets(String source) { - return source.startsWith("{") && source.endsWith("}"); - } - - @SuppressWarnings("unchecked") - public static List> unmarshalCustomFields(CharSequence sb) throws Exception { - return (List>) unmarshal(sb).get("string"); - } - - @SuppressWarnings("unchecked") - public static List> unmarshalCustomFields(CharSequence sb, String prefix) - throws Exception { - Map prefixed = (Map) unmarshal(sb).get(prefix); - return (List>) prefixed.get("string"); - } + private UnmarshallUtilities() { + } + + public static Map unmarshal(CharSequence sb) throws Exception { + String source = sb.toString().trim(); + String json = isEnclosedInBrackets(source) ? source : "{" + source + "}"; + return JSON.std.mapFrom(json); + } + + private static boolean isEnclosedInBrackets(String source) { + return source.startsWith("{") && source.endsWith("}"); + } + + @SuppressWarnings("unchecked") + public static List> unmarshalCustomFields(CharSequence sb, String prefix) throws Exception { + Map prefixed = (Map) unmarshal(sb).get(prefix); + return (List>) prefixed.get("string"); + } } diff --git a/cf-java-logging-support-log4j2/pom.xml b/cf-java-logging-support-log4j2/pom.xml index c8e852b9..7e5ec8db 100644 --- a/cf-java-logging-support-log4j2/pom.xml +++ b/cf-java-logging-support-log4j2/pom.xml @@ -1,73 +1,73 @@ - 4.0.0 + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - cf-java-logging-support-log4j2 - jar + cf-java-logging-support-log4j2 + jar - cf-java-logging-support-log4j2 - - ../pom.xml - com.sap.hcp.cf.logging - cf-java-logging-support-parent - 3.5.7 - + cf-java-logging-support-log4j2 + + ../pom.xml + com.sap.hcp.cf.logging + cf-java-logging-support-parent + 3.5.7 + - - - org.apache.logging.log4j - log4j-api - ${log4j2.version} - provided - - - org.apache.logging.log4j - log4j-core - ${log4j2.version} - provided - - - org.apache.logging.log4j - log4j-slf4j-impl - ${log4j2.version} - provided - - - com.sap.hcp.cf.logging - cf-java-logging-support-core - ${project.version} - - - com.sap.hcp.cf.logging - cf-java-logging-support-core - test-jar - ${project.version} - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - - log4j-plugin-processor - - compile - - process-classes - - only - - org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor - - - - - - - + + + org.apache.logging.log4j + log4j-api + ${log4j2.version} + provided + + + org.apache.logging.log4j + log4j-core + ${log4j2.version} + provided + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j2.version} + provided + + + com.sap.hcp.cf.logging + cf-java-logging-support-core + ${project.version} + + + com.sap.hcp.cf.logging + cf-java-logging-support-core + test-jar + ${project.version} + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + + log4j-plugin-processor + + compile + + process-classes + + only + + org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor + + + + + + + diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/CategoriesConverter.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/CategoriesConverter.java deleted file mode 100644 index 8cb03ab1..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/CategoriesConverter.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import java.io.IOException; - -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.pattern.ConverterKeys; -import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.jr.ob.JSON; -import com.fasterxml.jackson.jr.ob.JSONComposer; -import com.fasterxml.jackson.jr.ob.comp.ArrayComposer; - -@Plugin(name = "CategoriesConverter", category = "Converter") -@ConverterKeys({ "categories" }) -public class CategoriesConverter extends LogEventPatternConverter { - - public static final String WORD = "categories"; - - public CategoriesConverter(String[] options) { - super(WORD, WORD); - } - - public static CategoriesConverter newInstance(final String[] options) { - return new CategoriesConverter(options); - } - - @Override - public void format(LogEvent event, StringBuilder toAppendTo) { - getMarkers(event.getMarker(), toAppendTo); - } - - private void getMarkers(Marker marker, StringBuilder toAppendTo) { - try { - ArrayComposer> ac = JSON.std.composeString().startArray(); - getMarkersRecursively(marker, ac); - toAppendTo.append(ac.end().finish()); - } catch (IOException ex) { - LoggerFactory.getLogger(CategoriesConverter.class).error("conversion failed", ex); - } - } - - private void getMarkersRecursively(Marker marker, ArrayComposer> ac) throws IOException { - if (marker != null) { - ac.add(marker.getName()); - Marker[] parents = marker.getParents(); - if (parents != null) { - for (Marker parent: parents) { - getMarkersRecursively(parent, ac); - } - } - } - } -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/ContextPropsConverter.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/ContextPropsConverter.java deleted file mode 100644 index 10994880..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/ContextPropsConverter.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.pattern.ConverterKeys; -import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; -import org.apache.logging.log4j.message.Message; - -import com.sap.hcp.cf.logging.common.converter.DefaultPropertiesConverter; -import com.sap.hcp.cf.logging.common.customfields.CustomField; - -/** - * A simple {@link LogEventPatternConverter} implementation that converts - * key/value pairs from the {@link org.slf4j.MDC}. These key/value pairs are - * embedded in the JSON message, i.e. the appear as fields at the top-level JSON - * object. - *

- * There are two exceptions to this: - *

    - *
  1. The predefined keys from - * {@link com.sap.hcp.cf.logging.common.Fields}
  2. - *
  3. The list of key names that have been passed in as options.
  4. - *
- * - */ -@Plugin(name = "ContextPropsConverter", category = "Converter") -@ConverterKeys({ "ctxp" }) -public class ContextPropsConverter extends LogEventPatternConverter { - - public static final String WORD = "ctxp"; - private final DefaultPropertiesConverter converter = new DefaultPropertiesConverter(); - - public ContextPropsConverter(String[] options) { - super(WORD, WORD); - if (options != null) { - converter.setSendDefaultValues(Boolean.parseBoolean(options[0])); - converter.setExclusions(Arrays.asList(Arrays.copyOfRange(options, 1, options.length))); - } - } - - public static ContextPropsConverter newInstance(final String[] options) { - return new ContextPropsConverter(options); - } - - @Override - public void format(LogEvent event, StringBuilder toAppendTo) { - Map contextData = event.getContextData().toMap(); - contextData = addCustomFieldsFromArguments(contextData, event); - int lengthBefore = toAppendTo.length(); - converter.convert(toAppendTo, contextData); - // remove comma from pattern, when no properties are added - // this is to avoid a double comma in the JSON - // Do not do this on empty messages - if (toAppendTo.length() == lengthBefore && lengthBefore > 0 && toAppendTo.charAt(lengthBefore - 1) == ',') { - toAppendTo.setLength(lengthBefore - 1); - } - } - - private Map addCustomFieldsFromArguments(Map contextData, LogEvent event) { - Message message = event.getMessage(); - Object[] parameters = message.getParameters(); - if (parameters == null) { - return contextData; - } - boolean unchangedContextData = true; - Map result = contextData; - for (Object current: parameters) { - if (current instanceof CustomField) { - CustomField field = (CustomField) current; - if (unchangedContextData) { - // contextData might be an unmodifiable map - result = new HashMap<>(contextData); - unchangedContextData = false; - } - result.put(field.getKey(), String.valueOf(field.getValue())); - } - } - return result; - } - -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/CustomFieldsConverter.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/CustomFieldsConverter.java deleted file mode 100644 index 5f6b2447..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/CustomFieldsConverter.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.unmodifiableList; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.pattern.ConverterKeys; -import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; -import org.apache.logging.log4j.core.pattern.PatternConverter; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.util.ReadOnlyStringMap; - -import com.sap.hcp.cf.logging.common.converter.DefaultCustomFieldsConverter; -import com.sap.hcp.cf.logging.common.customfields.CustomField; - -/** - * This is a simple {@link LogEventPatternConverter} implementation that - * converts key/value pairs stored in {@link CustomField} instances which have - * been passed as arguments. - *

- * We allow to types of addition to a log message, either embedded, i.e. - * the key/value pairs appear as a list of JSON fields in the message, or as a - * nested object where the field name has been specified as an option to this - * converter. - */ -@Plugin(name = "ArgsConverter", category = PatternConverter.CATEGORY) -@ConverterKeys({ "cf" }) -public class CustomFieldsConverter extends LogEventPatternConverter { - - public static final String WORD = "cf"; - - private final List customFieldMdcKeyNames; - private DefaultCustomFieldsConverter converter = new DefaultCustomFieldsConverter(); - - public CustomFieldsConverter(String[] options) { - super(WORD, WORD); - - customFieldMdcKeyNames = options == null ? emptyList() : unmodifiableList(asList(options)); - converter.setCustomFieldKeyNames(customFieldMdcKeyNames); - } - - public static CustomFieldsConverter newInstance(final String[] options) { - return new CustomFieldsConverter(options); - } - - void setConverter(DefaultCustomFieldsConverter converter) { - this.converter = converter; - } - - @Override - public void format(LogEvent event, StringBuilder appendTo) { - converter.convert(appendTo, getCustomFieldsFromMdc(event), getMessageParameters(event)); - } - - private Object[] getMessageParameters(LogEvent event) { - Message message = event.getMessage(); - return message == null ? null : message.getParameters(); - } - - private Map getCustomFieldsFromMdc(LogEvent event) { - ReadOnlyStringMap contextData = event.getContextData(); - return contextData != null ? contextData.toMap() : Collections.emptyMap(); - } -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/JsonMessageConverter.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/JsonMessageConverter.java deleted file mode 100644 index ffc72670..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/JsonMessageConverter.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.pattern.ConverterKeys; -import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; - -import com.sap.hcp.cf.logging.common.converter.DefaultMessageConverter; - -/** - * A simple {@link LogEventPatternConverter} that converts a message into a JSON - * message. - *

- * The main point are that we may need to do escaping and/or flattening - * depending on the context. Escaping means that we write the message as - * a quoted string and thus need to escape properly within the message - * string. If a message is flattened, objects or arrays are turned into a - * list of fields or values. - * - */ -@Plugin(name = "JsonMessageConverter", category = "Converter") -@ConverterKeys({ "jsonmsg" }) -public class JsonMessageConverter extends LogEventPatternConverter { - - public static final String WORD = "jsonmsg"; - public static final String OPT_ESCAPE = "escape"; - public static final String OPT_FLATTEN = "flatten"; - - private final DefaultMessageConverter converter = new DefaultMessageConverter(); - - public JsonMessageConverter(String[] options) { - super(WORD, WORD); - if (options != null) { - for (String option: options) { - if (OPT_FLATTEN.equalsIgnoreCase(option)) { - converter.setFlatten(true); - } else if (OPT_ESCAPE.equalsIgnoreCase(option)) { - converter.setEscape(true); - } - } - } - } - - public static JsonMessageConverter newInstance(final String[] options) { - return new JsonMessageConverter(options); - } - - @Override - public void format(LogEvent event, StringBuilder toAppendTo) { - converter.convert(event.getMessage().getFormattedMessage(), toAppendTo); - } -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/Log4JStacktraceConverter.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/Log4JStacktraceConverter.java deleted file mode 100644 index d65cc4d9..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/Log4JStacktraceConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.pattern.ConverterKeys; -import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; - -import com.sap.hcp.cf.logging.common.converter.StacktraceConverter; - -@Plugin(name = "Log4JStacktraceConverter", category = "Converter") -@ConverterKeys({ "stacktrace" }) -public class Log4JStacktraceConverter extends LogEventPatternConverter { - public static final String WORD = "stacktrace"; - - public Log4JStacktraceConverter(String[] options) { - super(WORD, WORD); - } - - public static Log4JStacktraceConverter newInstance(final String[] options) { - return new Log4JStacktraceConverter(options); - } - - @Override - public void format(LogEvent event, StringBuilder toAppendTo) { - StacktraceConverter.CONVERTER.convert(event.getThrown(), toAppendTo); - } - -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/TimestampConverter.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/TimestampConverter.java deleted file mode 100644 index ea828529..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/TimestampConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import java.time.Instant; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.pattern.ConverterKeys; -import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; - -@Plugin(name = "TimestampConverter", category = "Converter") -@ConverterKeys({ "tstamp" }) -/** - * This is a simple {@link LogEventPatternConverter} implementation that prints - * the timestamp as a long in nano second resolution. Note: nano second - * precision is only available from Java 9 or newer. Java 8 will only have milli - * seconds. - */ -public class TimestampConverter extends LogEventPatternConverter { - - public static final String WORD = "tstamp"; - - public TimestampConverter(String[] options) { - super(WORD, WORD); - } - - public static TimestampConverter newInstance(final String[] options) { - return new TimestampConverter(options); - } - - @Override - public void format(LogEvent event, StringBuilder toAppendTo) { - Instant now = Instant.now(); - long timestamp = now.getEpochSecond() * 1_000_000_000L + now.getNano(); - toAppendTo.append(timestamp); - } - -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/api/Log4jContextFieldSupplier.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/api/Log4jContextFieldSupplier.java new file mode 100644 index 00000000..b3b95e3a --- /dev/null +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/converter/api/Log4jContextFieldSupplier.java @@ -0,0 +1,10 @@ +package com.sap.hcp.cf.log4j2.converter.api; + +import org.apache.logging.log4j.core.LogEvent; + +import com.sap.hcp.cf.logging.common.serialization.EventContextFieldSupplier; + +@FunctionalInterface +public interface Log4jContextFieldSupplier extends EventContextFieldSupplier { + +} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/ContextFieldSupplierElement.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/ContextFieldSupplierElement.java new file mode 100644 index 00000000..24a9c078 --- /dev/null +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/ContextFieldSupplierElement.java @@ -0,0 +1,49 @@ +package com.sap.hcp.cf.log4j2.layout; + +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; + +@Plugin(name = "contextFieldSupplier", category = Node.CATEGORY, printObject = true) +public class ContextFieldSupplierElement { + + private String supplierClass; + + public ContextFieldSupplierElement(Builder builder) { + this.supplierClass = builder.clazz; + } + + public String getSupplierClass() { + return supplierClass; + } + + @Override + public String toString() { + return supplierClass; + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements + org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute("class") + private String clazz; + + public Builder setClazz(String clazz) { + this.clazz = clazz; + return this; + } + + @Override + public ContextFieldSupplierElement build() { + return new ContextFieldSupplierElement(this); + } + + } + +} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomField.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomFieldElement.java similarity index 86% rename from cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomField.java rename to cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomFieldElement.java index 57c1b9ea..a0e4171b 100644 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomField.java +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomFieldElement.java @@ -6,12 +6,12 @@ import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; @Plugin(name = "customField", category = Node.CATEGORY, printObject = true) -public class CustomField { +public class CustomFieldElement { private final String key; private final boolean retainOriginal; - private CustomField(Builder builder) { + private CustomFieldElement(Builder builder) { this.key = builder.key; this.retainOriginal = builder.retainOriginal; } @@ -34,7 +34,7 @@ public static Builder newBuilder() { return new Builder(); } - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.core.util.Builder { @PluginBuilderAttribute("mdcKeyName") private String key; @@ -53,8 +53,8 @@ public Builder setRetainOriginal(boolean retainOriginal) { } @Override - public CustomField build() { - return new CustomField(this); + public CustomFieldElement build() { + return new CustomFieldElement(this); } } diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomFieldsAdapter.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomFieldsAdapter.java deleted file mode 100644 index 729b1b2c..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/CustomFieldsAdapter.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.sap.hcp.cf.log4j2.layout; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import com.sap.hcp.cf.logging.common.LogContext; - -public class CustomFieldsAdapter { - - private List customFields; - - public CustomFieldsAdapter(CustomField... customFields) { - this.customFields = customFields == null ? emptyList() : asList(customFields); - } - - public List getCustomFieldKeyNames() { - List result = new ArrayList<>(customFields.size()); - for (CustomField customField : customFields) { - result.add(customField.getKey()); - } - return result; - } - - public List getExcludedFieldKeyNames() { - Collection contextFieldsKeys = LogContext.getContextFieldsKeys(); - List result = new ArrayList<>(customFields.size()); - for (CustomField customField : customFields) { - if (!customField.isRetainOriginal() && !contextFieldsKeys.contains(customField.getKey())) { - result.add(customField.getKey()); - } - } - return result; - } - -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/JsonPatternLayout.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/JsonPatternLayout.java index b8cb39e4..d23dd9bb 100644 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/JsonPatternLayout.java +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/JsonPatternLayout.java @@ -1,11 +1,16 @@ package com.sap.hcp.cf.log4j2.layout; -import static java.util.Collections.emptyList; - +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; @@ -13,85 +18,219 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.layout.AbstractStringLayout; -import org.apache.logging.log4j.core.layout.MarkerPatternSelector; -import org.apache.logging.log4j.core.layout.PatternMatch; -import org.apache.logging.log4j.core.layout.PatternSelector; -import org.apache.logging.log4j.core.pattern.PatternFormatter; +import org.apache.logging.log4j.core.util.StringBuilderWriter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.jr.ob.JSON; +import com.fasterxml.jackson.jr.ob.JSONComposer; +import com.fasterxml.jackson.jr.ob.comp.ArrayComposer; +import com.fasterxml.jackson.jr.ob.comp.ComposerBase; +import com.fasterxml.jackson.jr.ob.comp.ObjectComposer; +import com.sap.hcp.cf.log4j2.converter.api.Log4jContextFieldSupplier; +import com.sap.hcp.cf.log4j2.layout.supppliers.BaseFieldSupplier; +import com.sap.hcp.cf.log4j2.layout.supppliers.EventContextFieldSupplier; +import com.sap.hcp.cf.log4j2.layout.supppliers.LogEventUtilities; +import com.sap.hcp.cf.log4j2.layout.supppliers.RequestRecordFieldSupplier; +import com.sap.hcp.cf.logging.common.Fields; +import com.sap.hcp.cf.logging.common.converter.LineWriter; +import com.sap.hcp.cf.logging.common.converter.StacktraceLines; +import com.sap.hcp.cf.logging.common.serialization.ContextFieldConverter; +import com.sap.hcp.cf.logging.common.serialization.ContextFieldSupplier; +import com.sap.hcp.cf.logging.common.serialization.JsonSerializationException; -import com.sap.hcp.cf.logging.common.Markers; +/** + * A {@link StringLayout} implementation that encodes an {@link LogEvent} as a + * JSON object. + *

+ * Under the hood, it's using Jackson to serialize the logging event into JSON. + * The encoder can be confiugred in the log4j.xml:

+ * + *
+ * <Appenders>
+ *   <Console name="STDOUT-JSON" target="SYSTEM_OUT" follow="true">
+ *     <JsonPatternLayout />
+ *   </Console>
+ * </Appenders>
+ * 
+ * + *
The layout can be customized by several xml elements. See the + * annotations on the factory method {@link #createLayout}. + */ @Plugin(name = "JsonPatternLayout", category = "Core", elementType = "Layout", printObject = true) public final class JsonPatternLayout extends AbstractStringLayout { - private final PatternSelector markerSelector; - private final PatternSelector execptionSelector; + private static final String NEWLINE = "\n"; + + private List log4jContextFieldSuppliers = new ArrayList<>(); + private List contextFieldSuppliers = new ArrayList<>(); + + private final ContextFieldConverter contextFieldConverter; + + private int maxStacktraceSize; - private final Configuration config; - private final CustomFieldsAdapter customFieldsAdapter; - private final boolean sendDefaultValues; + private boolean sendDefaultValues; + + private JSON json; + + @PluginFactory + public static JsonPatternLayout createLayout(@PluginAttribute(value = "charset") final Charset charset, + @PluginAttribute(value = "sendDefaultValues") final boolean sendDefaultValues, + @PluginAttribute(value = "maxStacktraceSize") final int maxStacktraceSize, + @PluginAttribute(value = "jsonBuilder") final String jsonBuilderClass, + @PluginElement(value = "customField") CustomFieldElement[] customFieldMdcKeyNames, + @PluginElement(value = "log4jContextFieldSupplier") Log4jContextFieldSupplierElement[] log4jContextFieldSupplierElements, + @PluginElement(value = "contextFieldSupplier") ContextFieldSupplierElement[] contextFieldSupplierElements, + @PluginConfiguration final Configuration config) { + return new JsonPatternLayout(charset, sendDefaultValues, maxStacktraceSize, jsonBuilderClass, + customFieldMdcKeyNames, log4jContextFieldSupplierElements, + contextFieldSupplierElements); + } - protected JsonPatternLayout(Configuration config, Charset charset, boolean sendDefaultValues, - CustomField... customFieldMdcKeyNames) { + protected JsonPatternLayout(Charset charset, boolean sendDefaultValues, int maxStacktraceSize, + String jsonBuilderClass, CustomFieldElement[] customFieldMdcKeys, + Log4jContextFieldSupplierElement[] log4jContextFieldSupplierElements, + ContextFieldSupplierElement[] contextFieldSupplierElements) { super(charset); - this.config = config; this.sendDefaultValues = sendDefaultValues; - this.customFieldsAdapter = new CustomFieldsAdapter(customFieldMdcKeyNames); - markerSelector = createPatternSelector(customFieldMdcKeyNames); - execptionSelector = createExceptionSelector(customFieldMdcKeyNames); + this.maxStacktraceSize = maxStacktraceSize > 0 ? maxStacktraceSize : 55 * 1024; + this.contextFieldConverter = contextFieldConverter(sendDefaultValues, customFieldMdcKeys); + this.json = createJson(jsonBuilderClass); + this.log4jContextFieldSuppliers.add(new BaseFieldSupplier()); + this.log4jContextFieldSuppliers.add(new EventContextFieldSupplier()); + this.log4jContextFieldSuppliers.add(new RequestRecordFieldSupplier()); + if (log4jContextFieldSupplierElements != null) { + for (Log4jContextFieldSupplierElement current: log4jContextFieldSupplierElements) { + try { + Log4jContextFieldSupplier instance = createInstance(current.getSupplierClass(), + Log4jContextFieldSupplier.class); + log4jContextFieldSuppliers.add(instance); + } catch (ReflectiveOperationException cause) { + LOGGER.warn("Cannot register Log4jContextFieldSupplier.", cause); + } + } + } + if (contextFieldSupplierElements != null) { + for (ContextFieldSupplierElement current: contextFieldSupplierElements) { + try { + ContextFieldSupplier instance = createInstance(current.getSupplierClass(), + ContextFieldSupplier.class); + contextFieldSuppliers.add(instance); + } catch (ReflectiveOperationException cause) { + LOGGER.warn("Cannot register ContextFieldSupplier.", cause); + } + } + } + } + + private static JSON createJson(String jsonBuilderClass) { + if (jsonBuilderClass == null || jsonBuilderClass.isEmpty()) { + return JSON.std; + } + try { + + return createInstance(jsonBuilderClass, JSON.Builder.class).build(); + } catch (ReflectiveOperationException cause) { + LOGGER.warn("Cannot register JsonBuilder, using default.", cause); + return JSON.std; + } + } + + private static ContextFieldConverter contextFieldConverter(boolean sendDefaultValues, + CustomFieldElement... customFieldMdcKeys) { + List customFieldMdcKeyNames = new ArrayList<>(customFieldMdcKeys.length); + List retainFieldMdcKeyNames = new ArrayList<>(customFieldMdcKeys.length); + for (CustomFieldElement customField: customFieldMdcKeys) { + customFieldMdcKeyNames.add(customField.getKey()); + if (customField.isRetainOriginal()) { + retainFieldMdcKeyNames.add(customField.getKey()); + } + } + return new ContextFieldConverter(sendDefaultValues, customFieldMdcKeyNames, retainFieldMdcKeyNames); + } + + private static T createInstance(String className, Class interfaceClass) throws ReflectiveOperationException { + ClassLoader classLoader = JsonPatternLayout.class.getClassLoader(); + Class clazz = classLoader.loadClass(className).asSubclass(interfaceClass); + return clazz.getDeclaredConstructor().newInstance(); } @Override public String toSerializable(LogEvent event) { - PatternSelector selector = getSelector(event); - final StringBuilder buf = getStringBuilder(); - PatternFormatter[] formatters = selector.getFormatters(event); - final int len = formatters.length; - for (int i = 0; i < len; i++) { - formatters[i].format(event, buf); + try (StringBuilderWriter writer = new StringBuilderWriter(getStringBuilder())) { + ObjectComposer> oc = json.composeTo(writer).startObject(); + addMarkers(oc, event); + Map contextFields = collectContextFields(event); + contextFieldConverter.addContextFields(oc, contextFields); + contextFieldConverter.addCustomFields(oc, contextFields); + addStacktrace(oc, event); + oc.end().finish(); + return writer.append(NEWLINE).toString(); + } catch (IOException | JsonSerializationException cause) { + // Fallback to emit just the formatted message + LOGGER.error("Conversion failed ", cause); + return LogEventUtilities.getFormattedMessage(event); } - String str = buf.toString(); - return str; } - @PluginFactory - public static JsonPatternLayout createLayout(@PluginAttribute(value = "charset") final Charset charset, - @PluginAttribute(value = "sendDefaultValues") final boolean sendDefaultValues, - @PluginElement(value = "customField") CustomField[] customFieldMdcKeyNames, - @PluginConfiguration final Configuration config) { - return new JsonPatternLayout(config, charset, sendDefaultValues, customFieldMdcKeyNames); + private

void addMarkers(ObjectComposer

oc, LogEvent event) throws IOException, + JsonProcessingException { + if (sendDefaultValues || event.getMarker() != null) { + ArrayComposer> ac = oc.startArrayField(Fields.CATEGORIES); + addMarker(ac, event.getMarker()); + ac.end(); + } } - private MarkerPatternSelector createPatternSelector(CustomField... customFieldMdcKeyNames) { - PatternMatch[] pMatches = new PatternMatch[1]; - String requestPattern = new LayoutPatternBuilder(sendDefaultValues).addContextProperties(emptyList()) - .addRequestMetrics().suppressExceptions() - .build(); - pMatches[0] = new PatternMatch(Markers.REQUEST_MARKER.getName(), requestPattern); - List customFields = customFieldsAdapter.getCustomFieldKeyNames(); - List excludedFields = customFieldsAdapter.getExcludedFieldKeyNames(); - String applicationPattern = new LayoutPatternBuilder(sendDefaultValues).addBasicApplicationLogs() - .addContextProperties(excludedFields) - .addCustomFields(customFields) - .suppressExceptions().build(); - return new MarkerPatternSelector.Builder().setProperties(pMatches).setDefaultPattern(applicationPattern) - .setAlwaysWriteExceptions(false).setNoConsoleNoAnsi(false) - .setConfiguration(config).build(); + private

void addMarker(ArrayComposer

ac, org.apache.logging.log4j.Marker marker) + throws IOException { + if (marker == null) { + return; + } + ac.add(marker.getName()); + if (marker.hasParents()) { + for (org.apache.logging.log4j.Marker current: marker.getParents()) { + addMarker(ac, current); + } + } } - private PatternSelector createExceptionSelector(CustomField... customFieldMdcKeyNames) { - List customFields = customFieldsAdapter.getCustomFieldKeyNames(); - List excludedFields = customFieldsAdapter.getExcludedFieldKeyNames(); - String exceptionPattern = new LayoutPatternBuilder(sendDefaultValues).addBasicApplicationLogs() - .addContextProperties(excludedFields) - .addCustomFields(customFields) - .addStacktraces().build(); - return new MarkerPatternSelector(new PatternMatch[0], exceptionPattern, false, false, config); + private Map collectContextFields(LogEvent event) { + Map contextFields = new HashMap<>(); + contextFieldSuppliers.forEach(s -> contextFields.putAll(s.get())); + log4jContextFieldSuppliers.forEach(s -> contextFields.putAll(s.map(event))); + return contextFields; } - private PatternSelector getSelector(LogEvent event) { - if (event.getThrownProxy() != null || event.getThrown() != null) { - return execptionSelector; + private

void addStacktrace(ObjectComposer

oc, LogEvent event) throws IOException, + JsonProcessingException { + if (event.getThrown() != null) { + LineWriter lw = new LineWriter(); + event.getThrown().printStackTrace(new PrintWriter(lw)); + List lines = lw.getLines(); + StacktraceLines stacktraceLines = new StacktraceLines(lines); + ArrayComposer> ac = oc.startArrayField(Fields.STACKTRACE); + if (stacktraceLines.getTotalLineLength() <= maxStacktraceSize) { + for (String line: stacktraceLines.getLines()) { + ac.add(line); + } + } else { + ac.add("-------- STACK TRACE TRUNCATED --------"); + for (String line: stacktraceLines.getFirstLines(maxStacktraceSize / 3)) { + ac.add(line); + } + ac.add("-------- OMITTED --------"); + for (String line: stacktraceLines.getLastLines((maxStacktraceSize / 3) * 2)) { + ac.add(line); + } + } + ac.end(); } - return markerSelector; } + + @Override + public String getContentType() { + return "application/json"; + } + } diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/LayoutPatternBuilder.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/LayoutPatternBuilder.java deleted file mode 100644 index b4550075..00000000 --- a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/LayoutPatternBuilder.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.sap.hcp.cf.log4j2.layout; - -import static java.util.Arrays.asList; - -import java.util.List; - -import com.sap.hcp.cf.log4j2.converter.ContextPropsConverter; -import com.sap.hcp.cf.log4j2.converter.CustomFieldsConverter; -import com.sap.hcp.cf.log4j2.converter.Log4JStacktraceConverter; -import com.sap.hcp.cf.logging.common.Defaults; -import com.sap.hcp.cf.logging.common.Fields; - -/** - * Defines the "built-in" layout pattern for our JSON encoder. - * - */ -public final class LayoutPatternBuilder { - - /* - * -- all layout patterns end like this - */ - private static final String COMMON_POSTFIX_PATTERN = "}%n"; - - private final StringBuilder sb; - private final boolean sendDefaultValues; - - /* - * -- this defines the common prefix to all variants. -- the final line will - * add non-predefined context parameters from the MDC -- as this list may be - * empty, we use "replace" to at a colon if it's not - */ - public LayoutPatternBuilder(boolean sendDefaultValues) { - this.sendDefaultValues = sendDefaultValues; - this.sb = new StringBuilder("{ "); - appendQuoted(Fields.WRITTEN_AT, "%d{yyyy-MM-dd'T'HH:mm:ss.SSSX}{UTC}").append(","); - appendUnquoted(Fields.WRITTEN_TS, "%tstamp").append(","); - } - - public LayoutPatternBuilder addRequestMetrics() { - appendQuoted(Fields.TYPE, Defaults.TYPE_REQUEST).append(","); - sb.append("%jsonmsg{flatten}").append(","); - return this; - } - - public LayoutPatternBuilder addBasicApplicationLogs() { - appendQuoted(Fields.TYPE, Defaults.TYPE_LOG).append(","); - appendQuoted(Fields.LOGGER, "%replace{%logger}{\"}{\\\\\"}").append(","); - appendQuoted(Fields.THREAD, "%replace{%thread}{\"}{\\\\\"}").append(","); - appendQuoted(Fields.LEVEL, "%p").append(","); - appendUnquoted(Fields.CATEGORIES, "%categories").append(","); - appendUnquoted(Fields.MSG, "%jsonmsg{escape}").append(","); - return this; - } - - public LayoutPatternBuilder addContextProperties(List exclusions) { - sb.append("%").append(ContextPropsConverter.WORD); - appendParameters(asList(Boolean.toString(sendDefaultValues))); - appendParameters(exclusions); - sb.append(","); - return this; - } - - public LayoutPatternBuilder addCustomFields(List mdcKeyNames) { - if (mdcKeyNames == null || mdcKeyNames.isEmpty()) { - return this; - } - sb.append("\"").append(Fields.CUSTOM_FIELDS).append("\":"); - sb.append("{%").append(CustomFieldsConverter.WORD); - appendParameters(mdcKeyNames); - sb.append("}"); - sb.append(","); - return this; - } - - public LayoutPatternBuilder addStacktraces() { - sb.append("\"").append(Fields.STACKTRACE).append("\":"); - sb.append("%").append(Log4JStacktraceConverter.WORD); - sb.append(","); - return this; - } - - public LayoutPatternBuilder suppressExceptions() { - removeTrailingComma(); - sb.append("%ex{0} "); - sb.append(","); - return this; - } - - private StringBuilder appendQuoted(String key, String value) { - return sb.append("\"").append(key).append("\":") // - .append("\"").append(value).append("\""); - } - - private StringBuilder appendUnquoted(String key, String value) { - return sb.append("\"").append(key).append("\":").append(value); - } - - private void appendParameters(List parameters) { - sb.append("{"); - for (int i = 0; i < parameters.size(); i++) { - sb.append(parameters.get(i)); - if (i < parameters.size() - 1) { - sb.append("}{"); - } - } - sb.append("}"); - } - - private void removeTrailingComma() { - if (sb.length() == sb.lastIndexOf(",") + 1) { - sb.delete(sb.length() - 1, sb.length()); - } - } - - public String build() { - removeTrailingComma(); - return sb.append(COMMON_POSTFIX_PATTERN).toString(); - } - -} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/Log4jContextFieldSupplierElement.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/Log4jContextFieldSupplierElement.java new file mode 100644 index 00000000..f29096d4 --- /dev/null +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/Log4jContextFieldSupplierElement.java @@ -0,0 +1,49 @@ +package com.sap.hcp.cf.log4j2.layout; + +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; + +@Plugin(name = "log4jContextFieldSupplier", category = Node.CATEGORY, printObject = true) +public class Log4jContextFieldSupplierElement { + + private String supplierClass; + + public Log4jContextFieldSupplierElement(Builder builder) { + this.supplierClass = builder.clazz; + } + + public String getSupplierClass() { + return supplierClass; + } + + @Override + public String toString() { + return supplierClass; + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements + org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute("class") + private String clazz; + + public Builder setClazz(String clazz) { + this.clazz = clazz; + return this; + } + + @Override + public Log4jContextFieldSupplierElement build() { + return new Log4jContextFieldSupplierElement(this); + } + + } + +} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/BaseFieldSupplier.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/BaseFieldSupplier.java new file mode 100644 index 00000000..2e0f36b2 --- /dev/null +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/BaseFieldSupplier.java @@ -0,0 +1,38 @@ +package com.sap.hcp.cf.log4j2.layout.supppliers; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.LogEvent; + +import com.sap.hcp.cf.log4j2.converter.api.Log4jContextFieldSupplier; +import com.sap.hcp.cf.logging.common.Defaults; +import com.sap.hcp.cf.logging.common.Fields; + +public class BaseFieldSupplier implements Log4jContextFieldSupplier { + + @Override + public Map map(LogEvent event) { + Map fields = new HashMap<>(6); + fields.put(Fields.WRITTEN_AT, getIsoTs(event)); + fields.put(Fields.WRITTEN_TS, getNanoTs(event)); + fields.put(Fields.TYPE, LogEventUtilities.isRequestLog(event) ? Defaults.TYPE_REQUEST : Defaults.TYPE_LOG); + fields.put(Fields.LEVEL, String.valueOf(event.getLevel())); + fields.put(Fields.LOGGER, event.getLoggerName()); + if (!LogEventUtilities.isRequestLog(event) && event.getMessage() != null) { + fields.put(Fields.MSG, LogEventUtilities.getFormattedMessage(event)); + } + return fields; + } + + private String getIsoTs(LogEvent event) { + org.apache.logging.log4j.core.time.Instant instant = event.getInstant(); + return Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNanoOfSecond()).toString(); + } + + private String getNanoTs(LogEvent event) { + org.apache.logging.log4j.core.time.Instant instant = event.getInstant(); + return String.valueOf(instant.getEpochSecond() * 1_000_000_000L + instant.getNanoOfSecond()); + } +} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/EventContextFieldSupplier.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/EventContextFieldSupplier.java new file mode 100644 index 00000000..60dd6efc --- /dev/null +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/EventContextFieldSupplier.java @@ -0,0 +1,23 @@ +package com.sap.hcp.cf.log4j2.layout.supppliers; + +import java.util.Map; + +import org.apache.logging.log4j.core.LogEvent; + +import com.sap.hcp.cf.log4j2.converter.api.Log4jContextFieldSupplier; +import com.sap.hcp.cf.logging.common.serialization.AbstractContextFieldSupplier; + +public class EventContextFieldSupplier extends AbstractContextFieldSupplier implements + Log4jContextFieldSupplier { + + @Override + protected Map getContextMap(LogEvent event) { + return event.getContextData().toMap(); + } + + @Override + protected Object[] getParameterArray(LogEvent event) { + return LogEventUtilities.getParameterArray(event); + } + +} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/LogEventUtilities.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/LogEventUtilities.java new file mode 100644 index 00000000..92993352 --- /dev/null +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/LogEventUtilities.java @@ -0,0 +1,26 @@ +package com.sap.hcp.cf.log4j2.layout.supppliers; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; + +import com.sap.hcp.cf.logging.common.Markers; + +public final class LogEventUtilities { + + private LogEventUtilities() { + } + + public static String getFormattedMessage(LogEvent event) { + Message message = event.getMessage(); + return message == null ? null : message.getFormattedMessage(); + } + + public static Object[] getParameterArray(LogEvent event) { + Message message = event.getMessage(); + return message == null ? null : message.getParameters(); + } + + public static boolean isRequestLog(LogEvent event) { + return event.getMarker() != null && Markers.REQUEST_MARKER.getName().equals(event.getMarker().getName()); + } +} diff --git a/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/RequestRecordFieldSupplier.java b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/RequestRecordFieldSupplier.java new file mode 100644 index 00000000..961a529e --- /dev/null +++ b/cf-java-logging-support-log4j2/src/main/java/com/sap/hcp/cf/log4j2/layout/supppliers/RequestRecordFieldSupplier.java @@ -0,0 +1,26 @@ +package com.sap.hcp.cf.log4j2.layout.supppliers; + +import org.apache.logging.log4j.core.LogEvent; + +import com.sap.hcp.cf.log4j2.converter.api.Log4jContextFieldSupplier; +import com.sap.hcp.cf.logging.common.serialization.AbstractRequestRecordFieldSupplier; + +public class RequestRecordFieldSupplier extends AbstractRequestRecordFieldSupplier implements + Log4jContextFieldSupplier { + + @Override + protected boolean isRequestLog(LogEvent event) { + return LogEventUtilities.isRequestLog(event); + } + + @Override + protected String getFormattedMessage(LogEvent event) { + return LogEventUtilities.getFormattedMessage(event); + } + + @Override + protected Object[] getParameterArray(LogEvent event) { + return LogEventUtilities.getParameterArray(event); + } + +} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/benchmark/EncodingBenchmarks.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/benchmark/EncodingBenchmarks.java index 7180941c..c24193d1 100644 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/benchmark/EncodingBenchmarks.java +++ b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/benchmark/EncodingBenchmarks.java @@ -23,7 +23,7 @@ import com.sap.hcp.cf.logging.common.request.RequestRecord; @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) public class EncodingBenchmarks { public static Logger LOG = LoggerFactory.getLogger(EncodingBenchmarks.class); @@ -72,7 +72,7 @@ public void minimalRequestRecord(BenchmarkState state) { state.componentId).build(); requestRecord.start(); requestRecord.stop(); - LOG.info(Markers.REQUEST_MARKER, "{}", requestRecord); + LOG.info(Markers.REQUEST_MARKER, "", requestRecord); } } diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/AbstractConverterTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/AbstractConverterTest.java deleted file mode 100644 index 2362cd38..00000000 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/AbstractConverterTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.Log4jLogEvent.Builder; -import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Before; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; - -import com.fasterxml.jackson.jr.ob.JSON; -import com.fasterxml.jackson.jr.ob.JSONObjectException; - -public abstract class AbstractConverterTest { - protected static final String SOME_KEY = "some_key"; - protected static final String SOME_VALUE = "some value"; - protected static final String STRANGE_SEQ = "}{:\",\""; - protected static final String SOME_OTHER_KEY = "some_other_key"; - protected static final String SOME_OTHER_VALUE = "some other value"; - protected static final String TEST_MSG_NO_ARGS = "This is a test "; - protected static final Object[] NO_ARGS = new Object[0]; - - @Before - public void start() { - Logger logger = LoggerFactory.getLogger(AbstractConverterTest.class); - logger.debug("starting"); - } - - protected String format(LogEventPatternConverter converter, LogEvent event) { - StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - return sb.toString(); - } - - protected LogEvent makeEvent(String msg, Object... args) { - return makeEvent(msg, null, args); - } - - protected LogEvent makeEvent(String msg, Throwable t, Object[] args) { - Message message; - if (args == null || args.length == 0) { - message = new SimpleMessage(msg); - } else { - message = new ParameterizedMessage(msg, args); - } - LogEvent event = new Builder().setLevel(Level.INFO).setMessage(message).setThrown(t).build(); - return event; - } - - protected Object arrayElem(String serialized, int i) throws JSONObjectException, IOException { - return arrayFrom(serialized)[i]; - } - - protected Object[] arrayFrom(String serialized) throws JSONObjectException, IOException { - return JSON.std.arrayFrom(serialized); - } - - - - protected Map mapFrom(String serialized) throws JSONObjectException, IOException { - return mapFrom(serialized, true); - } - - protected Map mapFrom(String serialized, boolean wrap) throws JSONObjectException, IOException { - if (wrap) { - return JSON.std.mapFrom("{" + serialized + "}"); - } - return JSON.std.mapFrom(serialized); - } - - protected Map mdcMap() { - return mdcMap(null); - } - - protected Map mdcMap(String[] exclusions) { - Map result = new HashMap(); - List exclusionList; - if (exclusions == null) { - exclusionList = Arrays.asList(new String[0]); - } else { - exclusionList = Arrays.asList(exclusions); - } - for (Entry t: MDC.getCopyOfContextMap().entrySet()) { - if (!exclusionList.contains(t.getKey())) { - result.put(t.getKey(), t.getValue()); - } - } - return result; - } -} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/ContextPropsConverterTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/ContextPropsConverterTest.java deleted file mode 100644 index c86f1f0f..00000000 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/ContextPropsConverterTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.core.Is.is; - -import java.io.IOException; -import java.util.Map; - -import org.junit.Test; -import org.slf4j.MDC; - -import com.fasterxml.jackson.jr.ob.JSONObjectException; - -public class ContextPropsConverterTest extends AbstractConverterTest { - - private static final String[] EXCLUDE_SOME_KEY = new String[] { Boolean.FALSE.toString(), SOME_KEY }; - private static final String[] NO_EXCLUSIONS = new String[] { Boolean.FALSE.toString() }; - - @Test - public void testEmpty() throws JSONObjectException, IOException { - ContextPropsConverter cpc = new ContextPropsConverter(NO_EXCLUSIONS); - MDC.clear(); - assertThat(mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS))).size(), is(0)); - } - - @Test - public void testEmptyWithDefaults() throws JSONObjectException, IOException { - ContextPropsConverter cpc = new ContextPropsConverter(new String[] { Boolean.TRUE.toString() }); - MDC.clear(); - assertThat(mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS))), is(mdcMap())); - } - - @Test - public void testSingleArg() throws JSONObjectException, IOException { - ContextPropsConverter cpc = new ContextPropsConverter(NO_EXCLUSIONS); - MDC.clear(); - MDC.put(SOME_KEY, SOME_VALUE); - Map resultMap = mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS))); - assertThat(resultMap, hasEntry(SOME_KEY, SOME_VALUE)); - assertThat(resultMap.size(), is(1)); - } - - @Test - public void testTwoArgs() throws JSONObjectException, IOException { - ContextPropsConverter cpc = new ContextPropsConverter(NO_EXCLUSIONS); - MDC.clear(); - MDC.put(SOME_KEY, SOME_VALUE); - MDC.put(SOME_OTHER_KEY, SOME_OTHER_VALUE); - Map resultMap = mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS))); - assertThat(resultMap, hasEntry(SOME_KEY, SOME_VALUE)); - assertThat(resultMap, hasEntry(SOME_OTHER_KEY, SOME_OTHER_VALUE)); - assertThat(resultMap.size(), is(2)); - - } - - @Test - public void testStrangeArgs() throws JSONObjectException, IOException { - ContextPropsConverter cpc = new ContextPropsConverter(NO_EXCLUSIONS); - MDC.clear(); - MDC.put(SOME_KEY, SOME_VALUE); - MDC.put(STRANGE_SEQ, STRANGE_SEQ); - Map resultMap = mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS))); - assertThat(resultMap, hasEntry(SOME_KEY, SOME_VALUE)); - assertThat(resultMap, hasEntry(STRANGE_SEQ, STRANGE_SEQ)); - assertThat(resultMap.size(), is(2)); - } - - @Test - public void testExclusion() throws JSONObjectException, IOException { - ContextPropsConverter cpc = new ContextPropsConverter(EXCLUDE_SOME_KEY); - MDC.clear(); - MDC.put(SOME_KEY, SOME_VALUE); - MDC.put(SOME_OTHER_KEY, SOME_OTHER_VALUE); - Map resultMap = mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS))); - assertThat(resultMap, hasEntry(SOME_OTHER_KEY, SOME_OTHER_VALUE)); - assertThat(resultMap.size(), is(1)); - } - - @Test - public void testExclusionStrangeSeq() throws JSONObjectException, IOException { - String[] exclusions = new String[] { Boolean.FALSE.toString(), STRANGE_SEQ }; - ContextPropsConverter cpc = new ContextPropsConverter(exclusions); - MDC.clear(); - MDC.put(SOME_KEY, SOME_VALUE); - MDC.put(STRANGE_SEQ, STRANGE_SEQ); - Map resultMap = mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS))); - assertThat(resultMap, hasEntry(SOME_KEY, SOME_VALUE)); - assertThat(resultMap.size(), is(1)); - } - - @Test - public void testUnregisteredCustomField() throws Exception { - ContextPropsConverter cpc = new ContextPropsConverter(NO_EXCLUSIONS); - assertThat(mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, customField(SOME_KEY, SOME_VALUE)))), - hasEntry(SOME_KEY, SOME_VALUE)); - } - - @Test - public void testRegisteredCustomField() throws Exception { - ContextPropsConverter cpc = new ContextPropsConverter(EXCLUDE_SOME_KEY); - assertThat(mapFrom(format(cpc, makeEvent(TEST_MSG_NO_ARGS, customField(SOME_KEY, SOME_VALUE)))), - not(hasEntry(SOME_KEY, SOME_VALUE))); - } - -} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/CustomFieldsConverterTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/CustomFieldsConverterTest.java deleted file mode 100644 index 273ebbcd..00000000 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/CustomFieldsConverterTest.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.sameInstance; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.same; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.util.SortedArrayStringMap; -import org.hamcrest.Matcher; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import com.sap.hcp.cf.logging.common.converter.DefaultCustomFieldsConverter; -import com.sap.hcp.cf.logging.common.customfields.CustomField; - -@RunWith(MockitoJUnitRunner.class) -public class CustomFieldsConverterTest { - - private static final Object[] NO_PARAMETERS = new Object[0]; - private static final String[] CUSTOM_FIELD_KEYS = new String[] { "this key", "that key" }; - @SuppressWarnings("serial") - private static Map MDC_PROPERTIES = new HashMap() { - { - put("this key", "this value"); - put("that key", "that value"); - put("other key", "other value"); - } - }; - - @Mock - private DefaultCustomFieldsConverter defaultConverter; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private LogEvent event; - - @Captor - private ArgumentCaptor> mdcFields; - - @Captor - private ArgumentCaptor arguments; - - private CustomFieldsConverter converter; - - @Before - public void initializeConverter() { - this.converter = new CustomFieldsConverter(CUSTOM_FIELD_KEYS); - converter.setConverter(defaultConverter); - } - - @Test - public void forwardsStringBuilder() throws Exception { - StringBuilder sb = new StringBuilder(); - - converter.format(event, sb); - - verify(defaultConverter).convert(same(sb), any(), any()); - } - - @Test - public void emptyMdcAndArguments() throws Exception { - StringBuilder sb = new StringBuilder(); - when(event.getContextData()).thenReturn(new SortedArrayStringMap()); - when(event.getMessage().getParameters()).thenReturn(NO_PARAMETERS); - - converter.format(event, sb); - - verifyConverterCall(emptyMap()); - } - - @Test - public void standardArgument() throws Exception { - StringBuilder sb = new StringBuilder(); - when(event.getContextData()).thenReturn(new SortedArrayStringMap()); - when(event.getMessage().getParameters()).thenReturn(new Object[] { "standard argument" }); - - converter.format(event, sb); - - verifyConverterCall(emptyMap(), is("standard argument")); - } - - @Test - public void singleCustomFieldArgument() throws Exception { - StringBuilder sb = new StringBuilder(); - when(event.getContextData()).thenReturn(new SortedArrayStringMap()); - CustomField customField = customField("some key", "some value"); - when(event.getMessage().getParameters()).thenReturn(new Object[] { customField }); - - converter.format(event, sb); - - verifyConverterCall(emptyMap(), is(sameInstance(customField))); - } - - @Test - public void mdcFields() throws Exception { - StringBuilder sb = new StringBuilder(); - when(event.getContextData()).thenReturn(new SortedArrayStringMap(MDC_PROPERTIES)); - when(event.getMessage().getParameters()).thenReturn(NO_PARAMETERS); - - converter.format(event, sb); - - verifyConverterCall(allOf(hasEntry("this key", "this value"), hasEntry("that key", "that value"), - hasEntry("other key", "other value"))); - } - - @Test - public void mergesMdcFieldsAndArguments() throws Exception { - StringBuilder sb = new StringBuilder(); - when(event.getContextData()).thenReturn(new SortedArrayStringMap(MDC_PROPERTIES)); - CustomField customField = customField("some key", "some value"); - when(event.getMessage().getParameters()).thenReturn(new Object[] { customField }); - - converter.format(event, sb); - - verifyConverterCall(allOf(hasEntry("this key", "this value"), hasEntry("that key", "that value"), - hasEntry("other key", "other value")), is(sameInstance(customField))); - - } - - private static Matcher> emptyMap() { - return not(hasEntry(anyString(), anyString())); - } - - private static Matcher anyString() { - return org.hamcrest.Matchers.any(String.class); - } - - private void verifyConverterCall(Matcher> mdcFieldsMatcher) { - verify(defaultConverter).convert(any(), mdcFields.capture()); - assertThat(mdcFields.getValue(), mdcFieldsMatcher); - } - - private void verifyConverterCall(Matcher> mdcFieldsMatcher, - Matcher argumentsMatcher) { - verify(defaultConverter).convert(any(), mdcFields.capture(), arguments.capture()); - assertThat(mdcFields.getValue(), mdcFieldsMatcher); - assertThat(arguments.getValue(), argumentsMatcher); - } - -} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/TestJsonMessageConverter.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/TestJsonMessageConverter.java deleted file mode 100644 index 5b23667e..00000000 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/TestJsonMessageConverter.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -import org.junit.Test; - -import com.sap.hcp.cf.logging.common.request.RequestRecord; - -public class TestJsonMessageConverter extends AbstractConverterTest { - - private static final String ARRAY_MSG = "[1, 2, 3, 4]"; - private static final String OBJ_MSG = "{\"foo\":\"bar\", \"baz\":1}"; - private static final String LOG_PROVIDER = "test"; - - @Test - public void testSimple() { - JsonMessageConverter jmc = new JsonMessageConverter(null); - assertThat(format(jmc, makeEvent(TEST_MSG_NO_ARGS, NO_ARGS)), is(TEST_MSG_NO_ARGS)); - } - - @Test - public void testSimpleQuoted() { - JsonMessageConverter jmc = new JsonMessageConverter(null); - String quotedMsg = TEST_MSG_NO_ARGS + " with a \"quote\""; - assertThat(format(jmc, makeEvent(quotedMsg, NO_ARGS)), is(quotedMsg)); - } - - @Test - public void testArrayMsgNotFlattened() { - JsonMessageConverter jmc = new JsonMessageConverter(null); - assertThat(format(jmc, makeEvent(ARRAY_MSG, NO_ARGS)), is(ARRAY_MSG)); - } - - @Test - public void testArrayMsgFlattened() { - JsonMessageConverter jmc = new JsonMessageConverter(new String[] { "flatten" }); - assertThat(format(jmc, makeEvent(ARRAY_MSG, NO_ARGS)), is(ARRAY_MSG.substring(1, ARRAY_MSG.length() - 1))); - } - - @Test - public void testObjMsgNotFlattened() { - JsonMessageConverter jmc = new JsonMessageConverter(null); - assertThat(format(jmc, makeEvent(OBJ_MSG, NO_ARGS)), is(OBJ_MSG)); - } - - @Test - public void testObjMsgFlattened() { - JsonMessageConverter jmc = new JsonMessageConverter(new String[] { "flatten" }); - assertThat(format(jmc, makeEvent(OBJ_MSG, NO_ARGS)), is(OBJ_MSG.substring(1, OBJ_MSG.length() - 1))); - } - - @Test - public void testLogRecordMsgNotFlattened() { - JsonMessageConverter jmc = new JsonMessageConverter(null); - RequestRecord lrec = new RequestRecord(LOG_PROVIDER); - String lmsg = lrec.toString(); - assertThat(format(jmc, makeEvent(lmsg, NO_ARGS)), is(lmsg)); - } - - @Test - public void testLogRecordMsgFlattened() { - JsonMessageConverter jmc = new JsonMessageConverter(new String[] { "flatten" }); - RequestRecord lrec = new RequestRecord(LOG_PROVIDER); - String lmsg = lrec.toString(); - assertThat(format(jmc, makeEvent(lmsg, NO_ARGS)), is(lmsg.substring(1, lmsg.length() - 1))); - } - - @Test - public void testEscapedMessage() { - JsonMessageConverter jmc = new JsonMessageConverter(null); - String strangeMsg = TEST_MSG_NO_ARGS + STRANGE_SEQ; - assertThat(format(jmc, makeEvent(strangeMsg, NO_ARGS)), is(strangeMsg)); - - } -} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/TestStracktraceConverter.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/TestStracktraceConverter.java deleted file mode 100644 index d336afd4..00000000 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/converter/TestStracktraceConverter.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.sap.hcp.cf.log4j2.converter; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.isEmptyString; -import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.io.IOException; - -import org.hamcrest.Matchers; -import org.junit.Test; - -import com.fasterxml.jackson.jr.ob.JSONObjectException; -import com.sap.hcp.cf.logging.common.helper.StacktraceGenerator; -import com.sap.hcp.cf.logging.common.helper.SubstringCounter; - -public class TestStracktraceConverter extends AbstractConverterTest { - private static final String[] NO_STRING_ARGS = new String[0]; - - @Test - public void testEmpty() { - Log4JStacktraceConverter stc = new Log4JStacktraceConverter(NO_STRING_ARGS); - assertThat(format(stc, makeEvent(null, null, null)), isEmptyString()); - } - - @Test - public void testSynthetic() throws Exception { - Log4JStacktraceConverter stc = new Log4JStacktraceConverter(NO_STRING_ARGS); - String actual = format(stc, makeEvent(null, new NullPointerException(), null)); - assertThat(actual, not(isEmptyString())); - assertThat(arrayElem(actual, 0).toString(), equalTo(NullPointerException.class.getName())); - } - - @Test - public void testReal() throws Exception { - Log4JStacktraceConverter stc = new Log4JStacktraceConverter(NO_STRING_ARGS); - double a = 1.0, b = 0.0; - try { - /* -- force exception -- */ - new Double(a / b); - } catch (Exception ex) { - String actual = format(stc, makeEvent(null, ex, null)); - int stackDepth = ex.getStackTrace().length + 1; - assertThat(actual, not(isEmptyString())); - assertThat(arrayElem(actual, 0).toString(), equalTo(ex.getClass().getName())); - assertThat(arrayFrom(actual).length, lessThanOrEqualTo(stackDepth)); - } - } - - /** - * We create a long stack trace by calling functions recursively in the - * Class StacktraceGenerator. The f1 and f3 parts are considered important, - * whereas we're less interested in f2. The recursion depths are - * hand-crafted according to DefaultStacktraceConverter.MAX_SIZE so that: - * The total size of the stack trace exceeds MAX_SIZE; thus truncation - * occurs. All the f3 lines are within the first MAX_SIZE/3 chars. All the - * f1 lines are within the last MAX_SIZE/3*2 chars. The test succeeds if the - * truncated stack trace contains all the f1 and f3 lines. - */ - - @Test - public void testHuge() throws Exception { - StacktraceGenerator stacktraceGenerator = new StacktraceGenerator(245, 330, 115); - Exception ex = stacktraceGenerator.generateException(); - Log4JStacktraceConverter stc = new Log4JStacktraceConverter(NO_STRING_ARGS); - String actual = format(stc, makeEvent(null, ex, null)); - - // Did this return anything? - assertThat(actual, not(isEmptyString())); - // Was the stack trace really truncated? - assertThat(getStacktraceLine(actual, 0), containsString("STACK TRACE TRUNCATED")); - // Do we see the exception (in the line after "STACK TRACE - // TRUNCATED")? - assertThat(getStacktraceLine(actual, 1), containsString(ex.getClass().getName())); - - SubstringCounter substringCounter = new SubstringCounter(); - - // Tests that all f1-functions which are considered important are - // present in the stacktrace - String f1 = "StacktraceGenerator.f1IsASimpleFunctionWithAnExceptionallyLongName"; - int expectedOccurrencesOf_f1 = stacktraceGenerator.getF1RecursionDepth() + 1; - int countedOccurrencesOf_f1 = substringCounter.countOccurrencesOfSubstringInBigString(f1, actual); - assertEquals(expectedOccurrencesOf_f1, countedOccurrencesOf_f1); - - // Tests that some f2-functions which represent the middle-part of the - // stacktrace are missing - String f2 = "StacktraceGenerator.f2IsASimpleFunctionWithAnExceptionallyLongName"; - int expectedOccurrencesOf_f2 = stacktraceGenerator.getF2RecursionDepth() + 1; - int countedOccurrencesOf_f2 = substringCounter.countOccurrencesOfSubstringInBigString(f2, actual); - assertThat(expectedOccurrencesOf_f2, Matchers.greaterThan(countedOccurrencesOf_f2)); - - // Tests that all f3-functions which are considered important are - // present in the stacktrace - String f3 = "StacktraceGenerator.f3IsASimpleFunctionWithAnExceptionallyLongName"; - int expectedOccurrencesOf_f3 = stacktraceGenerator.getF3RecursionDepth() + 1; - int countedOccurrencesOf_f3 = substringCounter.countOccurrencesOfSubstringInBigString(f3, actual); - assertEquals(expectedOccurrencesOf_f3, countedOccurrencesOf_f3); - } - - private String getStacktraceLine(String stacktrace, int lineNumber) throws JSONObjectException, IOException { - return arrayElem(stacktrace, lineNumber).toString(); - } -} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/CustomFieldsAdapterTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/CustomFieldsAdapterTest.java deleted file mode 100644 index a82d7d5a..00000000 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/CustomFieldsAdapterTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.sap.hcp.cf.log4j2.layout; - -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; - -import com.sap.hcp.cf.logging.common.LogContext; - -public class CustomFieldsAdapterTest { - - @Test - public void returnsEmptyCustomFieldsWithoutConfig() throws Exception { - CustomFieldsAdapter adapter = new CustomFieldsAdapter(); - - assertThat(adapter.getCustomFieldKeyNames(), is(empty())); - } - - @Test - public void returnsEmptyExclusionsWithoutConfig() throws Exception { - CustomFieldsAdapter adapter = new CustomFieldsAdapter(); - - assertThat(adapter.getExcludedFieldKeyNames(), is(empty())); - } - - @Test - public void providesGivenCustomFields() throws Exception { - CustomFieldsAdapter adapter = new CustomFieldsAdapter(CustomField.newBuilder().setKey("this key").build(), - CustomField.newBuilder().setKey("that key").build()); - - assertThat(adapter.getCustomFieldKeyNames(), containsInAnyOrder("this key", "that key")); - } - - @Test - public void excludesGivenCustomFields() throws Exception { - CustomFieldsAdapter adapter = new CustomFieldsAdapter(CustomField.newBuilder().setKey("this key").build(), - CustomField.newBuilder().setKey("that key").build()); - - assertThat(adapter.getExcludedFieldKeyNames(), containsInAnyOrder("this key", "that key")); - } - - @Test - public void removesCustomFieldsFromExclusionsWhenRetained() throws Exception { - CustomFieldsAdapter adapter = new CustomFieldsAdapter( - CustomField.newBuilder().setKey("this key").setRetainOriginal(true).build(), - CustomField.newBuilder().setKey("that key").build()); - - assertThat(adapter.getExcludedFieldKeyNames(), contains("that key")); - } - - @Test - public void neverExcludesLogContextFieldsEvenWhenConfigured() throws Exception { - List customFields = new ArrayList<>(LogContext.getContextFieldsKeys().size()); - for (String key : LogContext.getContextFieldsKeys()) { - customFields.add(CustomField.newBuilder().setKey(key).build()); - } - CustomFieldsAdapter adapter = new CustomFieldsAdapter( - customFields.toArray(new CustomField[customFields.size()])); - - assertThat(adapter.getExcludedFieldKeyNames(), is(empty())); - } -} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/LayoutPatternBuilderTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/LayoutPatternBuilderTest.java deleted file mode 100644 index e5de172c..00000000 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/LayoutPatternBuilderTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.sap.hcp.cf.log4j2.layout; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isEmptyString; - -import java.util.Arrays; -import java.util.Collections; - -import org.hamcrest.FeatureMatcher; -import org.hamcrest.Matcher; -import org.junit.Test; - -public class LayoutPatternBuilderTest { - - private static final String COMMON_PREFIX = "{ \"written_at\":\"%d{yyyy-MM-dd'T'HH:mm:ss.SSSX}{UTC}\",\"written_ts\":%tstamp"; - private static final String COMMON_SUFFIX = "}%n"; - - @Test - public void minimalPattern() throws Exception { - String pattern = new LayoutPatternBuilder(false).build(); - - assertThat(pattern, is(COMMON_PREFIX + COMMON_SUFFIX)); - assertThat(pattern, specificPart(is(isEmptyString()))); - } - - @Test - public void requestPattern() throws Exception { - String pattern = new LayoutPatternBuilder(false).addRequestMetrics().build(); - - assertThat(pattern, specificPart(is(",\"type\":\"request\",%jsonmsg{flatten}"))); - } - - @Test - public void basicApplicationLogs() throws Exception { - String pattern = new LayoutPatternBuilder(false).addBasicApplicationLogs().build(); - - assertThat(pattern, specificPart(is( - ",\"type\":\"log\",\"logger\":\"%replace{%logger}{\"}{\\\\\"}\",\"thread\":\"%replace{%thread}{\"}{\\\\\"}\",\"level\":\"%p\",\"categories\":%categories,\"msg\":%jsonmsg{escape}"))); - } - - @Test - public void contextPropertiesWithoutDefaultValues() throws Exception { - String pattern = new LayoutPatternBuilder(false).addContextProperties(Arrays.asList("this key", "that key")) - .build(); - - assertThat(pattern, specificPart(is(",%ctxp{false}{this key}{that key}"))); - } - - @Test - public void contextPropertiesWithDefaultValues() throws Exception { - String pattern = new LayoutPatternBuilder(true).addContextProperties(Arrays.asList("this key", "that key")) - .build(); - - assertThat(pattern, specificPart(is(",%ctxp{true}{this key}{that key}"))); - } - - @Test - public void customFields() throws Exception { - String pattern = new LayoutPatternBuilder(false).addCustomFields(Arrays.asList("this key", "that key")).build(); - - assertThat(pattern, specificPart(is(",\"#cf\":{%cf{this key}{that key}}"))); - } - - @Test - public void emptyCustomFields() throws Exception { - String pattern = new LayoutPatternBuilder(false).addCustomFields(Collections.emptyList()).build(); - - assertThat(pattern, specificPart(is(""))); - } - - @Test - public void nullCustomFields() throws Exception { - String pattern = new LayoutPatternBuilder(false).addCustomFields(null).build(); - - assertThat(pattern, specificPart(is(""))); - } - - @Test - public void stacktrace() throws Exception { - String pattern = new LayoutPatternBuilder(false).addStacktraces().build(); - - assertThat(pattern, specificPart(is(",\"stacktrace\":%stacktrace"))); - } - - @Test - public void suppressExceptions() throws Exception { - String pattern = new LayoutPatternBuilder(false).suppressExceptions().build(); - - assertThat(pattern, specificPart(is("%ex{0} "))); - } - - @Test - public void requestMetricsScenario() throws Exception { - String pattern = new LayoutPatternBuilder(false).addRequestMetrics().addContextProperties(emptyList()) - .suppressExceptions().build(); - - assertThat(pattern, specificPart(is(",\"type\":\"request\",%jsonmsg{flatten},%ctxp{false}{}%ex{0} "))); - } - - @Test - public void applicationScenario() throws Exception { - String pattern = new LayoutPatternBuilder(false).addBasicApplicationLogs() - .addContextProperties(asList("excluded-field")).addCustomFields(asList("custom-field")) - .suppressExceptions().build(); - - assertThat(pattern, specificPart(is( - ",\"type\":\"log\",\"logger\":\"%replace{%logger}{\"}{\\\\\"}\",\"thread\":\"%replace{%thread}{\"}{\\\\\"}\",\"level\":\"%p\",\"categories\":%categories,\"msg\":%jsonmsg{escape},%ctxp{false}{excluded-field},\"#cf\":{%cf{custom-field}}%ex{0} "))); - } - - @Test - public void exceptionScenario() throws Exception { - String pattern = new LayoutPatternBuilder(false).addBasicApplicationLogs() - .addContextProperties(asList("excluded-field")).addCustomFields(asList("custom-field")).addStacktraces() - .build(); - - assertThat(pattern, specificPart(is( - ",\"type\":\"log\",\"logger\":\"%replace{%logger}{\"}{\\\\\"}\",\"thread\":\"%replace{%thread}{\"}{\\\\\"}\",\"level\":\"%p\",\"categories\":%categories,\"msg\":%jsonmsg{escape},%ctxp{false}{excluded-field},\"#cf\":{%cf{custom-field}},\"stacktrace\":%stacktrace"))); - } - - private static Matcher specificPart(Matcher expected) { - return new FeatureMatcher(expected, "specific part", "specific part") { - - @Override - protected String featureValueOf(String fullPattern) { - return fullPattern.substring(COMMON_PREFIX.length(), fullPattern.length() - COMMON_SUFFIX.length()); - } - }; - } -} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/suppliers/EventContextFieldSupplierTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/suppliers/EventContextFieldSupplierTest.java new file mode 100644 index 00000000..dd7d73bc --- /dev/null +++ b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/suppliers/EventContextFieldSupplierTest.java @@ -0,0 +1,76 @@ +package com.sap.hcp.cf.log4j2.layout.suppliers; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.LogEvent; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.sap.hcp.cf.log4j2.converter.api.Log4jContextFieldSupplier; +import com.sap.hcp.cf.log4j2.layout.supppliers.EventContextFieldSupplier; +import com.sap.hcp.cf.logging.common.customfields.CustomField; + +@RunWith(MockitoJUnitRunner.class) +public class EventContextFieldSupplierTest { + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LogEvent event; + + private Log4jContextFieldSupplier fieldSupplier = new EventContextFieldSupplier(); + + @Test + public void emptyMdcAndNoArguments() { + when(event.getContextData().toMap()).thenReturn(Collections.emptyMap()); + Map fields = fieldSupplier.map(event); + assertThat(fields.entrySet(), is(empty())); + } + + @Test + public void mdcFields() throws Exception { + HashMap mdc = new HashMap<>(); + mdc.put("key", "value"); + mdc.put("this", "that"); + when(event.getContextData().toMap()).thenReturn(mdc); + + Map fields = fieldSupplier.map(event); + assertThat(fields, hasEntry("key", "value")); + assertThat(fields, hasEntry("this", "that")); + } + + @Test + public void customFields() throws Exception { + Object[] arguments = new Object[] { // + new Object(), // + CustomField.customField("key", "value"), // + CustomField.customField("this", Double.valueOf(123.456d)) }; + when(event.getMessage().getParameters()).thenReturn(arguments); + + Map fields = fieldSupplier.map(event); + assertThat(fields, hasEntry("key", "value")); + assertThat(fields, hasEntry("this", Double.valueOf(123.456d))); + } + + @Test + public void customFieldOverwritesMdc() throws Exception { + HashMap mdc = new HashMap<>(); + mdc.put("key", "this"); + when(event.getContextData().toMap()).thenReturn(mdc); + Object[] arguments = new Object[] { CustomField.customField("key", "that") }; + when(event.getMessage().getParameters()).thenReturn(arguments); + + Map fields = fieldSupplier.map(event); + assertThat(fields, hasEntry("key", "that")); + } + +} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/suppliers/RequestRecordFieldSupplierTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/suppliers/RequestRecordFieldSupplierTest.java new file mode 100644 index 00000000..cc6291ff --- /dev/null +++ b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/log4j2/layout/suppliers/RequestRecordFieldSupplierTest.java @@ -0,0 +1,73 @@ +package com.sap.hcp.cf.log4j2.layout.suppliers; + +import static com.sap.hcp.cf.logging.common.request.RequestRecordBuilder.requestRecord; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; + +import java.util.Map; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent.Builder; +import org.apache.logging.log4j.core.impl.MutableLogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +import com.sap.hcp.cf.log4j2.converter.api.Log4jContextFieldSupplier; +import com.sap.hcp.cf.log4j2.layout.supppliers.RequestRecordFieldSupplier; +import com.sap.hcp.cf.logging.common.Markers; +import com.sap.hcp.cf.logging.common.request.RequestRecord; + +@RunWith(MockitoJUnitRunner.class) +public class RequestRecordFieldSupplierTest { + + private static final Marker MARKER = MarkerManager.getMarker(Markers.REQUEST_MARKER.getName()); + private Log4jContextFieldSupplier fieldSupplier = new RequestRecordFieldSupplier(); + + @Test + public void nullArgumentArray() { + LogEvent event = requestLogEventBuilder().setMessage(new SimpleMessage()).build(); + Map fields = fieldSupplier.map(event); + assertThat(fields.entrySet(), is(empty())); + } + + private static Builder requestLogEventBuilder() { + return Log4jLogEvent.newBuilder().setMarker(MARKER); + } + + @Test + public void emptyArgumentArray() { + MutableLogEvent event = new MutableLogEvent(new StringBuilder(""), new Object[0]); + event.setMarker(MARKER); + Map fields = fieldSupplier.map(event.createMemento()); + assertThat(fields.entrySet(), is(empty())); + } + + @Test + public void requestRecordArgument() { + RequestRecord requestRecord = requestRecord("test").build(); + Message message = new ParameterizedMessage("", requestRecord); + LogEvent event = requestLogEventBuilder().setMessage(message).build(); + Map fields = fieldSupplier.map(event); + assertThat(fields, hasEntry("layer", "test")); + } + + + @Test + public void requestRecordMessageText() { + RequestRecord requestRecord = requestRecord("test").build(); + SimpleMessage message = new SimpleMessage(requestRecord.toString()); + LogEvent event = requestLogEventBuilder().setMessage(message).build(); + Map fields = fieldSupplier.map(event); + assertThat(fields, hasEntry("layer", "test")); + } + +} diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/TestCustomFields.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/TestCustomFields.java index 5ce304f4..fc6a7817 100644 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/TestCustomFields.java +++ b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/TestCustomFields.java @@ -2,9 +2,9 @@ import static com.sap.hcp.cf.logging.common.converter.CustomFieldMatchers.hasCustomField; import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; import org.junit.Test; import org.slf4j.Logger; @@ -13,97 +13,97 @@ public class TestCustomFields extends AbstractTest { - private static final Logger LOGGER = LoggerFactory.getLogger(TestCustomFields.class); + private static final Logger LOGGER = LoggerFactory.getLogger(TestCustomFields.class); - @Test - public void testLogMessage() { - LOGGER.info(TEST_MESSAGE); - assertThat(getMessage(), is(TEST_MESSAGE)); - } + @Test + public void testLogMessage() { + LOGGER.info(TEST_MESSAGE); + assertThat(getMessage(), is(TEST_MESSAGE)); + } - @Test - public void testLogMessageWithCustomField() throws Exception { - LOGGER.info(TEST_MESSAGE, customField(CUSTOM_FIELD_KEY, SOME_VALUE)); + @Test + public void testLogMessageWithCustomField() throws Exception { + LOGGER.info(TEST_MESSAGE, customField(CUSTOM_FIELD_KEY, SOME_VALUE)); - assertThat(getMessage(), is(TEST_MESSAGE)); - assertThat(getCustomField(CUSTOM_FIELD_KEY), hasCustomField(CUSTOM_FIELD_KEY, SOME_VALUE, CUSTOM_FIELD_INDEX)); - } + assertThat(getMessage(), is(TEST_MESSAGE)); + assertThat(getCustomField(CUSTOM_FIELD_KEY), hasCustomField(CUSTOM_FIELD_KEY, SOME_VALUE, CUSTOM_FIELD_INDEX)); + } - @Test - public void testCustomFieldWithoutRegistration() throws Exception { - LOGGER.info(TEST_MESSAGE, customField("ungregistered", SOME_VALUE)); + @Test + public void testCustomFieldWithoutRegistration() throws Exception { + LOGGER.info(TEST_MESSAGE, customField("ungregistered", SOME_VALUE)); - assertThat(getField("ungregistered"), is(SOME_VALUE)); - assertThat(getCustomField("unregistered"), is(nullValue())); - } + assertThat(getField("ungregistered"), is(SOME_VALUE)); + assertThat(getCustomField("unregistered"), is(nullValue())); + } - @Test - public void testCustomFieldAsPartOfMessage() throws Exception { - String messageWithPattern = TEST_MESSAGE + " {}"; - String messageWithKeyValue = TEST_MESSAGE + " " + CUSTOM_FIELD_KEY + "=" + SOME_VALUE; + @Test + public void testCustomFieldAsPartOfMessage() throws Exception { + String messageWithPattern = TEST_MESSAGE + " {}"; + String messageWithKeyValue = TEST_MESSAGE + " " + CUSTOM_FIELD_KEY + "=" + SOME_VALUE; - LOGGER.info(messageWithPattern, customField(CUSTOM_FIELD_KEY, SOME_VALUE)); + LOGGER.info(messageWithPattern, customField(CUSTOM_FIELD_KEY, SOME_VALUE)); - assertThat(getMessage(), is(messageWithKeyValue)); - assertThat(getCustomField(CUSTOM_FIELD_KEY), hasCustomField(CUSTOM_FIELD_KEY, SOME_VALUE, CUSTOM_FIELD_INDEX)); - } + assertThat(getMessage(), is(messageWithKeyValue)); + assertThat(getCustomField(CUSTOM_FIELD_KEY), hasCustomField(CUSTOM_FIELD_KEY, SOME_VALUE, CUSTOM_FIELD_INDEX)); + } - @Test - public void testEscape() throws Exception { - String messageWithPattern = TEST_MESSAGE + " {}"; - String messageWithKeyValue = TEST_MESSAGE + " " + CUSTOM_FIELD_KEY + "=" + HACK_ATTEMPT; + @Test + public void testEscape() throws Exception { + String messageWithPattern = TEST_MESSAGE + " {}"; + String messageWithKeyValue = TEST_MESSAGE + " " + CUSTOM_FIELD_KEY + "=" + HACK_ATTEMPT; - LOGGER.info(messageWithPattern, customField(CUSTOM_FIELD_KEY, HACK_ATTEMPT)); + LOGGER.info(messageWithPattern, customField(CUSTOM_FIELD_KEY, HACK_ATTEMPT)); - assertThat(getMessage(), is(messageWithKeyValue)); - assertThat(getCustomField(CUSTOM_FIELD_KEY), - hasCustomField(CUSTOM_FIELD_KEY, HACK_ATTEMPT, CUSTOM_FIELD_INDEX)); - } + assertThat(getMessage(), is(messageWithKeyValue)); + assertThat(getCustomField(CUSTOM_FIELD_KEY), hasCustomField(CUSTOM_FIELD_KEY, HACK_ATTEMPT, + CUSTOM_FIELD_INDEX)); + } - @Test(expected = IllegalArgumentException.class) - public void testNullKey() { - customField(null, SOME_VALUE); - } + @Test(expected = IllegalArgumentException.class) + public void testNullKey() { + customField(null, SOME_VALUE); + } - @Test - public void testNullValue() throws Exception { - LOGGER.info(TEST_MESSAGE, customField(CUSTOM_FIELD_KEY, null)); + @Test + public void testNullValue() throws Exception { + LOGGER.info(TEST_MESSAGE, customField(CUSTOM_FIELD_KEY, null)); - assertThat(getMessage(), is(TEST_MESSAGE)); - assertThat(getCustomField(CUSTOM_FIELD_KEY), hasCustomField(CUSTOM_FIELD_KEY, "null", CUSTOM_FIELD_INDEX)); - } + assertThat(getMessage(), is(TEST_MESSAGE)); + assertThat(getCustomField(CUSTOM_FIELD_KEY), is(nullValue())); + } - @Test - public void testLogMessageWithTwoCustomFields() throws Exception { - LOGGER.info(TEST_MESSAGE, customField(TEST_FIELD_KEY, SOME_VALUE), - customField(CUSTOM_FIELD_KEY, SOME_OTHER_VALUE)); + @Test + public void testLogMessageWithTwoCustomFields() throws Exception { + LOGGER.info(TEST_MESSAGE, customField(TEST_FIELD_KEY, SOME_VALUE), customField(CUSTOM_FIELD_KEY, + SOME_OTHER_VALUE)); - assertThat(getMessage(), is(TEST_MESSAGE)); + assertThat(getMessage(), is(TEST_MESSAGE)); - assertThat(getCustomField(TEST_FIELD_KEY), hasCustomField(TEST_FIELD_KEY, SOME_VALUE, TEST_FIELD_INDEX)); - assertThat(getCustomField(CUSTOM_FIELD_KEY), - hasCustomField(CUSTOM_FIELD_KEY, SOME_OTHER_VALUE, CUSTOM_FIELD_INDEX)); - } + assertThat(getCustomField(TEST_FIELD_KEY), hasCustomField(TEST_FIELD_KEY, SOME_VALUE, TEST_FIELD_INDEX)); + assertThat(getCustomField(CUSTOM_FIELD_KEY), hasCustomField(CUSTOM_FIELD_KEY, SOME_OTHER_VALUE, + CUSTOM_FIELD_INDEX)); + } - @Test - public void testCustomFieldFromMdcWithoutRetention() throws Exception { - MDC.put(TEST_FIELD_KEY, SOME_VALUE); + @Test + public void testCustomFieldFromMdcWithoutRetention() throws Exception { + MDC.put(TEST_FIELD_KEY, SOME_VALUE); - LOGGER.info(TEST_MESSAGE); + LOGGER.info(TEST_MESSAGE); - assertThat(getCustomField(TEST_FIELD_KEY), hasCustomField(TEST_FIELD_KEY, SOME_VALUE, TEST_FIELD_INDEX)); - assertThat(getField(TEST_FIELD_KEY), is(nullValue())); - } + assertThat(getCustomField(TEST_FIELD_KEY), hasCustomField(TEST_FIELD_KEY, SOME_VALUE, TEST_FIELD_INDEX)); + assertThat(getField(TEST_FIELD_KEY), is(nullValue())); + } - @Test - public void testCustomFieldFromMdcWithRetention() throws Exception { - MDC.put(RETAINED_FIELD_KEY, SOME_VALUE); + @Test + public void testCustomFieldFromMdcWithRetention() throws Exception { + MDC.put(RETAINED_FIELD_KEY, SOME_VALUE); - LOGGER.info(TEST_MESSAGE); + LOGGER.info(TEST_MESSAGE); - assertThat(getCustomField(RETAINED_FIELD_KEY), - hasCustomField(RETAINED_FIELD_KEY, SOME_VALUE, RETAINED_FIELD_INDEX)); - assertThat(getField(RETAINED_FIELD_KEY), is(SOME_VALUE)); - } + assertThat(getCustomField(RETAINED_FIELD_KEY), hasCustomField(RETAINED_FIELD_KEY, SOME_VALUE, + RETAINED_FIELD_INDEX)); + assertThat(getField(RETAINED_FIELD_KEY), is(SOME_VALUE)); + } } diff --git a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/request/RequestRecordTest.java b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/request/RequestRecordTest.java index f3219d8c..05f6df78 100644 --- a/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/request/RequestRecordTest.java +++ b/cf-java-logging-support-log4j2/src/test/java/com/sap/hcp/cf/logging/common/request/RequestRecordTest.java @@ -1,7 +1,6 @@ package com.sap.hcp.cf.logging.common.request; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; @@ -17,7 +16,6 @@ import com.fasterxml.jackson.jr.ob.JSONObjectException; import com.sap.hcp.cf.logging.common.AbstractTest; -import com.sap.hcp.cf.logging.common.Defaults; import com.sap.hcp.cf.logging.common.DoubleValue; import com.sap.hcp.cf.logging.common.Fields; import com.sap.hcp.cf.logging.common.Markers; @@ -28,34 +26,6 @@ public class RequestRecordTest extends AbstractTest { private final Logger logger = LoggerFactory.getLogger(RequestRecordTest.class); private RequestRecord rrec; - @Test - public void testDefaults() throws JSONObjectException, IOException { - String layer = "testDefaults"; - rrec = new RequestRecord(layer); - logger.info(Markers.REQUEST_MARKER, rrec.toString()); - - assertThat(getField(Fields.DIRECTION), is(Direction.IN.toString())); - assertThat(getField(Fields.LAYER), is(layer)); - assertThat(getField(Fields.RESPONSE_SIZE_B), is("-1")); - assertThat(getField(Fields.REQUEST_SIZE_B), is("-1")); - assertThat(getField(Fields.REQUEST_RECEIVED_AT), not(nullValue())); - assertThat(getField(Fields.REQUEST_RECEIVED_AT), not(nullValue())); - assertThat(Double.valueOf(getField(Fields.RESPONSE_TIME_MS)), greaterThan(0.0d)); - - assertThat(getField(Fields.REQUEST), is(Defaults.UNKNOWN)); - assertThat(getField(Fields.REMOTE_IP), is(Defaults.UNKNOWN)); - assertThat(getField(Fields.REMOTE_HOST), is(Defaults.UNKNOWN)); - assertThat(getField(Fields.PROTOCOL), is(Defaults.UNKNOWN)); - assertThat(getField(Fields.METHOD), is(Defaults.UNKNOWN)); - assertThat(getField(Fields.REMOTE_IP), is(Defaults.UNKNOWN)); - assertThat(getField(Fields.REMOTE_HOST), is(Defaults.UNKNOWN)); - assertThat(getField(Fields.RESPONSE_CONTENT_TYPE), is(Defaults.UNKNOWN)); - - assertThat(getField(Fields.REFERER), is(nullValue())); - assertThat(getField(Fields.X_FORWARDED_FOR), is(nullValue())); - assertThat(getField(Fields.REMOTE_PORT), is(nullValue())); - assertThat(getField(Fields.WRITTEN_TS), is(notNullValue())); - } @Test public void testNonDefaults() throws JSONObjectException, IOException { diff --git a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/converter/api/LogbackContextFieldSupplier.java b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/converter/api/LogbackContextFieldSupplier.java index 4cb4a237..ab75e238 100644 --- a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/converter/api/LogbackContextFieldSupplier.java +++ b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/converter/api/LogbackContextFieldSupplier.java @@ -1,17 +1,10 @@ package com.sap.hcp.cf.logback.converter.api; -import java.util.Map; - -import com.sap.hcp.cf.logging.common.serialization.ContextFieldSupplier; +import com.sap.hcp.cf.logging.common.serialization.EventContextFieldSupplier; import ch.qos.logback.classic.spi.ILoggingEvent; @FunctionalInterface -public interface LogbackContextFieldSupplier extends ContextFieldSupplier { - - Map map(ILoggingEvent event); +public interface LogbackContextFieldSupplier extends EventContextFieldSupplier { - default Map get() { - return map(null); - } } diff --git a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/EventContextFieldSupplier.java b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/EventContextFieldSupplier.java index f25004b6..781c74da 100644 --- a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/EventContextFieldSupplier.java +++ b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/EventContextFieldSupplier.java @@ -1,29 +1,23 @@ package com.sap.hcp.cf.logback.encoder; -import java.util.HashMap; import java.util.Map; import com.sap.hcp.cf.logback.converter.api.LogbackContextFieldSupplier; -import com.sap.hcp.cf.logging.common.customfields.CustomField; +import com.sap.hcp.cf.logging.common.serialization.AbstractContextFieldSupplier; import ch.qos.logback.classic.spi.ILoggingEvent; -public class EventContextFieldSupplier implements LogbackContextFieldSupplier { +public class EventContextFieldSupplier extends AbstractContextFieldSupplier implements + LogbackContextFieldSupplier { @Override - public Map map(ILoggingEvent event) { - Map result = new HashMap<>(); - result.putAll(event.getMDCPropertyMap()); - Object[] arguments = event.getArgumentArray(); - if (arguments != null) { - for (Object argument: arguments) { - if (argument instanceof CustomField) { - CustomField customField = (CustomField) argument; - result.put(customField.getKey(), customField.getValue()); - } - } - } - return result; + protected Object[] getParameterArray(ILoggingEvent event) { + return event.getArgumentArray(); + } + + @Override + protected Map getContextMap(ILoggingEvent event) { + return event.getMDCPropertyMap(); } } diff --git a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/ILoggingEventUtilities.java b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/ILoggingEventUtilities.java new file mode 100644 index 00000000..fb61cc61 --- /dev/null +++ b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/ILoggingEventUtilities.java @@ -0,0 +1,22 @@ +package com.sap.hcp.cf.logback.encoder; + +import java.util.Map; + +import com.sap.hcp.cf.logging.common.Markers; + +import ch.qos.logback.classic.spi.ILoggingEvent; + +public final class ILoggingEventUtilities { + + private ILoggingEventUtilities() { + } + + public static boolean isRequestLog(ILoggingEvent event) { + return Markers.REQUEST_MARKER.equals(event.getMarker()); + } + + public static Map getMap(ILoggingEvent event) { + return event.getMDCPropertyMap(); + } + +} diff --git a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/JsonEncoder.java b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/JsonEncoder.java index 52ed83b7..a0b94fa9 100644 --- a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/JsonEncoder.java +++ b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/JsonEncoder.java @@ -26,11 +26,11 @@ import com.fasterxml.jackson.jr.ob.comp.ComposerBase; import com.fasterxml.jackson.jr.ob.comp.ObjectComposer; import com.sap.hcp.cf.logback.converter.api.LogbackContextFieldSupplier; -import com.sap.hcp.cf.logging.common.Defaults; import com.sap.hcp.cf.logging.common.Fields; -import com.sap.hcp.cf.logging.common.LogContext; import com.sap.hcp.cf.logging.common.converter.StacktraceLines; +import com.sap.hcp.cf.logging.common.serialization.ContextFieldConverter; import com.sap.hcp.cf.logging.common.serialization.ContextFieldSupplier; +import com.sap.hcp.cf.logging.common.serialization.JsonSerializationException; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.StackTraceElementProxy; @@ -65,6 +65,8 @@ public class JsonEncoder extends EncoderBase { private int maxStacktraceSize = 55 * 1024; private JSON.Builder jsonBuilder = JSON.builder(); private JSON json; + private ContextFieldConverter contextFieldConverter; + public JsonEncoder() { logbackContextFieldSuppliers.add(new BaseFieldSupplier()); @@ -260,6 +262,8 @@ public void addContextFieldSupplier(String className) { @Override public void start() { this.json = new JSON(jsonBuilder); + this.contextFieldConverter = new ContextFieldConverter(sendDefaultValues, customFieldMdcKeyNames, + retainFieldMdcKeyNames); super.start(); } @@ -283,8 +287,8 @@ private String getJson(ILoggingEvent event) { ObjectComposer> oc = json.composeTo(writer).startObject(); addMarkers(oc, event); Map contextFields = collectContextFields(event); - addContextFields(oc, contextFields); - addCustomFields(oc, contextFields); + contextFieldConverter.addContextFields(oc, contextFields); + contextFieldConverter.addCustomFields(oc, contextFields); addStacktrace(oc, event); oc.end().finish(); return writer.append(NEWLINE).toString(); @@ -324,77 +328,6 @@ private Map collectContextFields(ILoggingEvent event) { return contextFields; } - private

void addContextFields(ObjectComposer

oc, Map contextFields) { - contextFields.keySet().stream().filter(this::isContextField).forEach(n -> addContextField(oc, n, contextFields - .get(n))); - } - - private boolean isContextField(String name) { - return retainFieldMdcKeyNames.contains(name) || !customFieldMdcKeyNames.contains(name); - } - - private

void addContextField(ObjectComposer

oc, String name, Object value) { - try { - if (sendDefaultValues) { - put(oc, name, value); - } else { - String defaultValue = getDefaultValue(name); - if (!defaultValue.equals(value)) { - put(oc, name, value); - } - } - } catch (IOException ignored) { - try { - oc.put(name, "invalid value"); - } catch (IOException cause) { - throw new JsonSerializationException("Cannot create field \"" + name + "\".", cause); - } - } - } - - private

void put(ObjectComposer

oc, String name, Object value) throws IOException, - JsonProcessingException { - if (value instanceof String) { - oc.put(name, (String) value); - } else if (value instanceof Long) { - oc.put(name, ((Long) value).longValue()); - } else if (value instanceof Double) { - oc.put(name, ((Double) value).doubleValue()); - } else if (value instanceof Boolean) { - oc.put(name, ((Boolean) value).booleanValue()); - } else if (value instanceof Integer) { - oc.put(name, ((Integer) value).intValue()); - } else if (value instanceof Float) { - oc.put(name, ((Float) value).floatValue()); - } else { - oc.put(name, String.valueOf(value)); - } - } - - private String getDefaultValue(String key) { - String defaultValue = LogContext.getDefault(key); - return defaultValue == null ? Defaults.UNKNOWN : defaultValue; - } - - private

void addCustomFields(ObjectComposer

oc, Map contextFields) - throws IOException, - JsonProcessingException { - ArrayComposer>> customFieldComposer = null; - for (int i = 0; i < customFieldMdcKeyNames.size(); i++) { - String key = customFieldMdcKeyNames.get(i); - Object value = contextFields.get(key); - if (value != null) { - if (customFieldComposer == null) { - customFieldComposer = oc.startObjectField(Fields.CUSTOM_FIELDS).startArrayField("string"); - } - customFieldComposer.startObject().put("k", key).put("v", String.valueOf(value)).put("i", i).end(); - } - } - if (customFieldComposer != null) { - customFieldComposer.end().end(); - } - } - private

void addStacktrace(ObjectComposer

oc, ILoggingEvent event) throws IOException, JsonProcessingException { if (event.getThrowableProxy() != null) { diff --git a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/RequestRecordFieldSupplier.java b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/RequestRecordFieldSupplier.java index a0bbc777..5965ad77 100644 --- a/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/RequestRecordFieldSupplier.java +++ b/cf-java-logging-support-logback/src/main/java/com/sap/hcp/cf/logback/encoder/RequestRecordFieldSupplier.java @@ -1,42 +1,26 @@ package com.sap.hcp.cf.logback.encoder; -import java.io.IOException; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.fasterxml.jackson.jr.ob.JSON; import com.sap.hcp.cf.logback.converter.api.LogbackContextFieldSupplier; -import com.sap.hcp.cf.logging.common.Markers; -import com.sap.hcp.cf.logging.common.request.RequestRecord; +import com.sap.hcp.cf.logging.common.serialization.AbstractRequestRecordFieldSupplier; import ch.qos.logback.classic.spi.ILoggingEvent; -public class RequestRecordFieldSupplier implements LogbackContextFieldSupplier { +public class RequestRecordFieldSupplier extends AbstractRequestRecordFieldSupplier implements + LogbackContextFieldSupplier { + + @Override + protected boolean isRequestLog(ILoggingEvent event) { + return ILoggingEventUtilities.isRequestLog(event); + } + + @Override + protected String getFormattedMessage(ILoggingEvent event) { + return event.getFormattedMessage(); + } @Override - public Map map(ILoggingEvent event) { - if (!Markers.REQUEST_MARKER.equals(event.getMarker())) { - return Collections.emptyMap(); - } - if (event.getArgumentArray() == null) { - try { - return JSON.std.mapFrom(event.getFormattedMessage()); - } catch (IOException cause) { - return Collections.emptyMap(); - } - } - Optional requestRecord = Stream.of(event.getArgumentArray()).filter( - o -> o instanceof RequestRecord) - .map(o -> (RequestRecord) o).findFirst(); - if (requestRecord.isPresent()) { - RequestRecord record = requestRecord.get(); - return record.getFields().entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue() - .getValue())); - } - return Collections.emptyMap(); + protected Object[] getParameterArray(ILoggingEvent event) { + return event.getArgumentArray(); } } diff --git a/cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/RequestLogger.java b/cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/RequestLogger.java index 20d4f9a4..fa82f458 100644 --- a/cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/RequestLogger.java +++ b/cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/RequestLogger.java @@ -76,7 +76,7 @@ private void generateLog() { Map currentContextMap = MDC.getCopyOfContextMap(); try { MDC.setContextMap(contextMap); - LOG.info(Markers.REQUEST_MARKER, "{}", requestRecord); + LOG.info(Markers.REQUEST_MARKER, "", requestRecord); } finally { if (currentContextMap != null) { MDC.setContextMap(currentContextMap); diff --git a/pom.xml b/pom.xml index ebc1b46c..9ee3aca3 100644 --- a/pom.xml +++ b/pom.xml @@ -168,8 +168,8 @@ UTF-8 2.13.1 1.7.32 - 1.2.10 - 2.17.1 + 1.2.11 + 2.17.2 1.4 3.2.1 3.1.0 diff --git a/sample-spring-boot/pom.xml b/sample-spring-boot/pom.xml index 0b1fee4c..a33bd608 100644 --- a/sample-spring-boot/pom.xml +++ b/sample-spring-boot/pom.xml @@ -18,7 +18,7 @@ UTF-8 11 11 - 2.6.2 + 2.6.4 1.5 0bzhBRNUXBR5 diff --git a/sample-spring-boot/src/main/java/com/sap/hcp/cf/logging/sample/springboot/statistics/StatisticsContextFieldSupplier.java b/sample-spring-boot/src/main/java/com/sap/hcp/cf/logging/sample/springboot/statistics/StatisticsContextFieldSupplier.java new file mode 100644 index 00000000..2e2b0fbb --- /dev/null +++ b/sample-spring-boot/src/main/java/com/sap/hcp/cf/logging/sample/springboot/statistics/StatisticsContextFieldSupplier.java @@ -0,0 +1,25 @@ +package com.sap.hcp.cf.logging.sample.springboot.statistics; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import com.sap.hcp.cf.logging.common.serialization.ContextFieldSupplier; + +public class StatisticsContextFieldSupplier implements ContextFieldSupplier { + + private static final String KEY = "message_count"; + + private static AtomicLong count = new AtomicLong(); + + @SuppressWarnings("serial") + @Override + public Map get() { + return new HashMap() { + { + put(KEY, count.incrementAndGet()); + } + }; + } + +} diff --git a/sample-spring-boot/src/main/resources/log4j2.xml b/sample-spring-boot/src/main/resources/log4j2.xml index 7a2d97fc..3a8a9a32 100644 --- a/sample-spring-boot/src/main/resources/log4j2.xml +++ b/sample-spring-boot/src/main/resources/log4j2.xml @@ -7,7 +7,11 @@ - + + + + + - - - - - - - - - %d %-5level [%thread] %logger{0} [%mdc]>: %msg - %replace(%xEx){'\n', ' | '}%nopex%n - - - - - - + + + + + + com.sap.hcp.cf.logging.sample.springboot.statistics.StatisticsContextFieldSupplier + + + + + + + %d %-5level [%thread] %logger{0} [%mdc]>: %msg + %replace(%xEx){'\n', ' | '}%nopex%n + + + + + + - + diff --git a/sample/pom.xml b/sample/pom.xml index 6727ee89..9c4e91ae 100644 --- a/sample/pom.xml +++ b/sample/pom.xml @@ -12,7 +12,7 @@ 8.0.39 - 2.17.1 + 2.17.2 1.1.3 2.5.4 1.7.12