This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/master by this push:
new 693c99b [LOG4J2-3359] Adds Log4j 1.x specific syslog layout (#807)
693c99b is described below
commit 693c99b81e4ddf679a78b1f50ad4b78e81d8e2b8
Author: ppkarwasz <[email protected]>
AuthorDate: Thu Mar 31 21:05:20 2022 +0200
[LOG4J2-3359] Adds Log4j 1.x specific syslog layout (#807)
Log4j 1.x always used the BSD syslog layout, but allowed for an internal
layout to specify the _MESSAGE_ format.
---
.../builders/appender/SyslogAppenderBuilder.java | 53 +++--
.../apache/log4j/layout/Log4j1SyslogLayout.java | 223 +++++++++++++++++++++
.../log4j/layout/Log4j1SyslogLayoutTest.java | 90 +++++++++
.../log4j/core/pattern/DatePatternConverter.java | 8 +-
4 files changed, 354 insertions(+), 20 deletions(-)
diff --git
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
index dd5be72..f710cfc 100644
---
a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
+++
b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
@@ -25,6 +25,7 @@ import static
org.apache.log4j.xml.XmlConfiguration.forEachElement;
import java.io.Serializable;
import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -32,15 +33,14 @@ import org.apache.log4j.Appender;
import org.apache.log4j.Layout;
import org.apache.log4j.bridge.AppenderWrapper;
import org.apache.log4j.bridge.LayoutAdapter;
-import org.apache.log4j.bridge.LayoutWrapper;
import org.apache.log4j.builders.AbstractBuilder;
import org.apache.log4j.config.Log4j1Configuration;
import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.layout.Log4j1SyslogLayout;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.xml.XmlConfiguration;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.appender.SyslogAppender;
-import org.apache.logging.log4j.core.layout.SyslogLayout;
import org.apache.logging.log4j.core.net.Facility;
import org.apache.logging.log4j.core.net.Protocol;
import org.apache.logging.log4j.plugins.Plugin;
@@ -59,8 +59,10 @@ public class SyslogAppenderBuilder extends AbstractBuilder
implements AppenderBu
private static final String DEFAULT_FACILITY = "LOCAL0";
private static final Logger LOGGER = StatusLogger.getLogger();
private static final String FACILITY_PARAM = "Facility";
- private static final String SYSLOG_HOST_PARAM = "SyslogHost";
+ private static final String FACILITY_PRINTING_PARAM = "FacilityPrinting";
+ private static final String HEADER_PARAM = "Header";
private static final String PROTOCOL_PARAM = "Protocol";
+ private static final String SYSLOG_HOST_PARAM = "SyslogHost";
public SyslogAppenderBuilder() {
}
@@ -78,6 +80,8 @@ public class SyslogAppenderBuilder extends AbstractBuilder
implements AppenderBu
final AtomicReference<String> level = new AtomicReference<>();
final AtomicReference<String> host = new AtomicReference<>();
final AtomicReference<Protocol> protocol = new
AtomicReference<>(Protocol.TCP);
+ final AtomicBoolean header = new AtomicBoolean(false);
+ final AtomicBoolean facilityPrinting = new AtomicBoolean(false);
forEachElement(appenderElement.getChildNodes(), currentElement -> {
switch (currentElement.getTagName()) {
case LAYOUT_TAG:
@@ -88,24 +92,31 @@ public class SyslogAppenderBuilder extends AbstractBuilder
implements AppenderBu
break;
case PARAM_TAG:
switch (getNameAttributeKey(currentElement)) {
- case SYSLOG_HOST_PARAM:
- set(SYSLOG_HOST_PARAM, currentElement, host);
- break;
case FACILITY_PARAM:
set(FACILITY_PARAM, currentElement, facility);
break;
- case THRESHOLD_PARAM:
- set(THRESHOLD_PARAM, currentElement, level);
+ case FACILITY_PRINTING_PARAM:
+ set(FACILITY_PRINTING_PARAM, currentElement,
facilityPrinting);
+ break;
+ case HEADER_PARAM:
+ set(HEADER_PARAM, currentElement, header);
break;
case PROTOCOL_PARAM:
protocol.set(Protocol.valueOf(getValueAttribute(currentElement,
Protocol.TCP.name())));
break;
+ case SYSLOG_HOST_PARAM:
+ set(SYSLOG_HOST_PARAM, currentElement, host);
+ break;
+ case THRESHOLD_PARAM:
+ set(THRESHOLD_PARAM, currentElement, level);
+ break;
}
break;
}
});
- return createAppender(name, config, layout.get(), facility.get(),
filter.get(), host.get(), level.get(), protocol.get());
+ return createAppender(name, config, layout.get(), facility.get(),
filter.get(), host.get(), level.get(),
+ protocol.get(), header.get(), facilityPrinting.get());
}
@@ -116,24 +127,28 @@ public class SyslogAppenderBuilder extends
AbstractBuilder implements AppenderBu
final Layout layout = configuration.parseLayout(layoutPrefix, name,
props);
final String level = getProperty(THRESHOLD_PARAM);
final String facility = getProperty(FACILITY_PARAM, DEFAULT_FACILITY);
- final String syslogHost = getProperty(SYSLOG_HOST_PARAM, DEFAULT_HOST
+ ":" + DEFAULT_PORT);
+ final boolean facilityPrinting =
getBooleanProperty(FACILITY_PRINTING_PARAM, false);
+ final boolean header = getBooleanProperty(HEADER_PARAM, false);
final String protocol = getProperty(PROTOCOL_PARAM,
Protocol.TCP.name());
+ final String syslogHost = getProperty(SYSLOG_HOST_PARAM, DEFAULT_HOST
+ ":" + DEFAULT_PORT);
- return createAppender(name, configuration, layout, facility, filter,
syslogHost, level, Protocol.valueOf(protocol));
+ return createAppender(name, configuration, layout, facility, filter,
syslogHost, level,
+ Protocol.valueOf(protocol), header, facilityPrinting);
}
private Appender createAppender(final String name, final
Log4j1Configuration configuration, final Layout layout,
- final String facility, final Filter filter, final String
syslogHost, final String level, final Protocol protocol) {
+ final String facility, final Filter filter, final String
syslogHost, final String level, final Protocol protocol,
+ final boolean header, final boolean facilityPrinting) {
final AtomicReference<String> host = new AtomicReference<>();
final AtomicInteger port = new AtomicInteger();
resolveSyslogHost(syslogHost, host, port);
- org.apache.logging.log4j.core.Layout<? extends Serializable>
appenderLayout = LayoutAdapter.adapt(layout);
- if (appenderLayout == null) {
- appenderLayout = SyslogLayout.newBuilder()
- .setFacility(Facility.toFacility(facility))
- .setConfiguration(configuration)
- .build();
- }
+ final org.apache.logging.log4j.core.Layout<? extends Serializable>
messageLayout = LayoutAdapter.adapt(layout);
+ final Log4j1SyslogLayout appenderLayout =
Log4j1SyslogLayout.newBuilder()
+ .setHeader(header)
+ .setFacility(Facility.toFacility(facility))
+ .setFacilityPrinting(facilityPrinting)
+ .setMessageLayout(messageLayout)
+ .build();
final org.apache.logging.log4j.core.Filter fileFilter =
buildFilters(level, filter);
return AppenderWrapper.adapt(SyslogAppender.newSyslogAppenderBuilder()
diff --git
a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
new file mode 100644
index 0000000..7dcf44c
--- /dev/null
+++
b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
@@ -0,0 +1,223 @@
+/*
+ * 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.log4j.layout;
+
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.StringLayout;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.layout.AbstractStringLayout;
+import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.core.net.Priority;
+import org.apache.logging.log4j.core.pattern.DatePatternConverter;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.util.Chars;
+
+/**
+ * Port of the layout used by SyslogAppender in Log4j 1.x. Provided for
+ * compatibility with existing Log4j 1 configurations.
+ *
+ * Originally developed by Ceki Gülcü and Anders Kristensen.
+ */
+@Plugin(name = "Log4j1SyslogLayout", category = Node.CATEGORY, elementType =
Layout.ELEMENT_TYPE, printObject = true)
+public final class Log4j1SyslogLayout extends AbstractStringLayout {
+
+ /**
+ * Builds a SyslogLayout.
+ * <p>The main arguments are</p>
+ * <ul>
+ * <li>facility: The Facility is used to try to classify the message.</li>
+ * <li>includeNewLine: If true a newline will be appended to the
result.</li>
+ * <li>escapeNL: Pattern to use for replacing newlines.</li>
+ * <li>charset: The character set.</li>
+ * </ul>
+ * @param <B> the builder type
+ */
+ public static class Builder<B extends Builder<B>> extends
AbstractStringLayout.Builder<B>
+ implements
org.apache.logging.log4j.core.util.Builder<Log4j1SyslogLayout> {
+
+ public Builder() {
+ setCharset(StandardCharsets.UTF_8);
+ }
+
+ @PluginBuilderAttribute
+ private Facility facility = Facility.USER;
+
+ @PluginBuilderAttribute
+ private boolean facilityPrinting;
+
+ @PluginBuilderAttribute
+ private boolean header;
+
+ @PluginElement("Layout")
+ private Layout<? extends Serializable> messageLayout;
+
+ @Override
+ public Log4j1SyslogLayout build() {
+ if (messageLayout != null && !(messageLayout instanceof
StringLayout)) {
+ LOGGER.error("Log4j1SyslogLayout: the message layout must be a
StringLayout.");
+ return null;
+ }
+ return new Log4j1SyslogLayout(facility, facilityPrinting, header,
(StringLayout) messageLayout, getCharset());
+ }
+
+ public Facility getFacility() {
+ return facility;
+ }
+
+ public boolean isFacilityPrinting() {
+ return facilityPrinting;
+ }
+
+ public boolean isHeader() {
+ return header;
+ }
+
+ public Layout<? extends Serializable> getMessageLayout() {
+ return messageLayout;
+ }
+
+ public B setFacility(final Facility facility) {
+ this.facility = facility;
+ return asBuilder();
+ }
+
+ public B setFacilityPrinting(final boolean facilityPrinting) {
+ this.facilityPrinting = facilityPrinting;
+ return asBuilder();
+ }
+
+ public B setHeader(final boolean header) {
+ this.header = header;
+ return asBuilder();
+ }
+
+ public B setMessageLayout(final Layout<? extends Serializable>
messageLayout) {
+ this.messageLayout = messageLayout;
+ return asBuilder();
+ }
+ }
+
+ @PluginBuilderFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
+ }
+
+ /**
+ * Host name used to identify messages from this appender.
+ */
+ private static final String localHostname = NetUtils.getLocalHostname();
+
+ private final Facility facility;
+ private final boolean facilityPrinting;
+ private final boolean header;
+ private final StringLayout messageLayout;
+
+ /**
+ * Date format used if header = true.
+ */
+ private static final String[] dateFormatOptions = {"MMM dd HH:mm:ss",
null, "en"};
+ private final LogEventPatternConverter dateConverter =
DatePatternConverter.newInstance(dateFormatOptions);
+
+
+ private Log4j1SyslogLayout(final Facility facility, final boolean
facilityPrinting, final boolean header,
+ final StringLayout messageLayout, final Charset charset) {
+ super(charset);
+ this.facility = facility;
+ this.facilityPrinting = facilityPrinting;
+ this.header = header;
+ this.messageLayout = messageLayout;
+ }
+
+ /**
+ * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance
with the BSD Log record format.
+ *
+ * @param event The LogEvent
+ * @return the event formatted as a String.
+ */
+ @Override
+ public String toSerializable(final LogEvent event) {
+ // The messageLayout also uses the thread-bound StringBuilder,
+ // so we generate the message first
+ final String message = messageLayout != null ?
messageLayout.toSerializable(event)
+ : event.getMessage().getFormattedMessage();
+ final StringBuilder buf = getStringBuilder();
+
+ buf.append('<');
+ buf.append(Priority.getPriority(facility, event.getLevel()));
+ buf.append('>');
+
+ if (header) {
+ final int index = buf.length() + 4;
+ dateConverter.format(event, buf);
+ // RFC 3164 says leading space, not leading zero on days 1-9
+ if (buf.charAt(index) == '0') {
+ buf.setCharAt(index, Chars.SPACE);
+ }
+
+ buf.append(Chars.SPACE);
+ buf.append(localHostname);
+ buf.append(Chars.SPACE);
+ }
+
+ if (facilityPrinting) {
+ buf.append(facility != null ? facility.name().toLowerCase() :
"user").append(':');
+ }
+
+ buf.append(message);
+ // TODO: splitting message into 1024 byte chunks?
+ return buf.toString();
+ }
+
+ /**
+ * Gets this SyslogLayout's content format. Specified by:
+ * <ul>
+ * <li>Key: "structured" Value: "false"</li>
+ * <li>Key: "dateFormat" Value: "MMM dd HH:mm:ss"</li>
+ * <li>Key: "format" Value: "<LEVEL>TIMESTAMP PROP(HOSTNAME)
MESSAGE"</li>
+ * <li>Key: "formatType" Value: "logfilepatternreceiver" (format uses the
keywords supported by
+ * LogFilePatternReceiver)</li>
+ * </ul>
+ *
+ * @return Map of content format keys supporting SyslogLayout
+ */
+ @Override
+ public Map<String, String> getContentFormat() {
+ final Map<String, String> result = new HashMap<>();
+ result.put("structured", "false");
+ result.put("formatType", "logfilepatternreceiver");
+ result.put("dateFormat", dateFormatOptions[0]);
+ if (header) {
+ result.put("format", "<LEVEL>TIMESTAMP PROP(HOSTNAME) MESSAGE");
+ } else {
+ result.put("format", "<LEVEL>MESSAGE");
+ }
+ return result;
+ }
+
+}
diff --git
a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java
b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java
new file mode 100644
index 0000000..2a1e0b9
--- /dev/null
+++
b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.log4j.layout;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.stream.Stream;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.StringLayout;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.core.time.MutableInstant;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class Log4j1SyslogLayoutTest {
+
+ private static final SimpleMessage MESSAGE = new SimpleMessage("Hello
world!");
+ private static final long TIMESTAMP = LocalDateTime.of(2022, 4, 5, 12, 34,
56).atZone(ZoneId.systemDefault())
+ .toEpochSecond();
+ private static final String localhostName = NetUtils.getLocalHostname();
+
+ private static LogEvent createLogEvent() {
+ final MutableInstant instant = new MutableInstant();
+ instant.initFromEpochSecond(TIMESTAMP, 0);
+ final LogEvent event = mock(LogEvent.class);
+ when(event.getInstant()).thenReturn(instant);
+ when(event.getMessage()).thenReturn(MESSAGE);
+ when(event.getLevel()).thenReturn(Level.INFO);
+ return event;
+ }
+
+ static Stream<Arguments> configurations() {
+ return Stream
+ .of(Arguments.of("<30>Hello world!", Facility.DAEMON, false,
false),
+ Arguments.of("<30>Apr 5 12:34:56 %s Hello world!",
Facility.DAEMON, true, false),
+ Arguments.of("<30>daemon:Hello world!",
Facility.DAEMON, false, true),
+ Arguments.of("<30>Apr 5 12:34:56 %s daemon:Hello
world!", Facility.DAEMON, true, true))
+ .map(args -> {
+ final Object[] objs = args.get();
+ objs[0] = String.format((String) objs[0], localhostName);
+ return Arguments.of(objs);
+ });
+ }
+
+ @ParameterizedTest
+ @MethodSource("configurations")
+ public void testSimpleLayout(String expected, Facility facility, boolean
header, boolean facilityPrinting) {
+ final LogEvent logEvent = createLogEvent();
+ StringLayout appenderLayout = Log4j1SyslogLayout.newBuilder()
+ .setFacility(facility)
+ .setHeader(header)
+ .setFacilityPrinting(facilityPrinting)
+ .build();
+ assertEquals(expected, appenderLayout.toSerializable(logEvent));
+ final StringLayout messageLayout = PatternLayout.newBuilder()
+ .setPattern("%m")
+ .build();
+ appenderLayout = Log4j1SyslogLayout.newBuilder()
+ .setFacility(facility)
+ .setHeader(header)
+ .setFacilityPrinting(facilityPrinting)
+ .setMessageLayout(messageLayout)
+ .build();
+ assertEquals(expected, appenderLayout.toSerializable(logEvent));
+ }
+}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index 5492fc0..3113a2c 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.pattern;
import java.util.Arrays;
import java.util.Date;
+import java.util.Locale;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
@@ -236,8 +237,13 @@ public final class DatePatternConverter extends
LogEventPatternConverter impleme
tz = TimeZone.getTimeZone(options[1]);
}
+ Locale locale = null;
+ if (options.length > 2 && options[2] != null) {
+ locale = Locale.forLanguageTag(options[2]);
+ }
+
try {
- final FastDateFormat tempFormat =
FastDateFormat.getInstance(pattern, tz);
+ final FastDateFormat tempFormat =
FastDateFormat.getInstance(pattern, tz, locale);
return new PatternFormatter(tempFormat);
} catch (final IllegalArgumentException e) {
LOGGER.warn("Could not instantiate FastDateFormat with pattern " +
pattern, e);