gelfLayoutMap) {
- final BigDecimal jsonTemplateLayoutTimestamp = (BigDecimal) jsonTemplateLayoutMap.remove("timestamp");
- final BigDecimal gelfLayoutTimestamp = (BigDecimal) gelfLayoutMap.remove("timestamp");
- final String description = String.format(
- "instantEpochSecs=%d.%d, jsonTemplateLayoutTimestamp=%s, gelfLayoutTimestamp=%s",
- logEventInstant.getEpochSecond(),
- logEventInstant.getNanoOfSecond(),
- jsonTemplateLayoutTimestamp,
- gelfLayoutTimestamp);
- Assertions.assertThat(jsonTemplateLayoutTimestamp.compareTo(gelfLayoutTimestamp))
- .as(description)
- .isEqualTo(0);
+ final Number jsonTemplateLayoutTimestamp = (Number) jsonTemplateLayoutMap.remove("timestamp");
+ final Number gelfLayoutTimestamp = (Number) gelfLayoutMap.remove("timestamp");
+ Assertions.assertThat(jsonTemplateLayoutTimestamp.doubleValue())
+ .as(
+ "instantEpochSecs=%d.%d, jsonTemplateLayoutTimestamp=%s, gelfLayoutTimestamp=%s",
+ logEventInstant.getEpochSecond(),
+ logEventInstant.getNanoOfSecond(),
+ jsonTemplateLayoutTimestamp,
+ gelfLayoutTimestamp)
+ .isEqualTo(gelfLayoutTimestamp.doubleValue());
}
}
diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatterTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatterTest.java
deleted file mode 100644
index 87df6556988..00000000000
--- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatterTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.logging.log4j.layout.template.json.util;
-
-import java.util.Locale;
-import java.util.TimeZone;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.time.MutableInstant;
-import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
-import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
-import org.apache.logging.log4j.test.ListStatusListener;
-import org.apache.logging.log4j.test.junit.UsingStatusListener;
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.CsvSource;
-
-class InstantFormatterTest {
-
- @ParameterizedTest
- @CsvSource({
- "yyyy-MM-dd'T'HH:mm:ss.SSS" + ",FixedDateFormat",
- "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + ",FastDateFormat",
- "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'" + ",DateTimeFormatter"
- })
- void all_internal_implementations_should_be_used(final String pattern, final String className) {
- final InstantFormatter formatter =
- InstantFormatter.newBuilder().setPattern(pattern).build();
- Assertions.assertThat(formatter.getInternalImplementationClass())
- .asString()
- .describedAs("pattern=%s", pattern)
- .endsWith("." + className);
- }
-
- @Test
- void nanoseconds_should_be_formatted() {
- final InstantFormatter formatter = InstantFormatter.newBuilder()
- .setPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'")
- .setTimeZone(TimeZone.getTimeZone("UTC"))
- .build();
- final MutableInstant instant = new MutableInstant();
- instant.initFromEpochSecond(0, 123_456_789);
- Assertions.assertThat(formatter.format(instant)).isEqualTo("1970-01-01T00:00:00.123456789Z");
- }
-
- /**
- * Reproduces LOG4J2-3614.
- */
- @Test
- void FastDateFormat_failures_should_be_handled() {
-
- // Define a pattern causing `FastDateFormat` to fail.
- final String pattern = "ss.nnnnnnnnn";
- final TimeZone timeZone = TimeZone.getTimeZone("UTC");
- final Locale locale = Locale.US;
-
- // Assert that the pattern is not supported by `FixedDateFormat`.
- final FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported(pattern, timeZone.getID());
- Assertions.assertThat(fixedDateFormat).isNull();
-
- // Assert that the pattern indeed causes a `FastDateFormat` failure.
- Assertions.assertThatThrownBy(() -> FastDateFormat.getInstance(pattern, timeZone, locale))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Illegal pattern component: nnnnnnnnn");
-
- // Assert that `InstantFormatter` falls back to `DateTimeFormatter`.
- final InstantFormatter formatter = InstantFormatter.newBuilder()
- .setPattern(pattern)
- .setTimeZone(timeZone)
- .build();
- Assertions.assertThat(formatter.getInternalImplementationClass())
- .asString()
- .endsWith(".DateTimeFormatter");
-
- // Assert that formatting works.
- final MutableInstant instant = new MutableInstant();
- instant.initFromEpochSecond(0, 123_456_789);
- Assertions.assertThat(formatter.format(instant)).isEqualTo("00.123456789");
- }
-
- @Test
- @UsingStatusListener
- void FixedFormatter_large_enough_buffer(ListStatusListener listener) {
- final String pattern = "yyyy-MM-dd'T'HH:mm:ss,SSSXXX";
- final TimeZone timeZone = TimeZone.getTimeZone("America/Chicago");
- final Locale locale = Locale.ENGLISH;
- final InstantFormatter formatter = InstantFormatter.newBuilder()
- .setPattern(pattern)
- .setTimeZone(timeZone)
- .setLocale(locale)
- .build();
-
- // On this pattern the FixedFormatter used a buffer shorter than necessary,
- // which caused exceptions and warnings.
- Assertions.assertThat(listener.findStatusData(Level.WARN)).hasSize(0);
- Assertions.assertThat(formatter.getInternalImplementationClass())
- .asString()
- .endsWith(".FixedDateFormat");
- }
-}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java
index f023f603aa3..1cd3f6bb0db 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/TimestampResolver.java
@@ -18,13 +18,12 @@
import java.util.Locale;
import java.util.TimeZone;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.time.Instant;
-import org.apache.logging.log4j.core.time.MutableInstant;
+import org.apache.logging.log4j.core.util.internal.instant.InstantFormatter;
+import org.apache.logging.log4j.core.util.internal.instant.InstantNumberFormatter;
+import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter;
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults;
-import org.apache.logging.log4j.layout.template.json.util.InstantFormatter;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
/**
@@ -55,15 +54,14 @@
* rounded = "rounded" -> boolean
*
*
- * If no configuration options are provided, pattern-config is
- * employed. There {@link
- * JsonTemplateLayoutDefaults#getTimestampFormatPattern()}, {@link
- * JsonTemplateLayoutDefaults#getTimeZone()}, {@link
- * JsonTemplateLayoutDefaults#getLocale()} are used as defaults for
- * pattern, timeZone, and locale, respectively.
+ *
+ * If no configuration options are provided, pattern-config is employed.
+ * There {@link JsonTemplateLayoutDefaults#getTimestampFormatPattern()}, {@link JsonTemplateLayoutDefaults#getTimeZone()}, {@link JsonTemplateLayoutDefaults#getLocale()} are used as defaults for pattern, timeZone, and locale, respectively.
+ *
*
- * In epoch-config, millis.nanos, secs.nanos stand
- * for the fractional component in nanoseconds.
+ *
+ * In epoch-config, millis.nanos, secs.nanos stand for the fractional component in nanoseconds.
+ *
*
* Examples
*
@@ -209,109 +207,53 @@ private static EventResolver createResolver(final TemplateResolverConfig config)
return epochProvided ? createEpochResolver(config) : createPatternResolver(config);
}
- private static final class PatternResolverContext {
-
- private final InstantFormatter formatter;
-
- private final StringBuilder lastFormattedInstantBuffer = new StringBuilder();
-
- private final MutableInstant lastFormattedInstant = new MutableInstant();
-
- private PatternResolverContext(final String pattern, final TimeZone timeZone, final Locale locale) {
- this.formatter = InstantFormatter.newBuilder()
- .setPattern(pattern)
- .setTimeZone(timeZone)
- .setLocale(locale)
- .build();
- lastFormattedInstant.initFromEpochSecond(-1, 0);
- }
+ private static EventResolver createPatternResolver(final TemplateResolverConfig config) {
+ final String pattern = readPattern(config);
+ final TimeZone timeZone = readTimeZone(config);
+ final Locale locale = config.getLocale(new String[] {"pattern", "locale"});
+ final InstantFormatter formatter = InstantPatternFormatter.newBuilder()
+ .setPattern(pattern)
+ .setTimeZone(timeZone)
+ .setLocale(locale)
+ .build();
+ return new PatternResolver(formatter);
+ }
- private static PatternResolverContext fromConfig(final TemplateResolverConfig config) {
- final String pattern = readPattern(config);
- final TimeZone timeZone = readTimeZone(config);
- final Locale locale = config.getLocale(new String[] {"pattern", "locale"});
- return new PatternResolverContext(pattern, timeZone, locale);
- }
+ private static String readPattern(final TemplateResolverConfig config) {
+ final String format = config.getString(new String[] {"pattern", "format"});
+ return format != null ? format : JsonTemplateLayoutDefaults.getTimestampFormatPattern();
+ }
- private static String readPattern(final TemplateResolverConfig config) {
- final String format = config.getString(new String[] {"pattern", "format"});
- return format != null ? format : JsonTemplateLayoutDefaults.getTimestampFormatPattern();
+ private static TimeZone readTimeZone(final TemplateResolverConfig config) {
+ final String timeZoneId = config.getString(new String[] {"pattern", "timeZone"});
+ if (timeZoneId == null) {
+ return JsonTemplateLayoutDefaults.getTimeZone();
}
-
- private static TimeZone readTimeZone(final TemplateResolverConfig config) {
- final String timeZoneId = config.getString(new String[] {"pattern", "timeZone"});
- if (timeZoneId == null) {
- return JsonTemplateLayoutDefaults.getTimeZone();
- }
- boolean found = false;
- for (final String availableTimeZone : TimeZone.getAvailableIDs()) {
- if (availableTimeZone.equalsIgnoreCase(timeZoneId)) {
- found = true;
- break;
- }
- }
- if (!found) {
- throw new IllegalArgumentException("invalid timestamp time zone: " + config);
+ boolean found = false;
+ for (final String availableTimeZone : TimeZone.getAvailableIDs()) {
+ if (availableTimeZone.equalsIgnoreCase(timeZoneId)) {
+ found = true;
+ break;
}
- return TimeZone.getTimeZone(timeZoneId);
}
+ if (!found) {
+ throw new IllegalArgumentException("invalid timestamp time zone: " + config);
+ }
+ return TimeZone.getTimeZone(timeZoneId);
}
private static final class PatternResolver implements EventResolver {
- private final Lock lock = new ReentrantLock();
-
- private final PatternResolverContext patternResolverContext;
+ private final InstantFormatter formatter;
- private PatternResolver(final PatternResolverContext patternResolverContext) {
- this.patternResolverContext = patternResolverContext;
+ private PatternResolver(final InstantFormatter formatter) {
+ this.formatter = formatter;
}
@Override
public void resolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
- lock.lock();
- try {
- unsynchronizedResolve(logEvent, jsonWriter);
- } finally {
- lock.unlock();
- }
+ jsonWriter.writeString(formatter::formatTo, logEvent.getInstant());
}
-
- private void unsynchronizedResolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
-
- // Format timestamp if it doesn't match the last cached one.
- final boolean instantMatching = patternResolverContext.formatter.isInstantMatching(
- patternResolverContext.lastFormattedInstant, logEvent.getInstant());
- if (!instantMatching) {
-
- // Format the timestamp.
- patternResolverContext.lastFormattedInstantBuffer.setLength(0);
- patternResolverContext.lastFormattedInstant.initFrom(logEvent.getInstant());
- patternResolverContext.formatter.format(
- patternResolverContext.lastFormattedInstant, patternResolverContext.lastFormattedInstantBuffer);
-
- // Write the formatted timestamp.
- final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder();
- final int startIndex = jsonWriterStringBuilder.length();
- jsonWriter.writeString(patternResolverContext.lastFormattedInstantBuffer);
-
- // Cache the written value.
- patternResolverContext.lastFormattedInstantBuffer.setLength(0);
- patternResolverContext.lastFormattedInstantBuffer.append(
- jsonWriterStringBuilder, startIndex, jsonWriterStringBuilder.length());
-
- }
-
- // Write the cached formatted timestamp.
- else {
- jsonWriter.writeRawString(patternResolverContext.lastFormattedInstantBuffer);
- }
- }
- }
-
- private static EventResolver createPatternResolver(final TemplateResolverConfig config) {
- final PatternResolverContext patternResolverContext = PatternResolverContext.fromConfig(config);
- return new PatternResolver(patternResolverContext);
}
private static EventResolver createEpochResolver(final TemplateResolverConfig config) {
@@ -331,119 +273,48 @@ private static EventResolver createEpochResolver(final TemplateResolverConfig co
throw new IllegalArgumentException("invalid epoch configuration: " + config);
}
- private static final class EpochResolutionRecord {
-
- private static final int MAX_LONG_LENGTH =
- String.valueOf(Long.MAX_VALUE).length();
-
- private final MutableInstant instant = new MutableInstant();
-
- private final char[] resolution =
- new char[ /* integral: */MAX_LONG_LENGTH + /* dot: */ 1 + /* fractional: */ MAX_LONG_LENGTH];
-
- private int resolutionLength;
-
- private EpochResolutionRecord() {
- instant.initFromEpochSecond(-1, 0);
- }
- }
-
- private abstract static class EpochResolver implements EventResolver {
-
- private final Lock lock = new ReentrantLock();
-
- private final EpochResolutionRecord resolutionRecord = new EpochResolutionRecord();
-
- @Override
- public void resolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
- lock.lock();
- try {
- unsynchronizedResolve(logEvent, jsonWriter);
- } finally {
- lock.unlock();
- }
- }
-
- private void unsynchronizedResolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
- final Instant logEventInstant = logEvent.getInstant();
- if (logEventInstant.equals(resolutionRecord.instant)) {
- jsonWriter.writeRawString(resolutionRecord.resolution, 0, resolutionRecord.resolutionLength);
- } else {
- resolutionRecord.instant.initFrom(logEventInstant);
- final StringBuilder stringBuilder = jsonWriter.getStringBuilder();
- final int startIndex = stringBuilder.length();
- resolve(logEventInstant, jsonWriter);
- resolutionRecord.resolutionLength = stringBuilder.length() - startIndex;
- stringBuilder.getChars(startIndex, stringBuilder.length(), resolutionRecord.resolution, 0);
- }
- }
-
- abstract void resolve(Instant logEventInstant, JsonWriter jsonWriter);
- }
-
- private static final EventResolver EPOCH_NANOS_RESOLVER = new EpochResolver() {
- @Override
- void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
- final long nanos = epochNanos(logEventInstant);
- jsonWriter.writeNumber(nanos);
- }
+ private static final EventResolver EPOCH_NANOS_RESOLVER = (logEvent, jsonWriter) -> {
+ final StringBuilder buffer = jsonWriter.getStringBuilder();
+ final Instant instant = logEvent.getInstant();
+ InstantNumberFormatter.EPOCH_NANOS.formatTo(buffer, instant);
};
- private static final EventResolver EPOCH_MILLIS_RESOLVER = new EpochResolver() {
- @Override
- void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
- final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder();
- final long nanos = epochNanos(logEventInstant);
- jsonWriterStringBuilder.append(nanos);
- jsonWriterStringBuilder.insert(jsonWriterStringBuilder.length() - 6, '.');
- }
+ private static final EventResolver EPOCH_MILLIS_RESOLVER = (logEvent, jsonWriter) -> {
+ final StringBuilder buffer = jsonWriter.getStringBuilder();
+ final Instant instant = logEvent.getInstant();
+ InstantNumberFormatter.EPOCH_MILLIS.formatTo(buffer, instant);
};
- private static final EventResolver EPOCH_MILLIS_ROUNDED_RESOLVER = new EpochResolver() {
- @Override
- void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
- jsonWriter.writeNumber(logEventInstant.getEpochMillisecond());
- }
+ private static final EventResolver EPOCH_MILLIS_ROUNDED_RESOLVER = (logEvent, jsonWriter) -> {
+ final StringBuilder buffer = jsonWriter.getStringBuilder();
+ final Instant instant = logEvent.getInstant();
+ InstantNumberFormatter.EPOCH_MILLIS_ROUNDED.formatTo(buffer, instant);
};
- private static final EventResolver EPOCH_MILLIS_NANOS_RESOLVER = new EpochResolver() {
- @Override
- void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
- final long nanos = epochNanos(logEventInstant);
- final long fraction = nanos % 1_000_000L;
- jsonWriter.writeNumber(fraction);
- }
+ private static final EventResolver EPOCH_MILLIS_NANOS_RESOLVER = (logEvent, jsonWriter) -> {
+ final StringBuilder buffer = jsonWriter.getStringBuilder();
+ final Instant instant = logEvent.getInstant();
+ InstantNumberFormatter.EPOCH_MILLIS_NANOS.formatTo(buffer, instant);
};
- private static final EventResolver EPOCH_SECS_RESOLVER = new EpochResolver() {
- @Override
- void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
- final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder();
- final long nanos = epochNanos(logEventInstant);
- jsonWriterStringBuilder.append(nanos);
- jsonWriterStringBuilder.insert(jsonWriterStringBuilder.length() - 9, '.');
- }
+ private static final EventResolver EPOCH_SECS_RESOLVER = (logEvent, jsonWriter) -> {
+ final StringBuilder buffer = jsonWriter.getStringBuilder();
+ final Instant instant = logEvent.getInstant();
+ InstantNumberFormatter.EPOCH_SECONDS.formatTo(buffer, instant);
};
- private static final EventResolver EPOCH_SECS_ROUNDED_RESOLVER = new EpochResolver() {
- @Override
- void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
- jsonWriter.writeNumber(logEventInstant.getEpochSecond());
- }
+ private static final EventResolver EPOCH_SECS_ROUNDED_RESOLVER = (logEvent, jsonWriter) -> {
+ final StringBuilder buffer = jsonWriter.getStringBuilder();
+ final Instant instant = logEvent.getInstant();
+ InstantNumberFormatter.EPOCH_SECONDS_ROUNDED.formatTo(buffer, instant);
};
- private static final EventResolver EPOCH_SECS_NANOS_RESOLVER = new EpochResolver() {
- @Override
- void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
- jsonWriter.writeNumber(logEventInstant.getNanoOfSecond());
- }
+ private static final EventResolver EPOCH_SECS_NANOS_RESOLVER = (logEvent, jsonWriter) -> {
+ final StringBuilder buffer = jsonWriter.getStringBuilder();
+ final Instant instant = logEvent.getInstant();
+ InstantNumberFormatter.EPOCH_SECONDS_NANOS.formatTo(buffer, instant);
};
- private static long epochNanos(final Instant instant) {
- final long nanos = Math.multiplyExact(1_000_000_000L, instant.getEpochSecond());
- return Math.addExact(nanos, instant.getNanoOfSecond());
- }
-
static String getName() {
return "timestamp";
}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatter.java
index c9a47d1f2c1..4b385da2315 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatter.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/InstantFormatter.java
@@ -37,7 +37,10 @@
* Note that {@link FixedDateFormat} and {@link FastDateFormat} only support
* millisecond precision. If the pattern asks for a higher precision,
* {@link DateTimeFormatter} will be employed, which is significantly slower.
+ *
+ * @deprecated Starting with version {@code 2.25.0}, this class is planned to be removed in the next major release.
*/
+@Deprecated
public final class InstantFormatter {
private static final StatusLogger LOGGER = StatusLogger.getLogger();
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/DateTimeFormatBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/DateTimeFormatBenchmark.java
deleted file mode 100644
index 3ad0f039dbb..00000000000
--- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/DateTimeFormatBenchmark.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.logging.log4j.perf.jmh;
-
-import java.time.Instant;
-import java.time.format.DateTimeFormatter;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-import java.util.stream.IntStream;
-import org.apache.logging.log4j.core.time.MutableInstant;
-import org.apache.logging.log4j.core.util.datetime.FastDatePrinter;
-import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.State;
-import org.openjdk.jmh.infra.Blackhole;
-
-/**
- * Compares {@link MutableInstant} formatting efficiency of
- * {@link FastDatePrinter}, {@link FixedDateFormat}, and {@link DateTimeFormatter}.
- *
- * The major formatting efficiency is mostly provided by caching, i.e.,
- * reusing the earlier formatter output if timestamps match. We deliberately
- * exclude this optimization, since it is applicable to all formatters. This
- * benchmark rather focuses on only and only the formatting efficiency.
- */
-@State(Scope.Thread)
-public class DateTimeFormatBenchmark {
-
- /**
- * The pattern to be tested.
- *
- * Note that neither {@link FastDatePrinter}, nor {@link FixedDateFormat}
- * supports nanosecond precision.
- */
- private static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
-
- private static final Locale LOCALE = Locale.US;
-
- private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
-
- private static final Instant INIT_INSTANT = Instant.parse("2020-05-14T10:44:23.901Z");
-
- private static final MutableInstant[] INSTANTS = IntStream.range(0, 1_000)
- .mapToObj((final int index) -> {
- final MutableInstant instant = new MutableInstant();
- instant.initFromEpochSecond(
- Math.addExact(INIT_INSTANT.getEpochSecond(), index),
- Math.addExact(INIT_INSTANT.getNano(), index));
- return instant;
- })
- .toArray(MutableInstant[]::new);
-
- private static final Calendar[] CALENDARS = Arrays.stream(INSTANTS)
- .map((final MutableInstant instant) -> {
- final Calendar calendar = Calendar.getInstance(TIME_ZONE, LOCALE);
- calendar.setTimeInMillis(instant.getEpochMillisecond());
- return calendar;
- })
- .toArray(Calendar[]::new);
-
- private static final FastDatePrinter FAST_DATE_PRINTER = new FastDatePrinter(PATTERN, TIME_ZONE, LOCALE) {};
-
- private static final FixedDateFormat FIXED_DATE_FORMAT = Objects.requireNonNull(
- FixedDateFormat.createIfSupported(PATTERN, TIME_ZONE.getID()),
- "couldn't create FixedDateTime for pattern " + PATTERN + " and time zone " + TIME_ZONE.getID());
-
- private static final DateTimeFormatter DATE_TIME_FORMATTER =
- DateTimeFormatter.ofPattern(PATTERN).withZone(TIME_ZONE.toZoneId()).withLocale(LOCALE);
-
- private final StringBuilder stringBuilder = new StringBuilder(PATTERN.length() * 2);
-
- private final char[] charBuffer = new char[stringBuilder.capacity()];
-
- @Benchmark
- public void fastDatePrinter(final Blackhole blackhole) {
- for (final Calendar calendar : CALENDARS) {
- stringBuilder.setLength(0);
- FAST_DATE_PRINTER.format(calendar, stringBuilder);
- blackhole.consume(stringBuilder.length());
- }
- }
-
- @Benchmark
- public void fixedDateFormat(final Blackhole blackhole) {
- for (final MutableInstant instant : INSTANTS) {
- final int length = FIXED_DATE_FORMAT.formatInstant(instant, charBuffer, 0);
- blackhole.consume(length);
- }
- }
-
- @Benchmark
- public void dateTimeFormatter(final Blackhole blackhole) {
- for (final MutableInstant instant : INSTANTS) {
- stringBuilder.setLength(0);
- DATE_TIME_FORMATTER.formatTo(instant, stringBuilder);
- blackhole.consume(stringBuilder.length());
- }
- }
-}
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/InstantFormatBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/InstantFormatBenchmark.java
index 6dfe084100d..6cd6de640e2 100644
--- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/InstantFormatBenchmark.java
+++ b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/InstantFormatBenchmark.java
@@ -43,6 +43,7 @@
* Benchmarks certain {@link Instant} formatters with various patterns and instant collections.
*/
@State(Scope.Thread)
+@SuppressWarnings("deprecation")
public class InstantFormatBenchmark {
private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
deleted file mode 100644
index 0a59cb23903..00000000000
--- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * 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.logging.log4j.perf.jmh;
-
-import java.nio.ByteBuffer;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
-import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
-import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.BenchmarkMode;
-import org.openjdk.jmh.annotations.Mode;
-import org.openjdk.jmh.annotations.OutputTimeUnit;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.State;
-
-/**
- * Tests performance of various time format implementation.
- */
-@State(Scope.Benchmark)
-public class TimeFormatBenchmark {
-
- ThreadLocal threadLocalSimpleDateFormat = new ThreadLocal<>() {
- @Override
- protected SimpleDateFormat initialValue() {
- return new SimpleDateFormat("HH:mm:ss.SSS");
- }
- };
- FastDateFormat fastDateFormat = FastDateFormat.getInstance("HH:mm:ss.SSS");
- FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported("ABSOLUTE");
- volatile long midnightToday;
- volatile long midnightTomorrow;
-
- @State(Scope.Thread)
- public static class BufferState {
- final ByteBuffer buffer = ByteBuffer.allocate(12);
- final StringBuilder stringBuilder = new StringBuilder(12);
- final char[] charArray = new char[12];
- }
-
- private long millisSinceMidnight(final long now) {
- if (now >= midnightTomorrow) {
- midnightToday = calcMidnightMillis(now, 0);
- midnightTomorrow = calcMidnightMillis(now, 1);
- }
- return now - midnightToday;
- }
-
- private long calcMidnightMillis(final long time, final int addDays) {
- final Calendar cal = Calendar.getInstance();
- cal.setTimeInMillis(time);
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 0);
- cal.add(Calendar.DATE, addDays);
- return cal.getTimeInMillis();
- }
-
- public static void main(final String[] args) {
- System.out.println(new TimeFormatBenchmark().fixedBitFiddlingReuseCharArray(new BufferState()));
- System.out.println(new TimeFormatBenchmark().fixedFormatReuseStringBuilder(new BufferState()));
- }
-
- @Benchmark
- @BenchmarkMode(Mode.SampleTime)
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public String simpleDateFormat() {
- return threadLocalSimpleDateFormat.get().format(new Date());
- }
-
- @Benchmark
- @BenchmarkMode(Mode.SampleTime)
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public String fastDateFormatCreateNewStringBuilder() {
- return fastDateFormat.format(new Date());
- }
-
- @Benchmark
- @BenchmarkMode(Mode.SampleTime)
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public String fastDateFormatReuseStringBuilder(final BufferState state) {
- state.stringBuilder.setLength(0);
- fastDateFormat.format(new Date(), state.stringBuilder);
- return new String(state.stringBuilder);
- }
-
- @Benchmark
- @BenchmarkMode(Mode.SampleTime)
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public String fixedBitFiddlingReuseCharArray(final BufferState state) {
- final int len = formatCharArrayBitFiddling(System.currentTimeMillis(), state.charArray, 0);
- return new String(state.charArray, 0, len);
- }
-
- @Benchmark
- @BenchmarkMode(Mode.SampleTime)
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public String fixedDateFormatCreateNewCharArray(final BufferState state) {
- return fixedDateFormat.format(System.currentTimeMillis());
- }
-
- @Benchmark
- @BenchmarkMode(Mode.SampleTime)
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public String fixedDateFormatReuseCharArray(final BufferState state) {
- final int len = fixedDateFormat.format(System.currentTimeMillis(), state.charArray, 0);
- return new String(state.charArray, 0, len);
- }
-
- @Benchmark
- @BenchmarkMode(Mode.SampleTime)
- @OutputTimeUnit(TimeUnit.NANOSECONDS)
- public String fixedFormatReuseStringBuilder(final BufferState state) {
- state.stringBuilder.setLength(0);
- formatStringBuilder(System.currentTimeMillis(), state.stringBuilder);
- return new String(state.stringBuilder);
- }
-
- int formatCharArrayBitFiddling(final long time, final char[] buffer, final int pos) {
- // Calculate values by getting the ms values first and do then
- // shave off the hour minute and second values with multiplications
- // and bit shifts instead of simple but expensive divisions.
-
- // Get daytime in ms which does fit into an int
- // int ms = (int) (time % 86400000);
- int ms = (int) (millisSinceMidnight(time));
-
- // well ... it works
- final int hour = (int) (((ms >> 7) * 9773437L) >> 38);
- ms -= 3600000 * hour;
-
- final int minute = (int) (((ms >> 5) * 2290650L) >> 32);
- ms -= 60000 * minute;
-
- final int second = ((ms >> 3) * 67109) >> 23;
- ms -= 1000 * second;
-
- // Hour
- // 13/128 is nearly the same as /10 for values up to 65
- int temp = (hour * 13) >> 7;
- int p = pos;
- buffer[p++] = ((char) (temp + '0'));
-
- // Do subtract to get remainder instead of doing % 10
- buffer[p++] = ((char) (hour - 10 * temp + '0'));
- buffer[p++] = ((char) ':');
-
- // Minute
- // 13/128 is nearly the same as /10 for values up to 65
- temp = (minute * 13) >> 7;
- buffer[p++] = ((char) (temp + '0'));
-
- // Do subtract to get remainder instead of doing % 10
- buffer[p++] = ((char) (minute - 10 * temp + '0'));
- buffer[p++] = ((char) ':');
-
- // Second
- // 13/128 is nearly the same as /10 for values up to 65
- temp = (second * 13) >> 7;
- buffer[p++] = ((char) (temp + '0'));
- buffer[p++] = ((char) (second - 10 * temp + '0'));
- buffer[p++] = ((char) '.');
-
- // Millisecond
- // 41/4096 is nearly the same as /100
- temp = (ms * 41) >> 12;
- buffer[p++] = ((char) (temp + '0'));
-
- ms -= 100 * temp;
- temp = (ms * 205) >> 11; // 205/2048 is nearly the same as /10
- buffer[p++] = ((char) (temp + '0'));
-
- ms -= 10 * temp;
- buffer[p++] = ((char) (ms + '0'));
- return p;
- }
-
- StringBuilder formatStringBuilder(final long time, final StringBuilder buffer) {
- // Calculate values by getting the ms values first and do then
- // calculate the hour minute and second values divisions.
-
- // Get daytime in ms which does fit into an int
- // int ms = (int) (time % 86400000);
- int ms = (int) (millisSinceMidnight(time));
-
- final int hours = ms / 3600000;
- ms -= 3600000 * hours;
-
- final int minutes = ms / 60000;
- ms -= 60000 * minutes;
-
- final int seconds = ms / 1000;
- ms -= 1000 * seconds;
-
- // Hour
- int temp = hours / 10;
- buffer.append((char) (temp + '0'));
-
- // Do subtract to get remainder instead of doing % 10
- buffer.append((char) (hours - 10 * temp + '0'));
- buffer.append((char) ':');
-
- // Minute
- temp = minutes / 10;
- buffer.append((char) (temp + '0'));
-
- // Do subtract to get remainder instead of doing % 10
- buffer.append((char) (minutes - 10 * temp + '0'));
- buffer.append((char) ':');
-
- // Second
- temp = seconds / 10;
- buffer.append((char) (temp + '0'));
- buffer.append((char) (seconds - 10 * temp + '0'));
- buffer.append((char) '.');
-
- // Millisecond
- temp = ms / 100;
- buffer.append((char) (temp + '0'));
-
- ms -= 100 * temp;
- temp = ms / 10;
- buffer.append((char) (temp + '0'));
-
- ms -= 10 * temp;
- buffer.append((char) (ms + '0'));
- return buffer;
- }
-
- int formatCharArray(final long time, final char[] buffer, final int pos) {
- // Calculate values by getting the ms values first and do then
- // calculate the hour minute and second values divisions.
-
- // Get daytime in ms which does fit into an int
- // int ms = (int) (time % 86400000);
- int ms = (int) (millisSinceMidnight(time));
-
- final int hours = ms / 3600000;
- ms -= 3600000 * hours;
-
- final int minutes = ms / 60000;
- ms -= 60000 * minutes;
-
- final int seconds = ms / 1000;
- ms -= 1000 * seconds;
-
- // Hour
- int temp = hours / 10;
- int p = pos;
- buffer[p++] = ((char) (temp + '0'));
-
- // Do subtract to get remainder instead of doing % 10
- buffer[p++] = ((char) (hours - 10 * temp + '0'));
- buffer[p++] = ((char) ':');
-
- // Minute
- temp = minutes / 10;
- buffer[p++] = ((char) (temp + '0'));
-
- // Do subtract to get remainder instead of doing % 10
- buffer[p++] = ((char) (minutes - 10 * temp + '0'));
- buffer[p++] = ((char) ':');
-
- // Second
- temp = seconds / 10;
- buffer[p++] = ((char) (temp + '0'));
- buffer[p++] = ((char) (seconds - 10 * temp + '0'));
- buffer[p++] = ((char) '.');
-
- // Millisecond
- temp = ms / 100;
- buffer[p++] = ((char) (temp + '0'));
-
- ms -= 100 * temp;
- temp = ms / 10;
- buffer[p++] = ((char) (temp + '0'));
-
- ms -= 10 * temp;
- buffer[p++] = ((char) (ms + '0'));
- return p;
- }
-}
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternDynamicFormatterSequencingBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternDynamicFormatterSequencingBenchmark.java
new file mode 100644
index 00000000000..d46c431ea0b
--- /dev/null
+++ b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternDynamicFormatterSequencingBenchmark.java
@@ -0,0 +1,96 @@
+/*
+ * 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.logging.log4j.perf.jmh.instant;
+
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.stream.IntStream;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Compares {@link DateTimeFormatter} efficiency for formatting the {@code ss.SSS} singleton versus formatting the {@code ss}, {@code .}, and {@code SSS} sequence.
+ * This comparison is influential on the sequence merging strategies of {@code InstantPatternDynamicFormatter}.
+ */
+@State(Scope.Thread)
+public class InstantPatternDynamicFormatterSequencingBenchmark {
+
+ static final Locale LOCALE = Locale.US;
+
+ static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
+
+ private static final Instant[] INSTANTS = createInstants();
+
+ private static Instant[] createInstants() {
+ final Instant initInstant = Instant.parse("2020-05-14T10:44:23.901Z");
+ return IntStream.range(0, 1_000)
+ .mapToObj((final int index) -> Instant.ofEpochSecond(
+ Math.addExact(initInstant.getEpochSecond(), index),
+ Math.addExact(initInstant.getNano(), index)))
+ .toArray(Instant[]::new);
+ }
+
+ @FunctionalInterface
+ private interface Formatter {
+
+ void formatTo(TemporalAccessor instantAccessor, StringBuilder buffer);
+ }
+
+ private static final Formatter SINGLETON_FORMATTER =
+ DateTimeFormatter.ofPattern("ss.SSS").withLocale(LOCALE).withZone(TIME_ZONE.toZoneId())::formatTo;
+
+ private static final Formatter SEQUENCED_FORMATTER = new Formatter() {
+
+ private final Formatter[] formatters = {
+ DateTimeFormatter.ofPattern("ss").withLocale(LOCALE).withZone(TIME_ZONE.toZoneId())::formatTo,
+ (temporal, appendable) -> appendable.append("."),
+ DateTimeFormatter.ofPattern("SSS").withLocale(LOCALE).withZone(TIME_ZONE.toZoneId())::formatTo
+ };
+
+ @Override
+ public void formatTo(final TemporalAccessor instantAccessor, final StringBuilder buffer) {
+ for (Formatter formatter : formatters) {
+ formatter.formatTo(instantAccessor, buffer);
+ }
+ }
+ };
+
+ private final StringBuilder buffer = new StringBuilder();
+
+ @Benchmark
+ public void singleton(final Blackhole blackhole) {
+ benchmark(blackhole, SINGLETON_FORMATTER);
+ }
+
+ @Benchmark
+ public void sequenced(final Blackhole blackhole) {
+ benchmark(blackhole, SEQUENCED_FORMATTER);
+ }
+
+ private void benchmark(final Blackhole blackhole, final Formatter formatter) {
+ for (final Instant instant : INSTANTS) {
+ formatter.formatTo(instant, buffer);
+ blackhole.consume(buffer);
+ buffer.setLength(0);
+ }
+ }
+}
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternFormatterBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternFormatterBenchmark.java
new file mode 100644
index 00000000000..18ed205b621
--- /dev/null
+++ b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternFormatterBenchmark.java
@@ -0,0 +1,215 @@
+/*
+ * 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.logging.log4j.perf.jmh.instant;
+
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import org.apache.logging.log4j.core.time.MutableInstant;
+import org.apache.logging.log4j.core.util.datetime.FastDatePrinter;
+import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
+import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Compares {@link MutableInstant} formatting efficiency of {@link InstantPatternFormatter}, {@link FastDatePrinter}, {@link FixedDateFormat}, and {@link DateTimeFormatter}.
+ *
+ * The major formatting efficiency is mostly provided by caching, i.e., reusing the earlier formatter output if timestamps match.
+ * We deliberately exclude this optimization (by means of always distinct instants), since it is applicable to all formatters.
+ * This benchmark rather focuses on only and only the formatting efficiency.
+ *
+ *
+ * @see InstantPatternFormatterImpactBenchmark for the performance impact of different date & time formatters on a typical layout
+ */
+@State(Scope.Thread)
+@SuppressWarnings("deprecation")
+public class InstantPatternFormatterBenchmark {
+
+ static final Locale LOCALE = Locale.US;
+
+ static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
+
+ private static final MutableInstant[] INSTANTS = createInstants();
+
+ private static MutableInstant[] createInstants() {
+ final Instant initInstant = Instant.parse("2020-05-14T10:44:23.901Z");
+ MutableInstant[] instants = IntStream.range(0, 1_000)
+ .mapToObj((final int index) -> {
+ final Instant instant = initInstant.plusMillis(index).plusNanos(1);
+ final MutableInstant mutableInstant = new MutableInstant();
+ mutableInstant.initFromEpochSecond(instant.getEpochSecond(), instant.getNano());
+ return mutableInstant;
+ })
+ .toArray(MutableInstant[]::new);
+ validateInstants(instants);
+ return instants;
+ }
+
+ @SuppressWarnings("OptionalGetWithoutIsPresent")
+ static void validateInstants(final I[] instants) {
+
+ // Find the instant offset
+ final Supplier millisStreamSupplier = () ->
+ Arrays.stream(instants).mapToLong(org.apache.logging.log4j.core.time.Instant::getEpochMillisecond);
+ final long minMillis = millisStreamSupplier.get().min().getAsLong();
+ final long maxMillis = millisStreamSupplier.get().max().getAsLong();
+ final long offMillis = maxMillis - minMillis;
+
+ // Validate for `FixedDateFormat`
+ if (TimeUnit.DAYS.toMillis(1) <= offMillis) {
+ final String message = String.format(
+ "instant samples must be of the same day to exploit the `%s` caching",
+ FixedDateFormat.class.getSimpleName());
+ throw new IllegalStateException(message);
+ }
+
+ // Validate for `InstantPatternDynamicFormatter`
+ if (TimeUnit.MINUTES.toMillis(1) <= offMillis) {
+ final String message = String.format(
+ "instant samples must be of the same week to exploit the `%s` caching",
+ InstantPatternFormatter.class.getSimpleName());
+ throw new IllegalStateException(message);
+ }
+ }
+
+ private static final Formatters DATE_TIME_FORMATTERS = new Formatters("yyyy-MM-dd'T'HH:mm:ss.SSS");
+
+ private static final Formatters TIME_FORMATTERS = new Formatters("HH:mm:ss.SSS");
+
+ static final class Formatters {
+
+ private final String pattern;
+
+ final FastDatePrinter fastFormatter;
+
+ final FixedDateFormat fixedFormatter;
+
+ final InstantPatternFormatter instantFormatter;
+
+ final DateTimeFormatter javaFormatter;
+
+ Formatters(final String pattern) {
+ this.pattern = pattern;
+ this.fastFormatter = new FastDatePrinter(pattern, TIME_ZONE, LOCALE) {};
+ this.fixedFormatter = FixedDateFormat.createIfSupported(pattern, TIME_ZONE.getID());
+ if (fixedFormatter == null) {
+ final String message = String.format(
+ "couldn't create `%s` for pattern `%s` and time zone `%s`",
+ FixedDateFormat.class.getSimpleName(), pattern, TIME_ZONE.getID());
+ throw new IllegalStateException(message);
+ }
+ this.instantFormatter = InstantPatternFormatter.newBuilder()
+ .setPattern(pattern)
+ .setLocale(LOCALE)
+ .setTimeZone(TIME_ZONE)
+ .setCachingEnabled(false)
+ .build();
+ this.javaFormatter = DateTimeFormatter.ofPattern(pattern)
+ .withZone(TIME_ZONE.toZoneId())
+ .withLocale(LOCALE);
+ }
+ }
+
+ private final StringBuilder stringBuilder =
+ new StringBuilder(Math.max(DATE_TIME_FORMATTERS.pattern.length(), TIME_FORMATTERS.pattern.length()) * 2);
+
+ private final char[] charBuffer = new char[stringBuilder.capacity()];
+
+ private final Calendar calendar = Calendar.getInstance(TIME_ZONE, LOCALE);
+
+ @Benchmark
+ public void instantFormatter_dateTime(final Blackhole blackhole) {
+ instantFormatter(blackhole, DATE_TIME_FORMATTERS.instantFormatter);
+ }
+
+ @Benchmark
+ public void instantFormatter_time(final Blackhole blackhole) {
+ instantFormatter(blackhole, TIME_FORMATTERS.instantFormatter);
+ }
+
+ private void instantFormatter(final Blackhole blackhole, final InstantPatternFormatter formatter) {
+ for (final MutableInstant instant : INSTANTS) {
+ stringBuilder.setLength(0);
+ formatter.formatTo(stringBuilder, instant);
+ blackhole.consume(stringBuilder.length());
+ }
+ }
+
+ @Benchmark
+ public void fastFormatter_dateTime(final Blackhole blackhole) {
+ fastFormatter(blackhole, DATE_TIME_FORMATTERS.fastFormatter);
+ }
+
+ @Benchmark
+ public void fastFormatter_time(final Blackhole blackhole) {
+ fastFormatter(blackhole, TIME_FORMATTERS.fastFormatter);
+ }
+
+ private void fastFormatter(final Blackhole blackhole, final FastDatePrinter formatter) {
+ for (final MutableInstant instant : INSTANTS) {
+ stringBuilder.setLength(0);
+ calendar.setTimeInMillis(instant.getEpochMillisecond());
+ formatter.format(calendar, stringBuilder);
+ blackhole.consume(stringBuilder.length());
+ }
+ }
+
+ @Benchmark
+ public void fixedFormatter_dateTime(final Blackhole blackhole) {
+ fixedFormatter(blackhole, DATE_TIME_FORMATTERS.fixedFormatter);
+ }
+
+ @Benchmark
+ public void fixedFormatter_time(final Blackhole blackhole) {
+ fixedFormatter(blackhole, DATE_TIME_FORMATTERS.fixedFormatter);
+ }
+
+ private void fixedFormatter(final Blackhole blackhole, final FixedDateFormat formatter) {
+ for (final MutableInstant instant : INSTANTS) {
+ final int length = formatter.formatInstant(instant, charBuffer, 0);
+ blackhole.consume(length);
+ }
+ }
+
+ @Benchmark
+ public void javaFormatter_dateTime(final Blackhole blackhole) {
+ javaFormatter(blackhole, DATE_TIME_FORMATTERS.javaFormatter);
+ }
+
+ @Benchmark
+ public void javaFormatter_time(final Blackhole blackhole) {
+ javaFormatter(blackhole, TIME_FORMATTERS.javaFormatter);
+ }
+
+ private void javaFormatter(final Blackhole blackhole, final DateTimeFormatter formatter) {
+ for (final MutableInstant instant : INSTANTS) {
+ stringBuilder.setLength(0);
+ formatter.formatTo(instant, stringBuilder);
+ blackhole.consume(stringBuilder.length());
+ }
+ }
+}
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternFormatterImpactBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternFormatterImpactBenchmark.java
new file mode 100644
index 00000000000..292d1f041a9
--- /dev/null
+++ b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/instant/InstantPatternFormatterImpactBenchmark.java
@@ -0,0 +1,187 @@
+/*
+ * 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.logging.log4j.perf.jmh.instant;
+
+import static org.apache.logging.log4j.perf.jmh.instant.InstantPatternFormatterBenchmark.validateInstants;
+
+import java.time.format.DateTimeFormatter;
+import java.util.Calendar;
+import java.util.List;
+import java.util.function.BiFunction;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.NullConfiguration;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.time.Instant;
+import org.apache.logging.log4j.core.time.MutableInstant;
+import org.apache.logging.log4j.core.util.datetime.FastDatePrinter;
+import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
+import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter;
+import org.apache.logging.log4j.layout.template.json.LogEventFixture;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Benchmarks the impact of different date & time formatters on a typical layout.
+ *
+ * @see InstantPatternFormatterBenchmark for isolated benchmarks of date & time formatters
+ */
+@State(Scope.Thread)
+@SuppressWarnings("deprecation")
+public class InstantPatternFormatterImpactBenchmark {
+
+ private static final List LITE_LOG_EVENTS = createLogEvents(LogEventFixture::createLiteLogEvents);
+
+ private static final List FULL_LOG_EVENTS = createLogEvents(LogEventFixture::createFullLogEvents);
+
+ private static List createLogEvents(final BiFunction> supplier) {
+ final int logEventCount = 1_000;
+ final List logEvents = supplier.apply(
+ logEventCount,
+ // Avoid overlapping instants to ensure the impact of date & time formatting at event encoding:
+ 1);
+ final Instant[] instants = logEvents.stream().map(LogEvent::getInstant).toArray(Instant[]::new);
+ validateInstants(instants);
+ return logEvents;
+ }
+
+ private static final PatternLayout LAYOUT = PatternLayout.newBuilder()
+ .withConfiguration(new NullConfiguration())
+ // Use a typical pattern *without* a date & time converter!
+ .withPattern("[%t] %p %-40.40c{1.} %notEmpty{%x }- %m%n")
+ .withAlwaysWriteExceptions(true)
+ .build();
+
+ private static final InstantPatternFormatterBenchmark.Formatters FORMATTERS =
+ new InstantPatternFormatterBenchmark.Formatters("yyyy-MM-dd'T'HH:mm:ss.SSS");
+
+ private final StringBuilder stringBuilder = new StringBuilder(1_1024 * 16);
+
+ private final char[] charBuffer = new char[stringBuilder.capacity()];
+
+ private final Calendar calendar =
+ Calendar.getInstance(InstantPatternFormatterBenchmark.TIME_ZONE, InstantPatternFormatterBenchmark.LOCALE);
+
+ @Benchmark
+ public void fastFormatter_lite(final Blackhole blackhole) {
+ fastFormatter(blackhole, LITE_LOG_EVENTS, FORMATTERS.fastFormatter);
+ }
+
+ @Benchmark
+ public void fastFormatter_full(final Blackhole blackhole) {
+ fastFormatter(blackhole, FULL_LOG_EVENTS, FORMATTERS.fastFormatter);
+ }
+
+ private void fastFormatter(
+ final Blackhole blackhole, final List logEvents, final FastDatePrinter formatter) {
+ // noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+ for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) {
+
+ // 1. Encode event
+ final LogEvent logEvent = logEvents.get(logEventIndex);
+ stringBuilder.setLength(0);
+ LAYOUT.serialize(logEvent, stringBuilder);
+
+ // 2. Encode date & time
+ calendar.setTimeInMillis(logEvent.getInstant().getEpochMillisecond());
+ formatter.format(calendar, stringBuilder);
+ blackhole.consume(stringBuilder.length());
+ }
+ }
+
+ @Benchmark
+ public void fixedFormatter_lite(final Blackhole blackhole) {
+ fixedFormatter(blackhole, LITE_LOG_EVENTS, FORMATTERS.fixedFormatter);
+ }
+
+ @Benchmark
+ public void fixedFormatter_full(final Blackhole blackhole) {
+ fixedFormatter(blackhole, FULL_LOG_EVENTS, FORMATTERS.fixedFormatter);
+ }
+
+ private void fixedFormatter(
+ final Blackhole blackhole, final List logEvents, final FixedDateFormat formatter) {
+ // noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+ for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) {
+
+ // 1. Encode event
+ final LogEvent logEvent = logEvents.get(logEventIndex);
+ stringBuilder.setLength(0);
+ LAYOUT.serialize(logEvent, stringBuilder);
+
+ // 2. Encode date & time
+ final MutableInstant instant = (MutableInstant) logEvent.getInstant();
+ final int length = formatter.formatInstant(instant, charBuffer, 0);
+ blackhole.consume(length);
+ }
+ }
+
+ @Benchmark
+ public void instantFormatter_lite(final Blackhole blackhole) {
+ instantFormatter(blackhole, LITE_LOG_EVENTS, FORMATTERS.instantFormatter);
+ }
+
+ @Benchmark
+ public void instantFormatter_full(final Blackhole blackhole) {
+ instantFormatter(blackhole, FULL_LOG_EVENTS, FORMATTERS.instantFormatter);
+ }
+
+ private void instantFormatter(
+ final Blackhole blackhole, final List logEvents, final InstantPatternFormatter formatter) {
+ // noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+ for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) {
+
+ // 1. Encode event
+ final LogEvent logEvent = logEvents.get(logEventIndex);
+ stringBuilder.setLength(0);
+ LAYOUT.serialize(logEvent, stringBuilder);
+
+ // 2. Encode date & time
+ final MutableInstant instant = (MutableInstant) logEvent.getInstant();
+ formatter.formatTo(stringBuilder, instant);
+ blackhole.consume(stringBuilder.length());
+ }
+ }
+
+ @Benchmark
+ public void javaFormatter_lite(final Blackhole blackhole) {
+ javaFormatter(blackhole, LITE_LOG_EVENTS, FORMATTERS.javaFormatter);
+ }
+
+ @Benchmark
+ public void javaFormatter_full(final Blackhole blackhole) {
+ javaFormatter(blackhole, FULL_LOG_EVENTS, FORMATTERS.javaFormatter);
+ }
+
+ private void javaFormatter(
+ final Blackhole blackhole, final List logEvents, final DateTimeFormatter formatter) {
+ // noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
+ for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) {
+
+ // 1. Encode event
+ final LogEvent logEvent = logEvents.get(logEventIndex);
+ stringBuilder.setLength(0);
+ LAYOUT.serialize(logEvent, stringBuilder);
+
+ // 2. Encode date & time
+ final MutableInstant instant = (MutableInstant) logEvent.getInstant();
+ formatter.formatTo(instant, stringBuilder);
+ blackhole.consume(stringBuilder.length());
+ }
+ }
+}
diff --git a/src/changelog/.2.x.x/.release-notes.adoc.ftl b/src/changelog/.2.x.x/.release-notes.adoc.ftl
index 47b4447337a..ce8c2df4033 100644
--- a/src/changelog/.2.x.x/.release-notes.adoc.ftl
+++ b/src/changelog/.2.x.x/.release-notes.adoc.ftl
@@ -38,6 +38,13 @@ This effectively helped with fixing some bugs by matching the feature parity of
Additionally, rendered stack traces are ensured to be prefixed with a newline, which used to be a whitespace in earlier versions.
The support for the `\{ansi}` option in exception converters is removed too.
+[#release-notes-2-25-0-instant-format]
+=== Date & time formatting
+
+Historically, Log4j contains custom date & time formatting utilities for performance reasons, i.e., link:javadoc/log4j-core/org/apache/logging/log4j/core/util/datetime/FixedDateFormat.html[`FixedDateFormat`] and link:javadoc/log4j-core/org/apache/logging/log4j/core/util/datetime/FastDateFormat.html[`FastDateFormat`].
+These have been deprecated for removal in favor of Java's https://docs.oracle.com/javase/{java-target-version}/docs/api/java/time/format/DateTimeFormatter.html[`DateTimeFormatter`].
+After upgrading, if you experience any date & time formatting problems (in particular, related with the usage of `n` and `x` directives), please {logging-services-url}/support.html#issues[submit an issue ticket] – as a temporary workaround, you can set xref:manual/systemproperties.adoc#log4j2.instant.formatter[the `log4j2.instant.formatter` property] to `legacy` to switch to the old behaviour.
+
=== ANSI support on Windows
Since 2017, Windows 10 and newer have offered native support for ANSI escapes.
diff --git a/src/changelog/.2.x.x/2936_deprecate_AbstractLogger_checkMessageFactory.xml b/src/changelog/.2.x.x/2936_deprecate_AbstractLogger_checkMessageFactory.xml
index e72a33566a4..d57d4d14282 100644
--- a/src/changelog/.2.x.x/2936_deprecate_AbstractLogger_checkMessageFactory.xml
+++ b/src/changelog/.2.x.x/2936_deprecate_AbstractLogger_checkMessageFactory.xml
@@ -4,5 +4,5 @@
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="deprecated">
- Deprecate `AbstractLogger.checkMessageFactory()`, since all created `Logger`s are already `MessageFactory`-namespaced
+ Deprecate `AbstractLogger.checkMessageFactory()`, since all created ``Logger``s are already `MessageFactory`-namespaced
diff --git a/src/changelog/.2.x.x/3121_deprecate_FixedDateFormat.xml b/src/changelog/.2.x.x/3121_deprecate_FixedDateFormat.xml
new file mode 100644
index 00000000000..f6c318a25f4
--- /dev/null
+++ b/src/changelog/.2.x.x/3121_deprecate_FixedDateFormat.xml
@@ -0,0 +1,8 @@
+
+
+
+ Deprecated `FixedDateTime`, `FastDateTime`, and supporting classes
+
diff --git a/src/changelog/.2.x.x/3121_instant_format.xml b/src/changelog/.2.x.x/3121_instant_format.xml
new file mode 100644
index 00000000000..271c3c3dd7a
--- /dev/null
+++ b/src/changelog/.2.x.x/3121_instant_format.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ Switch to using Java's `DateTimeFormatter` for date & time formatting of log event instants
+
diff --git a/src/changelog/.index.adoc.ftl b/src/changelog/.index.adoc.ftl
index b71f9905f31..24c1ef38957 100644
--- a/src/changelog/.index.adoc.ftl
+++ b/src/changelog/.index.adoc.ftl
@@ -37,7 +37,7 @@
:page-toclevels: 1
[#release-notes]
-= Release Notes
+= Release notes
<#list releases as release><#if release.changelogEntryCount gt 0>
include::_release-notes/${release.version}.adoc[]
diff --git a/src/changelog/2.23.1/fix_StatusLogger_instant_formatting.xml b/src/changelog/2.23.1/fix_StatusLogger_instant_formatting.xml
index 4b965ca3c09..39d948505ac 100644
--- a/src/changelog/2.23.1/fix_StatusLogger_instant_formatting.xml
+++ b/src/changelog/2.23.1/fix_StatusLogger_instant_formatting.xml
@@ -5,9 +5,7 @@
type="fixed">
- Add
- xref:manual/statusLogger.adoc#log4j2.statusLoggerDateFormatZone[`log4j2.statusLoggerDateFormatZone`]
- system property to set the time-zone `StatusLogger` uses to format `java.time.Instant`.
+ Add xref:manual/status-logger.adoc#log4j2.statusLoggerDateFormatZone[`log4j2.statusLoggerDateFormatZone`] system property to set the time-zone `StatusLogger` uses to format `java.time.Instant`.
Without this, formatting patterns accessing to time-zone-specific fields (e.g., year-of-era) cause failures.
diff --git a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc
index 49723986482..b2067b95cba 100644
--- a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc
@@ -385,7 +385,7 @@ See xref:manual/layouts.adoc#LocationInformation[this section of the layouts pag
[#converter-date]
==== Date
-Outputs the date of the log event
+Outputs the instant of the log event
.link:../javadoc/log4j-core/org/apache/logging/log4j/core/pattern/DatePatternConverter.html[`DatePatternConverter`] specifier grammar
[source,text]
@@ -394,7 +394,7 @@ d{pattern}[{timezone}]
date{pattern}[{timezone}]
----
-The date conversion specifier may be followed by a set of braces containing a date and time pattern string per https://docs.oracle.com/javase/{java-target-version}/docs/api/java/text/SimpleDateFormat.html[`SimpleDateFormat`].
+The date conversion specifier may be followed by a set of braces containing a date and time formatting pattern per https://docs.oracle.com/javase/{java-target-version}/docs/api/java/time/format/DateTimeFormatter.html[`DateTimeFormatter`].
The predefined _named_ formats are:
[%header,cols="2m,3m"]
@@ -448,8 +448,8 @@ The predefined _named_ formats are:
|1351866842781
|===
-You can also use a set of braces containing a time zone id per https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/TimeZone.html#getTimeZone(java.lang.String)[`java.util.TimeZone#getTimeZone(String)`].
-If no date format specifier is given then the `DEFAULT` format is used.
+You can also use a set of braces containing a time zone id per https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/TimeZone.html#getTimeZone(java.lang.String)[`TimeZone#getTimeZone(String)`].
+If no date format specifier is given, then the `DEFAULT` format is used.
You can also define custom date formats, see following examples:
@@ -461,36 +461,19 @@ You can also define custom date formats, see following examples:
|%d{HH:mm:ss,SSS}
|14:34:02,123
-|%d{HH:mm:ss,nnnn} to %d{HH:mm:ss,nnnnnnnnn}
-|14:34:02,1234 to 14:34:02,123456789
-
-|%d{dd MMM yyyy HH:mm:ss,SSS}
-|02 Nov 2012 14:34:02,123
-
-|%d{dd MMM yyyy HH:mm:ss,nnnn} to %d{dd MMM yyyy HH:mm:ss,nnnnnnnnn}
-|02 Nov 2012 14:34:02,1234 to 02 Nov 2012 14:34:02,123456789
-
-|%d{HH:mm:ss}{GMT+0}
-|18:34:02
+|%d{yyyy-mm-dd'T'HH:mm:ss.SSS'Z'}\{UTC}
+|2012-11-02T14:34:02.123Z
|===
-`%d\{UNIX}` outputs the UNIX time in seconds.
-`%d\{UNIX_MILLIS}` outputs the UNIX time in milliseconds.
-The `UNIX` time is the difference – in seconds for `UNIX` and in milliseconds for `UNIX_MILLIS` – between the current time and 1970-01-01 00:00:00 (UTC).
-While the time unit is milliseconds, the granularity depends on the platform.
-This is an efficient way to output the event time because only a conversion from `long` to `String` takes place, there is no `Date` formatting involved.
-
-There is also limited support for timestamps more precise than milliseconds when running on Java 9 or later.
-Note that not all
-https://docs.oracle.com/javase/{java-target-version}/docs/api/java/time/format/DateTimeFormatter.html[`DateTimeFormatter`]
-formats are supported.
-Only timestamps in the formats mentioned in the table above may use the _nano-of-second_ pattern letter `n` instead of the _fraction-of-second_ pattern letter `S`.
+`%d\{UNIX}` outputs the epoch time in seconds, i.e., the difference in seconds between the current time and 1970-01-01 00:00:00 (UTC).
+`%d\{UNIX_MILLIS}` outputs the epoch time in milliseconds.
+Note that the granularity of the sub-second formatters depends on the platform.
Users may revert to a millisecond-precision clock when running on Java 9 by setting xref:manual/systemproperties.adoc#log4j2.clock[the `log4j2.clock` system property] to `SystemMillisClock`.
[WARNING]
====
-Only named date formats (`DEFAULT`, `ISO8601`, `UNIX`, `UNIX_MILLIS`, etc.) are garbage-free.
+Except `UNIX` and `UNIX_MILLIS` named patterns, the rest of the date & time formatters are not garbage-free.
====
[#converter-encode]
diff --git a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-core-misc.adoc b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-core-misc.adoc
index 8ac34f1101d..beae1c22810 100644
--- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-core-misc.adoc
+++ b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-core-misc.adoc
@@ -251,6 +251,20 @@ link:../javadoc/log4j-api/org/apache/logging/log4j/message/FlowMessageFactory.ht
implementation to be used by all loggers.
// end::flow-tracing[]
+[id=log4j2.instant.formatter]
+== `log4j2.instant.formatter`
+
+[cols="1h,5"]
+|===
+| Env. variable | `LOG4J_INSTANT_FORMATTER`
+| Type | `String`
+|===
+
+Configures the date & time formatter used for log event instants.
+The following values are accepted:
+
+`legacy`:: Enables the usage of legacy formatters (i.e., link:javadoc/log4j-core/org/apache/logging/log4j/core/util/datetime/FixedDateFormat.html[`FixedDateFormat`] and link:javadoc/log4j-core/org/apache/logging/log4j/core/util/datetime/FastDateFormat.html[`FastDateFormat`])
+
[id=log4j2.loggerContextStacktraceOnStart]
== `log4j2.loggerContextStacktraceOnStart`