package org.apache.logging.log4j.core.layout;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.apache.logging.log4j.core.jackson.XmlConstants;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.core.util.StringBuilderWriter;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.Strings;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
abstract class AbstractJacksonLayout extends AbstractStringLayout {
protected static final String DEFAULT_EOL = "\r\n";
protected static final String COMPACT_EOL = Strings.EMPTY;
public static abstract class Builder<B extends Builder<B>> extends AbstractStringLayout.Builder<B> {
@PluginBuilderAttribute
private boolean eventEol;
@PluginBuilderAttribute
private String endOfLine;
@PluginBuilderAttribute
private boolean compact;
@PluginBuilderAttribute
private boolean complete;
@PluginBuilderAttribute
private boolean locationInfo;
@PluginBuilderAttribute
private boolean properties;
@PluginBuilderAttribute
private boolean includeStacktrace = true;
@PluginBuilderAttribute
private boolean stacktraceAsString = false;
@PluginBuilderAttribute
private boolean includeNullDelimiter = false;
@PluginElement("AdditionalField")
private KeyValuePair[] additionalFields;
protected String toStringOrNull(final byte[] header) {
return header == null ? null : new String(header, Charset.defaultCharset());
}
public boolean getEventEol() {
return eventEol;
}
public String getEndOfLine() {
return endOfLine;
}
public boolean isCompact() {
return compact;
}
public boolean isComplete() {
return complete;
}
public boolean isLocationInfo() {
return locationInfo;
}
public boolean isProperties() {
return properties;
}
public boolean isIncludeStacktrace() {
return includeStacktrace;
}
public boolean isStacktraceAsString() {
return stacktraceAsString;
}
public boolean isIncludeNullDelimiter() { return includeNullDelimiter; }
public KeyValuePair[] getAdditionalFields() {
return additionalFields;
}
public B setEventEol(final boolean eventEol) {
this.eventEol = eventEol;
return asBuilder();
}
public B setEndOfLine(final String endOfLine) {
this.endOfLine = endOfLine;
return asBuilder();
}
public B setCompact(final boolean compact) {
this.compact = compact;
return asBuilder();
}
public B setComplete(final boolean complete) {
this.complete = complete;
return asBuilder();
}
public B setLocationInfo(final boolean locationInfo) {
this.locationInfo = locationInfo;
return asBuilder();
}
public B setProperties(final boolean properties) {
this.properties = properties;
return asBuilder();
}
public B setIncludeStacktrace(final boolean includeStacktrace) {
this.includeStacktrace = includeStacktrace;
return asBuilder();
}
public B setStacktraceAsString(final boolean stacktraceAsString) {
this.stacktraceAsString = stacktraceAsString;
return asBuilder();
}
public B setIncludeNullDelimiter(final boolean includeNullDelimiter) {
this.includeNullDelimiter = includeNullDelimiter;
return asBuilder();
}
public B setAdditionalFields(final KeyValuePair[] additionalFields) {
this.additionalFields = additionalFields;
return asBuilder();
}
}
protected final String eol;
protected final ObjectWriter objectWriter;
protected final boolean compact;
protected final boolean complete;
protected final boolean includeNullDelimiter;
protected final ResolvableKeyValuePair[] additionalFields;
@Deprecated
protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
final Serializer footerSerializer) {
this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false);
}
@Deprecated
protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
final Serializer footerSerializer, final boolean includeNullDelimiter) {
this(config, objectWriter, charset, compact, complete, eventEol, null, headerSerializer, footerSerializer, includeNullDelimiter, null);
}
protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
final boolean compact, final boolean complete, final boolean eventEol, final String endOfLine, final Serializer headerSerializer,
final Serializer footerSerializer, final boolean includeNullDelimiter,
final KeyValuePair[] additionalFields) {
super(config, charset, headerSerializer, footerSerializer);
this.objectWriter = objectWriter;
this.compact = compact;
this.complete = complete;
this.eol = endOfLine != null ? endOfLine : compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL;
this.includeNullDelimiter = includeNullDelimiter;
this.additionalFields = prepareAdditionalFields(config, additionalFields);
}
protected static boolean valueNeedsLookup(final String value) {
return value != null && value.contains("${");
}
private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config, final KeyValuePair[] additionalFields) {
if (additionalFields == null || additionalFields.length == 0) {
return new ResolvableKeyValuePair[0];
}
final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length];
for (int i = 0; i < additionalFields.length; i++) {
final ResolvableKeyValuePair resolvable = resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]);
if (config == null && resolvable.valueNeedsLookup) {
throw new IllegalArgumentException("configuration needs to be set when there are additional fields with variables");
}
}
return resolvableFields;
}
@Override
public String toSerializable(final LogEvent event) {
final StringBuilderWriter writer = new StringBuilderWriter();
try {
toSerializable(event, writer);
return writer.toString();
} catch (final IOException e) {
LOGGER.error(e);
return Strings.EMPTY;
}
}
protected Object wrapLogEvent(final LogEvent event) {
if (additionalFields.length > 0) {
final Map<String, String> additionalFieldsMap = resolveAdditionalFields(event);
return new LogEventWithAdditionalFields(event, additionalFieldsMap);
} else if (event instanceof Message) {
return new ReadOnlyLogEventWrapper(event);
} else {
return event;
}
}
private Map<String, String> resolveAdditionalFields(final LogEvent logEvent) {
final Map<String, String> additionalFieldsMap = new LinkedHashMap<>(additionalFields.length);
final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor();
for (final ResolvableKeyValuePair pair : additionalFields) {
if (pair.valueNeedsLookup) {
additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value));
} else {
additionalFieldsMap.put(pair.key, pair.value);
}
}
return additionalFieldsMap;
}
public void toSerializable(final LogEvent event, final Writer writer)
throws JsonGenerationException, JsonMappingException, IOException {
objectWriter.writeValue(writer, wrapLogEvent(event));
writer.write(eol);
if (includeNullDelimiter) {
writer.write('\0');
}
markEvent();
}
@JsonRootName(XmlConstants.ELT_EVENT)
@JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT)
public static class LogEventWithAdditionalFields {
private final Object logEvent;
private final Map<String, String> additionalFields;
public LogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
this.logEvent = logEvent;
this.additionalFields = additionalFields;
}
@JsonUnwrapped
public Object getLogEvent() {
return logEvent;
}
@JsonAnyGetter
@SuppressWarnings("unused")
public Map<String, String> getAdditionalFields() {
return additionalFields;
}
}
protected static class ResolvableKeyValuePair {
final String key;
final String value;
final boolean valueNeedsLookup;
ResolvableKeyValuePair(final KeyValuePair pair) {
this.key = pair.getKey();
this.value = pair.getValue();
this.valueNeedsLookup = AbstractJacksonLayout.valueNeedsLookup(this.value);
}
}
private static class ReadOnlyLogEventWrapper implements LogEvent {
@JsonIgnore
private final LogEvent event;
public ReadOnlyLogEventWrapper(LogEvent event) {
this.event = event;
}
@Override
public LogEvent toImmutable() {
return event.toImmutable();
}
@Override
public Map<String, String> getContextMap() {
return event.getContextMap();
}
@Override
public ReadOnlyStringMap getContextData() {
return event.getContextData();
}
@Override
public ThreadContext.ContextStack getContextStack() {
return event.getContextStack();
}
@Override
public String getLoggerFqcn() {
return event.getLoggerFqcn();
}
@Override
public Level getLevel() {
return event.getLevel();
}
@Override
public String getLoggerName() {
return event.getLoggerName();
}
@Override
public Marker getMarker() {
return event.getMarker();
}
@Override
public Message getMessage() {
return event.getMessage();
}
@Override
public long getTimeMillis() {
return event.getTimeMillis();
}
@Override
public Instant getInstant() {
return event.getInstant();
}
@Override
public StackTraceElement getSource() {
return event.getSource();
}
@Override
public String getThreadName() {
return event.getThreadName();
}
@Override
public long getThreadId() {
return event.getThreadId();
}
@Override
public int getThreadPriority() {
return event.getThreadPriority();
}
@Override
public Throwable getThrown() {
return event.getThrown();
}
@Override
public ThrowableProxy getThrownProxy() {
return event.getThrownProxy();
}
@Override
public boolean isEndOfBatch() {
return event.isEndOfBatch();
}
@Override
public boolean isIncludeLocation() {
return event.isIncludeLocation();
}
@Override
public void setEndOfBatch(boolean endOfBatch) {
}
@Override
public void setIncludeLocation(boolean locationRequired) {
}
@Override
public long getNanoTime() {
return event.getNanoTime();
}
}
}