diff --git a/README.md b/README.md index ac31833a..c033fad7 100644 --- a/README.md +++ b/README.md @@ -1534,19 +1534,11 @@ The value can be written as a number (instead of a string) like this: ## Customizing Timestamp -By default, timestamps are written as string values in the format `yyyy-MM-dd'T'HH:mm:ss.SSSZZ` (e.g. `2018-04-28T22:23:59.164-07:00`), in the default TimeZone of the host Java platform. +By default, timestamps are written as string values in the format specified by +[`DateTimeFormatter.ISO_OFFSET_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_OFFSET_DATE_TIME) +(e.g. `2019-11-03T10:15:30.123+01:00`), in the default TimeZone of the host Java platform. -You can change the timezone like this: - -```xml - - UTC - -``` - -The value of the `timeZone` element can be any string accepted by java's `TimeZone.getTimeZone(String id)` method. - -You can change the pattern used like this: +You can change the pattern like this: ```xml @@ -1554,19 +1546,24 @@ You can change the pattern used like this: ``` -Use these timestamp pattern values to output the timestamp as a unix timestamp (number of milliseconds since unix epoch). +The value of the `timestampPattern` can be any of the following: -* `[UNIX_TIMESTAMP_AS_NUMBER]` - write the timestamp value as a numeric unix timestamp -* `[UNIX_TIMESTAMP_AS_STRING]` - write the timestamp value as a string verion of the numeric unix timestamp +* `[UNIX_TIMESTAMP_AS_NUMBER]` - timestamp written as a JSON number value of the milliseconds since unix epoch +* `[UNIX_TIMESTAMP_AS_STRING]` - timestamp written as a JSON string value of the milliseconds since unix epoch +* `[` _`constant`_ `]` - (e.g. `[ISO_OFFSET_DATE_TIME]`) timestamp written using the given `DateTimeFormatter` constant +* any other value - (e.g. `yyyy-MM-dd'T'HH:mm:ss.SSS`) timestamp written using a `DateTimeFormatter` created from the given pattern -For example: + +You can change the timezone like this: ```xml - [UNIX_TIMESTAMP_AS_NUMBER] + UTC ``` +The value of the `timeZone` element can be any string accepted by java's `TimeZone.getTimeZone(String id)` method. + ## Customizing Message @@ -1789,12 +1786,7 @@ For LoggingEvents, the available providers and their configuration properties (d

Event timestamp

@@ -2060,12 +2052,7 @@ For AccessEvents, the available providers and their configuration properties (de

Event timestamp

diff --git a/pom.xml b/pom.xml index 51ddb94a..ecfcc6a6 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,6 @@ 3.4.2 - 3.11 5.7.0 @@ -179,11 +178,6 @@ --> true - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - com.lmax disruptor @@ -332,10 +326,6 @@ - - org.apache.commons.lang3 - ${project.groupId}.encoder.org.apache.commons.lang3 - com.lmax.disruptor ${project.groupId}.encoder.com.lmax.disruptor @@ -364,7 +354,7 @@ ch.qos.logback.classic - !org.apache.commons.*,!com.lmax.disruptor.*,!ch.qos.logback.classic.*,!ch.qos.logback.core.*,!org.slf4j.*,* + !com.lmax.disruptor.*,!ch.qos.logback.classic.*,!ch.qos.logback.core.*,!org.slf4j.*,* diff --git a/src/main/java/net/logstash/logback/appender/listener/FailureSummaryLoggingAppenderListener.java b/src/main/java/net/logstash/logback/appender/listener/FailureSummaryLoggingAppenderListener.java index 266ac112..755a5785 100644 --- a/src/main/java/net/logstash/logback/appender/listener/FailureSummaryLoggingAppenderListener.java +++ b/src/main/java/net/logstash/logback/appender/listener/FailureSummaryLoggingAppenderListener.java @@ -14,11 +14,13 @@ package net.logstash.logback.appender.listener; import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.Objects; +import java.util.TimeZone; import ch.qos.logback.core.spi.DeferredProcessingAware; import net.logstash.logback.argument.StructuredArguments; -import org.apache.commons.lang3.time.DateFormatUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +54,7 @@ protected void handleFailureSummary(FailureSummary failureSummary, CallbackType logger.warn("{} {} failures since {} for {}.", StructuredArguments.value("failEventCount", failureSummary.getConsecutiveFailures()), StructuredArguments.value("failType", callbackType.name().toLowerCase()), - StructuredArguments.value("failStartTime", DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(failureSummary.getFirstFailureTime())), + StructuredArguments.value("failStartTime", DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getDefault().toZoneId()).format(Instant.ofEpochMilli(failureSummary.getFirstFailureTime()))), StructuredArguments.value("failDuration", Duration.ofMillis(System.currentTimeMillis() - failureSummary.getFirstFailureTime()).toString()), failureSummary.getMostRecentFailure()); } diff --git a/src/main/java/net/logstash/logback/composite/FormattedTimestampJsonProvider.java b/src/main/java/net/logstash/logback/composite/FormattedTimestampJsonProvider.java index a370775d..fde60ca5 100644 --- a/src/main/java/net/logstash/logback/composite/FormattedTimestampJsonProvider.java +++ b/src/main/java/net/logstash/logback/composite/FormattedTimestampJsonProvider.java @@ -14,20 +14,21 @@ package net.logstash.logback.composite; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.TimeZone; -import net.logstash.logback.fieldnames.LogstashCommonFieldNames; - -import org.apache.commons.lang3.time.FastDateFormat; - import ch.qos.logback.core.spi.DeferredProcessingAware; +import net.logstash.logback.fieldnames.LogstashCommonFieldNames; import com.fasterxml.jackson.core.JsonGenerator; /** * Writes the timestamp field as either: *
    - *
  • A string value formatted by a {@link FastDateFormat} pattern
  • + *
  • A string value formatted by a {@link DateTimeFormatter} pattern
  • *
  • A string value representing the number of milliseconds since unix epoch (designated by specifying the pattern value as {@value #UNIX_TIMESTAMP_AS_STRING})
  • *
  • A number value of the milliseconds since unix epoch (designated by specifying the pattern value as {@value #UNIX_TIMESTAMP_AS_NUMBER})
  • *
@@ -48,14 +49,20 @@ public abstract class FormattedTimestampJsonProviderPossible values:

+ * + *
    + *
  • {@value #UNIX_TIMESTAMP_AS_NUMBER} - timestamp written as a JSON number value of the milliseconds since unix epoch
  • + *
  • {@value #UNIX_TIMESTAMP_AS_STRING} - timestamp written as a JSON string value of the milliseconds since unix epoch
  • + *
  • [constant] - timestamp written using the {@link DateTimeFormatter} constant specified by constant (e.g. {@code [ISO_OFFSET_DATE_TIME]})
  • + *
  • any other value - timestamp written by a {@link DateTimeFormatter} created from the pattern string specified + *
*/ private String pattern = DEFAULT_PATTERN; @@ -68,12 +75,12 @@ public abstract class FormattedTimestampJsonProvider { + LoggingEventFormattedTimestampJsonProvider provider = new LoggingEventFormattedTimestampJsonProvider(); + provider.setPattern("[foo]"); + }); + } + + @Test + public void customPattern() throws IOException { + LoggingEventFormattedTimestampJsonProvider provider = new LoggingEventFormattedTimestampJsonProvider(); + String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + provider.setPattern(pattern); + when(event.getTimeStamp()).thenReturn(0L); + + provider.writeTo(generator, event); + + String expectedValue = DateTimeFormatter.ofPattern(pattern).withZone(TimeZone.getDefault().toZoneId()).format(Instant.ofEpochMilli(0)); + verify(generator).writeStringField(FormattedTimestampJsonProvider.FIELD_TIMESTAMP, expectedValue); + } + + @Test + public void unixEpochAsNumber() throws IOException { + LoggingEventFormattedTimestampJsonProvider provider = new LoggingEventFormattedTimestampJsonProvider(); + provider.setPattern(FormattedTimestampJsonProvider.UNIX_TIMESTAMP_AS_NUMBER); + when(event.getTimeStamp()).thenReturn(0L); + + provider.writeTo(generator, event); + + verify(generator).writeNumberField(FormattedTimestampJsonProvider.FIELD_TIMESTAMP, 0L); + } + + @Test + public void unixEpochAsString() throws IOException { + LoggingEventFormattedTimestampJsonProvider provider = new LoggingEventFormattedTimestampJsonProvider(); + provider.setPattern(FormattedTimestampJsonProvider.UNIX_TIMESTAMP_AS_STRING); + when(event.getTimeStamp()).thenReturn(0L); + + provider.writeTo(generator, event); + + verify(generator).writeStringField(FormattedTimestampJsonProvider.FIELD_TIMESTAMP, "0"); + } + + +} \ No newline at end of file diff --git a/src/test/java/net/logstash/logback/encoder/LogstashAccessEncoderTest.java b/src/test/java/net/logstash/logback/encoder/LogstashAccessEncoderTest.java index a514377c..dbd31056 100644 --- a/src/test/java/net/logstash/logback/encoder/LogstashAccessEncoderTest.java +++ b/src/test/java/net/logstash/logback/encoder/LogstashAccessEncoderTest.java @@ -19,10 +19,16 @@ import static org.mockito.Mockito.when; import java.io.ByteArrayOutputStream; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; +import java.util.TimeZone; -import org.apache.commons.lang3.time.FastDateFormat; +import ch.qos.logback.access.spi.IAccessEvent; +import ch.qos.logback.core.Context; +import net.logstash.logback.Logback11Support; +import net.logstash.logback.composite.FormattedTimestampJsonProvider; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -31,11 +37,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import ch.qos.logback.access.spi.IAccessEvent; -import ch.qos.logback.core.Context; -import net.logstash.logback.Logback11Support; -import net.logstash.logback.composite.FormattedTimestampJsonProvider; - @ExtendWith(MockitoExtension.class) public class LogstashAccessEncoderTest { @@ -89,12 +90,11 @@ public void basicsAreIncluded_logback12OrLater() throws Exception { } protected void verifyBasics(final long timestamp, IAccessEvent event, JsonNode node) { - assertThat(node.get("timestamp").textValue()).isEqualTo(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").format - (timestamp)); + assertThat(node.get("timestamp").textValue()).isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getDefault().toZoneId()).format(Instant.ofEpochMilli(timestamp))); assertThat(node.get("@version").textValue()).isEqualTo("1"); assertThat(node.get("message").textValue()).isEqualTo(String.format("%s - %s [%s] \"%s\" %s %s", event.getRemoteHost(), event.getRemoteUser(), - FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").format - (event.getTimeStamp()), event.getRequestURL(), event.getStatusCode(), + DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getDefault().toZoneId()) + .format(Instant.ofEpochMilli(event.getTimeStamp())), event.getRequestURL(), event.getStatusCode(), event.getContentLength())); assertThat(node.get("method").textValue()).isEqualTo(event.getMethod()); diff --git a/src/test/java/net/logstash/logback/encoder/LogstashEncoderTest.java b/src/test/java/net/logstash/logback/encoder/LogstashEncoderTest.java index 2192528d..b94008d5 100644 --- a/src/test/java/net/logstash/logback/encoder/LogstashEncoderTest.java +++ b/src/test/java/net/logstash/logback/encoder/LogstashEncoderTest.java @@ -30,6 +30,8 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -49,7 +51,7 @@ import net.logstash.logback.decorate.JsonGeneratorDecorator; import net.logstash.logback.fieldnames.LogstashCommonFieldNames; import net.logstash.logback.fieldnames.ShortenedFieldNames; -import org.apache.commons.lang3.time.FastDateFormat; + import org.assertj.core.util.Files; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -122,7 +124,7 @@ public void basicsAreIncluded_logback12() throws Exception { } protected void verifyBasics(final long timestamp, JsonNode node) { - assertThat(node.get("@timestamp").textValue()).isEqualTo(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").format(timestamp)); + assertThat(node.get("@timestamp").textValue()).isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getDefault().toZoneId()).format(Instant.ofEpochMilli(timestamp))); assertThat(node.get("@version").textValue()).isEqualTo("1"); assertThat(node.get("logger_name").textValue()).isEqualTo("LoggerName"); assertThat(node.get("thread_name").textValue()).isEqualTo("ThreadName"); @@ -143,9 +145,8 @@ public void basicsAreIncludedWithShortenedNames() throws Exception { byte[] encoded = encoder.encode(event); JsonNode node = MAPPER.readTree(encoded); - - assertThat(node.get("@timestamp").textValue()).isEqualTo(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").format - (timestamp)); + + assertThat(node.get("@timestamp").textValue()).isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getDefault().toZoneId()).format(Instant.ofEpochMilli(timestamp))); assertThat(node.get("@version").textValue()).isEqualTo("1"); assertThat(node.get("logger").textValue()).isEqualTo("LoggerName"); assertThat(node.get("thread").textValue()).isEqualTo("ThreadName"); @@ -185,7 +186,7 @@ public JsonGenerator decorate(JsonGenerator generator) { assertThat(output).isEqualTo(String.format( "{%n" - + " @timestamp : \"" + FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").format(timestamp) + "\",%n" + + " @timestamp : \"" + DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getDefault().toZoneId()).format(Instant.ofEpochMilli(timestamp)) + "\",%n" + " @version : \"1\",%n" + " message : \"My message\",%n" + " logger_name : \"LoggerName\",%n" @@ -200,10 +201,10 @@ public JsonGenerator decorate(JsonGenerator generator) { public void loggerNameIsShortenedProperly() throws Exception { final long timestamp = System.currentTimeMillis(); final int length = 36; - final String shortenedLoggerName = new TargetLengthBasedClassNameAbbreviator(length).abbreviate(FastDateFormat.class.getCanonicalName()); + final String shortenedLoggerName = new TargetLengthBasedClassNameAbbreviator(length).abbreviate(DateTimeFormatter.class.getCanonicalName()); ILoggingEvent event = mockBasicILoggingEvent(Level.ERROR); - when(event.getLoggerName()).thenReturn(FastDateFormat.class.getCanonicalName()); + when(event.getLoggerName()).thenReturn(DateTimeFormatter.class.getCanonicalName()); when(event.getTimeStamp()).thenReturn(timestamp); encoder.setFieldNames(new ShortenedFieldNames()); @@ -213,8 +214,7 @@ public void loggerNameIsShortenedProperly() throws Exception { JsonNode node = MAPPER.readTree(encoded); - assertThat(node.get("@timestamp").textValue()).isEqualTo(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ").format - (timestamp)); + assertThat(node.get("@timestamp").textValue()).isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getDefault().toZoneId()).format(Instant.ofEpochMilli(timestamp))); assertThat(node.get("@version").textValue()).isEqualTo("1"); assertThat(node.get("logger").textValue()).isEqualTo(shortenedLoggerName); assertThat(node.get("thread").textValue()).isEqualTo("ThreadName"); @@ -618,9 +618,8 @@ public void customTimeZone() throws Exception { byte[] encoded = encoder.encode(event); JsonNode node = MAPPER.readTree(encoded); - - assertThat(node.get("@timestamp").textValue()).isEqualTo(FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ", TimeZone.getTimeZone("UTC")).format - (timestamp)); + + assertThat(node.get("@timestamp").textValue()).isEqualTo(DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(TimeZone.getTimeZone("UTC").toZoneId()).format(Instant.ofEpochMilli(timestamp))); assertThat(node.get("@version").textValue()).isEqualTo("1"); assertThat(node.get("logger_name").textValue()).isEqualTo("LoggerName"); assertThat(node.get("thread_name").textValue()).isEqualTo("ThreadName");