You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by rp...@apache.org on 2015/01/12 09:53:15 UTC
[18/18] logging-log4j2 git commit: replace StringBuilder based API
with Buffer-based API (providing separate TextBuffer and BinaryBuffer
implementations where appropriate)
replace StringBuilder based API with Buffer-based API (providing
separate TextBuffer and BinaryBuffer implementations where appropriate)
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/08386d0e
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/08386d0e
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/08386d0e
Branch: refs/heads/LOG4J2-930
Commit: 08386d0e0dd578fc1d020f24975da54da9f15651
Parents: 8b748ca
Author: rpopma <rp...@apache.org>
Authored: Mon Jan 12 17:42:30 2015 +0900
Committer: rpopma <rp...@apache.org>
Committed: Mon Jan 12 17:42:30 2015 +0900
----------------------------------------------------------------------
.../log4j/core/layout/PatternLayout.java | 154 +-
.../log4j/core/layout/Rfc5424Layout.java | 1438 +++++++++---------
.../pattern/AbstractStyleNameConverter.java | 129 +-
.../core/pattern/ArrayPatternConverter.java | 4 +-
.../core/pattern/ClassNamePatternConverter.java | 36 +-
.../core/pattern/DatePatternConverter.java | 187 ++-
.../core/pattern/EncodingPatternConverter.java | 95 +-
.../ExtendedThrowablePatternConverter.java | 44 +-
.../core/pattern/FileDatePatternConverter.java | 14 +-
.../pattern/FileLocationPatternConverter.java | 30 +-
.../log4j/core/pattern/FormattingInfo.java | 86 +-
.../pattern/FullLocationPatternConverter.java | 29 +-
.../log4j/core/pattern/HighlightConverter.java | 472 +++---
.../core/pattern/IntegerPatternConverter.java | 19 +-
.../core/pattern/LevelPatternConverter.java | 102 +-
.../pattern/LineLocationPatternConverter.java | 43 +-
.../pattern/LineSeparatorPatternConverter.java | 32 +-
.../core/pattern/LiteralPatternConverter.java | 38 +-
.../core/pattern/LogEventPatternConverter.java | 151 +-
.../core/pattern/LoggerPatternConverter.java | 32 +-
.../log4j/core/pattern/MapPatternConverter.java | 63 +-
.../core/pattern/MarkerPatternConverter.java | 26 +-
.../log4j/core/pattern/MdcPatternConverter.java | 66 +-
.../core/pattern/MessagePatternConverter.java | 59 +-
.../pattern/MethodLocationPatternConverter.java | 29 +-
.../core/pattern/NamePatternConverter.java | 39 +-
.../log4j/core/pattern/NdcPatternConverter.java | 59 +-
.../core/pattern/RegexReplacementConverter.java | 40 +-
.../pattern/RelativeTimePatternConverter.java | 38 +-
.../pattern/RootThrowablePatternConverter.java | 42 +-
.../pattern/SequenceNumberPatternConverter.java | 37 +-
.../log4j/core/pattern/StyleConverter.java | 80 +-
.../core/pattern/ThreadPatternConverter.java | 42 +-
.../core/pattern/ThrowablePatternConverter.java | 80 +-
.../core/pattern/UuidPatternConverter.java | 28 +-
.../core/pattern/DatePatternConverterTest.java | 41 +-
.../pattern/EncodingPatternConverterTest.java | 4 +-
.../ExtendedThrowablePatternConverterTest.java | 32 +-
.../log4j/core/pattern/FormattingInfoTest.java | 142 ++
.../core/pattern/LevelPatternConverterTest.java | 30 +-
.../core/pattern/MapPatternConverterTest.java | 8 +-
.../pattern/MarkerPatternConverterTest.java | 4 +-
.../pattern/MessagePatternConverterTest.java | 16 +-
.../log4j/core/pattern/PatternParserTest.java | 6 +-
.../log4j/core/pattern/PatternParserTest2.java | 16 +-
.../pattern/RegexReplacementConverterTest.java | 4 +-
.../RootThrowablePatternConverterTest.java | 8 +-
.../pattern/ThrowablePatternConverterTest.java | 37 +-
48 files changed, 2479 insertions(+), 1732 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/08386d0e/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index 45079fa..75ce6a4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -33,10 +33,13 @@ import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.pattern.BinaryBuffer;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.core.pattern.RegexReplacement;
+import org.apache.logging.log4j.core.pattern.TextBuffer;
+import org.apache.logging.log4j.util.PropertiesUtil;
/**
* A flexible layout configurable with pattern string.
@@ -58,25 +61,20 @@ public final class PatternLayout extends AbstractStringLayout {
private static final long serialVersionUID = 1L;
/**
- * Default pattern string for log output. Currently set to the
- * string <b>"%m%n"</b> which just prints the application supplied
- * message.
+ * Default pattern string for log output. Currently set to the string <b>"%m%n"</b> which just prints the
+ * application supplied message.
*/
public static final String DEFAULT_CONVERSION_PATTERN = "%m%n";
/**
- * A conversion pattern equivalent to the TTCCCLayout.
- * Current value is <b>%r [%t] %p %c %x - %m%n</b>.
+ * A conversion pattern equivalent to the TTCCCLayout. Current value is <b>%r [%t] %p %c %x - %m%n</b>.
*/
- public static final String TTCC_CONVERSION_PATTERN =
- "%r [%t] %p %c %x - %m%n";
+ public static final String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n";
/**
- * A simple pattern.
- * Current value is <b>%d [%t] %p %c - %m%n</b>.
+ * A simple pattern. Current value is <b>%d [%t] %p %c - %m%n</b>.
*/
- public static final String SIMPLE_CONVERSION_PATTERN =
- "%d [%t] %p %c - %m%n";
+ public static final String SIMPLE_CONVERSION_PATTERN = "%d [%t] %p %c - %m%n";
/** Key to identify pattern converters. */
public static final String KEY = "Converter";
@@ -84,14 +82,14 @@ public final class PatternLayout extends AbstractStringLayout {
/**
* Initial converter for pattern.
*/
- private final List<PatternFormatter> formatters;
+ // store in raw array instead of a collection for performance reasons
+ private final PatternFormatter[] formatters;
/**
* Conversion pattern.
*/
private final String conversionPattern;
-
/**
* The current Configuration.
*/
@@ -103,22 +101,24 @@ public final class PatternLayout extends AbstractStringLayout {
private final boolean noConsoleNoAnsi;
+ private final boolean supportsBinaryOutput;
+
/**
- * Constructs a EnhancedPatternLayout using the supplied conversion pattern.
+ * Constructs a PatternLayout using the supplied conversion pattern.
*
* @param config The Configuration.
* @param replace The regular expression to match.
* @param pattern conversion pattern.
* @param charset The character set.
* @param alwaysWriteExceptions Whether or not exceptions should always be handled in this pattern (if {@code true},
- * exceptions will be written even if the pattern does not specify so).
- * @param noConsoleNoAnsi
- * If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI escape codes
+ * exceptions will be written even if the pattern does not specify so).
+ * @param noConsoleNoAnsi If {@code "true"} (default) and {@link System#console()} is null, do not output ANSI
+ * escape codes
* @param header
*/
private PatternLayout(final Configuration config, final RegexReplacement replace, final String pattern,
- final Charset charset, final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
- final String header, final String footer) {
+ final Charset charset, final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
+ final String header, final String footer) {
super(charset, toBytes(header, charset), toBytes(footer, charset));
this.replace = replace;
this.conversionPattern = pattern;
@@ -126,7 +126,17 @@ public final class PatternLayout extends AbstractStringLayout {
this.alwaysWriteExceptions = alwaysWriteExceptions;
this.noConsoleNoAnsi = noConsoleNoAnsi;
final PatternParser parser = createPatternParser(config);
- this.formatters = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern, this.alwaysWriteExceptions, this.noConsoleNoAnsi);
+ final List<PatternFormatter> list = parser.parse(pattern == null ? DEFAULT_CONVERSION_PATTERN : pattern,
+ this.alwaysWriteExceptions, this.noConsoleNoAnsi);
+ this.formatters = (PatternFormatter[]) list.toArray(new PatternFormatter[list.size()]);
+ this.supportsBinaryOutput = replace == null && supportBinaryOutput(formatters);
+ for (PatternFormatter patternFormatter : list) {
+ patternFormatter.getConverter().setCharset(charset);
+ }
+ }
+
+ private static boolean supportBinaryOutput(final PatternFormatter[] formatters) {
+ return PropertiesUtil.getProperties().getBooleanProperty("log4j.use.BinaryBuffers", true);
}
private static byte[] toBytes(final String str, final Charset charset) {
@@ -136,7 +146,7 @@ public final class PatternLayout extends AbstractStringLayout {
return null;
}
- private byte[] strSubstitutorReplace(final byte... b) {
+ private byte[] strSubstitutorReplace(final byte[] b) {
if (b != null && config != null) {
return getBytes(config.getStrSubstitutor().replace(new String(b, getCharset())));
}
@@ -173,14 +183,20 @@ public final class PatternLayout extends AbstractStringLayout {
* @return Map of content format keys supporting PatternLayout
*/
@Override
- public Map<String, String> getContentFormat()
- {
+ public Map<String, String> getContentFormat() {
final Map<String, String> result = new HashMap<String, String>();
result.put("structured", "false");
result.put("formatType", "conversion");
result.put("format", conversionPattern);
return result;
}
+
+ private ThreadLocal<TextBuffer> textBuffer = new ThreadLocal<TextBuffer>() {
+ @Override
+ protected TextBuffer initialValue() {
+ return new TextBuffer();
+ }
+ };
/**
* Formats a logging event to a writer.
@@ -191,9 +207,12 @@ public final class PatternLayout extends AbstractStringLayout {
*/
@Override
public String toSerializable(final LogEvent event) {
- final StringBuilder buf = new StringBuilder();
- for (final PatternFormatter formatter : formatters) {
- formatter.format(event, buf);
+ final TextBuffer buf = textBuffer.get();
+ buf.setLength(0);
+
+ // PERFORMANCE-SENSITIVE CODE: do not refactor to for-each loop
+ for (int i = 0, len = formatters.length; i < len; i++) {
+ formatters[i].format(event, buf);
}
String str = buf.toString();
if (replace != null) {
@@ -201,9 +220,39 @@ public final class PatternLayout extends AbstractStringLayout {
}
return str;
}
+
+ private ThreadLocal<BinaryBuffer> binaryBuffer = new ThreadLocal<BinaryBuffer>() {
+ @Override
+ protected BinaryBuffer initialValue() {
+ return new BinaryBuffer(getCharset());
+ }
+ };
+
+ /**
+ * Formats the Log Event as a byte array.
+ *
+ * @param event The Log Event.
+ * @return The formatted event as a byte array.
+ */
+ @Override
+ public byte[] toByteArray(final LogEvent event) {
+ if (!supportsBinaryOutput) {
+ return getBytes(toSerializable(event));
+ }
+ final Charset charset = getCharset();
+ final BinaryBuffer buf = binaryBuffer.get();
+ buf.setLength(0);
+
+ // PERFORMANCE-SENSITIVE CODE: do not refactor to for-each loop
+ for (int i = 0, len = formatters.length; i < len; i++) {
+ formatters[i].format(event, buf, charset);
+ }
+ return buf.toByteArray();
+ }
/**
* Create a PatternParser.
+ *
* @param config The Configuration.
* @return The PatternParser.
*/
@@ -228,22 +277,16 @@ public final class PatternLayout extends AbstractStringLayout {
/**
* Create a pattern layout.
*
- * @param pattern
- * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
- * @param config
- * The Configuration. Some Converters require access to the Interpolator.
- * @param replace
- * A Regex replacement String.
- * @param charset
- * The character set.
- * @param alwaysWriteExceptions
- * If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
- * @param noConsoleNoAnsi
- * If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
- * @param header
- * The footer to place at the top of the document, once.
- * @param footer
- * The footer to place at the bottom of the document, once.
+ * @param pattern The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
+ * @param config The Configuration. Some Converters require access to the Interpolator.
+ * @param replace A Regex replacement String.
+ * @param charset The character set.
+ * @param alwaysWriteExceptions If {@code "true"} (default) exceptions are always written even if the pattern
+ * contains no exception tokens.
+ * @param noConsoleNoAnsi If {@code "true"} (default is false) and {@link System#console()} is null, do not output
+ * ANSI escape codes
+ * @param header The footer to place at the top of the document, once.
+ * @param footer The footer to place at the bottom of the document, once.
* @return The PatternLayout.
*/
@PluginFactory
@@ -251,21 +294,14 @@ public final class PatternLayout extends AbstractStringLayout {
@PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern,
@PluginConfiguration final Configuration config,
@PluginElement("Replace") final RegexReplacement replace,
- @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset,
+ /* Cannot do defaultString = Charset.defaultCharset().name() for charset... */
+ @PluginAttribute(value = "charset") final Charset charset,
@PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions,
@PluginAttribute(value = "noConsoleNoAnsi", defaultBoolean = false) final boolean noConsoleNoAnsi,
- @PluginAttribute("header") final String header,
- @PluginAttribute("footer") final String footer) {
- return newBuilder()
- .withPattern(pattern)
- .withConfiguration(config)
- .withRegexReplacement(replace)
- .withCharset(charset)
- .withAlwaysWriteExceptions(alwaysWriteExceptions)
- .withNoConsoleNoAnsi(noConsoleNoAnsi)
- .withHeader(header)
- .withFooter(footer)
- .build();
+ @PluginAttribute("header") final String header, @PluginAttribute("footer") final String footer) {
+ return newBuilder().withPattern(pattern).withConfiguration(config).withRegexReplacement(replace)
+ .withCharset(charset).withAlwaysWriteExceptions(alwaysWriteExceptions)
+ .withNoConsoleNoAnsi(noConsoleNoAnsi).withHeader(header).withFooter(footer).build();
}
/**
@@ -281,6 +317,7 @@ public final class PatternLayout extends AbstractStringLayout {
/**
* Creates a builder for a custom PatternLayout.
+ *
* @return a PatternLayout builder.
*/
@PluginBuilderFactory
@@ -331,7 +368,6 @@ public final class PatternLayout extends AbstractStringLayout {
return this;
}
-
public Builder withConfiguration(final Configuration configuration) {
this.configuration = configuration;
return this;
@@ -343,7 +379,9 @@ public final class PatternLayout extends AbstractStringLayout {
}
public Builder withCharset(final Charset charset) {
- this.charset = charset;
+ if (charset != null) {
+ this.charset = charset; // else use platform default (NOT utf-8!)
+ }
return this;
}
@@ -374,7 +412,7 @@ public final class PatternLayout extends AbstractStringLayout {
configuration = new DefaultConfiguration();
}
return new PatternLayout(configuration, regexReplacement, pattern, charset, alwaysWriteExceptions,
- noConsoleNoAnsi, header, footer);
+ noConsoleNoAnsi, header, footer);
}
}
}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/08386d0e/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
----------------------------------------------------------------------
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
index 340c5b3..8aa7ab1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
@@ -1,718 +1,720 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.layout;
-
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.LoggingException;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.TlsSyslogFrame;
-import org.apache.logging.log4j.core.config.Configuration;
-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.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.net.Facility;
-import org.apache.logging.log4j.core.net.Priority;
-import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
-import org.apache.logging.log4j.core.pattern.PatternConverter;
-import org.apache.logging.log4j.core.pattern.PatternFormatter;
-import org.apache.logging.log4j.core.pattern.PatternParser;
-import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
-import org.apache.logging.log4j.core.util.Charsets;
-import org.apache.logging.log4j.core.util.NetUtils;
-import org.apache.logging.log4j.core.util.Patterns;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.StructuredDataId;
-import org.apache.logging.log4j.message.StructuredDataMessage;
-import org.apache.logging.log4j.util.Strings;
-
-
-/**
- * Formats a log event in accordance with RFC 5424.
- *
- * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
- */
-@Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
-public final class Rfc5424Layout extends AbstractStringLayout {
-
- private static final long serialVersionUID = 1L;
-
- private static final String LF = "\n";
-
- /**
- * Not a very good default - it is the Apache Software Foundation's enterprise number.
- */
- public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
- /**
- * The default event id.
- */
- public static final String DEFAULT_ID = "Audit";
- /**
- * Match newlines in a platform-independent manner.
- */
- public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
- /**
- * Match characters which require escaping
- */
- public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = Pattern.compile("[\\\"\\]\\\\]");
-
- public static final String DEFAULT_MDCID = "mdc";
- private static final int TWO_DIGITS = 10;
- private static final int THREE_DIGITS = 100;
- private static final int MILLIS_PER_MINUTE = 60000;
- private static final int MINUTES_PER_HOUR = 60;
-
- private static final String COMPONENT_KEY = "RFC5424-Converter";
-
- private final Facility facility;
- private final String defaultId;
- private final int enterpriseNumber;
- private final boolean includeMdc;
- private final String mdcId;
- private final StructuredDataId mdcSdId;
- private final String localHostName;
- private final String appName;
- private final String messageId;
- private final String configName;
- private final String mdcPrefix;
- private final String eventPrefix;
- private final List<String> mdcExcludes;
- private final List<String> mdcIncludes;
- private final List<String> mdcRequired;
- private final ListChecker checker;
- private final ListChecker noopChecker = new NoopChecker();
- private final boolean includeNewLine;
- private final String escapeNewLine;
- private final boolean useTlsMessageFormat;
-
- private long lastTimestamp = -1;
- private String timestamppStr;
-
- private final List<PatternFormatter> exceptionFormatters;
- private final Map<String, FieldFormatter> fieldFormatters;
-
- private Rfc5424Layout(final Configuration config, final Facility facility, final String id, final int ein,
- final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId,
- final String mdcPrefix, final String eventPrefix,
- final String appName, final String messageId, final String excludes, final String includes,
- final String required, final Charset charset, final String exceptionPattern,
- final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) {
- super(charset);
- final PatternParser exceptionParser = createPatternParser(config, ThrowablePatternConverter.class);
- exceptionFormatters = exceptionPattern == null ? null : exceptionParser.parse(exceptionPattern, false, false);
- this.facility = facility;
- this.defaultId = id == null ? DEFAULT_ID : id;
- this.enterpriseNumber = ein;
- this.includeMdc = includeMDC;
- this.includeNewLine = includeNL;
- this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL);
- this.mdcId = mdcId;
- this.mdcSdId = new StructuredDataId(mdcId, enterpriseNumber, null, null);
- this.mdcPrefix = mdcPrefix;
- this.eventPrefix = eventPrefix;
- this.appName = appName;
- this.messageId = messageId;
- this.useTlsMessageFormat = useTLSMessageFormat;
- this.localHostName = NetUtils.getLocalHostname();
- ListChecker c = null;
- if (excludes != null) {
- final String[] array = excludes.split(Patterns.COMMA_SEPARATOR);
- if (array.length > 0) {
- c = new ExcludeChecker();
- mdcExcludes = new ArrayList<String>(array.length);
- for (final String str : array) {
- mdcExcludes.add(str.trim());
- }
- } else {
- mdcExcludes = null;
- }
- } else {
- mdcExcludes = null;
- }
- if (includes != null) {
- final String[] array = includes.split(Patterns.COMMA_SEPARATOR);
- if (array.length > 0) {
- c = new IncludeChecker();
- mdcIncludes = new ArrayList<String>(array.length);
- for (final String str : array) {
- mdcIncludes.add(str.trim());
- }
- } else {
- mdcIncludes = null;
- }
- } else {
- mdcIncludes = null;
- }
- if (required != null) {
- final String[] array = required.split(Patterns.COMMA_SEPARATOR);
- if (array.length > 0) {
- mdcRequired = new ArrayList<String>(array.length);
- for (final String str : array) {
- mdcRequired.add(str.trim());
- }
- } else {
- mdcRequired = null;
- }
-
- } else {
- mdcRequired = null;
- }
- this.checker = c != null ? c : noopChecker;
- final String name = config == null ? null : config.getName();
- configName = name != null && name.length() > 0 ? name : null;
- this.fieldFormatters = createFieldFormatters(loggerFields, config);
- }
-
- private Map<String, FieldFormatter> createFieldFormatters(final LoggerFields[] loggerFields,
- final Configuration config) {
- final Map<String, FieldFormatter> sdIdMap = new HashMap<String, FieldFormatter>();
-
- if (loggerFields != null) {
- for (final LoggerFields lField : loggerFields) {
- final StructuredDataId key = lField.getSdId() == null ? mdcSdId : lField.getSdId();
- final Map<String, List<PatternFormatter>> sdParams = new HashMap<String, List<PatternFormatter>>();
- final Map<String, String> fields = lField.getMap();
- if (!fields.isEmpty()) {
- final PatternParser fieldParser = createPatternParser(config, null);
-
- for (final Map.Entry<String, String> entry : fields.entrySet()) {
- final List<PatternFormatter> formatters = fieldParser.parse(entry.getValue(), false, false);
- sdParams.put(entry.getKey(), formatters);
- }
- final FieldFormatter fieldFormatter = new FieldFormatter(sdParams,
- lField.getDiscardIfAllFieldsAreEmpty());
- sdIdMap.put(key.toString(), fieldFormatter);
- }
- }
- }
- return sdIdMap.size() > 0 ? sdIdMap : null;
- }
-
- /**
- * Create a PatternParser.
- *
- * @param config The Configuration.
- * @param filterClass Filter the returned plugins after calling the plugin manager.
- * @return The PatternParser.
- */
- private static PatternParser createPatternParser(final Configuration config,
- final Class<? extends PatternConverter> filterClass) {
- if (config == null) {
- return new PatternParser(config, PatternLayout.KEY, LogEventPatternConverter.class, filterClass);
- }
- PatternParser parser = config.getComponent(COMPONENT_KEY);
- if (parser == null) {
- parser = new PatternParser(config, PatternLayout.KEY, ThrowablePatternConverter.class);
- config.addComponent(COMPONENT_KEY, parser);
- parser = (PatternParser) config.getComponent(COMPONENT_KEY);
- }
- return parser;
- }
-
- /**
- * Gets this Rfc5424Layout's content format. Specified by:
- * <ul>
- * <li>Key: "structured" Value: "true"</li>
- * <li>Key: "format" Value: "RFC5424"</li>
- * </ul>
- *
- * @return Map of content format keys supporting Rfc5424Layout
- */
- @Override
- public Map<String, String> getContentFormat() {
- final Map<String, String> result = new HashMap<String, String>();
- result.put("structured", "true");
- result.put("formatType", "RFC5424");
- return result;
- }
-
- /**
- * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the RFC 5424 Syslog specification.
- *
- * @param event The LogEvent.
- * @return The RFC 5424 String representation of the LogEvent.
- */
- @Override
- public String toSerializable(final LogEvent event) {
- final StringBuilder buf = new StringBuilder();
- appendPriority(buf, event.getLevel());
- appendTimestamp(buf, event.getTimeMillis());
- appendSpace(buf);
- appendHostName(buf);
- appendSpace(buf);
- appendAppName(buf);
- appendSpace(buf);
- appendProcessId(buf);
- appendSpace(buf);
- appendMessageId(buf, event.getMessage());
- appendSpace(buf);
- appendStructuredElements(buf, event);
- appendMessage(buf, event);
- if (useTlsMessageFormat) {
- return new TlsSyslogFrame(buf.toString()).toString();
- }
- return buf.toString();
- }
-
- private void appendPriority(final StringBuilder buffer, final Level logLevel) {
- buffer.append('<');
- buffer.append(Priority.getPriority(facility, logLevel));
- buffer.append(">1 ");
- }
-
- private void appendTimestamp(final StringBuilder buffer, final long milliseconds) {
- buffer.append(computeTimeStampString(milliseconds));
- }
-
- private void appendSpace(final StringBuilder buffer) {
- buffer.append(' ');
- }
-
- private void appendHostName(final StringBuilder buffer) {
- buffer.append(localHostName);
- }
-
- private void appendAppName(final StringBuilder buffer) {
- if (appName != null) {
- buffer.append(appName);
- } else if (configName != null) {
- buffer.append(configName);
- } else {
- buffer.append('-');
- }
- }
-
- private void appendProcessId(final StringBuilder buffer) {
- buffer.append(getProcId());
- }
-
- private void appendMessageId(final StringBuilder buffer, final Message message) {
- final boolean isStructured = message instanceof StructuredDataMessage;
- final String type = isStructured ? ((StructuredDataMessage) message).getType() : null;
- if (type != null) {
- buffer.append(type);
- } else if (messageId != null) {
- buffer.append(messageId);
- } else {
- buffer.append('-');
- }
- }
-
- private void appendMessage(final StringBuilder buffer, final LogEvent event) {
- final Message message = event.getMessage();
- // This layout formats StructuredDataMessages instead of delegating to the Message itself.
- final String text = (message instanceof StructuredDataMessage) ? message.getFormat() : message.getFormattedMessage();
-
- if (text != null && text.length() > 0) {
- buffer.append(' ').append(escapeNewlines(text, escapeNewLine));
- }
-
- if (exceptionFormatters != null && event.getThrown() != null) {
- final StringBuilder exception = new StringBuilder(LF);
- for (final PatternFormatter formatter : exceptionFormatters) {
- formatter.format(event, exception);
- }
- buffer.append(escapeNewlines(exception.toString(), escapeNewLine));
- }
- if (includeNewLine) {
- buffer.append(LF);
- }
- }
-
- private void appendStructuredElements(final StringBuilder buffer, final LogEvent event) {
- final Message message = event.getMessage();
- final boolean isStructured = message instanceof StructuredDataMessage;
-
- if (!isStructured && (fieldFormatters!= null && fieldFormatters.isEmpty()) && !includeMdc) {
- buffer.append('-');
- return;
- }
-
- final Map<String, StructuredDataElement> sdElements = new HashMap<String, StructuredDataElement>();
- final Map<String, String> contextMap = event.getContextMap();
-
- if (mdcRequired != null) {
- checkRequired(contextMap);
- }
-
- if (fieldFormatters != null) {
- for (final Map.Entry<String, FieldFormatter> sdElement: fieldFormatters.entrySet()) {
- final String sdId = sdElement.getKey();
- final StructuredDataElement elem = sdElement.getValue().format(event);
- sdElements.put(sdId, elem);
- }
- }
-
- if (includeMdc && contextMap.size() > 0) {
- if (sdElements.containsKey(mdcSdId.toString())) {
- final StructuredDataElement union = sdElements.get(mdcSdId.toString());
- union.union(contextMap);
- sdElements.put(mdcSdId.toString(), union);
- } else {
- final StructuredDataElement formattedContextMap = new StructuredDataElement(contextMap, false);
- sdElements.put(mdcSdId.toString(), formattedContextMap);
- }
- }
-
- if (isStructured) {
- final StructuredDataMessage data = (StructuredDataMessage) message;
- final Map<String, String> map = data.getData();
- final StructuredDataId id = data.getId();
- final String sdId = getId(id);
-
- if (sdElements.containsKey(sdId)) {
- final StructuredDataElement union = sdElements.get(id.toString());
- union.union(map);
- sdElements.put(sdId, union);
- } else {
- final StructuredDataElement formattedData = new StructuredDataElement(map, false);
- sdElements.put(sdId, formattedData);
- }
- }
-
- if (sdElements.isEmpty()) {
- buffer.append('-');
- return;
- }
-
- for (final Map.Entry<String, StructuredDataElement> entry: sdElements.entrySet()) {
- formatStructuredElement(entry.getKey(), mdcPrefix, entry.getValue(), buffer, checker);
- }
- }
-
- private String escapeNewlines(final String text, final String escapeNewLine) {
- if (null == escapeNewLine) {
- return text;
- }
- return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine);
- }
-
- protected String getProcId() {
- return "-";
- }
-
- protected List<String> getMdcExcludes() {
- return mdcExcludes;
- }
-
- protected List<String> getMdcIncludes() {
- return mdcIncludes;
- }
-
- private String computeTimeStampString(final long now) {
- long last;
- synchronized (this) {
- last = lastTimestamp;
- if (now == lastTimestamp) {
- return timestamppStr;
- }
- }
-
- final StringBuilder buffer = new StringBuilder();
- final Calendar cal = new GregorianCalendar();
- cal.setTimeInMillis(now);
- buffer.append(Integer.toString(cal.get(Calendar.YEAR)));
- buffer.append('-');
- pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer);
- buffer.append('-');
- pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer);
- buffer.append('T');
- pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer);
- buffer.append(':');
- pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer);
- buffer.append(':');
- pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer);
-
- final int millis = cal.get(Calendar.MILLISECOND);
- if (millis != 0) {
- buffer.append('.');
- pad(millis, THREE_DIGITS, buffer);
- }
-
- int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
- if (tzmin == 0) {
- buffer.append('Z');
- } else {
- if (tzmin < 0) {
- tzmin = -tzmin;
- buffer.append('-');
- } else {
- buffer.append('+');
- }
- final int tzhour = tzmin / MINUTES_PER_HOUR;
- tzmin -= tzhour * MINUTES_PER_HOUR;
- pad(tzhour, TWO_DIGITS, buffer);
- buffer.append(':');
- pad(tzmin, TWO_DIGITS, buffer);
- }
- synchronized (this) {
- if (last == lastTimestamp) {
- lastTimestamp = now;
- timestamppStr = buffer.toString();
- }
- }
- return buffer.toString();
- }
-
- private void pad(final int val, int max, final StringBuilder buf) {
- while (max > 1) {
- if (val < max) {
- buf.append('0');
- }
- max = max / TWO_DIGITS;
- }
- buf.append(Integer.toString(val));
- }
-
- private void formatStructuredElement(final String id, final String prefix, final StructuredDataElement data,
- final StringBuilder sb, final ListChecker checker) {
- if ((id == null && defaultId == null) || data.discard()) {
- return;
- }
-
- sb.append('[');
- sb.append(id);
- if (!mdcSdId.toString().equals(id)) {
- appendMap(prefix, data.getFields(), sb, noopChecker);
- } else {
- appendMap(prefix, data.getFields(), sb, checker);
- }
- sb.append(']');
- }
-
- private String getId(final StructuredDataId id) {
- final StringBuilder sb = new StringBuilder();
- if (id == null || id.getName() == null) {
- sb.append(defaultId);
- } else {
- sb.append(id.getName());
- }
- int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber;
- if (ein < 0) {
- ein = enterpriseNumber;
- }
- if (ein >= 0) {
- sb.append('@').append(ein);
- }
- return sb.toString();
- }
-
- private void checkRequired(final Map<String, String> map) {
- for (final String key : mdcRequired) {
- final String value = map.get(key);
- if (value == null) {
- throw new LoggingException("Required key " + key + " is missing from the " + mdcId);
- }
- }
- }
-
- private void appendMap(final String prefix, final Map<String, String> map, final StringBuilder sb,
- final ListChecker checker) {
- final SortedMap<String, String> sorted = new TreeMap<String, String>(map);
- for (final Map.Entry<String, String> entry : sorted.entrySet()) {
- if (checker.check(entry.getKey()) && entry.getValue() != null) {
- sb.append(' ');
- if (prefix != null) {
- sb.append(prefix);
- }
- sb.append(escapeNewlines(escapeSDParams(entry.getKey()), escapeNewLine)).append("=\"")
- .append(escapeNewlines(escapeSDParams(entry.getValue()), escapeNewLine)).append('"');
- }
- }
- }
-
- private String escapeSDParams(final String value) {
- return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
- }
-
- /**
- * Interface used to check keys in a Map.
- */
- private interface ListChecker {
- boolean check(String key);
- }
-
- /**
- * Includes only the listed keys.
- */
- private class IncludeChecker implements ListChecker {
- @Override
- public boolean check(final String key) {
- return mdcIncludes.contains(key);
- }
- }
-
- /**
- * Excludes the listed keys.
- */
- private class ExcludeChecker implements ListChecker {
- @Override
- public boolean check(final String key) {
- return !mdcExcludes.contains(key);
- }
- }
-
- /**
- * Does nothing.
- */
- private class NoopChecker implements ListChecker {
- @Override
- public boolean check(final String key) {
- return true;
- }
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("facility=").append(facility.name());
- sb.append(" appName=").append(appName);
- sb.append(" defaultId=").append(defaultId);
- sb.append(" enterpriseNumber=").append(enterpriseNumber);
- sb.append(" newLine=").append(includeNewLine);
- sb.append(" includeMDC=").append(includeMdc);
- sb.append(" messageId=").append(messageId);
- return sb.toString();
- }
-
- /**
- * Create the RFC 5424 Layout.
- *
- * @param facility The Facility is used to try to classify the message.
- * @param id The default structured data id to use when formatting according to RFC 5424.
- * @param enterpriseNumber The IANA enterprise number.
- * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
- * record. Defaults to "true:.
- * @param mdcId The id to use for the MDC Structured Data Element.
- * @param mdcPrefix The prefix to add to MDC key names.
- * @param eventPrefix The prefix to add to event key names.
- * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false.
- * @param escapeNL String that should be used to replace newlines within the message text.
- * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record.
- * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records.
- * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent.
- * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent.
- * @param required A comma separated list of MDC keys that must be present in the MDC.
- * @param exceptionPattern The pattern for formatting exceptions.
- * @param useTlsMessageFormat If true the message will be formatted according to RFC 5425.
- * @param loggerFields Container for the KeyValuePairs containing the patterns
- * @param config The Configuration. Some Converters require access to the Interpolator.
- * @return An Rfc5424Layout.
- */
- @PluginFactory
- public static Rfc5424Layout createLayout(
- @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility,
- @PluginAttribute("id") final String id,
- @PluginAttribute(value = "enterpriseNumber", defaultInt = DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber,
- @PluginAttribute(value = "includeMDC", defaultBoolean = true) final boolean includeMDC,
- @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) final String mdcId,
- @PluginAttribute("mdcPrefix") final String mdcPrefix,
- @PluginAttribute("eventPrefix") final String eventPrefix,
- @PluginAttribute(value = "newLine", defaultBoolean = false) final boolean newLine,
- @PluginAttribute("newLineEscape") final String escapeNL,
- @PluginAttribute("appName") final String appName,
- @PluginAttribute("messageId") final String msgId,
- @PluginAttribute("mdcExcludes") final String excludes,
- @PluginAttribute("mdcIncludes") String includes,
- @PluginAttribute("mdcRequired") final String required,
- @PluginAttribute("exceptionPattern") final String exceptionPattern,
- @PluginAttribute(value = "useTlsMessageFormat", defaultBoolean = false) final boolean useTlsMessageFormat, // RFC 5425
- @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
- @PluginConfiguration final Configuration config) {
- final Charset charset = Charsets.UTF_8;
- if (includes != null && excludes != null) {
- LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored");
- includes = null;
- }
-
- return new Rfc5424Layout(config, facility, id, enterpriseNumber, includeMDC, newLine, escapeNL, mdcId, mdcPrefix,
- eventPrefix, appName, msgId, excludes, includes, required, charset, exceptionPattern,
- useTlsMessageFormat, loggerFields);
- }
-
- private class FieldFormatter {
-
- private final Map<String, List<PatternFormatter>> delegateMap;
- private final boolean discardIfEmpty;
-
- public FieldFormatter(final Map<String, List<PatternFormatter>> fieldMap, final boolean discardIfEmpty) {
- this.discardIfEmpty = discardIfEmpty;
- this.delegateMap = fieldMap;
- }
-
- public StructuredDataElement format(final LogEvent event) {
- final Map<String, String> map = new HashMap<String, String>();
-
- for (final Map.Entry<String, List<PatternFormatter>> entry : delegateMap.entrySet()) {
- final StringBuilder buffer = new StringBuilder();
- for (final PatternFormatter formatter : entry.getValue()) {
- formatter.format(event, buffer);
- }
- map.put(entry.getKey(), buffer.toString());
- }
- return new StructuredDataElement(map, discardIfEmpty);
- }
- }
-
- private class StructuredDataElement {
-
- private final Map<String, String> fields;
- private final boolean discardIfEmpty;
-
- public StructuredDataElement(final Map<String, String> fields, final boolean discardIfEmpty) {
- this.discardIfEmpty = discardIfEmpty;
- this.fields = fields;
- }
-
- boolean discard() {
- if (discardIfEmpty == false) {
- return false;
- }
- boolean foundNotEmptyValue = false;
- for (final Map.Entry<String, String> entry : fields.entrySet()) {
- if (Strings.isNotEmpty(entry.getValue())) {
- foundNotEmptyValue = true;
- break;
- }
- }
- return !foundNotEmptyValue;
- }
-
- void union(final Map<String, String> fields) {
- this.fields.putAll(fields);
- }
-
- Map<String, String> getFields() {
- return this.fields;
- }
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.layout;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LoggingException;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.TlsSyslogFrame;
+import org.apache.logging.log4j.core.config.Configuration;
+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.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.core.net.Priority;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.core.pattern.TextBuffer;
+import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
+import org.apache.logging.log4j.core.util.Charsets;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.core.util.Patterns;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.StructuredDataId;
+import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.apache.logging.log4j.util.Strings;
+
+
+/**
+ * Formats a log event in accordance with RFC 5424.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
+ */
+@Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
+public final class Rfc5424Layout extends AbstractStringLayout {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String LF = "\n";
+
+ /**
+ * Not a very good default - it is the Apache Software Foundation's enterprise number.
+ */
+ public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
+ /**
+ * The default event id.
+ */
+ public static final String DEFAULT_ID = "Audit";
+ /**
+ * Match newlines in a platform-independent manner.
+ */
+ public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
+ /**
+ * Match characters which require escaping
+ */
+ public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = Pattern.compile("[\\\"\\]\\\\]");
+
+ public static final String DEFAULT_MDCID = "mdc";
+ private static final int TWO_DIGITS = 10;
+ private static final int THREE_DIGITS = 100;
+ private static final int MILLIS_PER_MINUTE = 60000;
+ private static final int MINUTES_PER_HOUR = 60;
+
+ private static final String COMPONENT_KEY = "RFC5424-Converter";
+
+ private final Facility facility;
+ private final String defaultId;
+ private final int enterpriseNumber;
+ private final boolean includeMdc;
+ private final String mdcId;
+ private final StructuredDataId mdcSdId;
+ private final String localHostName;
+ private final String appName;
+ private final String messageId;
+ private final String configName;
+ private final String mdcPrefix;
+ private final String eventPrefix;
+ private final List<String> mdcExcludes;
+ private final List<String> mdcIncludes;
+ private final List<String> mdcRequired;
+ private final ListChecker checker;
+ private final ListChecker noopChecker = new NoopChecker();
+ private final boolean includeNewLine;
+ private final String escapeNewLine;
+ private final boolean useTlsMessageFormat;
+
+ private long lastTimestamp = -1;
+ private String timestamppStr;
+
+ private final List<PatternFormatter> exceptionFormatters;
+ private final Map<String, FieldFormatter> fieldFormatters;
+
+ private Rfc5424Layout(final Configuration config, final Facility facility, final String id, final int ein,
+ final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId,
+ final String mdcPrefix, final String eventPrefix,
+ final String appName, final String messageId, final String excludes, final String includes,
+ final String required, final Charset charset, final String exceptionPattern,
+ final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) {
+ super(charset);
+ final PatternParser exceptionParser = createPatternParser(config, ThrowablePatternConverter.class);
+ exceptionFormatters = exceptionPattern == null ? null : exceptionParser.parse(exceptionPattern, false, false);
+ this.facility = facility;
+ this.defaultId = id == null ? DEFAULT_ID : id;
+ this.enterpriseNumber = ein;
+ this.includeMdc = includeMDC;
+ this.includeNewLine = includeNL;
+ this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL);
+ this.mdcId = mdcId;
+ this.mdcSdId = new StructuredDataId(mdcId, enterpriseNumber, null, null);
+ this.mdcPrefix = mdcPrefix;
+ this.eventPrefix = eventPrefix;
+ this.appName = appName;
+ this.messageId = messageId;
+ this.useTlsMessageFormat = useTLSMessageFormat;
+ this.localHostName = NetUtils.getLocalHostname();
+ ListChecker c = null;
+ if (excludes != null) {
+ final String[] array = excludes.split(Patterns.COMMA_SEPARATOR);
+ if (array.length > 0) {
+ c = new ExcludeChecker();
+ mdcExcludes = new ArrayList<String>(array.length);
+ for (final String str : array) {
+ mdcExcludes.add(str.trim());
+ }
+ } else {
+ mdcExcludes = null;
+ }
+ } else {
+ mdcExcludes = null;
+ }
+ if (includes != null) {
+ final String[] array = includes.split(Patterns.COMMA_SEPARATOR);
+ if (array.length > 0) {
+ c = new IncludeChecker();
+ mdcIncludes = new ArrayList<String>(array.length);
+ for (final String str : array) {
+ mdcIncludes.add(str.trim());
+ }
+ } else {
+ mdcIncludes = null;
+ }
+ } else {
+ mdcIncludes = null;
+ }
+ if (required != null) {
+ final String[] array = required.split(Patterns.COMMA_SEPARATOR);
+ if (array.length > 0) {
+ mdcRequired = new ArrayList<String>(array.length);
+ for (final String str : array) {
+ mdcRequired.add(str.trim());
+ }
+ } else {
+ mdcRequired = null;
+ }
+
+ } else {
+ mdcRequired = null;
+ }
+ this.checker = c != null ? c : noopChecker;
+ final String name = config == null ? null : config.getName();
+ configName = name != null && name.length() > 0 ? name : null;
+ this.fieldFormatters = createFieldFormatters(loggerFields, config);
+ }
+
+ private Map<String, FieldFormatter> createFieldFormatters(final LoggerFields[] loggerFields,
+ final Configuration config) {
+ final Map<String, FieldFormatter> sdIdMap = new HashMap<String, FieldFormatter>();
+
+ if (loggerFields != null) {
+ for (final LoggerFields lField : loggerFields) {
+ final StructuredDataId key = lField.getSdId() == null ? mdcSdId : lField.getSdId();
+ final Map<String, List<PatternFormatter>> sdParams = new HashMap<String, List<PatternFormatter>>();
+ final Map<String, String> fields = lField.getMap();
+ if (!fields.isEmpty()) {
+ final PatternParser fieldParser = createPatternParser(config, null);
+
+ for (final Map.Entry<String, String> entry : fields.entrySet()) {
+ final List<PatternFormatter> formatters = fieldParser.parse(entry.getValue(), false, false);
+ sdParams.put(entry.getKey(), formatters);
+ }
+ final FieldFormatter fieldFormatter = new FieldFormatter(sdParams,
+ lField.getDiscardIfAllFieldsAreEmpty());
+ sdIdMap.put(key.toString(), fieldFormatter);
+ }
+ }
+ }
+ return sdIdMap.size() > 0 ? sdIdMap : null;
+ }
+
+ /**
+ * Create a PatternParser.
+ *
+ * @param config The Configuration.
+ * @param filterClass Filter the returned plugins after calling the plugin manager.
+ * @return The PatternParser.
+ */
+ private static PatternParser createPatternParser(final Configuration config,
+ final Class<? extends PatternConverter> filterClass) {
+ if (config == null) {
+ return new PatternParser(config, PatternLayout.KEY, LogEventPatternConverter.class, filterClass);
+ }
+ PatternParser parser = config.getComponent(COMPONENT_KEY);
+ if (parser == null) {
+ parser = new PatternParser(config, PatternLayout.KEY, ThrowablePatternConverter.class);
+ config.addComponent(COMPONENT_KEY, parser);
+ parser = (PatternParser) config.getComponent(COMPONENT_KEY);
+ }
+ return parser;
+ }
+
+ /**
+ * Gets this Rfc5424Layout's content format. Specified by:
+ * <ul>
+ * <li>Key: "structured" Value: "true"</li>
+ * <li>Key: "format" Value: "RFC5424"</li>
+ * </ul>
+ *
+ * @return Map of content format keys supporting Rfc5424Layout
+ */
+ @Override
+ public Map<String, String> getContentFormat() {
+ final Map<String, String> result = new HashMap<String, String>();
+ result.put("structured", "true");
+ result.put("formatType", "RFC5424");
+ return result;
+ }
+
+ /**
+ * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the RFC 5424 Syslog specification.
+ *
+ * @param event The LogEvent.
+ * @return The RFC 5424 String representation of the LogEvent.
+ */
+ @Override
+ public String toSerializable(final LogEvent event) {
+ final StringBuilder buf = new StringBuilder();
+ appendPriority(buf, event.getLevel());
+ appendTimestamp(buf, event.getTimeMillis());
+ appendSpace(buf);
+ appendHostName(buf);
+ appendSpace(buf);
+ appendAppName(buf);
+ appendSpace(buf);
+ appendProcessId(buf);
+ appendSpace(buf);
+ appendMessageId(buf, event.getMessage());
+ appendSpace(buf);
+ appendStructuredElements(buf, event);
+ appendMessage(buf, event);
+ if (useTlsMessageFormat) {
+ return new TlsSyslogFrame(buf.toString()).toString();
+ }
+ return buf.toString();
+ }
+
+ private void appendPriority(final StringBuilder buffer, final Level logLevel) {
+ buffer.append('<');
+ buffer.append(Priority.getPriority(facility, logLevel));
+ buffer.append(">1 ");
+ }
+
+ private void appendTimestamp(final StringBuilder buffer, final long milliseconds) {
+ buffer.append(computeTimeStampString(milliseconds));
+ }
+
+ private void appendSpace(final StringBuilder buffer) {
+ buffer.append(' ');
+ }
+
+ private void appendHostName(final StringBuilder buffer) {
+ buffer.append(localHostName);
+ }
+
+ private void appendAppName(final StringBuilder buffer) {
+ if (appName != null) {
+ buffer.append(appName);
+ } else if (configName != null) {
+ buffer.append(configName);
+ } else {
+ buffer.append('-');
+ }
+ }
+
+ private void appendProcessId(final StringBuilder buffer) {
+ buffer.append(getProcId());
+ }
+
+ private void appendMessageId(final StringBuilder buffer, final Message message) {
+ final boolean isStructured = message instanceof StructuredDataMessage;
+ final String type = isStructured ? ((StructuredDataMessage) message).getType() : null;
+ if (type != null) {
+ buffer.append(type);
+ } else if (messageId != null) {
+ buffer.append(messageId);
+ } else {
+ buffer.append('-');
+ }
+ }
+
+ private void appendMessage(final StringBuilder buffer, final LogEvent event) {
+ final Message message = event.getMessage();
+ // This layout formats StructuredDataMessages instead of delegating to the Message itself.
+ final String text = (message instanceof StructuredDataMessage) ? message.getFormat() : message.getFormattedMessage();
+
+ if (text != null && text.length() > 0) {
+ buffer.append(' ').append(escapeNewlines(text, escapeNewLine));
+ }
+
+ if (exceptionFormatters != null && event.getThrown() != null) {
+ final TextBuffer exception = new TextBuffer();
+ exception.append(LF);
+ for (final PatternFormatter formatter : exceptionFormatters) {
+ formatter.format(event, exception);
+ }
+ buffer.append(escapeNewlines(exception.toString(), escapeNewLine));
+ }
+ if (includeNewLine) {
+ buffer.append(LF);
+ }
+ }
+
+ private void appendStructuredElements(final StringBuilder buffer, final LogEvent event) {
+ final Message message = event.getMessage();
+ final boolean isStructured = message instanceof StructuredDataMessage;
+
+ if (!isStructured && (fieldFormatters!= null && fieldFormatters.isEmpty()) && !includeMdc) {
+ buffer.append('-');
+ return;
+ }
+
+ final Map<String, StructuredDataElement> sdElements = new HashMap<String, StructuredDataElement>();
+ final Map<String, String> contextMap = event.getContextMap();
+
+ if (mdcRequired != null) {
+ checkRequired(contextMap);
+ }
+
+ if (fieldFormatters != null) {
+ for (final Map.Entry<String, FieldFormatter> sdElement: fieldFormatters.entrySet()) {
+ final String sdId = sdElement.getKey();
+ final StructuredDataElement elem = sdElement.getValue().format(event);
+ sdElements.put(sdId, elem);
+ }
+ }
+
+ if (includeMdc && contextMap.size() > 0) {
+ if (sdElements.containsKey(mdcSdId.toString())) {
+ final StructuredDataElement union = sdElements.get(mdcSdId.toString());
+ union.union(contextMap);
+ sdElements.put(mdcSdId.toString(), union);
+ } else {
+ final StructuredDataElement formattedContextMap = new StructuredDataElement(contextMap, false);
+ sdElements.put(mdcSdId.toString(), formattedContextMap);
+ }
+ }
+
+ if (isStructured) {
+ final StructuredDataMessage data = (StructuredDataMessage) message;
+ final Map<String, String> map = data.getData();
+ final StructuredDataId id = data.getId();
+ final String sdId = getId(id);
+
+ if (sdElements.containsKey(sdId)) {
+ final StructuredDataElement union = sdElements.get(id.toString());
+ union.union(map);
+ sdElements.put(sdId, union);
+ } else {
+ final StructuredDataElement formattedData = new StructuredDataElement(map, false);
+ sdElements.put(sdId, formattedData);
+ }
+ }
+
+ if (sdElements.isEmpty()) {
+ buffer.append('-');
+ return;
+ }
+
+ for (final Map.Entry<String, StructuredDataElement> entry: sdElements.entrySet()) {
+ formatStructuredElement(entry.getKey(), mdcPrefix, entry.getValue(), buffer, checker);
+ }
+ }
+
+ private String escapeNewlines(final String text, final String escapeNewLine) {
+ if (null == escapeNewLine) {
+ return text;
+ }
+ return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine);
+ }
+
+ protected String getProcId() {
+ return "-";
+ }
+
+ protected List<String> getMdcExcludes() {
+ return mdcExcludes;
+ }
+
+ protected List<String> getMdcIncludes() {
+ return mdcIncludes;
+ }
+
+ private String computeTimeStampString(final long now) {
+ long last;
+ synchronized (this) {
+ last = lastTimestamp;
+ if (now == lastTimestamp) {
+ return timestamppStr;
+ }
+ }
+
+ final StringBuilder buffer = new StringBuilder();
+ final Calendar cal = new GregorianCalendar();
+ cal.setTimeInMillis(now);
+ buffer.append(Integer.toString(cal.get(Calendar.YEAR)));
+ buffer.append('-');
+ pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer);
+ buffer.append('-');
+ pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer);
+ buffer.append('T');
+ pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer);
+ buffer.append(':');
+ pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer);
+ buffer.append(':');
+ pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer);
+
+ final int millis = cal.get(Calendar.MILLISECOND);
+ if (millis != 0) {
+ buffer.append('.');
+ pad(millis, THREE_DIGITS, buffer);
+ }
+
+ int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
+ if (tzmin == 0) {
+ buffer.append('Z');
+ } else {
+ if (tzmin < 0) {
+ tzmin = -tzmin;
+ buffer.append('-');
+ } else {
+ buffer.append('+');
+ }
+ final int tzhour = tzmin / MINUTES_PER_HOUR;
+ tzmin -= tzhour * MINUTES_PER_HOUR;
+ pad(tzhour, TWO_DIGITS, buffer);
+ buffer.append(':');
+ pad(tzmin, TWO_DIGITS, buffer);
+ }
+ synchronized (this) {
+ if (last == lastTimestamp) {
+ lastTimestamp = now;
+ timestamppStr = buffer.toString();
+ }
+ }
+ return buffer.toString();
+ }
+
+ private void pad(final int val, int max, final StringBuilder buf) {
+ while (max > 1) {
+ if (val < max) {
+ buf.append('0');
+ }
+ max = max / TWO_DIGITS;
+ }
+ buf.append(Integer.toString(val));
+ }
+
+ private void formatStructuredElement(final String id, final String prefix, final StructuredDataElement data,
+ final StringBuilder sb, final ListChecker checker) {
+ if ((id == null && defaultId == null) || data.discard()) {
+ return;
+ }
+
+ sb.append('[');
+ sb.append(id);
+ if (!mdcSdId.toString().equals(id)) {
+ appendMap(prefix, data.getFields(), sb, noopChecker);
+ } else {
+ appendMap(prefix, data.getFields(), sb, checker);
+ }
+ sb.append(']');
+ }
+
+ private String getId(final StructuredDataId id) {
+ final StringBuilder sb = new StringBuilder();
+ if (id == null || id.getName() == null) {
+ sb.append(defaultId);
+ } else {
+ sb.append(id.getName());
+ }
+ int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber;
+ if (ein < 0) {
+ ein = enterpriseNumber;
+ }
+ if (ein >= 0) {
+ sb.append('@').append(ein);
+ }
+ return sb.toString();
+ }
+
+ private void checkRequired(final Map<String, String> map) {
+ for (final String key : mdcRequired) {
+ final String value = map.get(key);
+ if (value == null) {
+ throw new LoggingException("Required key " + key + " is missing from the " + mdcId);
+ }
+ }
+ }
+
+ private void appendMap(final String prefix, final Map<String, String> map, final StringBuilder sb,
+ final ListChecker checker) {
+ final SortedMap<String, String> sorted = new TreeMap<String, String>(map);
+ for (final Map.Entry<String, String> entry : sorted.entrySet()) {
+ if (checker.check(entry.getKey()) && entry.getValue() != null) {
+ sb.append(' ');
+ if (prefix != null) {
+ sb.append(prefix);
+ }
+ sb.append(escapeNewlines(escapeSDParams(entry.getKey()), escapeNewLine)).append("=\"")
+ .append(escapeNewlines(escapeSDParams(entry.getValue()), escapeNewLine)).append('"');
+ }
+ }
+ }
+
+ private String escapeSDParams(final String value) {
+ return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
+ }
+
+ /**
+ * Interface used to check keys in a Map.
+ */
+ private interface ListChecker {
+ boolean check(String key);
+ }
+
+ /**
+ * Includes only the listed keys.
+ */
+ private class IncludeChecker implements ListChecker {
+ @Override
+ public boolean check(final String key) {
+ return mdcIncludes.contains(key);
+ }
+ }
+
+ /**
+ * Excludes the listed keys.
+ */
+ private class ExcludeChecker implements ListChecker {
+ @Override
+ public boolean check(final String key) {
+ return !mdcExcludes.contains(key);
+ }
+ }
+
+ /**
+ * Does nothing.
+ */
+ private class NoopChecker implements ListChecker {
+ @Override
+ public boolean check(final String key) {
+ return true;
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("facility=").append(facility.name());
+ sb.append(" appName=").append(appName);
+ sb.append(" defaultId=").append(defaultId);
+ sb.append(" enterpriseNumber=").append(enterpriseNumber);
+ sb.append(" newLine=").append(includeNewLine);
+ sb.append(" includeMDC=").append(includeMdc);
+ sb.append(" messageId=").append(messageId);
+ return sb.toString();
+ }
+
+ /**
+ * Create the RFC 5424 Layout.
+ *
+ * @param facility The Facility is used to try to classify the message.
+ * @param id The default structured data id to use when formatting according to RFC 5424.
+ * @param enterpriseNumber The IANA enterprise number.
+ * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
+ * record. Defaults to "true:.
+ * @param mdcId The id to use for the MDC Structured Data Element.
+ * @param mdcPrefix The prefix to add to MDC key names.
+ * @param eventPrefix The prefix to add to event key names.
+ * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false.
+ * @param escapeNL String that should be used to replace newlines within the message text.
+ * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record.
+ * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records.
+ * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent.
+ * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent.
+ * @param required A comma separated list of MDC keys that must be present in the MDC.
+ * @param exceptionPattern The pattern for formatting exceptions.
+ * @param useTlsMessageFormat If true the message will be formatted according to RFC 5425.
+ * @param loggerFields Container for the KeyValuePairs containing the patterns
+ * @param config The Configuration. Some Converters require access to the Interpolator.
+ * @return An Rfc5424Layout.
+ */
+ @PluginFactory
+ public static Rfc5424Layout createLayout(
+ @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility,
+ @PluginAttribute("id") final String id,
+ @PluginAttribute(value = "enterpriseNumber", defaultInt = DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber,
+ @PluginAttribute(value = "includeMDC", defaultBoolean = true) final boolean includeMDC,
+ @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) final String mdcId,
+ @PluginAttribute("mdcPrefix") final String mdcPrefix,
+ @PluginAttribute("eventPrefix") final String eventPrefix,
+ @PluginAttribute(value = "newLine", defaultBoolean = false) final boolean newLine,
+ @PluginAttribute("newLineEscape") final String escapeNL,
+ @PluginAttribute("appName") final String appName,
+ @PluginAttribute("messageId") final String msgId,
+ @PluginAttribute("mdcExcludes") final String excludes,
+ @PluginAttribute("mdcIncludes") String includes,
+ @PluginAttribute("mdcRequired") final String required,
+ @PluginAttribute("exceptionPattern") final String exceptionPattern,
+ @PluginAttribute(value = "useTlsMessageFormat", defaultBoolean = false) final boolean useTlsMessageFormat, // RFC 5425
+ @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
+ @PluginConfiguration final Configuration config) {
+ final Charset charset = Charsets.UTF_8;
+ if (includes != null && excludes != null) {
+ LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored");
+ includes = null;
+ }
+
+ return new Rfc5424Layout(config, facility, id, enterpriseNumber, includeMDC, newLine, escapeNL, mdcId, mdcPrefix,
+ eventPrefix, appName, msgId, excludes, includes, required, charset, exceptionPattern,
+ useTlsMessageFormat, loggerFields);
+ }
+
+ private class FieldFormatter {
+
+ private final Map<String, List<PatternFormatter>> delegateMap;
+ private final boolean discardIfEmpty;
+
+ public FieldFormatter(final Map<String, List<PatternFormatter>> fieldMap, final boolean discardIfEmpty) {
+ this.discardIfEmpty = discardIfEmpty;
+ this.delegateMap = fieldMap;
+ }
+
+ public StructuredDataElement format(final LogEvent event) {
+ final Map<String, String> map = new HashMap<String, String>();
+
+ for (final Map.Entry<String, List<PatternFormatter>> entry : delegateMap.entrySet()) {
+ final TextBuffer buffer = new TextBuffer();
+ for (final PatternFormatter formatter : entry.getValue()) {
+ formatter.format(event, buffer);
+ }
+ map.put(entry.getKey(), buffer.toString());
+ }
+ return new StructuredDataElement(map, discardIfEmpty);
+ }
+ }
+
+ private class StructuredDataElement {
+
+ private final Map<String, String> fields;
+ private final boolean discardIfEmpty;
+
+ public StructuredDataElement(final Map<String, String> fields, final boolean discardIfEmpty) {
+ this.discardIfEmpty = discardIfEmpty;
+ this.fields = fields;
+ }
+
+ boolean discard() {
+ if (discardIfEmpty == false) {
+ return false;
+ }
+ boolean foundNotEmptyValue = false;
+ for (final Map.Entry<String, String> entry : fields.entrySet()) {
+ if (Strings.isNotEmpty(entry.getValue())) {
+ foundNotEmptyValue = true;
+ break;
+ }
+ }
+ return !foundNotEmptyValue;
+ }
+
+ void union(final Map<String, String> fields) {
+ this.fields.putAll(fields);
+ }
+
+ Map<String, String> getFields() {
+ return this.fields;
+ }
+ }
+}