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&uuml;lc&uuml; 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: "&lt;LEVEL&gt;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);

Reply via email to