Repository: logging-log4j2 Updated Branches: refs/heads/master 5b1404b6b -> 75d33d96a
[LOG4J2-1244] Make header and footer values customizable in JSONLayout. [LOG4J2-1245] Make CSV Layout header and footers accept patterns. Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/75d33d96 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/75d33d96 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/75d33d96 Branch: refs/heads/master Commit: 75d33d96ac00356014cf11f8ad9e8c6ead4db37a Parents: 5b1404b Author: ggregory <[email protected]> Authored: Tue Jan 5 18:57:38 2016 -0800 Committer: ggregory <[email protected]> Committed: Tue Jan 5 18:57:38 2016 -0800 ---------------------------------------------------------------------- .../log4j/core/layout/AbstractCsvLayout.java | 9 +- .../core/layout/AbstractJacksonLayout.java | 11 +- .../log4j/core/layout/AbstractLayout.java | 45 +++++- .../log4j/core/layout/AbstractStringLayout.java | 158 ++++++++++++++----- .../log4j/core/layout/CsvLogEventLayout.java | 15 +- .../log4j/core/layout/CsvParameterLayout.java | 15 +- .../logging/log4j/core/layout/JsonLayout.java | 51 ++++-- .../log4j/core/layout/PatternLayout.java | 52 +----- .../log4j/core/layout/SerializedLayout.java | 2 +- .../logging/log4j/core/layout/XmlLayout.java | 2 +- .../logging/log4j/core/net/SmtpManager.java | 4 +- .../core/layout/CsvLogEventLayoutTest.java | 44 +++++- .../core/layout/CsvParameterLayoutTest.java | 4 +- .../log4j/core/layout/JsonLayoutTest.java | 17 +- .../net/server/AbstractSocketServerTest.java | 2 +- src/changes/changes.xml | 6 + 16 files changed, 295 insertions(+), 142 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractCsvLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractCsvLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractCsvLayout.java index ae55447..c5b0e90 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractCsvLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractCsvLayout.java @@ -20,7 +20,7 @@ import java.nio.charset.Charset; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.QuoteMode; -import org.apache.logging.log4j.core.util.StringEncoder; +import org.apache.logging.log4j.core.config.Configuration; /** * A superclass for Comma-Separated Value (CSV) layouts. @@ -38,9 +38,10 @@ public abstract class AbstractCsvLayout extends AbstractStringLayout { private final CSVFormat format; - protected AbstractCsvLayout(final Charset charset, final CSVFormat csvFormat, final String header, - final String footer) { - super(charset, StringEncoder.toBytes(header, charset), StringEncoder.toBytes(footer, charset)); + protected AbstractCsvLayout(final Configuration config, final Charset charset, final CSVFormat csvFormat, + final String header, final String footer) { + super(config, charset, PatternLayout.createSerializer(config, null, header, null, null, false, false), + PatternLayout.createSerializer(config, null, footer, null, null, false, false)); this.format = csvFormat; } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java index 3b122e1..ced02e2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java @@ -21,6 +21,7 @@ import java.io.Writer; import java.nio.charset.Charset; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.util.StringBuilderWriter; import org.apache.logging.log4j.util.Strings; @@ -39,8 +40,10 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { protected final boolean compact; protected final boolean complete; - protected AbstractJacksonLayout(final ObjectWriter objectWriter, final Charset charset, final boolean compact, final boolean complete, final boolean eventEol) { - super(charset); + 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) { + super(config, charset, headerSerializer, footerSerializer); this.objectWriter = objectWriter; this.compact = compact; this.complete = complete; @@ -55,7 +58,7 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { */ @Override public String toSerializable(final LogEvent event) { - StringBuilderWriter writer = new StringBuilderWriter(); + final StringBuilderWriter writer = new StringBuilderWriter(); try { toSerializable(event, writer); return writer.toString(); @@ -66,7 +69,7 @@ abstract class AbstractJacksonLayout extends AbstractStringLayout { } } - public void toSerializable(final LogEvent event, Writer writer) + public void toSerializable(final LogEvent event, final Writer writer) throws JsonGenerationException, JsonMappingException, IOException { objectWriter.writeValue(writer, event); writer.write(eol); http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java index 9c5166e..fae6349 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java @@ -22,13 +22,14 @@ import java.util.Map; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.status.StatusLogger; /** * Abstract base class for Layouts. * * @param <T> - * The Class that the Layout will format the LogEvent into. + * The Class that the Layout will format the LogEvent into. */ public abstract class AbstractLayout<T extends Serializable> implements Layout<T>, Serializable { @@ -40,9 +41,14 @@ public abstract class AbstractLayout<T extends Serializable> implements Layout<T private static final long serialVersionUID = 1L; /** - * The header to include when the stream is opened. May be null. + * The current Configuration. */ - protected final byte[] header; + protected final Configuration configuration; + + /** + * The number of events successfully processed by this layout. + */ + protected long eventCount; /** * The footer to add when the stream is closed. May be null. @@ -50,24 +56,47 @@ public abstract class AbstractLayout<T extends Serializable> implements Layout<T protected final byte[] footer; /** - * The count of events successfully processed by this layout. + * The header to include when the stream is opened. May be null. */ - protected long eventCount; + protected final byte[] header; /** * Constructs a layout with an optional header and footer. * + * @param configuration + * The configuration * @param header - * The header to include when the stream is opened. May be null. + * The header to include when the stream is opened. May be null. * @param footer - * The footer to add when the stream is closed. May be null. + * The footer to add when the stream is closed. May be null. + * @deprecated Use {@link #AbstractLayout(Configuration, byte[], byte[])} */ + @Deprecated public AbstractLayout(final byte[] header, final byte[] footer) { + this(null, header, footer); + } + + /** + * Constructs a layout with an optional header and footer. + * + * @param configuration + * The configuration + * @param header + * The header to include when the stream is opened. May be null. + * @param footer + * The footer to add when the stream is closed. May be null. + */ + public AbstractLayout(final Configuration configuration, final byte[] header, final byte[] footer) { super(); + this.configuration = configuration; this.header = header; this.footer = footer; } + public Configuration getConfiguration() { + return configuration; + } + @Override public Map<String, String> getContentFormat() { return new HashMap<>(); @@ -92,7 +121,7 @@ public abstract class AbstractLayout<T extends Serializable> implements Layout<T public byte[] getHeader() { return header; } - + protected void markEvent() { eventCount++; } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java index 96a80a5..91e21ef 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java @@ -25,7 +25,10 @@ import java.nio.charset.StandardCharsets; 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.LoggerConfig; import org.apache.logging.log4j.core.util.StringEncoder; +import org.apache.logging.log4j.util.Strings; /** * Abstract base class for Layouts that result in a String. @@ -39,41 +42,19 @@ import org.apache.logging.log4j.core.util.StringEncoder; */ public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout { + public interface Serializer { + + String toSerializable(final LogEvent event); + } + /** * Default length for new StringBuilder instances: {@value} . */ protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; - private final static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>(); - private static final long serialVersionUID = 1L; - /** - * The charset for the formatted message. - */ - // LOG4J2-1099: charset cannot be final due to serialization needs, so we serialize as charset name instead - private transient Charset charset; - private final String charsetName; - private final boolean useCustomEncoding; - - protected AbstractStringLayout(final Charset charset) { - this(charset, null, null); - } - - /** - * Builds a new layout. - * @param charset the charset used to encode the header bytes, footer bytes and anything else that needs to be - * converted from strings to bytes. - * @param header the header bytes - * @param footer the footer bytes - */ - protected AbstractStringLayout(final Charset charset, final byte[] header, final byte[] footer) { - super(header, footer); - this.charset = charset == null ? StandardCharsets.UTF_8 : charset; - this.charsetName = this.charset.name(); - useCustomEncoding = isPreJava8() - && (StandardCharsets.ISO_8859_1.equals(charset) || StandardCharsets.US_ASCII.equals(charset)); - } + private final static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>(); /** * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to. @@ -89,28 +70,68 @@ public abstract class AbstractStringLayout extends AbstractLayout<String> implem result.setLength(0); return result; } - // LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them. private static boolean isPreJava8() { final String version = System.getProperty("java.version"); final String[] parts = version.split("\\."); try { - int major = Integer.parseInt(parts[1]); + final int major = Integer.parseInt(parts[1]); return major < 8; - } catch (Exception ex) { + } catch (final Exception ex) { return true; } } + /** + * The charset for the formatted message. + */ + // LOG4J2-1099: charset cannot be final due to serialization needs, so we serialize as charset name instead + private transient Charset charset; - private void writeObject(final ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - out.writeUTF(charset.name()); + private final String charsetName; + + private final Serializer footerSerializer; + + private final Serializer headerSerializer; + + private final boolean useCustomEncoding; + + protected AbstractStringLayout(final Charset charset) { + this(charset, (byte[]) null, (byte[]) null); + } + + /** + * Builds a new layout. + * @param charset the charset used to encode the header bytes, footer bytes and anything else that needs to be + * converted from strings to bytes. + * @param header the header bytes + * @param footer the footer bytes + */ + protected AbstractStringLayout(final Charset charset, final byte[] header, final byte[] footer) { + super(null, header, footer); + this.headerSerializer = null; + this.footerSerializer = null; + this.charset = charset == null ? StandardCharsets.UTF_8 : charset; + this.charsetName = this.charset.name(); + useCustomEncoding = isPreJava8() + && (StandardCharsets.ISO_8859_1.equals(charset) || StandardCharsets.US_ASCII.equals(charset)); } - private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - final String csName = in.readUTF(); - charset = Charset.forName(csName); + /** + * Builds a new layout. + * @param Configuration config the configuration + * @param charset the charset used to encode the header bytes, footer bytes and anything else that needs to be + * converted from strings to bytes. + * @param header the header bytes serializer + * @param footer the footer bytes serializer + */ + protected AbstractStringLayout(final Configuration config, final Charset charset, final Serializer headerSerializer, final Serializer footerSerializer) { + super(config, null, null); + this.headerSerializer = headerSerializer; + this.footerSerializer = footerSerializer; + this.charset = charset == null ? StandardCharsets.UTF_8 : charset; + this.charsetName = this.charset.name(); + useCustomEncoding = isPreJava8() + && (StandardCharsets.ISO_8859_1.equals(charset) || StandardCharsets.US_ASCII.equals(charset)); } protected byte[] getBytes(final String s) { @@ -119,7 +140,7 @@ public abstract class AbstractStringLayout extends AbstractLayout<String> implem } try { // LOG4J2-935: String.getBytes(String) gives better performance return s.getBytes(charsetName); - } catch (UnsupportedEncodingException e) { + } catch (final UnsupportedEncodingException e) { return s.getBytes(charset); } } @@ -129,6 +150,7 @@ public abstract class AbstractStringLayout extends AbstractLayout<String> implem return charset; } + /** * @return The default content type for Strings. */ @@ -138,6 +160,59 @@ public abstract class AbstractStringLayout extends AbstractLayout<String> implem } /** + * Returns the footer, if one is available. + * + * @return A byte array containing the footer. + */ + @Override + public byte[] getFooter() { + return serializeToBytes(footerSerializer, super.getFooter()); + } + + public Serializer getFooterSerializer() { + return footerSerializer; + } + + /** + * Returns the header, if one is available. + * + * @return A byte array containing the header. + */ + @Override + public byte[] getHeader() { + return serializeToBytes(headerSerializer, super.getHeader()); + } + + public Serializer getHeaderSerializer() { + return headerSerializer; + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + final String csName = in.readUTF(); + charset = Charset.forName(csName); + } + + protected byte[] serializeToBytes(final Serializer serializer, byte[] defaultValue) { + final String serializable = serializeToString(serializer); + if (serializer == null) { + return defaultValue; + } + return StringEncoder.toBytes(serializable, getCharset()); + } + + protected String serializeToString(final Serializer serializer) { + if (serializer == null) { + return null; + } + final LoggerConfig rootLogger = getConfiguration().getRootLogger(); + // Using "" for the FQCN, does it matter? + final LogEvent logEvent = rootLogger.getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, + rootLogger.getLevel(), null, null, null); + return serializer.toSerializable(logEvent); + } + + /** * Formats the Log Event as a byte array. * * @param event The Log Event. @@ -147,5 +222,10 @@ public abstract class AbstractStringLayout extends AbstractLayout<String> implem public byte[] toByteArray(final LogEvent event) { return getBytes(toSerializable(event)); } + + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeUTF(charset.name()); + } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java index 9b737fe..dd751f9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvLogEventLayout.java @@ -24,9 +24,11 @@ import org.apache.commons.csv.CSVPrinter; import org.apache.commons.csv.QuoteMode; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +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.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; @@ -43,16 +45,17 @@ public class CsvLogEventLayout extends AbstractCsvLayout { private static final long serialVersionUID = 1L; public static CsvLogEventLayout createDefaultLayout() { - return new CsvLogEventLayout(Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); + return new CsvLogEventLayout(null, Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); } public static CsvLogEventLayout createLayout(final CSVFormat format) { - return new CsvLogEventLayout(Charset.forName(DEFAULT_CHARSET), format, null, null); + return new CsvLogEventLayout(null, Charset.forName(DEFAULT_CHARSET), format, null, null); } @PluginFactory public static CsvLogEventLayout createLayout( // @formatter:off + @PluginConfiguration final Configuration config, @PluginAttribute(value = "format", defaultString = DEFAULT_FORMAT) final String format, @PluginAttribute("delimiter") final Character delimiter, @PluginAttribute("escape") final Character escape, @@ -61,17 +64,17 @@ public class CsvLogEventLayout extends AbstractCsvLayout { @PluginAttribute("nullString") final String nullString, @PluginAttribute("recordSeparator") final String recordSeparator, @PluginAttribute(value = "charset", defaultString = DEFAULT_CHARSET) final Charset charset, - @PluginAttribute("header") final String header, + @PluginAttribute("header") final String header, @PluginAttribute("footer") final String footer) // @formatter:on { final CSVFormat csvFormat = createFormat(format, delimiter, escape, quote, quoteMode, nullString, recordSeparator); - return new CsvLogEventLayout(charset, csvFormat, header, footer); + return new CsvLogEventLayout(config, charset, csvFormat, header, footer); } - protected CsvLogEventLayout(final Charset charset, final CSVFormat csvFormat, final String header, final String footer) { - super(charset, csvFormat, header, footer); + protected CsvLogEventLayout(final Configuration config, final Charset charset, final CSVFormat csvFormat, final String header, final String footer) { + super(config, charset, csvFormat, header, footer); } @Override http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java index fb43964..324a08a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java @@ -24,9 +24,11 @@ import org.apache.commons.csv.CSVPrinter; import org.apache.commons.csv.QuoteMode; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +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.PluginFactory; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.status.StatusLogger; @@ -52,16 +54,17 @@ public class CsvParameterLayout extends AbstractCsvLayout { private static final long serialVersionUID = 1L; public static AbstractCsvLayout createDefaultLayout() { - return new CsvParameterLayout(Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); + return new CsvParameterLayout(null, Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); } public static AbstractCsvLayout createLayout(final CSVFormat format) { - return new CsvParameterLayout(Charset.forName(DEFAULT_CHARSET), format, null, null); + return new CsvParameterLayout(null, Charset.forName(DEFAULT_CHARSET), format, null, null); } @PluginFactory public static AbstractCsvLayout createLayout( // @formatter:off + @PluginConfiguration final Configuration config, @PluginAttribute(value = "format", defaultString = DEFAULT_FORMAT) final String format, @PluginAttribute("delimiter") final Character delimiter, @PluginAttribute("escape") final Character escape, @@ -70,17 +73,17 @@ public class CsvParameterLayout extends AbstractCsvLayout { @PluginAttribute("nullString") final String nullString, @PluginAttribute("recordSeparator") final String recordSeparator, @PluginAttribute(value = "charset", defaultString = DEFAULT_CHARSET) final Charset charset, - @PluginAttribute("header") final String header, + @PluginAttribute("header") final String header, @PluginAttribute("footer") final String footer) // @formatter:on { final CSVFormat csvFormat = createFormat(format, delimiter, escape, quote, quoteMode, nullString, recordSeparator); - return new CsvParameterLayout(charset, csvFormat, header, footer); + return new CsvParameterLayout(config, charset, csvFormat, header, footer); } - public CsvParameterLayout(final Charset charset, final CSVFormat csvFormat, final String header, final String footer) { - super(charset, csvFormat, header, footer); + public CsvParameterLayout(final Configuration config, final Charset charset, final CSVFormat csvFormat, final String header, final String footer) { + super(config, charset, csvFormat, header, footer); } @Override http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java index e403731..fe1a3b4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java @@ -25,9 +25,12 @@ import java.util.Map; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; 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.PluginFactory; /** @@ -798,14 +801,21 @@ import org.apache.logging.log4j.core.config.plugins.PluginFactory; @Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) public final class JsonLayout extends AbstractJacksonLayout { + private static final String DEFAULT_FOOTER = "]"; + + private static final String DEFAULT_HEADER = "["; + static final String CONTENT_TYPE = "application/json"; private static final long serialVersionUID = 1L; - protected JsonLayout(final boolean locationInfo, final boolean properties, final boolean complete, - final boolean compact, final boolean eventEol, final Charset charset) { - super(new JacksonFactory.JSON().newWriter(locationInfo, properties, compact), charset, compact, complete, - eventEol); + protected JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, + final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, + final String footerPattern, final Charset charset) { + super(config, new JacksonFactory.JSON().newWriter(locationInfo, properties, compact), charset, compact, + complete, eventEol, + PatternLayout.createSerializer(config, null, headerPattern, DEFAULT_HEADER, null, false, false), + PatternLayout.createSerializer(config, null, footerPattern, DEFAULT_FOOTER, null, false, false)); } /** @@ -819,7 +829,10 @@ public final class JsonLayout extends AbstractJacksonLayout { return null; } final StringBuilder buf = new StringBuilder(); - buf.append('['); + final String str = serializeToString(getHeaderSerializer()); + if (str != null) { + buf.append(str); + } buf.append(this.eol); return getBytes(buf.toString()); } @@ -834,7 +847,14 @@ public final class JsonLayout extends AbstractJacksonLayout { if (!this.complete) { return null; } - return getBytes(this.eol + ']' + this.eol); + final StringBuilder buf = new StringBuilder(); + buf.append(this.eol); + final String str = serializeToString(getFooterSerializer()); + if (str != null) { + buf.append(str); + } + buf.append(this.eol); + return getBytes(buf.toString()); } @Override @@ -854,7 +874,8 @@ public final class JsonLayout extends AbstractJacksonLayout { /** * Creates a JSON Layout. - * + * @param config + * The plugin configuration. * @param locationInfo * If "true", includes the location information in the generated JSON. * @param properties @@ -866,6 +887,11 @@ public final class JsonLayout extends AbstractJacksonLayout { * @param eventEol * If "true", forces an EOL after each log event (even if compact is "true"), defaults to "false". This * allows one even per line, even in compact mode. + * @param headerPattern + * The header pattern, defaults to {@code "["} if null. + * @param footerPattern + * The header pattern, defaults to {@code "]"} if null. + * @param footerPattern * @param charset * The character set to use, if {@code null}, uses "UTF-8". * @return A JSON Layout. @@ -873,28 +899,31 @@ public final class JsonLayout extends AbstractJacksonLayout { @PluginFactory public static AbstractJacksonLayout createLayout( // @formatter:off + @PluginConfiguration final Configuration config, @PluginAttribute(value = "locationInfo", defaultBoolean = false) final boolean locationInfo, @PluginAttribute(value = "properties", defaultBoolean = false) final boolean properties, @PluginAttribute(value = "complete", defaultBoolean = false) final boolean complete, @PluginAttribute(value = "compact", defaultBoolean = false) final boolean compact, @PluginAttribute(value = "eventEol", defaultBoolean = false) final boolean eventEol, + @PluginAttribute(value = "header", defaultString = DEFAULT_HEADER) final String headerPattern, + @PluginAttribute(value = "footer", defaultString = DEFAULT_FOOTER) final String footerPattern, @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset // @formatter:on ) { - return new JsonLayout(locationInfo, properties, complete, compact, eventEol, charset); + return new JsonLayout(config, locationInfo, properties, complete, compact, eventEol, headerPattern, footerPattern, charset); } /** - * Creates a JSON Layout using the default settings. + * Creates a JSON Layout using the default settings. Useful for testing. * * @return A JSON Layout. */ public static AbstractJacksonLayout createDefaultLayout() { - return new JsonLayout(false, false, false, false, false, StandardCharsets.UTF_8); + return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8); } @Override - public void toSerializable(final LogEvent event, Writer writer) throws IOException { + public void toSerializable(final LogEvent event, final Writer writer) throws IOException { if (complete && eventCount > 0) { writer.append(", "); } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/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 0503fc8..518f6a4 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 @@ -26,7 +26,6 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.LoggerConfig; 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; @@ -39,7 +38,6 @@ 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.util.StringEncoder; import org.apache.logging.log4j.util.Strings; /** @@ -89,15 +87,6 @@ public final class PatternLayout extends AbstractStringLayout { private final Serializer eventSerializer; - private final Serializer headerSerializer; - - private final Serializer footerSerializer; - - /** - * The current Configuration. - */ - private final Configuration config; - /** * Constructs a EnhancedPatternLayout using the supplied conversion pattern. * @@ -116,16 +105,15 @@ public final class PatternLayout extends AbstractStringLayout { private PatternLayout(final Configuration config, final RegexReplacement replace, final String eventPattern, final PatternSelector patternSelector, final Charset charset, final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi, final String headerPattern, final String footerPattern) { - super(charset, StringEncoder.toBytes(headerPattern, charset), StringEncoder.toBytes(footerPattern, charset)); + super(config, charset, + createSerializer(config, replace, headerPattern, null, patternSelector, alwaysWriteExceptions, + noConsoleNoAnsi), + createSerializer(config, replace, footerPattern, null, patternSelector, alwaysWriteExceptions, + noConsoleNoAnsi)); this.conversionPattern = eventPattern; this.patternSelector = patternSelector; - this.config = config; - this.eventSerializer = createSerializer(config, replace, eventPattern, DEFAULT_CONVERSION_PATTERN, patternSelector, - alwaysWriteExceptions, noConsoleNoAnsi); - this.headerSerializer = createSerializer(config, replace, headerPattern, null, patternSelector, - alwaysWriteExceptions, noConsoleNoAnsi); - this.footerSerializer = createSerializer(config, replace, footerPattern, null, patternSelector, - alwaysWriteExceptions, noConsoleNoAnsi); + this.eventSerializer = createSerializer(config, replace, eventPattern, DEFAULT_CONVERSION_PATTERN, + patternSelector, alwaysWriteExceptions, noConsoleNoAnsi); } public static Serializer createSerializer(final Configuration configuration, final RegexReplacement replace, @@ -148,27 +136,6 @@ public final class PatternLayout extends AbstractStringLayout { return new PatternSelectorSerializer(patternSelector, replace); } - @Override - public byte[] getHeader() { - return serializeHeaderFooter(headerSerializer); - } - - @Override - public byte[] getFooter() { - return serializeHeaderFooter(footerSerializer); - } - - private byte[] serializeHeaderFooter(final Serializer serializer) { - if (serializer == null) { - return null; - } - final LoggerConfig rootLogger = config.getRootLogger(); - // Using "" for the FQCN, does it matter? - final LogEvent logEvent = rootLogger.getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, - rootLogger.getLevel(), null, null, null); - return StringEncoder.toBytes(serializer.toSerializable(logEvent), getCharset()); - } - /** * Gets the conversion pattern. * @@ -279,11 +246,6 @@ public final class PatternLayout extends AbstractStringLayout { .build(); } - public interface Serializer { - - String toSerializable(final LogEvent event); - } - private static class PatternSerializer implements Serializer { private final PatternFormatter[] formatters; http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java index f60c1b2..6d2349d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java @@ -48,7 +48,7 @@ public final class SerializedLayout extends AbstractLayout<LogEvent> { } private SerializedLayout() { - super(null, null); + super(null, null, null); } /** http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java index 5badaca..8e01bd3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java @@ -197,7 +197,7 @@ public final class XmlLayout extends AbstractJacksonLayout { private static final String ROOT_TAG = "Events"; protected XmlLayout(final boolean locationInfo, final boolean properties, final boolean complete, final boolean compact, final Charset charset) { - super(new JacksonFactory.XML().newWriter(locationInfo, properties, compact), charset, compact, complete, false); + super(null, new JacksonFactory.XML().newWriter(locationInfo, properties, compact), charset, compact, complete, false, null, null); } /** http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java index 67f0932..2f15042 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SmtpManager.java @@ -42,10 +42,8 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractManager; import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer; import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.core.layout.PatternLayout.Serializer; import org.apache.logging.log4j.core.util.CyclicBuffer; import org.apache.logging.log4j.core.util.NameUtil; import org.apache.logging.log4j.core.util.NetUtils; http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java index f0f6d95..688cb42 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvLogEventLayoutTest.java @@ -64,12 +64,21 @@ public class CsvLogEventLayoutTest { @Test public void testCustomCharset() { - final AbstractCsvLayout layout = CsvLogEventLayout.createLayout("Excel", null, null, null, null, null, null, - StandardCharsets.UTF_16, null, null); + final AbstractCsvLayout layout = CsvLogEventLayout.createLayout(null, "Excel", null, null, null, null, null, + null, StandardCharsets.UTF_16, null, null); assertEquals("text/csv; charset=UTF-16", layout.getContentType()); } @Test + public void testHeaderFooter() { + final String header = "# Header"; + final String footer = "# Footer "; + final AbstractCsvLayout layout = CsvLogEventLayout.createLayout(ctx.getConfiguration(), "Excel", null, null, + null, null, null, null, null, header, footer); + testLayout(CSVFormat.DEFAULT, layout, header, footer); + } + + @Test public void testDefaultCharset() { final AbstractCsvLayout layout = CsvLogEventLayout.createDefaultLayout(); assertEquals(StandardCharsets.UTF_8, layout.getCharset()); @@ -82,7 +91,10 @@ public class CsvLogEventLayoutTest { } private void testLayout(final CSVFormat format) { - final AbstractCsvLayout layout = CsvLogEventLayout.createLayout(format); + testLayout(format, CsvLogEventLayout.createLayout(format), null, null); + } + + private void testLayout(final CSVFormat format, final AbstractCsvLayout layout, String header, String footer) { final Map<String, Appender> appenders = root.getAppenders(); for (final Appender appender : appenders.values()) { root.removeAppender(appender); @@ -100,13 +112,35 @@ public class CsvLogEventLayoutTest { appender.stop(); final List<String> list = appender.getMessages(); - final String event0 = list.get(0); + final boolean hasHeaderSerializer = layout.getHeaderSerializer() != null; + final boolean hasFooterSerializer = layout.getFooterSerializer() != null; + final int headerOffset = hasHeaderSerializer ? 1 : 0; + final String event0 = list.get(0 + headerOffset); + final String event1 = list.get(1 + headerOffset); final char del = format.getDelimiter(); Assert.assertTrue(event0, event0.contains(del + "DEBUG" + del)); final String quote = del == ',' ? "\"" : ""; Assert.assertTrue(event0, event0.contains(del + quote + "one=1, two=2, three=3" + quote + del)); - final String event1 = list.get(1); Assert.assertTrue(event1, event1.contains(del + "INFO" + del)); + + if (hasHeaderSerializer && header == null) { + Assert.fail(); + } + if (!hasHeaderSerializer && header != null) { + Assert.fail(); + } + if (hasFooterSerializer && footer == null) { + Assert.fail(); + } + if (!hasFooterSerializer && footer != null) { + Assert.fail(); + } + if (hasHeaderSerializer) { + Assert.assertEquals(list.toString(), header, list.get(0)); + } + if (hasFooterSerializer) { + Assert.assertEquals(list.toString(), footer, list.get(list.size() - 1)); + } } @Test http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java index 0bbe3c6..27a7318 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/CsvParameterLayoutTest.java @@ -65,8 +65,8 @@ public class CsvParameterLayoutTest { @Test public void testCustomCharset() { - final AbstractCsvLayout layout = CsvParameterLayout.createLayout("Excel", null, null, null, null, null, null, - StandardCharsets.UTF_16, null, null); + final AbstractCsvLayout layout = CsvParameterLayout.createLayout(null, "Excel", null, null, null, null, null, + null, StandardCharsets.UTF_16, null, null); assertEquals("text/csv; charset=UTF-16", layout.getContentType()); } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java index 87b075c..22b017f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java @@ -30,6 +30,7 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.BasicConfigurationFactory; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; @@ -105,8 +106,8 @@ public class JsonLayoutTest { private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean eventEol, final boolean includeContext) throws Exception { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); - final AbstractJacksonLayout layout = JsonLayout.createLayout(includeSource, - includeContext, false, compact, eventEol, StandardCharsets.UTF_8); + final AbstractJacksonLayout layout = JsonLayout.createLayout(null, includeSource, + includeContext, false, compact, eventEol, null, null, StandardCharsets.UTF_8); final String str = layout.toSerializable(expected); // System.out.println(str); final String propSep = this.toPropertySeparator(compact); @@ -176,8 +177,9 @@ public class JsonLayoutTest { for (final Appender appender : appenders.values()) { this.rootLogger.removeAppender(appender); } + final Configuration configuration = rootLogger.getContext().getConfiguration(); // set up appender - final AbstractJacksonLayout layout = JsonLayout.createLayout(true, true, true, false, false, null); + final AbstractJacksonLayout layout = JsonLayout.createLayout(configuration, true, true, true, false, false, null, null, null); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -211,8 +213,10 @@ public class JsonLayoutTest { for (final Appender appender : appenders.values()) { this.rootLogger.removeAppender(appender); } + final Configuration configuration = rootLogger.getContext().getConfiguration(); // set up appender - final AbstractJacksonLayout layout = JsonLayout.createLayout(true, true, true, false, false, null); + // Use [[ and ]] to test header and footer (instead of [ and ]) + final AbstractJacksonLayout layout = JsonLayout.createLayout(configuration, true, true, true, false, false, "[[", "]]", null); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -239,7 +243,7 @@ public class JsonLayoutTest { final List<String> list = appender.getMessages(); - this.checkAt("[", 0, list); + this.checkAt("[[", 0, list); this.checkAt("{", 1, list); this.checkContains("\"loggerFqcn\" : \"" + AbstractLogger.class.getName() + "\",", list); this.checkContains("\"level\" : \"DEBUG\",", list); @@ -251,7 +255,8 @@ public class JsonLayoutTest { @Test public void testLayoutLoggerName() throws Exception { - final AbstractJacksonLayout layout = JsonLayout.createLayout(false, false, false, true, false, StandardCharsets.UTF_8); + final AbstractJacksonLayout layout = JsonLayout.createLayout(null, false, false, false, true, false, null, null, + StandardCharsets.UTF_8); final Log4jLogEvent expected = Log4jLogEvent.newBuilder() // .setLoggerName("a.B") // .setLoggerFqcn("f.q.c.n") // http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java index 06adc8a..ee5c2de 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/server/AbstractSocketServerTest.java @@ -78,7 +78,7 @@ public abstract class AbstractSocketServerTest { } protected Layout<String> createJsonLayout() { - return JsonLayout.createLayout(true, true, false, false, false, null); + return JsonLayout.createLayout(null, true, true, false, false, false, null, null, null); } protected abstract Layout<? extends Serializable> createLayout(); http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/75d33d96/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3182a0c..d6de059 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -31,6 +31,12 @@ <action issue="LOG4J2-1237" dev="ggregory" type="add" due-to="Mike Calmus, Gary Gregory"> Make PatternLayout header and footer accept a pattern. </action> + <action issue="LOG4J2-1244" dev="ggregory" type="add" due-to="Anshu Garg, Remko Popma, Gary Gregory"> + Make header and footer values customizable in JSONLayout. + </action> + <action issue="LOG4J2-1245" dev="ggregory" type="add"> + Make CSV Layout header and footers accept patterns. + </action> <action issue="LOG4J2-1192" dev="ggregory" type="add" due-to="Jörg Bretschneider, Gary Gregory"> Dynamic Subject for SMTP Appender. </action>
