You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2020/10/04 01:03:49 UTC

[logging-log4j2] branch release-2.x updated: [LOG4J2-2889] HtmlLayout support datePattern and timezone

This is an automated email from the ASF dual-hosted git repository.

mattsicker pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/release-2.x by this push:
     new 6673bd9  [LOG4J2-2889] HtmlLayout support datePattern and timezone
     new e69401d  Merge pull request #423 from gengyuanzhe/release-2.x
6673bd9 is described below

commit 6673bd9ed8d96dbe5c0c76876b3542bc4aeb6a09
Author: gengyuanzhe <ge...@gmail.com>
AuthorDate: Sun Sep 20 22:59:42 2020 +0800

    [LOG4J2-2889] HtmlLayout support datePattern and timezone
---
 .../logging/log4j/core/layout/HtmlLayout.java      |  36 +++++-
 .../logging/log4j/core/layout/HtmlLayoutTest.java  | 123 +++++++++++++++++++++
 .../core/pattern/DatePatternConverterTest.java     |   4 +-
 src/site/xdoc/manual/layouts.xml.vm                |  28 +++++
 4 files changed, 185 insertions(+), 6 deletions(-)

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
index efc3407..ea9d7e8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
@@ -37,6 +37,7 @@ import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.pattern.DatePatternConverter;
 import org.apache.logging.log4j.core.util.Transform;
 import org.apache.logging.log4j.util.Strings;
 
@@ -59,6 +60,7 @@ public final class HtmlLayout extends AbstractStringLayout {
     private static final String REGEXP = Strings.LINE_SEPARATOR.equals("\n") ? "\n" : Strings.LINE_SEPARATOR + "|\n";
     private static final String DEFAULT_TITLE = "Log4j Log Messages";
     private static final String DEFAULT_CONTENT_TYPE = "text/html";
+    private static final String DEFAULT_DATE_PATTERN = "JVM_ELAPSE_TIME";
 
     private final long jvmStartTime = ManagementFactory.getRuntimeMXBean().getStartTime();
 
@@ -69,6 +71,7 @@ public final class HtmlLayout extends AbstractStringLayout {
     private final String font;
     private final String fontSize;
     private final String headerSize;
+    private final DatePatternConverter datePatternConverter;
 
     /**Possible font sizes */
     public static enum FontSize {
@@ -100,7 +103,7 @@ public final class HtmlLayout extends AbstractStringLayout {
     }
 
     private HtmlLayout(final boolean locationInfo, final String title, final String contentType, final Charset charset,
-            final String font, final String fontSize, final String headerSize) {
+        final String font, final String fontSize, final String headerSize, String datePattern, String timezone) {
         super(charset);
         this.locationInfo = locationInfo;
         this.title = title;
@@ -108,6 +111,8 @@ public final class HtmlLayout extends AbstractStringLayout {
         this.font = font;
         this.fontSize = fontSize;
         this.headerSize = headerSize;
+        this.datePatternConverter = DEFAULT_DATE_PATTERN.equals(datePattern) ? null
+            : DatePatternConverter.newInstance(new String[] {datePattern, timezone});
     }
 
     /**
@@ -149,7 +154,12 @@ public final class HtmlLayout extends AbstractStringLayout {
         sbuf.append(Strings.LINE_SEPARATOR).append("<tr>").append(Strings.LINE_SEPARATOR);
 
         sbuf.append("<td>");
-        sbuf.append(event.getTimeMillis() - jvmStartTime);
+
+        if (datePatternConverter == null) {
+            sbuf.append(event.getTimeMillis() - jvmStartTime);
+        } else {
+            datePatternConverter.format(event, sbuf);
+        }
         sbuf.append("</td>").append(Strings.LINE_SEPARATOR);
 
         final String escapedThread = Transform.escapeHtmlTags(event.getThreadName());
@@ -339,6 +349,7 @@ public final class HtmlLayout extends AbstractStringLayout {
      * @param font The font to use for the text.
      * @return An HTML Layout.
      */
+    @Deprecated
     @PluginFactory
     public static HtmlLayout createLayout(
             @PluginAttribute(value = "locationInfo") final boolean locationInfo,
@@ -353,7 +364,8 @@ public final class HtmlLayout extends AbstractStringLayout {
         if (contentType == null) {
             contentType = DEFAULT_CONTENT_TYPE + "; charset=" + charset;
         }
-        return new HtmlLayout(locationInfo, title, contentType, charset, font, fontSize, headerSize);
+        return new HtmlLayout(locationInfo, title, contentType, charset, font, fontSize, headerSize, DEFAULT_DATE_PATTERN,
+                null);
     }
 
     /**
@@ -390,6 +402,12 @@ public final class HtmlLayout extends AbstractStringLayout {
         @PluginBuilderAttribute
         private String fontName = DEFAULT_FONT_FAMILY;
 
+        @PluginBuilderAttribute
+        private String datePattern = DEFAULT_DATE_PATTERN;
+
+        @PluginBuilderAttribute
+        private String timezone = null; // null means default timezone
+
         private Builder() {
         }
 
@@ -423,6 +441,16 @@ public final class HtmlLayout extends AbstractStringLayout {
             return this;
         }
 
+        public Builder setDatePattern(final String datePattern) {
+            this.datePattern = datePattern;
+            return this;
+        }
+
+        public Builder setTimezone(final String timezone) {
+            this.timezone = timezone;
+            return this;
+        }
+
         @Override
         public HtmlLayout build() {
             // TODO: extract charset from content-type
@@ -430,7 +458,7 @@ public final class HtmlLayout extends AbstractStringLayout {
                 contentType = DEFAULT_CONTENT_TYPE + "; charset=" + charset;
             }
             return new HtmlLayout(locationInfo, title, contentType, charset, fontName, fontSize.getFontSize(),
-                fontSize.larger().getFontSize());
+                fontSize.larger().getFontSize(), datePattern, timezone);
         }
     }
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
index b6ad4c4..8915e9b 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
@@ -16,27 +16,69 @@
  */
 package org.apache.logging.log4j.core.layout;
 
+import java.lang.management.ManagementFactory;
 import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
 import java.util.List;
 import java.util.Map;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.AbstractLogEvent;
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.BasicConfigurationFactory;
+import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.time.Instant;
+import org.apache.logging.log4j.core.time.MutableInstant;
 import org.apache.logging.log4j.junit.UsingAnyThreadContext;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.test.appender.ListAppender;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
+import static org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat;
 
 @UsingAnyThreadContext
 public class HtmlLayoutTest {
+    private static class MyLogEvent extends AbstractLogEvent {
+        private static final long serialVersionUID = 0;
+
+        @Override
+        public Instant getInstant() {
+            MutableInstant result = new MutableInstant();
+            result.initFromEpochMilli(getTimeMillis(), 456789);
+            return result;
+        }
+
+        @Override
+        public long getTimeMillis() {
+            final Calendar cal = Calendar.getInstance();
+            cal.set(2012, Calendar.NOVEMBER, 02, 14, 34, 02);
+            cal.set(Calendar.MILLISECOND, 123);
+            return cal.getTimeInMillis();
+        }
+
+        @Override
+        public Level getLevel() {
+            return Level.DEBUG;
+        }
+
+        @Override
+        public Message getMessage() {
+            return new SimpleMessage("msg");
+        }
+    }
+
     private final LoggerContext ctx = LoggerContext.getContext();
     private final Logger root = ctx.getRootLogger();
 
@@ -150,4 +192,85 @@ public class HtmlLayoutTest {
             root.addAppender(app);
         }
     }
+
+    @Test
+    public void testLayoutWithoutDataPattern() {
+        final HtmlLayout layout = HtmlLayout.newBuilder().build();
+
+        MyLogEvent event = new MyLogEvent();
+        String actual = getDateLine(layout.toSerializable(event));
+
+        long jvmStratTime = ManagementFactory.getRuntimeMXBean().getStartTime();
+        assertEquals("<td>" + (event.getTimeMillis() - jvmStratTime) + "</td>", actual, "Incorrect date:" + actual);
+    }
+
+    @Test
+    public void testLayoutWithDatePatternJvmElapseTime() {
+        final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("JVM_ELAPSE_TIME").build();
+
+        MyLogEvent event = new MyLogEvent();
+        String actual = getDateLine(layout.toSerializable(event));
+
+        long jvmStratTime = ManagementFactory.getRuntimeMXBean().getStartTime();
+        assertEquals("<td>" + (event.getTimeMillis() - jvmStratTime) + "</td>", actual, "Incorrect date:" + actual);
+    }
+
+    @Test
+    public void testLayoutWithDatePatternUnix() {
+        final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("UNIX").build();
+
+        MyLogEvent event = new MyLogEvent();
+        String actual = getDateLine(layout.toSerializable(event));
+
+        assertEquals("<td>" + event.getInstant().getEpochSecond() + "</td>", actual, "Incorrect date:" + actual);
+    }
+
+    @Test
+    public void testLayoutWithDatePatternUnixMillis() {
+        final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("UNIX_MILLIS").build();
+
+        MyLogEvent event = new MyLogEvent();
+        String actual = getDateLine(layout.toSerializable(event));
+
+        assertEquals("<td>" + event.getTimeMillis() + "</td>", actual, "Incorrect date:" + actual);
+    }
+
+    @Test
+    public void testLayoutWithDatePatternFixedFormat() {
+        for (final String timeZone : new String[] {"GMT+8", "UTC", null}) {
+            for (final FixedFormat format : FixedFormat.values()) {
+                testLayoutWithDatePatternFixedFormat(format, timeZone);
+            }
+        }
+    }
+    
+    private String getDateLine(String logEventString) {
+        return logEventString.split(System.lineSeparator())[2];
+    }
+
+    private void testLayoutWithDatePatternFixedFormat(FixedFormat format, String timezone) {
+        final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern(format.name()).setTimezone(timezone).build();
+
+        LogEvent event = new MyLogEvent();
+        String actual = getDateLine(layout.toSerializable(event));
+
+        // build expected date string
+        java.time.Instant instant =
+            java.time.Instant.ofEpochSecond(event.getInstant().getEpochSecond(), event.getInstant().getNanoOfSecond());
+        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
+        if (timezone != null) {
+            zonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of(timezone));
+        }
+
+        // For DateTimeFormatter of jdk,
+        // Pattern letter 'S' means fraction-of-second, 'n' means nano-of-second. Log4j2 needs S.
+        // Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero,
+        // whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'. Log4j2 needs x.
+        DateTimeFormatter dateTimeFormatter =
+            DateTimeFormatter.ofPattern(format.getPattern().replace('n', 'S').replace('X', 'x'));
+        String expected = zonedDateTime.format(dateTimeFormatter);
+
+        assertEquals("<td>" + expected + "</td>", actual,
+            MessageFormat.format("Incorrect date={0}, format={1}, timezone={2}", actual, format.name(), timezone));
+    }
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
index 04a93b9..b3c7499 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
@@ -417,7 +417,7 @@ public class DatePatternConverterTest {
         final StringBuilder milliBuilder = new StringBuilder();
         final LogEvent event = new MyLogEvent();
 
-        for (final String timeZone : new String[]{"PDT", null}) { // Pacific Daylight Time=UTC-8:00
+        for (final String timeZone : new String[]{"PST", null}) { // Pacific Standard Time=UTC-8:00
             for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
                 for (int i = 1; i <= 9; i++) {
                     final String pattern = format.getPattern();
@@ -467,7 +467,7 @@ public class DatePatternConverterTest {
     @Test
     public void testPredefinedFormatWithTimezone() {
         for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
-            final String[] options = {format.name(), "PDT"}; // Pacific Daylight Time=UTC-8:00
+            final String[] options = {format.name(), "PST"}; // Pacific Standard Time=UTC-8:00
             final DatePatternConverter converter = DatePatternConverter.newInstance(options);
             assertEquals(format.getPattern(), converter.getPattern());
         }
diff --git a/src/site/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm
index 8b56f0a..52b415d 100644
--- a/src/site/xdoc/manual/layouts.xml.vm
+++ b/src/site/xdoc/manual/layouts.xml.vm
@@ -447,8 +447,35 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3);
               <td>String</td>
               <td>The <code>font-size</code> to use. The default is "small".</td>
             </tr>
+            <tr>
+              <td>datePattern</td>
+              <td>String</td>
+              <td>The date format of the logging event. The default is "JVM_ELAPSE_TIME", which outputs the
+                milliseconds since JVM started. For other valid values, refer to the
+                <a href="#PatternDate">date pattern</a> of PatternLayout.</td>
+            </tr>
+            <tr>
+              <td>timezone</td>
+              <td>String</td>
+              <td>The timezone id of the logging event. If not specified, this layout uses the
+                <a class="javadoc" href="${javadocRoot}/java/util/TimeZone.html${sharp}getDefault()">
+                  java.util.TimeZone.getDefault</a> as default timezone.
+                Like <a href="#PatternDate">date pattern</a> of PatternLayout, you can use timezone id from
+                <a class="javadoc" href="${javadocRoot}/java/util/TimeZone.html${sharp}getTimeZone(java.lang.String)">
+                  java.util.TimeZone.getTimeZone</a>.</td>
+            </tr>
             <caption align="top">HtmlLayout Parameters</caption>
           </table>
+          <p>
+            Configure as follows to use dataPattern and timezone in HtmlLayout:
+          </p>
+          <pre class="prettyprint linenums">
+&lt;Appenders&gt;
+  &lt;Console name="console"&gt;
+    &lt;HtmlLayout datePattern="ISO8601" timezone="GMT+0"/&gt;
+  &lt;/Console&gt;
+&lt;/Appenders&gt;
+</pre>
         </subsection>
         <a name="JSONLayout"/>
         <subsection name="JSON Layout">
@@ -850,6 +877,7 @@ WARN  [main]: Message 2</pre>
             </tr>
             <tr>
               <td align="center">
+              <a name="PatternDate" />
                 <b>d</b>{pattern}<br />
                 <b>date</b>{pattern}
               </td>