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 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 @@ 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; } 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..a5e5875 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,8 @@ 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.layout.PatternLayout.Serializer; import org.apache.logging.log4j.core.util.StringBuilderWriter; import org.apache.logging.log4j.util.Strings; @@ -39,8 +41,10 @@ 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 +59,7 @@ */ @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 +70,7 @@ } } - 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); 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 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 - * The Class that the Layout will format the LogEvent into. + * The Class that the Layout will format the LogEvent into. */ public abstract class AbstractLayout implements Layout, Serializable { @@ -40,9 +41,14 @@ 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,22 +56,45 @@ 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 @@ -92,7 +121,7 @@ public byte[] getHeader() { return header; } - + protected void markEvent() { eventCount++; } 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..240e1df 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,11 @@ 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.layout.PatternLayout.Serializer; 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. @@ -44,36 +48,9 @@ */ protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; - private final static ThreadLocal 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 threadLocal = new ThreadLocal<>(); /** * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to. @@ -89,28 +66,68 @@ 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 +136,7 @@ } try { // LOG4J2-935: String.getBytes(String) gives better performance return s.getBytes(charsetName); - } catch (UnsupportedEncodingException e) { + } catch (final UnsupportedEncodingException e) { return s.getBytes(charset); } } @@ -128,6 +145,7 @@ public Charset getCharset() { return charset; } + /** * @return The default content type for Strings. @@ -138,6 +156,59 @@ } /** + * Returns the footer, if one is available. + * + * @return A byte array containing the footer. + */ + @Override + public byte[] getFooter() { + return serializeToBytes(footerSerializer); + } + + 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); + } + + 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) { + final String serializable = serializeToString(serializer); + if (serializer == null) { + return null; + } + 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 +218,10 @@ public byte[] toByteArray(final LogEvent event) { return getBytes(toSerializable(event)); } + + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeUTF(charset.name()); + } } 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.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 @@ 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 @@ @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 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.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 @@ 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 @@ @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 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 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 @@ @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 @@ 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 @@ 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 @@ /** * 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 @@ * @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 @@ @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(", "); } 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..409fe61 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.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.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 @@ 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 @@ 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, @@ -146,27 +134,6 @@ } } 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()); } /** 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 @@ } private SerializedLayout() { - super(null, null); + super(null, null, null); } /** 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 @@ 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); } /** 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,9 +64,18 @@ @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 @@ -82,7 +91,10 @@ } 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 appenders = root.getAppenders(); for (final Appender appender : appenders.values()) { root.removeAppender(appender); @@ -100,13 +112,35 @@ appender.stop(); final List 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 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 @@ @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()); } 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.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 @@ 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 @@ 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 @@ 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 @@ final List 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 @@ @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") // 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 @@ } protected Layout 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 createLayout();