From b552557655d7cde33cd71f56dac99f087c5b5225 Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Sat, 28 Jun 2025 13:41:44 +0300 Subject: [PATCH 01/34] Created an enum for choosing a named pattern. Signed-off-by: Roy Ash --- .../core/pattern/DatePatternConverter.java | 70 +++++-------------- .../log4j/core/pattern/NamedPattern.java | 43 ++++++++++++ 2 files changed, 59 insertions(+), 54 deletions(-) create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index f26a6d54c56..a17453480e2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -16,14 +16,6 @@ */ package org.apache.logging.log4j.core.pattern; -import static java.util.Objects.requireNonNull; - -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; -import java.util.stream.Collectors; import org.apache.commons.lang3.time.FastDateFormat; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.Plugin; @@ -37,6 +29,15 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + /** * Converts and formats the event's date in a StringBuilder. */ @@ -140,53 +141,14 @@ static String decodeNamedPattern(final String pattern) { // This is the correct behaviour for `SimpleDateFormat`. // Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. - final boolean compat = InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED; - - switch (pattern) { - case "ABSOLUTE": - return "HH:mm:ss,SSS"; - case "ABSOLUTE_MICROS": - return "HH:mm:ss," + (compat ? "nnnnnn" : "SSSSSS"); - case "ABSOLUTE_NANOS": - return "HH:mm:ss," + (compat ? "nnnnnnnnn" : "SSSSSSSSS"); - case "ABSOLUTE_PERIOD": - return "HH:mm:ss.SSS"; - case "COMPACT": - return "yyyyMMddHHmmssSSS"; - case "DATE": - return "dd MMM yyyy HH:mm:ss,SSS"; - case "DATE_PERIOD": - return "dd MMM yyyy HH:mm:ss.SSS"; - case "DEFAULT": - return "yyyy-MM-dd HH:mm:ss,SSS"; - case "DEFAULT_MICROS": - return "yyyy-MM-dd HH:mm:ss," + (compat ? "nnnnnn" : "SSSSSS"); - case "DEFAULT_NANOS": - return "yyyy-MM-dd HH:mm:ss," + (compat ? "nnnnnnnnn" : "SSSSSSSSS"); - case "DEFAULT_PERIOD": - return "yyyy-MM-dd HH:mm:ss.SSS"; - case "ISO8601_BASIC": - return "yyyyMMdd'T'HHmmss,SSS"; - case "ISO8601_BASIC_PERIOD": - return "yyyyMMdd'T'HHmmss.SSS"; - case "ISO8601": - return "yyyy-MM-dd'T'HH:mm:ss,SSS"; - case "ISO8601_OFFSET_DATE_TIME_HH": - return "yyyy-MM-dd'T'HH:mm:ss,SSS" + (compat ? "X" : "x"); - case "ISO8601_OFFSET_DATE_TIME_HHMM": - return "yyyy-MM-dd'T'HH:mm:ss,SSS" + (compat ? "XX" : "xx"); - case "ISO8601_OFFSET_DATE_TIME_HHCMM": - return "yyyy-MM-dd'T'HH:mm:ss,SSS" + (compat ? "XXX" : "xxx"); - case "ISO8601_PERIOD": - return "yyyy-MM-dd'T'HH:mm:ss.SSS"; - case "ISO8601_PERIOD_MICROS": - return "yyyy-MM-dd'T'HH:mm:ss." + (compat ? "nnnnnn" : "SSSSSS"); - case "US_MONTH_DAY_YEAR2_TIME": - return "dd/MM/yy HH:mm:ss.SSS"; - case "US_MONTH_DAY_YEAR4_TIME": - return "dd/MM/yyyy HH:mm:ss.SSS"; + try { + final NamedPattern namedPattern = NamedPattern.valueOf(pattern); + return InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? + namedPattern.getLegacyPattern() : + namedPattern.getNonLegacyPattern(); + } catch (IllegalArgumentException ignored) { // for Java 22+ it can be changed to `IllegalArgumentException _` + return pattern; } - return pattern; } private static TimeZone readTimeZone(@Nullable final String[] options) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java new file mode 100644 index 00000000000..e5de3451b4b --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java @@ -0,0 +1,43 @@ +package org.apache.logging.log4j.core.pattern; + +@SuppressWarnings("SpellCheckingInspection") +public enum NamedPattern { + ABSOLUTE("HH:mm:ss,SSS", "HH:mm:ss,SSS"), + ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", "HH:mm:ss,SSSSSS"), + ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", "HH:mm:ss,SSSSSSSSS"), + ABSOLUTE_PERIOD("HH:mm:ss.SSS", "HH:mm:ss.SSS"), + COMPACT("yyyyMMddHHmmssSSS", "yyyyMMddHHmmssSSS"), + DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy HH:mm:ss,SSS"), + DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy HH:mm:ss.SSS"), + DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd HH:mm:ss,SSS"), + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd HH:mm:ss,SSSSSS"), + DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd HH:mm:ss,SSSSSSSSS"), + DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss.SSS"), + ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'HHmmss,SSS"), + ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'HHmmss.SSS"), + ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'HH:mm:ss,SSS"), + ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'HH:mm:ss,SSSx"), + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxx"), + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), + ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss.SSS"), + ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), + US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS", "dd/MM/yy HH:mm:ss.SSS"), + US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS", "dd/MM/yyyy HH:mm:ss.SSS"); + + private final String + legacyPattern, + nonLegacyPattern; + + NamedPattern(String legacyPattern, String nonLegacyPattern) { + this.legacyPattern = legacyPattern; + this.nonLegacyPattern = nonLegacyPattern; + } + + public String getLegacyPattern() { + return legacyPattern; + } + + public String getNonLegacyPattern() { + return nonLegacyPattern; + } +} From 9d330d494e9b6912296bea169edb86b01b37fb4a Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Sat, 28 Jun 2025 13:50:09 +0300 Subject: [PATCH 02/34] Applied `spotless:apply` Signed-off-by: Roy Ash --- .../core/pattern/DatePatternConverter.java | 23 ++++++++-------- .../log4j/core/pattern/NamedPattern.java | 27 ++++++++++++++----- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index a17453480e2..ac88a495769 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -16,6 +16,14 @@ */ package org.apache.logging.log4j.core.pattern; +import static java.util.Objects.requireNonNull; + +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.stream.Collectors; import org.apache.commons.lang3.time.FastDateFormat; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.Plugin; @@ -29,15 +37,6 @@ import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; -import java.util.stream.Collectors; - -import static java.util.Objects.requireNonNull; - /** * Converts and formats the event's date in a StringBuilder. */ @@ -143,9 +142,9 @@ static String decodeNamedPattern(final String pattern) { // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. try { final NamedPattern namedPattern = NamedPattern.valueOf(pattern); - return InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? - namedPattern.getLegacyPattern() : - namedPattern.getNonLegacyPattern(); + return InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED + ? namedPattern.getLegacyPattern() + : namedPattern.getNonLegacyPattern(); } catch (IllegalArgumentException ignored) { // for Java 22+ it can be changed to `IllegalArgumentException _` return pattern; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java index e5de3451b4b..a32497046bb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java @@ -1,3 +1,19 @@ +/* + * 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.core.pattern; @SuppressWarnings("SpellCheckingInspection") @@ -17,16 +33,15 @@ public enum NamedPattern { ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'HHmmss.SSS"), ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'HH:mm:ss,SSS"), ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'HH:mm:ss,SSSx"), - ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxx"), - ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), + ISO8601_OFFSET_DATE_TIME_HHMM( + "yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxx"), + ISO8601_OFFSET_DATE_TIME_HHCMM( + "yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss.SSS"), ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS", "dd/MM/yy HH:mm:ss.SSS"), US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS", "dd/MM/yyyy HH:mm:ss.SSS"); - - private final String - legacyPattern, - nonLegacyPattern; + private final String legacyPattern, nonLegacyPattern; NamedPattern(String legacyPattern, String nonLegacyPattern) { this.legacyPattern = legacyPattern; From 8f427a0d77fe320914b2c916590068d7035ca228 Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Sat, 28 Jun 2025 14:19:18 +0300 Subject: [PATCH 03/34] Added changelog entry Signed-off-by: Roy Ash --- .../.2.x.x/exported_named_patterns_into_public_enum.xml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml diff --git a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml new file mode 100644 index 00000000000..377bf063178 --- /dev/null +++ b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml @@ -0,0 +1,7 @@ + + + Exported named-patterns into its own public enum + From b19d830e5c292981e281521c9037f1e1c6173e02 Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Sat, 28 Jun 2025 14:27:24 +0300 Subject: [PATCH 04/34] No need for (another) default hard-coded named pattern Signed-off-by: Roy Ash --- .../logging/log4j/core/pattern/DatePatternConverter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index ac88a495769..88f4588ab0e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -49,8 +49,6 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme private static final String CLASS_NAME = DatePatternConverter.class.getSimpleName(); - private static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS"; - private final InstantFormatter formatter; private DatePatternConverter(@Nullable final String[] options) { @@ -64,7 +62,9 @@ private static InstantFormatter createFormatter(@Nullable final String[] options } catch (final Exception error) { logOptionReadFailure(options, error, "failed for options: {}, falling back to the default instance"); } - return InstantPatternFormatter.newBuilder().setPattern(DEFAULT_PATTERN).build(); + return InstantPatternFormatter.newBuilder() + .setPattern(NamedPattern.DEFAULT.getNonLegacyPattern()) + .build(); } private static InstantFormatter createFormatterUnsafely(@Nullable final String[] options) { @@ -94,7 +94,7 @@ private static InstantFormatter createFormatterUnsafely(@Nullable final String[] private static String readPattern(@Nullable final String[] options) { return options != null && options.length > 0 && options[0] != null ? decodeNamedPattern(options[0]) - : DEFAULT_PATTERN; + : NamedPattern.DEFAULT.getNonLegacyPattern(); } /** From 3df9d887b21d8d628211eb47f31ea5d1b786ed9e Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Sat, 28 Jun 2025 14:42:10 +0300 Subject: [PATCH 05/34] Fixed small bug Signed-off-by: Roy Ash --- .../org/apache/logging/log4j/core/pattern/NamedPattern.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java index a32497046bb..cef77decfb8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java @@ -33,10 +33,8 @@ public enum NamedPattern { ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'HHmmss.SSS"), ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'HH:mm:ss,SSS"), ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'HH:mm:ss,SSSx"), - ISO8601_OFFSET_DATE_TIME_HHMM( - "yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxx"), - ISO8601_OFFSET_DATE_TIME_HHCMM( - "yyyy-MM-dd'T'HH:mm:ss,SSSyyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxx"), + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss.SSS"), ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS", "dd/MM/yy HH:mm:ss.SSS"), From 1966a311c57af5cdf3173e287454be0f6ac6c136 Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Thu, 3 Jul 2025 16:03:09 +0300 Subject: [PATCH 06/34] fixes some of @vy comments Signed-off-by: Roy Ash --- .../core/pattern/DatePatternConverter.java | 40 +------- .../log4j/core/pattern/NamedPattern.java | 98 +++++++++++++------ .../log4j/core/pattern/package-info.java | 2 +- 3 files changed, 71 insertions(+), 69 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index 88f4588ab0e..0a3fb0bf8b6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -63,7 +63,7 @@ private static InstantFormatter createFormatter(@Nullable final String[] options logOptionReadFailure(options, error, "failed for options: {}, falling back to the default instance"); } return InstantPatternFormatter.newBuilder() - .setPattern(NamedPattern.DEFAULT.getNonLegacyPattern()) + .setPattern(NamedPattern.DEFAULT.getPattern()) .build(); } @@ -94,7 +94,7 @@ private static InstantFormatter createFormatterUnsafely(@Nullable final String[] private static String readPattern(@Nullable final String[] options) { return options != null && options.length > 0 && options[0] != null ? decodeNamedPattern(options[0]) - : NamedPattern.DEFAULT.getNonLegacyPattern(); + : NamedPattern.DEFAULT.getPattern(); } /** @@ -109,42 +109,8 @@ private static String readPattern(@Nullable final String[] options) { * @since 2.25.0 */ static String decodeNamedPattern(final String pattern) { - - // If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`. - // Otherwise, we need to produce output aimed for `DateTimeFormatter`. - // In conclusion, we need to check if legacy formatters enabled and apply following transformations. - // - // | Microseconds | Nanoseconds | Time-zone - // ------------------------------+--------------+-------------+----------- - // Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX - // `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx - // - // Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated - // `FixedDateFormat` and `FastDateFormat`. - // These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives: - // - // - They say they adhere to `SimpleDateFormat` specification, but use `n` directive. - // `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions. - // `n` is probably manually introduced by Log4j to support sub-millisecond precisions. - // - // - `n` denotes nano-of-second for `DateTimeFormatter`. - // In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision. - // This is independent of how many times they occur consequently. - // Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length. - // This doesn't work for `DateTimeFormatter`, which needs - // - // - `SSSSSS` for 6-digit microsecond precision - // - `SSSSSSSSS` for 9-digit nanosecond precision - // - // - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`. - // This is the correct behaviour for `SimpleDateFormat`. - // Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. - // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. try { - final NamedPattern namedPattern = NamedPattern.valueOf(pattern); - return InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED - ? namedPattern.getLegacyPattern() - : namedPattern.getNonLegacyPattern(); + return NamedPattern.valueOf(pattern).getPattern(); } catch (IllegalArgumentException ignored) { // for Java 22+ it can be changed to `IllegalArgumentException _` return pattern; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java index cef77decfb8..78343d52668 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java @@ -16,41 +16,77 @@ */ package org.apache.logging.log4j.core.pattern; +import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter; + +// If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`. +// Otherwise, we need to produce output aimed for `DateTimeFormatter`. +// In conclusion, we need to check if legacy formatters enabled and apply following transformations. +// +// | Microseconds | Nanoseconds | Time-zone +// ------------------------------+--------------+-------------+----------- +// Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX +// `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx +// +// Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated +// `FixedDateFormat` and `FastDateFormat`. +// These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives: +// +// - They say they adhere to `SimpleDateFormat` specification, but use `n` directive. +// `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions. +// `n` is probably manually introduced by Log4j to support sub-millisecond precisions. +// +// - `n` denotes nano-of-second for `DateTimeFormatter`. +// In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision. +// This is independent of how many times they occur consequently. +// Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length. +// This doesn't work for `DateTimeFormatter`, which needs +// +// - `SSSSSS` for 6-digit microsecond precision +// - `SSSSSSSSS` for 9-digit nanosecond precision +// +// - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`. +// This is the correct behaviour for `SimpleDateFormat`. +// Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. +// To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. +/** + * Represents named date/time patterns for formatting log timestamps. + * Provides patterns for legacy and modern formatters based on configuration. + */ @SuppressWarnings("SpellCheckingInspection") public enum NamedPattern { - ABSOLUTE("HH:mm:ss,SSS", "HH:mm:ss,SSS"), - ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", "HH:mm:ss,SSSSSS"), - ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", "HH:mm:ss,SSSSSSSSS"), - ABSOLUTE_PERIOD("HH:mm:ss.SSS", "HH:mm:ss.SSS"), - COMPACT("yyyyMMddHHmmssSSS", "yyyyMMddHHmmssSSS"), - DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy HH:mm:ss,SSS"), - DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy HH:mm:ss.SSS"), - DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd HH:mm:ss,SSS"), - DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd HH:mm:ss,SSSSSS"), - DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd HH:mm:ss,SSSSSSSSS"), - DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss.SSS"), - ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'HHmmss,SSS"), - ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'HHmmss.SSS"), - ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'HH:mm:ss,SSS"), - ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'HH:mm:ss,SSSx"), - ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxx"), - ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), - ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss.SSS"), - ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), - US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS", "dd/MM/yy HH:mm:ss.SSS"), - US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS", "dd/MM/yyyy HH:mm:ss.SSS"); - private final String legacyPattern, nonLegacyPattern; - - NamedPattern(String legacyPattern, String nonLegacyPattern) { - this.legacyPattern = legacyPattern; - this.nonLegacyPattern = nonLegacyPattern; - } + ABSOLUTE("HH:mm:ss,SSS"), + ABSOLUTE_MICROS("HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), + ABSOLUTE_NANOS("HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), + ABSOLUTE_PERIOD("HH:mm:ss.SSS"), + COMPACT("yyyyMMddHHmmssSSS"), + DATE("dd MMM yyyy HH:mm:ss,SSS"), + DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS"), + DEFAULT("yyyy-MM-dd HH:mm:ss,SSS"), + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), + DEFAULT_NANOS( + "yyyy-MM-dd HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), + DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS"), + ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS"), + ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS"), + ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS"), + ISO8601_OFFSET_DATE_TIME_HH( + "yyyy-MM-dd'T'HH:mm:ss,SSS" + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "X" : "x")), + ISO8601_OFFSET_DATE_TIME_HHMM( + "yyyy-MM-dd'T'HH:mm:ss,SSS" + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "XX" : "xx")), + ISO8601_OFFSET_DATE_TIME_HHCMM( + "yyyy-MM-dd'T'HH:mm:ss,SSS" + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "XXX" : "xxx")), + ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS"), + ISO8601_PERIOD_MICROS( + "yyyy-MM-dd'T'HH:mm:ss." + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), + US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS"), + US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); + private final String pattern; - public String getLegacyPattern() { - return legacyPattern; + NamedPattern(String pattern) { + this.pattern = pattern; } - public String getNonLegacyPattern() { - return nonLegacyPattern; + public String getPattern() { + return pattern; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java index ac6407f47b3..df5bc576a25 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/package-info.java @@ -18,7 +18,7 @@ * Provides classes implementing format specifiers in conversion patterns. */ @Export -@Version("2.24.1") +@Version("2.26.0") package org.apache.logging.log4j.core.pattern; import org.osgi.annotation.bundle.Export; From 3418e4c0ebebe3e1a20e93dbcadf24e92fcfc916 Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Thu, 3 Jul 2025 17:56:37 +0300 Subject: [PATCH 07/34] changed enum to `NamedDatePattern` Signed-off-by: Roy Ash --- .../logging/log4j/core/pattern/DatePatternConverter.java | 6 +++--- .../pattern/{NamedPattern.java => NamedDatePattern.java} | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/{NamedPattern.java => NamedDatePattern.java} (98%) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index 0a3fb0bf8b6..90c1f589b19 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -63,7 +63,7 @@ private static InstantFormatter createFormatter(@Nullable final String[] options logOptionReadFailure(options, error, "failed for options: {}, falling back to the default instance"); } return InstantPatternFormatter.newBuilder() - .setPattern(NamedPattern.DEFAULT.getPattern()) + .setPattern(NamedDatePattern.DEFAULT.getPattern()) .build(); } @@ -94,7 +94,7 @@ private static InstantFormatter createFormatterUnsafely(@Nullable final String[] private static String readPattern(@Nullable final String[] options) { return options != null && options.length > 0 && options[0] != null ? decodeNamedPattern(options[0]) - : NamedPattern.DEFAULT.getPattern(); + : NamedDatePattern.DEFAULT.getPattern(); } /** @@ -110,7 +110,7 @@ private static String readPattern(@Nullable final String[] options) { */ static String decodeNamedPattern(final String pattern) { try { - return NamedPattern.valueOf(pattern).getPattern(); + return NamedDatePattern.valueOf(pattern).getPattern(); } catch (IllegalArgumentException ignored) { // for Java 22+ it can be changed to `IllegalArgumentException _` return pattern; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java similarity index 98% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java index 78343d52668..2eb87170e2a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java @@ -53,7 +53,7 @@ * Provides patterns for legacy and modern formatters based on configuration. */ @SuppressWarnings("SpellCheckingInspection") -public enum NamedPattern { +public enum NamedDatePattern { ABSOLUTE("HH:mm:ss,SSS"), ABSOLUTE_MICROS("HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), ABSOLUTE_NANOS("HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), @@ -82,7 +82,7 @@ public enum NamedPattern { US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); private final String pattern; - NamedPattern(String pattern) { + NamedDatePattern(String pattern) { this.pattern = pattern; } From 975841394d77e97ba378e8d7e6e50169e52ac109 Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Fri, 4 Jul 2025 11:30:31 +0300 Subject: [PATCH 08/34] now taking all named patterns automatically Signed-off-by: Roy Ash --- .../pattern/DatePatternConverterTestBase.java | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java index 99bd9c706de..a01457ed6e4 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java @@ -26,6 +26,7 @@ import java.util.Calendar; import java.util.Date; import java.util.TimeZone; +import java.util.stream.Stream; import org.apache.logging.log4j.core.AbstractLogEvent; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.time.Instant; @@ -329,29 +330,8 @@ void testNewInstanceAllowsNullParameter() { DatePatternConverter.newInstance(null); // no errors } - private static final String[] PATTERN_NAMES = { - "ABSOLUTE", - "ABSOLUTE_MICROS", - "ABSOLUTE_NANOS", - "ABSOLUTE_PERIOD", - "COMPACT", - "DATE", - "DATE_PERIOD", - "DEFAULT", - "DEFAULT_MICROS", - "DEFAULT_NANOS", - "DEFAULT_PERIOD", - "ISO8601_BASIC", - "ISO8601_BASIC_PERIOD", - "ISO8601", - "ISO8601_OFFSET_DATE_TIME_HH", - "ISO8601_OFFSET_DATE_TIME_HHMM", - "ISO8601_OFFSET_DATE_TIME_HHCMM", - "ISO8601_PERIOD", - "ISO8601_PERIOD_MICROS", - "US_MONTH_DAY_YEAR2_TIME", - "US_MONTH_DAY_YEAR4_TIME" - }; + private static final String[] PATTERN_NAMES = + Stream.of(NamedDatePattern.values()).map(Enum::name).toArray(String[]::new); @Test void testPredefinedFormatWithoutTimezone() { From 0ef914b2a18c48675979c20094a8ceb9a1b5a579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Tue, 8 Jul 2025 10:24:12 +0200 Subject: [PATCH 09/34] Improve style and cross references in docs --- .../core/pattern/DatePatternConverter.java | 2 +- .../log4j/core/pattern/NamedDatePattern.java | 114 +++++++++++------- ...ported_named_patterns_into_public_enum.xml | 5 +- 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index 90c1f589b19..44d6f403fb6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -111,7 +111,7 @@ private static String readPattern(@Nullable final String[] options) { static String decodeNamedPattern(final String pattern) { try { return NamedDatePattern.valueOf(pattern).getPattern(); - } catch (IllegalArgumentException ignored) { // for Java 22+ it can be changed to `IllegalArgumentException _` + } catch (IllegalArgumentException ignored) { return pattern; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java index 2eb87170e2a..6425f6b5621 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java @@ -16,70 +16,92 @@ */ package org.apache.logging.log4j.core.pattern; +import static org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED; + import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter; -// If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`. -// Otherwise, we need to produce output aimed for `DateTimeFormatter`. -// In conclusion, we need to check if legacy formatters enabled and apply following transformations. -// -// | Microseconds | Nanoseconds | Time-zone -// ------------------------------+--------------+-------------+----------- -// Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX -// `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx -// -// Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated -// `FixedDateFormat` and `FastDateFormat`. -// These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives: -// -// - They say they adhere to `SimpleDateFormat` specification, but use `n` directive. -// `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions. -// `n` is probably manually introduced by Log4j to support sub-millisecond precisions. -// -// - `n` denotes nano-of-second for `DateTimeFormatter`. -// In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision. -// This is independent of how many times they occur consequently. -// Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length. -// This doesn't work for `DateTimeFormatter`, which needs -// -// - `SSSSSS` for 6-digit microsecond precision -// - `SSSSSSSSS` for 9-digit nanosecond precision -// -// - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`. -// This is the correct behaviour for `SimpleDateFormat`. -// Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. -// To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. /** - * Represents named date/time patterns for formatting log timestamps. - * Provides patterns for legacy and modern formatters based on configuration. + * Represents named date & time patterns for formatting log timestamps. + * + * @see InstantPatternFormatter#LEGACY_FORMATTERS_ENABLED + * @see DatePatternConverter + * @since 2.26.0 */ -@SuppressWarnings("SpellCheckingInspection") public enum NamedDatePattern { + + // If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`. + // Otherwise, we need to produce output aimed for `DateTimeFormatter`. + // In conclusion, we need to check if legacy formatters enabled and apply following transformations. + // + // | Microseconds | Nanoseconds | Time-zone + // ------------------------------+--------------+-------------+----------- + // Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX + // `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx + // + // Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated + // `FixedDateFormat` and `FastDateFormat`. + // These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives: + // + // - They say they adhere to `SimpleDateFormat` specification, but use `n` directive. + // `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions. + // `n` is probably manually introduced by Log4j to support sub-millisecond precisions. + // + // - `n` denotes nano-of-second for `DateTimeFormatter`. + // In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision. + // This is independent of how many times they occur consequently. + // Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length. + // This doesn't work for `DateTimeFormatter`, which needs + // + // - `SSSSSS` for 6-digit microsecond precision + // - `SSSSSSSSS` for 9-digit nanosecond precision + // + // - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`. + // This is the correct behaviour for `SimpleDateFormat`. + // Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. + // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. + ABSOLUTE("HH:mm:ss,SSS"), - ABSOLUTE_MICROS("HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), - ABSOLUTE_NANOS("HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), + + ABSOLUTE_MICROS("HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), + + ABSOLUTE_NANOS("HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), + ABSOLUTE_PERIOD("HH:mm:ss.SSS"), + COMPACT("yyyyMMddHHmmssSSS"), + DATE("dd MMM yyyy HH:mm:ss,SSS"), + DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS"), + DEFAULT("yyyy-MM-dd HH:mm:ss,SSS"), - DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), - DEFAULT_NANOS( - "yyyy-MM-dd HH:mm:ss," + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), + + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), + + DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), + DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS"), + ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS"), + ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS"), + ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS"), - ISO8601_OFFSET_DATE_TIME_HH( - "yyyy-MM-dd'T'HH:mm:ss,SSS" + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "X" : "x")), - ISO8601_OFFSET_DATE_TIME_HHMM( - "yyyy-MM-dd'T'HH:mm:ss,SSS" + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "XX" : "xx")), - ISO8601_OFFSET_DATE_TIME_HHCMM( - "yyyy-MM-dd'T'HH:mm:ss,SSS" + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "XXX" : "xxx")), + + ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSS" + (LEGACY_FORMATTERS_ENABLED ? "X" : "x")), + + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSS" + (LEGACY_FORMATTERS_ENABLED ? "XX" : "xx")), + + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSS" + (LEGACY_FORMATTERS_ENABLED ? "XXX" : "xxx")), + ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS"), - ISO8601_PERIOD_MICROS( - "yyyy-MM-dd'T'HH:mm:ss." + (InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), + + ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss." + (LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), + US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS"), + US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); + private final String pattern; NamedDatePattern(String pattern) { diff --git a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml index 377bf063178..eaad2b2d133 100644 --- a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml +++ b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml @@ -2,6 +2,7 @@ - Exported named-patterns into its own public enum + type="added"> + + Add and export `org.apache.logging.log4j.core.pattern.NamedDatePattern` class where users can programmatically access named date & time patterns supported by Pattern Layout From 1f4916a0022a481708784cba59403a0bcddf7edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Tue, 8 Jul 2025 10:25:34 +0200 Subject: [PATCH 10/34] Fix XML typo --- .../.2.x.x/exported_named_patterns_into_public_enum.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml index eaad2b2d133..90d5205e4e6 100644 --- a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml +++ b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml @@ -4,5 +4,5 @@ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="added"> - Add and export `org.apache.logging.log4j.core.pattern.NamedDatePattern` class where users can programmatically access named date & time patterns supported by Pattern Layout + Add and export `org.apache.logging.log4j.core.pattern.NamedDatePattern` class where users can programmatically access named date & time patterns supported by Pattern Layout From 4ce71a1bbd5acfc68e4d957bcbddf700dcb9050f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Tue, 8 Jul 2025 10:26:30 +0200 Subject: [PATCH 11/34] Improve changelog description --- .../.2.x.x/exported_named_patterns_into_public_enum.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml index 90d5205e4e6..06a6efb335e 100644 --- a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml +++ b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml @@ -4,5 +4,5 @@ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="added"> - Add and export `org.apache.logging.log4j.core.pattern.NamedDatePattern` class where users can programmatically access named date & time patterns supported by Pattern Layout + Add and export `org.apache.logging.log4j.core.pattern.NamedDatePattern` enabling users to programmatically access named date & time patterns supported by Pattern Layout From 2c51fdd73b4b89ba1236bdfae20c97cf71fcf021 Mon Sep 17 00:00:00 2001 From: Johan Compagner Date: Mon, 30 Jun 2025 18:57:18 +0200 Subject: [PATCH 12/34] introduced a jakarta support range for the bnd tool (#3791) Fixes #3787 --- log4j-jakarta-web/pom.xml | 3 +++ ...rta-web_relax_the_import_package_constraints.xml | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/changelog/.2.x.x/3787_jakarta-web_relax_the_import_package_constraints.xml diff --git a/log4j-jakarta-web/pom.xml b/log4j-jakarta-web/pom.xml index 9dc66676119..4c5f33a4db8 100644 --- a/log4j-jakarta-web/pom.xml +++ b/log4j-jakarta-web/pom.xml @@ -39,6 +39,9 @@ --> org.apache.logging.log4j.web org.apache.logging.log4j.core + [5.0,7) + jakarta.servlet;version="${jakarta.support.range}", + jakarta.servlet.http;version="${jakarta.support.range}" diff --git a/src/changelog/.2.x.x/3787_jakarta-web_relax_the_import_package_constraints.xml b/src/changelog/.2.x.x/3787_jakarta-web_relax_the_import_package_constraints.xml new file mode 100644 index 00000000000..9602fb8af87 --- /dev/null +++ b/src/changelog/.2.x.x/3787_jakarta-web_relax_the_import_package_constraints.xml @@ -0,0 +1,13 @@ + + + + + add a bnd tool property that makes sure that the supported jakarta version has a wider range then 5 to 6 + Support the current release version for 5 to 7 + + From b1c0f4ecb2ccd5ab89003df21884a0584d7ba17b Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 3 Jul 2025 16:02:29 +0200 Subject: [PATCH 13/34] doc: Clarify and enhance `GraalVmProcessor` configuration example (#3786) This update refines the `GraalVmProcessor` configuration example in response to feedback from #3755. The goal is to make the example more reliable and aligned with common Maven project setups. Key improvements include: * **Ensuring consistent parameter usage:** The example now adds `-Alog4j.graalvm.groupId` and `-Alog4j.graalvm.artifactId` to **all executions** of the Maven Compiler Plugin. This guarantees correct behavior regardless of the execution ID used in user projects. * **Simplifying plugin execution setup:** Instead of introducing a dedicated `generate-log4j-plugin-descriptor` execution, the example now modifies the existing `default-compile` execution. This reflects the most typical use case, where annotation processing and compilation occur together. These changes aim to make the setup easier to adopt while reducing configuration errors. --- .../modules/ROOT/pages/manual/plugins.adoc | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/site/antora/modules/ROOT/pages/manual/plugins.adoc b/src/site/antora/modules/ROOT/pages/manual/plugins.adoc index 233b6fd3226..6b751f07366 100644 --- a/src/site/antora/modules/ROOT/pages/manual/plugins.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/plugins.adoc @@ -211,8 +211,8 @@ annotation processor creates such a file at compile-time. [WARNING] ==== -The `GraalVmProcessor` needs to know the `groupId` and `artifactId` coordinates of your project. -These must be supplied to the processor using the `log4j.graalvm.groupId` and `log4j.graalvm.artifactId` annotation processor options. +The `GraalVmProcessor` requires your project's `groupId` and `artifactId` to correctly generate the GraalVM reachability metadata file in the recommended location. +Provide these values to the processor using the `log4j.graalvm.groupId` and `log4j.graalvm.artifactId` annotation processor options. ==== You need to configure your build tool as follows to use both plugin processors: @@ -227,15 +227,23 @@ Maven:: org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} + + + + -Alog4j.graalvm.groupId=${project.groupId} + -Alog4j.graalvm.artifactId=${project.artifactId} + + - generate-log4j-plugin-descriptor - - compile - - process-classes + + default-compile - only org.apache.logging.log4j.core.config.plugins.processor.GraalVmProcessor - - - -Alog4j.graalvm.groupId=${project.groupId} - -Alog4j.graalvm.artifactId=${project.artifactId} - From 087751507504ad0affb93e460ee7409b717853d5 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 3 Jul 2025 16:55:32 +0200 Subject: [PATCH 14/34] fix: Fix Java type for annotated array parameters (#3797) This update fixes the GraalVM reachability metadata generation for methods with annotated array parameters, such as `@Nullable String[]`. Previously, the code computed the fully qualified class name for the parameter using the **raw** type, which retained the annotations (e.g., `@org.jspecify.annotations.Nullable java.lang.String`). This resulted in incorrect metadata that was ignored by GraalVM. The issue is resolved by transforming the `DeclaredType` into a `TypeElement`, effectively removing any annotations, and then calling `getQualifiedName()` to correctly generate the fully qualified class name without annotations. --- .../processor/GraalVmProcessorTest.java | 24 ++++++++-- .../java/FakeConverter.java | 46 +++++++++++++++++++ .../plugins/processor/GraalVmProcessor.java | 4 +- .../3796_annotated-array-parameters.xml | 12 +++++ 4 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeConverter.java create mode 100644 src/changelog/.2.x.x/3796_annotated-array-parameters.xml diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java index 9bdbcad8dd9..957bb3228cd 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessorTest.java @@ -103,6 +103,18 @@ class GraalVmProcessorTest { private static final Object FAKE_CONSTRAINT_VALIDATOR = onlyNoArgsConstructor(FAKE_CONSTRAINT_VALIDATOR_NAME); private static final String FAKE_PLUGIN_VISITOR_NAME = "example.FakeAnnotations$FakePluginVisitor"; private static final Object FAKE_PLUGIN_VISITOR = onlyNoArgsConstructor(FAKE_PLUGIN_VISITOR_NAME); + private static final String FAKE_CONVERTER_NAME = "example.FakeConverter"; + private static final Object FAKE_CONVERTER = asMap( + "name", + FAKE_CONVERTER_NAME, + "methods", + singletonList(asMap( + "name", + "newInstance", + "parameterTypes", + asList("org.apache.logging.log4j.core.config.Configuration", "java.lang.String[]"))), + "fields", + emptyList()); private static final String GROUP_ID = "groupId"; private static final String ARTIFACT_ID = "artifactId"; @@ -155,7 +167,8 @@ static Stream containsSpecificEntries() { Arguments.of(FAKE_PLUGIN_BUILDER_NAME, FAKE_PLUGIN_BUILDER), Arguments.of(FAKE_PLUGIN_NESTED_NAME, FAKE_PLUGIN_NESTED), Arguments.of(FAKE_CONSTRAINT_VALIDATOR_NAME, FAKE_CONSTRAINT_VALIDATOR), - Arguments.of(FAKE_PLUGIN_VISITOR_NAME, FAKE_PLUGIN_VISITOR)); + Arguments.of(FAKE_PLUGIN_VISITOR_NAME, FAKE_PLUGIN_VISITOR), + Arguments.of(FAKE_CONVERTER_NAME, FAKE_CONVERTER)); } @ParameterizedTest @@ -168,7 +181,9 @@ void containsSpecificEntries(String className, Object expectedJson) throws IOExc assertThatJson(reachabilityMetadata) .inPath(String.format("$[?(@.name == '%s')]", className)) .isArray() - .contains(json(expectedJson)); + .hasSize(1) + .first() + .isEqualTo(json(expectedJson)); } static Stream reachabilityMetadataPath() { @@ -214,7 +229,7 @@ void whenNoGroupIdAndArtifactId_thenWarningIsPrinted(@TempDir(cleanup = CleanupM } // The generated folder name should be deterministic and based solely on the descriptor content. // If the descriptor changes, this test and the expected folder name must be updated accordingly. - assertThat(reachabilityMetadataFolders).hasSize(1).containsExactly(path.resolve("62162090")); + assertThat(reachabilityMetadataFolders).hasSize(1).containsExactly(path.resolve("72c240aa")); assertThat(reachabilityMetadataFolders.get(0).resolve("reflect-config.json")) .as("Reachability metadata file") .exists(); @@ -250,7 +265,6 @@ private static List generateDescriptor( } // Compile the sources - final Path descriptorFilePath = outputDir.resolve("plugins.xml"); final DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); final JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, sources); @@ -260,6 +274,8 @@ private static List generateDescriptor( return diagnosticCollector.getDiagnostics().stream() .filter(d -> d.getKind() != Diagnostic.Kind.NOTE) .map(d -> d.getMessage(Locale.ROOT)) + // This message appears when the test runs on JDK 8 + .filter(m -> !"unknown enum constant java.lang.annotation.ElementType.MODULE".equals(m)) .collect(Collectors.toList()); } } diff --git a/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeConverter.java b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeConverter.java new file mode 100644 index 00000000000..0b15cb4ecc1 --- /dev/null +++ b/log4j-core-test/src/test/resources/GraalVmProcessorTest/java/FakeConverter.java @@ -0,0 +1,46 @@ +/* + * 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 example; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +@Plugin(name = "FakePatternConverter", category = PatternConverter.CATEGORY) +@ConverterKeys({"f", "fake"}) +public final class FakeConverter extends LogEventPatternConverter { + + private FakeConverter(@Nullable final Configuration config, @Nullable final String[] options) { + super("Fake", "fake"); + } + + public static FakeConverter newInstance( + @Nullable final Configuration config, @Nullable final String[] options) { + return new FakeConverter(config, options); + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + toAppendTo.append("FakeConverter: ").append(event.getMessage().getFormattedMessage()); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java index e6178f2a17e..a5ba7ba61c5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/GraalVmProcessor.java @@ -300,7 +300,9 @@ public String visitArray(final ArrayType t, @Nullable Void unused) { @Override public @Nullable String visitDeclared(final DeclaredType t, final Void unused) { - return processingEnv.getTypeUtils().erasure(t).toString(); + return safeCast(t.asElement(), TypeElement.class) + .getQualifiedName() + .toString(); } }, null); diff --git a/src/changelog/.2.x.x/3796_annotated-array-parameters.xml b/src/changelog/.2.x.x/3796_annotated-array-parameters.xml new file mode 100644 index 00000000000..5be57fe6418 --- /dev/null +++ b/src/changelog/.2.x.x/3796_annotated-array-parameters.xml @@ -0,0 +1,12 @@ + + + + + Fix GraalVM reachability metadata generation for methods with annotated array type parameters, such as `@Nullable String[]`. + + From 46a5fe2d70db0494b25ef2cc1e9a372e8889a73e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 22:49:21 +0200 Subject: [PATCH 15/34] Bump com.fasterxml.jackson:jackson-bom from 2.19.0 to 2.19.1 in /log4j-parent (#3745) * Update `com.fasterxml.jackson:jackson-bom` to version `2.19.1` (#3745) * Empty commit to trigger required checks --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Piotr P. Karwasz --- log4j-parent/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index f4e56806a27..4bfd9f733af 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -88,7 +88,7 @@ 3.0 2.2.2 2.7.4 - 2.19.0 + 2.19.1 2.1.3 2.1.3 diff --git a/pom.xml b/pom.xml index a6be2bd0295..f10bb2f171f 100644 --- a/pom.xml +++ b/pom.xml @@ -345,7 +345,7 @@ 1.2.21 4.0.0 1.11.0 - 2.19.0 + 2.19.1 1.6.2 4.0.5 18.3.12 From 8afe8925d8467a206d099cddcfe50dec8c079a59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 21:11:10 +0000 Subject: [PATCH 16/34] Update `org.junit:junit-bom` to version `5.13.2` (#3782) Co-authored-by: ASF Logging Services RM --- log4j-parent/pom.xml | 2 +- src/changelog/.2.x.x/update_org_junit_junit_bom.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/update_org_junit_junit_bom.xml diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 4bfd9f733af..d3ad93980cf 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -111,7 +111,7 @@ 1.37 1.0.0 4.13.2 - 5.13.1 + 5.13.2 1.9.1 3.9.1 0.2.0 diff --git a/src/changelog/.2.x.x/update_org_junit_junit_bom.xml b/src/changelog/.2.x.x/update_org_junit_junit_bom.xml new file mode 100644 index 00000000000..5c572a50a46 --- /dev/null +++ b/src/changelog/.2.x.x/update_org_junit_junit_bom.xml @@ -0,0 +1,8 @@ + + + + Update `org.junit:junit-bom` to version `5.13.2` + From 7f2fbe3aa7e6267ef73c728c8ae65a15b3438d1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 21:11:24 +0000 Subject: [PATCH 17/34] Update `com.github.luben:zstd-jni` to version `1.5.7-4` (#3799) Co-authored-by: ASF Logging Services RM --- log4j-parent/pom.xml | 2 +- src/changelog/.2.x.x/update_com_github_luben_zstd_jni.xml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/update_com_github_luben_zstd_jni.xml diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index d3ad93980cf..7da1ce0fafe 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -141,7 +141,7 @@ 2.35.2 2.10.3 1.10 - 1.5.7-3 + 1.5.7-4 + + + + Fix timestamp formatting concurrency issue, when `log4j2.enabledThreadlocals` is `true`. + + From b408f815974b3bcf29e92296441f9eba4f478330 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Fri, 4 Jul 2025 18:43:08 +0200 Subject: [PATCH 19/34] fix: Add GraalVM reachability metadata for non-plugin class instantiations (#3800) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `GraalVmProcessor` currently only generates metadata for Log4j plugins, overlooking other reflection usages in Log4j Core. This change adds reachability metadata for additional reflection cases to improve compatibility with GraalVM native images. ### Fixed Cases This PR addresses the following reflective instantiations: * **Context selectors and reliability strategies:** Ensures proper instantiation under GraalVM. * **`Configuration` instantiation in `DefaultConfigurationBuilder`:** Fixes support for the `log4j2.properties` configuration format when running on GraalVM. * **`BlockingQueue` instantiation in JSON Template Layout:** Enables GraalVM compatibility. Note: `MpmcArrayQueue` is not supported on GraalVM and must be fixed in the JCTools project. ### Known Limitations (Explicitly Ignored) The following cases are *not* addressed in this PR: * **JMX classes in `log4j-1.2-api`:** While GraalVM supports JMX, usage of Log4j 1.x’s JMX interface is likely minimal. * **`MulticastDnsAdvertiser`:** This feature is probably unused and could pose a security risk by advertising log file locations via mDNS. --- .../log4j-api/resource-config.json | 2 +- .../log4j-core/reflect-config.json | 120 ++++++++++++++++++ .../log4j-core/resource-config.json | 2 +- .../reflect-config.json | 24 ++++ .../resource-config.json | 1 + .../.2.x.x/3800_graalvm-misc-reflection.xml | 12 ++ 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/reflect-config.json create mode 100644 log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/reflect-config.json create mode 100644 src/changelog/.2.x.x/3800_graalvm-misc-reflection.xml diff --git a/log4j-api/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-api/resource-config.json b/log4j-api/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-api/resource-config.json index 1649e6be1ef..9b4eb75dc63 100644 --- a/log4j-api/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-api/resource-config.json +++ b/log4j-api/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-api/resource-config.json @@ -6,4 +6,4 @@ } ] } -} \ No newline at end of file +} diff --git a/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/reflect-config.json b/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/reflect-config.json new file mode 100644 index 00000000000..5411a855b98 --- /dev/null +++ b/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/reflect-config.json @@ -0,0 +1,120 @@ +[ + { + "name": "org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.apache.logging.log4j.core.LoggerContext", + "org.apache.logging.log4j.core.config.ConfigurationSource", + "org.apache.logging.log4j.core.config.builder.api.Component" + ] + } + ], + "fields": [] + }, + { + "name": "org.apache.logging.log4j.core.config.properties.PropertiesConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.apache.logging.log4j.core.LoggerContext", + "org.apache.logging.log4j.core.config.ConfigurationSource", + "org.apache.logging.log4j.core.config.builder.api.Component" + ] + } + ], + "fields": [] + }, + { + "name": "org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.selector.BasicContextSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.selector.JndiContextSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.selector.ClassLoaderContextSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.osgi.BundleContextSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.config.LockingReliabilityStrategy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.config.AwaitUnconditionallyReliabilityStrategy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.config.DefaultReliabilityStrategy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + } +] diff --git a/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/resource-config.json b/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/resource-config.json index d5b51667ed9..9f1049ed8ba 100644 --- a/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/resource-config.json +++ b/log4j-core/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-core/resource-config.json @@ -9,4 +9,4 @@ } ] } -} \ No newline at end of file +} diff --git a/log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/reflect-config.json b/log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/reflect-config.json new file mode 100644 index 00000000000..c4e3872cd69 --- /dev/null +++ b/log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/reflect-config.json @@ -0,0 +1,24 @@ +[ + { + "name": "org.jctools.queues.MpmcArrayQueue", + "methods": [ + { + "name": "", + "parameterTypes": [ + "int" + ] + } + ] + }, + { + "name": "java.util.concurrent.ArrayBlockingQueue", + "methods": [ + { + "name": "", + "parameterTypes": [ + "int" + ] + } + ] + } +] diff --git a/log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/resource-config.json b/log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/resource-config.json index c8afe14794a..55c503ebebc 100644 --- a/log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/resource-config.json +++ b/log4j-layout-template-json/src/main/resources/META-INF/native-image/org.apache.logging.log4j/log4j-layout-template-json/resource-config.json @@ -7,3 +7,4 @@ ] } } + diff --git a/src/changelog/.2.x.x/3800_graalvm-misc-reflection.xml b/src/changelog/.2.x.x/3800_graalvm-misc-reflection.xml new file mode 100644 index 00000000000..3e6653c121f --- /dev/null +++ b/src/changelog/.2.x.x/3800_graalvm-misc-reflection.xml @@ -0,0 +1,12 @@ + + + + + Resolves `PropertiesConfiguration` compatibility issues with GraalVM and addresses additional minor reflection-related problems. + + From ea6deb77d59757aca8e50804bc8b255970a04067 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Fri, 4 Jul 2025 20:33:11 +0200 Subject: [PATCH 20/34] fix: Add `resource:` protocol to allowed URL schemes by default (#3795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Add `resource:` protocol to allowed URL schemes by default This update includes `resource:` in the list of allowed URL schemes for retrieving configuration files. See [`log4j2.configurationAllowedProtocols`](https://logging.apache.org/log4j/2.x/manual/systemproperties.html#log4j2.configurationAllowedProtocols) Currently, the `resource:` protocol is used exclusively by a `URLStreamHandler` that retrieves files from the embedded resources in a GraalVM native image. This makes it a secure and appropriate source for trusted configuration files. This change cannot be easily and reliably tested through a unit test. An integration test will be provided in apache/logging-log4j-samples#345 Closes #3790 * fix: Add `resource` protocol only in native images This change introduces an internal `SystemUtils.isGraalVm()` method to detect the presence of GraalVM and enable the `resource` protocol. * Reword changelog entry --------- Co-authored-by: Volkan Yazıcı --- .../log4j/core/net/UrlConnectionFactory.java | 20 ++++++++++++++++++- .../log4j/core/util/internal/SystemUtils.java | 16 +++++++++++++++ .../.2.x.x/3790_allow-resource-protocol.xml | 12 +++++++++++ .../properties-transport-security.adoc | 14 ++++++++++--- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 src/changelog/.2.x.x/3790_allow-resource-protocol.xml diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java index e6ba2a13665..e98d3d9a9db 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java @@ -33,6 +33,7 @@ import org.apache.logging.log4j.core.net.ssl.SslConfiguration; import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory; import org.apache.logging.log4j.core.util.AuthorizationProvider; +import org.apache.logging.log4j.core.util.internal.SystemUtils; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.Strings; @@ -51,7 +52,24 @@ public class UrlConnectionFactory { private static final String HTTP = "http"; private static final String HTTPS = "https"; private static final String JAR = "jar"; - private static final String DEFAULT_ALLOWED_PROTOCOLS = "https, file, jar"; + /** + * Default list of protocols that are allowed to be used for configuration files and other trusted resources. + *

+ * By default, we trust the following protocols: + *

+ *
file
+ *
Local files
+ *
https
+ *
Resources retrieved through TLS to guarantee their integrity
+ *
jar
+ *
Resources retrieved from JAR files
+ *
resource
+ *
Resources embedded in a GraalVM native image
+ *
+ */ + private static final String DEFAULT_ALLOWED_PROTOCOLS = + SystemUtils.isGraalVm() ? "file, https, jar, resource" : "file, https, jar"; + private static final String NO_PROTOCOLS = "_none"; public static final String ALLOWED_PROTOCOLS = "log4j2.Configuration.allowedProtocols"; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/SystemUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/SystemUtils.java index b7b3d9cbb4d..23d60ce688a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/SystemUtils.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/SystemUtils.java @@ -36,5 +36,21 @@ public static boolean isOsAndroid() { return getJavaVendor().contains("Android"); } + /** + * Checks if the current runtime is GraalVM. + *

+ * See ImageInfo.PROPERTY_IMAGE_CODE_KEY. + *

+ * @return true if the current runtime is GraalVM, false otherwise. + */ + public static boolean isGraalVm() { + try { + return System.getProperty("org.graalvm.nativeimage.imagecode") != null; + } catch (final SecurityException e) { + LOGGER.debug("Unable to determine if the current runtime is GraalVM.", e); + return false; + } + } + private SystemUtils() {} } diff --git a/src/changelog/.2.x.x/3790_allow-resource-protocol.xml b/src/changelog/.2.x.x/3790_allow-resource-protocol.xml new file mode 100644 index 00000000000..ffa3c80e305 --- /dev/null +++ b/src/changelog/.2.x.x/3790_allow-resource-protocol.xml @@ -0,0 +1,12 @@ + + + + + Allow `resource:` protocol for configuration files by default, if the current runtime is GraalVM. + + diff --git a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-transport-security.adoc b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-transport-security.adoc index 445c9ec5418..3c662698c95 100644 --- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-transport-security.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-transport-security.adoc @@ -21,9 +21,17 @@ [cols="1h,5"] |=== -| Env. variable | `LOG4J_CONFIGURATION_ALLOWED_PROTOCOLS` -| Type | Comma-separated list of https://docs.oracle.com/javase/{java-target-version}/docs/api/java/net/URL.html[`URL`] protocols -| Default value | `file, https, jar` +| Env. variable +| `LOG4J_CONFIGURATION_ALLOWED_PROTOCOLS` + +| Type +| Comma-separated list of https://docs.oracle.com/javase/{java-target-version}/docs/api/java/net/URL.html[`URL`] protocols + +| Default value +| +`file, https, jar` (JVM) + +`file, https, jar, resource` (GraalVM) |=== A comma separated list of https://docs.oracle.com/javase/{java-target-version}/docs/api/java/net/URL.html[`URL`] protocols that may be used to load any kind of configuration source. From d1e3cb53e2bd9d50cf659b050f6584fe568b8733 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:59:03 +0200 Subject: [PATCH 21/34] Bump org.junit:junit-bom from 5.13.2 to 5.13.3 in /log4j-parent (#3806) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 5.13.2 to 5.13.3. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.2...r5.13.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-version: 5.13.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- log4j-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 7da1ce0fafe..8302fab4d2f 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -111,7 +111,7 @@ 1.37 1.0.0 4.13.2 - 5.13.2 + 5.13.3 1.9.1 3.9.1 0.2.0 From 94e060d09a4d9b1cf411701a28cd4a42d002cb56 Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 9 Jul 2025 11:17:44 +0300 Subject: [PATCH 22/34] Apply suggestion from @ppkarwasz Co-authored-by: Piotr P. Karwasz --- .../org/apache/logging/log4j/core/pattern/NamedDatePattern.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java index 6425f6b5621..2d974e98d09 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java @@ -21,7 +21,7 @@ import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter; /** - * Represents named date & time patterns for formatting log timestamps. + * Represents named date & time patterns for formatting log timestamps. * * @see InstantPatternFormatter#LEGACY_FORMATTERS_ENABLED * @see DatePatternConverter From 66c83825aa461b1086f817d58891613b41814af4 Mon Sep 17 00:00:00 2001 From: Roy Date: Wed, 9 Jul 2025 12:41:14 +0000 Subject: [PATCH 23/34] implemented @ppkarwasz & @vy chamges regarding `FixedDateFormat.FixedFormat` --- .../pattern/DatePatternConverterTestBase.java | 2 +- .../core/pattern/DatePatternConverter.java | 38 +++++- .../log4j/core/pattern/NamedDatePattern.java | 114 ------------------ .../core/pattern/NamedInstantPattern.java | 78 ++++++++++++ ...ported_named_patterns_into_public_enum.xml | 2 +- 5 files changed, 115 insertions(+), 119 deletions(-) delete mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java index a01457ed6e4..16fd89ac30b 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java @@ -331,7 +331,7 @@ void testNewInstanceAllowsNullParameter() { } private static final String[] PATTERN_NAMES = - Stream.of(NamedDatePattern.values()).map(Enum::name).toArray(String[]::new); + Stream.of(NamedInstantPattern.values()).map(Enum::name).toArray(String[]::new); @Test void testPredefinedFormatWithoutTimezone() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index 44d6f403fb6..7e173b33155 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -63,7 +63,7 @@ private static InstantFormatter createFormatter(@Nullable final String[] options logOptionReadFailure(options, error, "failed for options: {}, falling back to the default instance"); } return InstantPatternFormatter.newBuilder() - .setPattern(NamedDatePattern.DEFAULT.getPattern()) + .setPattern(NamedInstantPattern.DEFAULT.getPattern()) .build(); } @@ -94,7 +94,7 @@ private static InstantFormatter createFormatterUnsafely(@Nullable final String[] private static String readPattern(@Nullable final String[] options) { return options != null && options.length > 0 && options[0] != null ? decodeNamedPattern(options[0]) - : NamedDatePattern.DEFAULT.getPattern(); + : NamedInstantPattern.DEFAULT.getPattern(); } /** @@ -109,8 +109,40 @@ private static String readPattern(@Nullable final String[] options) { * @since 2.25.0 */ static String decodeNamedPattern(final String pattern) { + // If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`. + // Otherwise, we need to produce output aimed for `DateTimeFormatter`. + // In conclusion, we need to check if legacy formatters enabled and apply following transformations. + // + // | Microseconds | Nanoseconds | Time-zone + // ------------------------------+--------------+-------------+----------- + // Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX + // `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx + // + // Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated + // `FixedDateFormat` and `FastDateFormat`. + // These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives: + // + // - They say they adhere to `SimpleDateFormat` specification, but use `n` directive. + // `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions. + // `n` is probably manually introduced by Log4j to support sub-millisecond precisions. + // + // - `n` denotes nano-of-second for `DateTimeFormatter`. + // In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision. + // This is independent of how many times they occur consequently. + // Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length. + // This doesn't work for `DateTimeFormatter`, which needs + // + // - `SSSSSS` for 6-digit microsecond precision + // - `SSSSSSSSS` for 9-digit nanosecond precision + // + // - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`. + // This is the correct behaviour for `SimpleDateFormat`. + // Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. + // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. try { - return NamedDatePattern.valueOf(pattern).getPattern(); + return InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED + ? FixedDateFormat.FixedFormat.valueOf(pattern).getPattern() + : NamedInstantPattern.valueOf(pattern).getPattern(); } catch (IllegalArgumentException ignored) { return pattern; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.java deleted file mode 100644 index 2d974e98d09..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedDatePattern.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.core.pattern; - -import static org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED; - -import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter; - -/** - * Represents named date & time patterns for formatting log timestamps. - * - * @see InstantPatternFormatter#LEGACY_FORMATTERS_ENABLED - * @see DatePatternConverter - * @since 2.26.0 - */ -public enum NamedDatePattern { - - // If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`. - // Otherwise, we need to produce output aimed for `DateTimeFormatter`. - // In conclusion, we need to check if legacy formatters enabled and apply following transformations. - // - // | Microseconds | Nanoseconds | Time-zone - // ------------------------------+--------------+-------------+----------- - // Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX - // `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx - // - // Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated - // `FixedDateFormat` and `FastDateFormat`. - // These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives: - // - // - They say they adhere to `SimpleDateFormat` specification, but use `n` directive. - // `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions. - // `n` is probably manually introduced by Log4j to support sub-millisecond precisions. - // - // - `n` denotes nano-of-second for `DateTimeFormatter`. - // In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision. - // This is independent of how many times they occur consequently. - // Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length. - // This doesn't work for `DateTimeFormatter`, which needs - // - // - `SSSSSS` for 6-digit microsecond precision - // - `SSSSSSSSS` for 9-digit nanosecond precision - // - // - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`. - // This is the correct behaviour for `SimpleDateFormat`. - // Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. - // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. - - ABSOLUTE("HH:mm:ss,SSS"), - - ABSOLUTE_MICROS("HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), - - ABSOLUTE_NANOS("HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), - - ABSOLUTE_PERIOD("HH:mm:ss.SSS"), - - COMPACT("yyyyMMddHHmmssSSS"), - - DATE("dd MMM yyyy HH:mm:ss,SSS"), - - DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS"), - - DEFAULT("yyyy-MM-dd HH:mm:ss,SSS"), - - DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), - - DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss," + (LEGACY_FORMATTERS_ENABLED ? "nnnnnnnnn" : "SSSSSSSSS")), - - DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS"), - - ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS"), - - ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS"), - - ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS"), - - ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSS" + (LEGACY_FORMATTERS_ENABLED ? "X" : "x")), - - ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSS" + (LEGACY_FORMATTERS_ENABLED ? "XX" : "xx")), - - ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSS" + (LEGACY_FORMATTERS_ENABLED ? "XXX" : "xxx")), - - ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS"), - - ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss." + (LEGACY_FORMATTERS_ENABLED ? "nnnnnn" : "SSSSSS")), - - US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS"), - - US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); - - private final String pattern; - - NamedDatePattern(String pattern) { - this.pattern = pattern; - } - - public String getPattern() { - return pattern; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java new file mode 100644 index 00000000000..1c3bd14b484 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java @@ -0,0 +1,78 @@ +/* + * 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.core.pattern; + +/** + * Represents named date & time patterns for formatting log timestamps. + * + * @see DatePatternConverter + * @since 2.26.0 + */ +public enum NamedInstantPattern { + + ABSOLUTE("HH:mm:ss,SSS"), + + ABSOLUTE_MICROS("HH:mm:ss,SSSSSS"), + + ABSOLUTE_NANOS("HH:mm:ss,SSSSSSSSS"), + + ABSOLUTE_PERIOD("HH:mm:ss.SSS"), + + COMPACT("yyyyMMddHHmmssSSS"), + + DATE("dd MMM yyyy HH:mm:ss,SSS"), + + DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS"), + + DEFAULT("yyyy-MM-dd HH:mm:ss,SSS"), + + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,SSSSSS"), + + DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,SSSSSSSSS"), + + DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS"), + + ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS"), + + ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS"), + + ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS"), + + ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSx"), + + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSxx"), + + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), + + ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS"), + + ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), + + US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS"), + + US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); + + private final String pattern; + + NamedInstantPattern(String pattern) { + this.pattern = pattern; + } + + public String getPattern() { + return pattern; + } +} diff --git a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml index 06a6efb335e..20fd32265d6 100644 --- a/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml +++ b/src/changelog/.2.x.x/exported_named_patterns_into_public_enum.xml @@ -4,5 +4,5 @@ xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="added"> - Add and export `org.apache.logging.log4j.core.pattern.NamedDatePattern` enabling users to programmatically access named date & time patterns supported by Pattern Layout + Add and export `org.apache.logging.log4j.core.pattern.NamedInstantPattern` enabling users to programmatically access named date & time patterns supported by Pattern Layout From ee48520048aaca0bea44606f4cb661a86b91d053 Mon Sep 17 00:00:00 2001 From: Roy Ash Date: Wed, 9 Jul 2025 16:25:44 +0300 Subject: [PATCH 24/34] Added a JavaDoc for `NamedInstantPattern#getPattern` Signed-off-by: Roy Ash --- .../logging/log4j/core/pattern/NamedInstantPattern.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java index 1c3bd14b484..d5b75c6c057 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java @@ -23,7 +23,6 @@ * @since 2.26.0 */ public enum NamedInstantPattern { - ABSOLUTE("HH:mm:ss,SSS"), ABSOLUTE_MICROS("HH:mm:ss,SSSSSS"), @@ -72,6 +71,9 @@ public enum NamedInstantPattern { this.pattern = pattern; } + /** + * @return pattern that is compatible with {@link java.time.format.DateTimeFormatter} + */ public String getPattern() { return pattern; } From 8066ba6869e25e5d2c0abcdc892efad205316345 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 9 Jul 2025 21:21:07 +0200 Subject: [PATCH 25/34] fix: expose legacy `FixedFormat` patterns via `NamedInstantPattern` To decouple `DatePatternConverter` from `FixedFormat`, legacy pattern values are now provided through the `NamedInstantPattern.getLegacyPattern` method. These patterns are specific to `FixedDateFormat` and not intended for general use, so the method is kept package-private. --- .../core/pattern/NamedInstantPatternTest.java | 45 +++++++++++++++++++ .../core/pattern/DatePatternConverter.java | 41 +++++------------ .../core/pattern/NamedInstantPattern.java | 41 +++++++++++++---- 3 files changed, 87 insertions(+), 40 deletions(-) create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java new file mode 100644 index 00000000000..bbe5e6e45eb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java @@ -0,0 +1,45 @@ +/* + * 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.core.pattern; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.util.internal.instant.InstantPatternFormatter; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class NamedInstantPatternTest { + + @ParameterizedTest + @EnumSource(NamedInstantPattern.class) + void compatibilityOfLegacyPattern(NamedInstantPattern namedPattern) { + InstantPatternFormatter legacyFormatter = InstantPatternFormatter.newBuilder() + .setPattern(namedPattern.getLegacyPattern()) + .setLegacyFormattersEnabled(true) + .build(); + InstantPatternFormatter formatter = InstantPatternFormatter.newBuilder() + .setPattern(namedPattern.getPattern()) + .setLegacyFormattersEnabled(false) + .build(); + Instant javaTimeInstant = Instant.now(); + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(javaTimeInstant.getEpochSecond(), javaTimeInstant.getNano()); + assertThat(legacyFormatter.format(instant)).isEqualTo(formatter.format(instant)); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index 7e173b33155..e8ec1088c9c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -109,40 +109,19 @@ private static String readPattern(@Nullable final String[] options) { * @since 2.25.0 */ static String decodeNamedPattern(final String pattern) { - // If legacy formatters are enabled, we need to produce output aimed for `FixedDateFormat` and `FastDateFormat`. - // Otherwise, we need to produce output aimed for `DateTimeFormatter`. - // In conclusion, we need to check if legacy formatters enabled and apply following transformations. + // `FixedDateFormat` accepted two types of patterns: + // - the names of `FixedFormat` enum constants (identical to `NamedInstantPattern` enum names), + // - or custom pattern strings. // - // | Microseconds | Nanoseconds | Time-zone - // ------------------------------+--------------+-------------+----------- - // Legacy formatter directive | nnnnnn | nnnnnnnnn | X, XX, XXX - // `DateTimeFormatter` directive | SSSSSS | SSSSSSSSS | x, xx, xxx - // - // Enabling legacy formatters mean that user requests the pattern to be formatted using deprecated - // `FixedDateFormat` and `FastDateFormat`. - // These two have, let's not say _bogus_, but an _interesting_ way of handling certain pattern directives: - // - // - They say they adhere to `SimpleDateFormat` specification, but use `n` directive. - // `n` is neither defined by `SimpleDateFormat`, nor `SimpleDateFormat` supports sub-millisecond precisions. - // `n` is probably manually introduced by Log4j to support sub-millisecond precisions. - // - // - `n` denotes nano-of-second for `DateTimeFormatter`. - // In Java 17, `n` and `N` (nano-of-day) always output nanosecond precision. - // This is independent of how many times they occur consequently. - // Yet legacy formatters use repeated `n` to denote sub-milliseconds precision of certain length. - // This doesn't work for `DateTimeFormatter`, which needs - // - // - `SSSSSS` for 6-digit microsecond precision - // - `SSSSSSSSS` for 9-digit nanosecond precision - // - // - Legacy formatters use `X`, `XX,` and `XXX` to choose between `+00`, `+0000`, or `+00:00`. - // This is the correct behaviour for `SimpleDateFormat`. - // Though `X` in `DateTimeFormatter` produces `Z` for zero-offset. - // To avoid the `Z` output, one needs to use `x` with `DateTimeFormatter`. + // To determine the format's precision, we cannot return the legacy name directly; + // instead, we must return the equivalent `FixedDateFormat` pattern string. + // These patterns are only recognized by `FixedDateFormat` so we make them available only + // via the package-private `getLegacyPattern()` method. try { + NamedInstantPattern namedInstantPattern = NamedInstantPattern.valueOf(pattern); return InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED - ? FixedDateFormat.FixedFormat.valueOf(pattern).getPattern() - : NamedInstantPattern.valueOf(pattern).getPattern(); + ? namedInstantPattern.getLegacyPattern() + : namedInstantPattern.getPattern(); } catch (IllegalArgumentException ignored) { return pattern; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java index d5b75c6c057..a95a0ceda0b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java @@ -16,18 +16,21 @@ */ package org.apache.logging.log4j.core.pattern; +import org.jspecify.annotations.NullMarked; + /** * Represents named date & time patterns for formatting log timestamps. * * @see DatePatternConverter * @since 2.26.0 */ +@NullMarked public enum NamedInstantPattern { ABSOLUTE("HH:mm:ss,SSS"), - ABSOLUTE_MICROS("HH:mm:ss,SSSSSS"), + ABSOLUTE_MICROS("HH:mm:ss,SSSSSS", "HH:mm:ss,nnnnnn"), - ABSOLUTE_NANOS("HH:mm:ss,SSSSSSSSS"), + ABSOLUTE_NANOS("HH:mm:ss,SSSSSSSSS", "HH:mm:ss,nnnnnnnnn"), ABSOLUTE_PERIOD("HH:mm:ss.SSS"), @@ -39,9 +42,9 @@ public enum NamedInstantPattern { DEFAULT("yyyy-MM-dd HH:mm:ss,SSS"), - DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,SSSSSS"), + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,SSSSSS", "yyyy-MM-dd HH:mm:ss,nnnnnn"), - DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,SSSSSSSSS"), + DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,SSSSSSSSS", "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"), DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS"), @@ -51,30 +54,50 @@ public enum NamedInstantPattern { ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS"), - ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSx"), + ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSx", "yyyy-MM-dd'T'HH:mm:ss,SSSX"), - ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSxx"), + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSxx", "yyyy-MM-dd'T'HH:mm:ss,SSSXX"), - ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSxxx", "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"), ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS"), - ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), + ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", "yyyy-MM-dd'T'HH:mm:ss.nnnnnn"), US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS"), US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); private final String pattern; + private final String legacyPattern; NamedInstantPattern(String pattern) { + this(pattern, pattern); + } + + NamedInstantPattern(String pattern, String legacyPattern) { this.pattern = pattern; + this.legacyPattern = legacyPattern; } /** - * @return pattern that is compatible with {@link java.time.format.DateTimeFormatter} + * Returns the date-time pattern string compatible with {@link java.time.format.DateTimeFormatter} + * that is associated with this named pattern. + * + * @return the date-time pattern string for use with {@code DateTimeFormatter} */ public String getPattern() { return pattern; } + + /** + * Returns the legacy {@link org.apache.logging.log4j.core.util.datetime.FixedDateFormat} pattern + * associated with this named pattern. + * + * @return the legacy pattern string as used in + * {@link org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat} + */ + String getLegacyPattern() { + return legacyPattern; + } } From 6c7ed23cdeb0ce424cc96b0efc0b0fed501838e2 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 10 Jul 2025 10:00:20 +0200 Subject: [PATCH 26/34] fix: precision of `InstantPatternLegacyFormatter` To get the correct precision in the `InstantPatternLegacyFormatter` we need to replace the legacy `n` pattern letter with its `DateTimeFormatter` equivalent `S`. --- .../logging/log4j/core/pattern/NamedInstantPatternTest.java | 1 + .../util/internal/instant/InstantPatternLegacyFormatter.java | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java index bbe5e6e45eb..3754aa1bb53 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java @@ -41,5 +41,6 @@ void compatibilityOfLegacyPattern(NamedInstantPattern namedPattern) { MutableInstant instant = new MutableInstant(); instant.initFromEpochSecond(javaTimeInstant.getEpochSecond(), javaTimeInstant.getNano()); assertThat(legacyFormatter.format(instant)).isEqualTo(formatter.format(instant)); + assertThat(legacyFormatter.getPrecision()).isEqualTo(formatter.getPrecision()); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java index aaf380c7f0a..9d57493e5d6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java @@ -46,7 +46,10 @@ final class InstantPatternLegacyFormatter implements InstantPatternFormatter { private final BiConsumer formatter; InstantPatternLegacyFormatter(final String pattern, final Locale locale, final TimeZone timeZone) { - this.precision = new InstantPatternDynamicFormatter(pattern, locale, timeZone).getPrecision(); + // Replaces 'n' used in legacy patterns with 'S' to obtain the right precision. + // In legacy patterns, the precision of 'n' depends on the pattern length, but + // in `DateTimeFormatter`, it is always nanoseconds. + this.precision = new InstantPatternDynamicFormatter(pattern.replace('n', 'S'), locale, timeZone).getPrecision(); this.pattern = pattern; this.locale = locale; this.timeZone = timeZone; From 7d77e1fdb3e283361cc871fef32e036e3ee3009e Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 10 Jul 2025 10:03:41 +0200 Subject: [PATCH 27/34] fix: dynamically compute legacy date format pattern This change: * Restores detailed documentation explaining the differences between `FixedDateFormat` and `DateTimeFormatter` patterns. * Implements dynamic computation of the legacy pattern based on the documented rules, ensuring consistent behavior and improved maintainability. --- .../core/pattern/DatePatternConverter.java | 10 +-- .../core/pattern/NamedInstantPattern.java | 88 ++++++++++++++++--- 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index e8ec1088c9c..8b598c3de85 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -109,14 +109,8 @@ private static String readPattern(@Nullable final String[] options) { * @since 2.25.0 */ static String decodeNamedPattern(final String pattern) { - // `FixedDateFormat` accepted two types of patterns: - // - the names of `FixedFormat` enum constants (identical to `NamedInstantPattern` enum names), - // - or custom pattern strings. - // - // To determine the format's precision, we cannot return the legacy name directly; - // instead, we must return the equivalent `FixedDateFormat` pattern string. - // These patterns are only recognized by `FixedDateFormat` so we make them available only - // via the package-private `getLegacyPattern()` method. + // See `NamedInstantPattern.getLegacyPattern()` + // for the difference between legacy and `DateTimeFormatter` patterns. try { NamedInstantPattern namedInstantPattern = NamedInstantPattern.valueOf(pattern); return InstantPatternFormatter.LEGACY_FORMATTERS_ENABLED diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java index a95a0ceda0b..449e95a456d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.core.pattern; +import java.util.regex.Pattern; import org.jspecify.annotations.NullMarked; /** @@ -28,9 +29,9 @@ public enum NamedInstantPattern { ABSOLUTE("HH:mm:ss,SSS"), - ABSOLUTE_MICROS("HH:mm:ss,SSSSSS", "HH:mm:ss,nnnnnn"), + ABSOLUTE_MICROS("HH:mm:ss,SSSSSS"), - ABSOLUTE_NANOS("HH:mm:ss,SSSSSSSSS", "HH:mm:ss,nnnnnnnnn"), + ABSOLUTE_NANOS("HH:mm:ss,SSSSSSSSS"), ABSOLUTE_PERIOD("HH:mm:ss.SSS"), @@ -42,9 +43,9 @@ public enum NamedInstantPattern { DEFAULT("yyyy-MM-dd HH:mm:ss,SSS"), - DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,SSSSSS", "yyyy-MM-dd HH:mm:ss,nnnnnn"), + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,SSSSSS"), - DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,SSSSSSSSS", "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"), + DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,SSSSSSSSS"), DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS"), @@ -54,30 +55,26 @@ public enum NamedInstantPattern { ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS"), - ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSx", "yyyy-MM-dd'T'HH:mm:ss,SSSX"), + ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSx"), - ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSxx", "yyyy-MM-dd'T'HH:mm:ss,SSSXX"), + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSxx"), - ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSxxx", "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"), + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS"), - ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", "yyyy-MM-dd'T'HH:mm:ss.nnnnnn"), + ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS"), US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); + private static final Pattern NANO_PATTERN = Pattern.compile("S{4,}"); + private final String pattern; - private final String legacyPattern; NamedInstantPattern(String pattern) { - this(pattern, pattern); - } - - NamedInstantPattern(String pattern, String legacyPattern) { this.pattern = pattern; - this.legacyPattern = legacyPattern; } /** @@ -93,11 +90,74 @@ public String getPattern() { /** * Returns the legacy {@link org.apache.logging.log4j.core.util.datetime.FixedDateFormat} pattern * associated with this named pattern. + *

+ * If legacy formatters are enabled, output is produced for + * {@code FixedDateFormat} and {@code FastDateFormat}. To convert the {@code DateTimeFormatter} + * to its legacy counterpart, the following transformations need to be applied: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Pattern Differences
MicrosecondsNanosecondsTime-zone
Legacy formatter directivennnnnnnnnnnnnnnX, XX, XXX
{@code DateTimeFormatter} directiveSSSSSSSSSSSSSSSx, xx, xxx
+ *

+ *
    + *
  • + *

    + * Legacy formatters are largely compatible with the {@code SimpleDateFormat} specification, + * but introduce a custom {@code n} pattern letter, unique to Log4j, to represent sub-millisecond precision. + * This {@code n} is not part of the standard {@code SimpleDateFormat}. + *

    + *

    + * In legacy formatters, repeating {@code n} increases the precision, similar to how repeated {@code S} + * is used for fractional seconds in {@code DateTimeFormatter}. + *

    + *

    + * In contrast, {@code DateTimeFormatter} interprets {@code n} as nano-of-second. + * In Java 17, both {@code n} and {@code N} always output nanosecond precision, + * regardless of the number of pattern letters. + *

    + *
  • + *
  • + *

    + * Legacy formatters use X, XX, and XXX to format time zones as + * +00, +0000, or +00:00, following {@code SimpleDateFormat} conventions. + * In contrast, {@code DateTimeFormatter} outputs Z for zero-offset when using X. + * To ensure numeric output for zero-offset (e.g., +00), + * we use x, xx, or xxx instead. + *

    + *
  • + *
* * @return the legacy pattern string as used in * {@link org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat} */ String getLegacyPattern() { + String legacyPattern = pattern.replace('x', 'X'); + if (NANO_PATTERN.matcher(pattern).find()) { + // If the pattern contains sub-millis, replace 'S' with 'n' for legacy formatters + return legacyPattern.replace('S', 'n'); + } return legacyPattern; } } From 782bfb2a642cfb64f0d4d66090f6d54c69b9ad83 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Thu, 10 Jul 2025 10:21:29 +0200 Subject: [PATCH 28/34] fix: missing Javadoc heading --- .../apache/logging/log4j/core/pattern/NamedInstantPattern.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java index 449e95a456d..0804b2ae919 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java @@ -120,7 +120,7 @@ public String getPattern() { * * * - *

+ *

Rationale

*
    *
  • *

    From fb29e3cd4cb971a6e64c82ed9810329eea545011 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 13 Jul 2025 09:47:33 +0200 Subject: [PATCH 29/34] fix: hardcode legacy patterns --- .../core/pattern/NamedInstantPattern.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java index 0804b2ae919..4ce82f15f82 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NamedInstantPattern.java @@ -16,7 +16,6 @@ */ package org.apache.logging.log4j.core.pattern; -import java.util.regex.Pattern; import org.jspecify.annotations.NullMarked; /** @@ -29,9 +28,9 @@ public enum NamedInstantPattern { ABSOLUTE("HH:mm:ss,SSS"), - ABSOLUTE_MICROS("HH:mm:ss,SSSSSS"), + ABSOLUTE_MICROS("HH:mm:ss,SSSSSS", "HH:mm:ss,nnnnnn"), - ABSOLUTE_NANOS("HH:mm:ss,SSSSSSSSS"), + ABSOLUTE_NANOS("HH:mm:ss,SSSSSSSSS", "HH:mm:ss,nnnnnnnnn"), ABSOLUTE_PERIOD("HH:mm:ss.SSS"), @@ -43,9 +42,9 @@ public enum NamedInstantPattern { DEFAULT("yyyy-MM-dd HH:mm:ss,SSS"), - DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,SSSSSS"), + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,SSSSSS", "yyyy-MM-dd HH:mm:ss,nnnnnn"), - DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,SSSSSSSSS"), + DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,SSSSSSSSS", "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"), DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS"), @@ -55,26 +54,30 @@ public enum NamedInstantPattern { ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS"), - ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSx"), + ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSx", "yyyy-MM-dd'T'HH:mm:ss,SSSX"), - ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSxx"), + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSxx", "yyyy-MM-dd'T'HH:mm:ss,SSSXX"), - ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSxxx"), + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSxxx", "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"), ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS"), - ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"), + ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", "yyyy-MM-dd'T'HH:mm:ss.nnnnnn"), US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS"), US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS"); - private static final Pattern NANO_PATTERN = Pattern.compile("S{4,}"); - private final String pattern; + private final String legacyPattern; NamedInstantPattern(String pattern) { + this(pattern, pattern); + } + + NamedInstantPattern(String pattern, String legacyPattern) { this.pattern = pattern; + this.legacyPattern = legacyPattern; } /** @@ -153,11 +156,6 @@ public String getPattern() { * {@link org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat} */ String getLegacyPattern() { - String legacyPattern = pattern.replace('x', 'X'); - if (NANO_PATTERN.matcher(pattern).find()) { - // If the pattern contains sub-millis, replace 'S' with 'n' for legacy formatters - return legacyPattern.replace('S', 'n'); - } return legacyPattern; } } From 952c790cc923a23178eb4c0cc6c81778d40dd880 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 13 Jul 2025 09:52:00 +0200 Subject: [PATCH 30/34] fix: revert fix for #3816 The fix will be provided separately. --- .../util/internal/instant/InstantPatternLegacyFormatter.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java index 9d57493e5d6..aaf380c7f0a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternLegacyFormatter.java @@ -46,10 +46,7 @@ final class InstantPatternLegacyFormatter implements InstantPatternFormatter { private final BiConsumer formatter; InstantPatternLegacyFormatter(final String pattern, final Locale locale, final TimeZone timeZone) { - // Replaces 'n' used in legacy patterns with 'S' to obtain the right precision. - // In legacy patterns, the precision of 'n' depends on the pattern length, but - // in `DateTimeFormatter`, it is always nanoseconds. - this.precision = new InstantPatternDynamicFormatter(pattern.replace('n', 'S'), locale, timeZone).getPrecision(); + this.precision = new InstantPatternDynamicFormatter(pattern, locale, timeZone).getPrecision(); this.pattern = pattern; this.locale = locale; this.timeZone = timeZone; From cad6534ded47758096869a9f6609b0c726bc6811 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 13 Jul 2025 11:10:27 +0200 Subject: [PATCH 31/34] fix: Do not test precision of NamedInstantPattern --- .../logging/log4j/core/pattern/NamedInstantPatternTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java index 3754aa1bb53..bbe5e6e45eb 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NamedInstantPatternTest.java @@ -41,6 +41,5 @@ void compatibilityOfLegacyPattern(NamedInstantPattern namedPattern) { MutableInstant instant = new MutableInstant(); instant.initFromEpochSecond(javaTimeInstant.getEpochSecond(), javaTimeInstant.getNano()); assertThat(legacyFormatter.format(instant)).isEqualTo(formatter.format(instant)); - assertThat(legacyFormatter.getPrecision()).isEqualTo(formatter.getPrecision()); } } From 7674eeaf857acd45eef08402b84ad642063c4d5b Mon Sep 17 00:00:00 2001 From: Junhyeok Lee <118912510+jhl221123@users.noreply.github.com> Date: Sun, 20 Jul 2025 16:51:45 +0900 Subject: [PATCH 32/34] Correct log message format in `AbstractDriverManagerConnectionSource` (#3831) * Correct log message format in AbstractDriverManagerConnectionSource The debug log message in AbstractDriverManagerConnectionSource had 4 placeholders but was supplied with 5 arguments. This corrects the format string to match the argument count, resolving the warning. Fixes #3828 * Add changelog entry --- .../jdbc/AbstractDriverManagerConnectionSource.java | 2 +- .../.2.x.x/3828_fix_log_placeholder_mismatch.xml | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/changelog/.2.x.x/3828_fix_log_placeholder_mismatch.xml diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java index 6f46be6bdc5..1facaa157a4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/AbstractDriverManagerConnectionSource.java @@ -160,7 +160,7 @@ public Connection getConnection() throws SQLException { connection = DriverManager.getConnection(actualConnectionString, toString(userName), toString(password)); } LOGGER.debug( - "{} acquired connection for '{}': {} ({}{@})", + "{} acquired connection for '{}': {} ({}@{})", getClass().getSimpleName(), actualConnectionString, connection, diff --git a/src/changelog/.2.x.x/3828_fix_log_placeholder_mismatch.xml b/src/changelog/.2.x.x/3828_fix_log_placeholder_mismatch.xml new file mode 100644 index 00000000000..884b93985d9 --- /dev/null +++ b/src/changelog/.2.x.x/3828_fix_log_placeholder_mismatch.xml @@ -0,0 +1,12 @@ + + + + + Corrected an incorrect placeholder count in the AbstractDriverManagerConnectionSource debug log message to prevent unnecessary warnings. + + From 16bb60b5d77f94adab721740e1d3cc817cd2c7bc Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 20 Jul 2025 19:38:55 +0200 Subject: [PATCH 33/34] Merge release `2.25.1` back into `2.x` (#3820) --- pom.xml | 8 ++--- .../.2.x.x/3758_fix_jspecify_dep.xml | 13 -------- ...b_relax_the_import_package_constraints.xml | 13 -------- .../update_com_github_luben_zstd_jni.xml | 8 ----- ...ersistence_org_eclipse_persistence_jpa.xml | 8 ----- .../.2.x.x/update_org_junit_junit_bom.xml | 8 ----- src/changelog/2.25.1/.release-notes.adoc.ftl | 30 +++++++++++++++++++ src/changelog/2.25.1/.release.xml | 21 +++++++++++++ .../3706_disruptor-tccl.xml | 2 +- .../3754_fix-gradle-build.xml} | 8 +++-- .../2.25.1/3758_fix_jspecify_dep.xml | 13 ++++++++ .../3770_LoggerContext_start.xml | 2 +- .../3771_graalvm-params.xml | 4 +-- ...b_relax_the_import_package_constraints.xml | 12 ++++++++ .../3790_allow-resource-protocol.xml | 2 +- .../3792_formatted-datetime-sharing.xml | 0 .../3796_annotated-array-parameters.xml | 9 +++--- .../3800_graalvm-misc-reflection.xml | 2 +- 18 files changed, 96 insertions(+), 67 deletions(-) delete mode 100644 src/changelog/.2.x.x/3758_fix_jspecify_dep.xml delete mode 100644 src/changelog/.2.x.x/3787_jakarta-web_relax_the_import_package_constraints.xml delete mode 100644 src/changelog/.2.x.x/update_com_github_luben_zstd_jni.xml delete mode 100644 src/changelog/.2.x.x/update_org_eclipse_persistence_org_eclipse_persistence_jpa.xml delete mode 100644 src/changelog/.2.x.x/update_org_junit_junit_bom.xml create mode 100644 src/changelog/2.25.1/.release-notes.adoc.ftl create mode 100644 src/changelog/2.25.1/.release.xml rename src/changelog/{.2.x.x => 2.25.1}/3706_disruptor-tccl.xml (80%) rename src/changelog/{.2.x.x/update_org_xmlunit_xmlunit_core.xml => 2.25.1/3754_fix-gradle-build.xml} (55%) create mode 100644 src/changelog/2.25.1/3758_fix_jspecify_dep.xml rename src/changelog/{.2.x.x => 2.25.1}/3770_LoggerContext_start.xml (80%) rename src/changelog/{.2.x.x => 2.25.1}/3771_graalvm-params.xml (73%) create mode 100644 src/changelog/2.25.1/3787_jakarta-web_relax_the_import_package_constraints.xml rename src/changelog/{.2.x.x => 2.25.1}/3790_allow-resource-protocol.xml (81%) rename src/changelog/{.2.x.x => 2.25.1}/3792_formatted-datetime-sharing.xml (100%) rename src/changelog/{.2.x.x => 2.25.1}/3796_annotated-array-parameters.xml (54%) rename src/changelog/{.2.x.x => 2.25.1}/3800_graalvm-misc-reflection.xml (77%) diff --git a/pom.xml b/pom.xml index f10bb2f171f..986073efed0 100644 --- a/pom.xml +++ b/pom.xml @@ -309,9 +309,9 @@ 2.26.0-SNAPSHOT - 2.25.0 - 2.25.0 - 2.25.0 + 2.25.1 + 2.25.1 + 2.25.1 - 2025-06-13T17:08:55Z + 2025-07-05T19:48:56Z + diff --git a/src/changelog/.2.x.x/3706_disruptor-tccl.xml b/src/changelog/2.25.1/3706_disruptor-tccl.xml similarity index 80% rename from src/changelog/.2.x.x/3706_disruptor-tccl.xml rename to src/changelog/2.25.1/3706_disruptor-tccl.xml index a6a446fb14f..959f1b78d2a 100644 --- a/src/changelog/.2.x.x/3706_disruptor-tccl.xml +++ b/src/changelog/2.25.1/3706_disruptor-tccl.xml @@ -7,6 +7,6 @@ type="fixed"> - Fix detection of the Disruptor major version in some environments. + Fix detection of the Disruptor major version in environments with non-standard thread context classloader. diff --git a/src/changelog/.2.x.x/update_org_xmlunit_xmlunit_core.xml b/src/changelog/2.25.1/3754_fix-gradle-build.xml similarity index 55% rename from src/changelog/.2.x.x/update_org_xmlunit_xmlunit_core.xml rename to src/changelog/2.25.1/3754_fix-gradle-build.xml index e00bd7e1546..cfaae2512a8 100644 --- a/src/changelog/.2.x.x/update_org_xmlunit_xmlunit_core.xml +++ b/src/changelog/2.25.1/3754_fix-gradle-build.xml @@ -2,7 +2,9 @@ - - Update `org.xmlunit:xmlunit-core` to version `2.10.3` + type="fixed"> + + + Downgrade `spotbugs-annotations` to resolve Gradle build failures. + diff --git a/src/changelog/2.25.1/3758_fix_jspecify_dep.xml b/src/changelog/2.25.1/3758_fix_jspecify_dep.xml new file mode 100644 index 00000000000..db97f93c0ef --- /dev/null +++ b/src/changelog/2.25.1/3758_fix_jspecify_dep.xml @@ -0,0 +1,13 @@ + + + + + + Fix incorrect version resolution of `jspecify` and `error_prone_annotations` dependencies in published POM files. + + diff --git a/src/changelog/.2.x.x/3770_LoggerContext_start.xml b/src/changelog/2.25.1/3770_LoggerContext_start.xml similarity index 80% rename from src/changelog/.2.x.x/3770_LoggerContext_start.xml rename to src/changelog/2.25.1/3770_LoggerContext_start.xml index 84416d9c546..33fd627d56f 100644 --- a/src/changelog/.2.x.x/3770_LoggerContext_start.xml +++ b/src/changelog/2.25.1/3770_LoggerContext_start.xml @@ -7,6 +7,6 @@ type="fixed"> - Restore backward compatibility with the Spring Boot reconfiguration process. + Restore compatibility with Spring Boot by allowing reconfiguration using the `LoggerContext.start` method. diff --git a/src/changelog/.2.x.x/3771_graalvm-params.xml b/src/changelog/2.25.1/3771_graalvm-params.xml similarity index 73% rename from src/changelog/.2.x.x/3771_graalvm-params.xml rename to src/changelog/2.25.1/3771_graalvm-params.xml index ff3c79b1efa..640f3d3b3da 100644 --- a/src/changelog/.2.x.x/3771_graalvm-params.xml +++ b/src/changelog/2.25.1/3771_graalvm-params.xml @@ -2,9 +2,9 @@ + type="fixed"> - Make `-Alog4j.graalvm.groupId` and `-Alog4j.graalvm.artifactId` arguments optional. + Allow omission of the `-Alog4j.graalvm.groupId` and `-Alog4j.graalvm.artifactId` arguments when building Log4j plugins. diff --git a/src/changelog/2.25.1/3787_jakarta-web_relax_the_import_package_constraints.xml b/src/changelog/2.25.1/3787_jakarta-web_relax_the_import_package_constraints.xml new file mode 100644 index 00000000000..4e0182c1e97 --- /dev/null +++ b/src/changelog/2.25.1/3787_jakarta-web_relax_the_import_package_constraints.xml @@ -0,0 +1,12 @@ + + + + + Broaden the OSGi manifest's `Import-Package` constraints to support Jakarta Servlet API up to version 6. + + diff --git a/src/changelog/.2.x.x/3790_allow-resource-protocol.xml b/src/changelog/2.25.1/3790_allow-resource-protocol.xml similarity index 81% rename from src/changelog/.2.x.x/3790_allow-resource-protocol.xml rename to src/changelog/2.25.1/3790_allow-resource-protocol.xml index ffa3c80e305..dd0394225b6 100644 --- a/src/changelog/.2.x.x/3790_allow-resource-protocol.xml +++ b/src/changelog/2.25.1/3790_allow-resource-protocol.xml @@ -7,6 +7,6 @@ type="fixed"> - Allow `resource:` protocol for configuration files by default, if the current runtime is GraalVM. + Enable the `resource:` protocol for configuration files by default when running on GraalVM. diff --git a/src/changelog/.2.x.x/3792_formatted-datetime-sharing.xml b/src/changelog/2.25.1/3792_formatted-datetime-sharing.xml similarity index 100% rename from src/changelog/.2.x.x/3792_formatted-datetime-sharing.xml rename to src/changelog/2.25.1/3792_formatted-datetime-sharing.xml diff --git a/src/changelog/.2.x.x/3796_annotated-array-parameters.xml b/src/changelog/2.25.1/3796_annotated-array-parameters.xml similarity index 54% rename from src/changelog/.2.x.x/3796_annotated-array-parameters.xml rename to src/changelog/2.25.1/3796_annotated-array-parameters.xml index 5be57fe6418..ac8be7cedcd 100644 --- a/src/changelog/.2.x.x/3796_annotated-array-parameters.xml +++ b/src/changelog/2.25.1/3796_annotated-array-parameters.xml @@ -5,8 +5,9 @@ https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="fixed"> - - - Fix GraalVM reachability metadata generation for methods with annotated array type parameters, such as `@Nullable String[]`. - + + + Fix GraalVM reachability metadata generation for methods with annotated array type parameters, such as `@Nullable + String[]`. + diff --git a/src/changelog/.2.x.x/3800_graalvm-misc-reflection.xml b/src/changelog/2.25.1/3800_graalvm-misc-reflection.xml similarity index 77% rename from src/changelog/.2.x.x/3800_graalvm-misc-reflection.xml rename to src/changelog/2.25.1/3800_graalvm-misc-reflection.xml index 3e6653c121f..8c2e9583344 100644 --- a/src/changelog/.2.x.x/3800_graalvm-misc-reflection.xml +++ b/src/changelog/2.25.1/3800_graalvm-misc-reflection.xml @@ -7,6 +7,6 @@ type="fixed"> - Resolves `PropertiesConfiguration` compatibility issues with GraalVM and addresses additional minor reflection-related problems. + Resolve `PropertiesConfiguration` compatibility issues with GraalVM and address additional minor reflection-related problems. From e0ac7af56a11c1ce87b4c4ba3e0fd89f9391e982 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 20 Jul 2025 20:01:15 +0200 Subject: [PATCH 34/34] fix: Prevent `LogBuilder` memory leak in Log4j API to Logback bridge (#3824) --- log4j-to-slf4j/pom.xml | 39 +++++++++++++++++ .../org/apache/logging/slf4j/SLF4JLogger.java | 16 ++++--- .../{LoggerTest.java => SLF4JLoggerTest.java} | 43 ++++++++++++++++++- .../{LoggerTest.xml => SLF4JLoggerTest.xml} | 0 .../.2.x.x/3819_logback-builder-reuse.xml | 12 ++++++ 5 files changed, 103 insertions(+), 7 deletions(-) rename log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/{LoggerTest.java => SLF4JLoggerTest.java} (82%) rename log4j-to-slf4j/src/test/resources/org/apache/logging/slf4j/{LoggerTest.xml => SLF4JLoggerTest.xml} (100%) create mode 100644 src/changelog/.2.x.x/3819_logback-builder-reuse.xml diff --git a/log4j-to-slf4j/pom.xml b/log4j-to-slf4j/pom.xml index 3a6b0d9e850..a2d6f0a6647 100644 --- a/log4j-to-slf4j/pom.xml +++ b/log4j-to-slf4j/pom.xml @@ -153,6 +153,45 @@ + + + + + + + + java8-incompat-fixes + + + + + !env.CI + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens java.base/java.lang=ALL-UNNAMED + + + + + + + + diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java index 26e94c67b35..ff9410f33e1 100644 --- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java +++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java @@ -42,17 +42,21 @@ public class SLF4JLogger extends AbstractLogger { private final org.slf4j.Logger logger; private final LocationAwareLogger locationAwareLogger; + private final boolean useThreadLocal; public SLF4JLogger(final String name, final MessageFactory messageFactory, final org.slf4j.Logger logger) { - super(name, messageFactory); - this.logger = logger; - this.locationAwareLogger = logger instanceof LocationAwareLogger ? (LocationAwareLogger) logger : null; + this(name, messageFactory, logger, Constants.ENABLE_THREADLOCALS); } public SLF4JLogger(final String name, final org.slf4j.Logger logger) { - super(name); + this(name, null, logger); + } + + SLF4JLogger(String name, MessageFactory messageFactory, org.slf4j.Logger logger, boolean useThreadLocal) { + super(name, messageFactory); this.logger = logger; this.locationAwareLogger = logger instanceof LocationAwareLogger ? (LocationAwareLogger) logger : null; + this.useThreadLocal = useThreadLocal; } private int convertLevel(final Level level) { @@ -364,8 +368,8 @@ public LogBuilder atFatal() { @Override protected LogBuilder getLogBuilder(final Level level) { - final SLF4JLogBuilder builder = logBuilder.get(); - return Constants.ENABLE_THREADLOCALS && !builder.isInUse() + SLF4JLogBuilder builder; + return useThreadLocal && !(builder = logBuilder.get()).isInUse() ? builder.reset(this, level) : new SLF4JLogBuilder(this, level); } diff --git a/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/SLF4JLoggerTest.java similarity index 82% rename from log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java rename to log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/SLF4JLoggerTest.java index 6c378ad9583..9e3a5856a06 100644 --- a/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java +++ b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/SLF4JLoggerTest.java @@ -32,10 +32,13 @@ import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.testUtil.StringListAppender; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Date; import java.util.List; +import org.apache.logging.log4j.LogBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; @@ -45,13 +48,17 @@ import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.spi.MessageFactory2Adapter; import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junitpioneer.jupiter.Issue; import org.slf4j.MDC; @UsingStatusListener @LoggerContextSource -class LoggerTest { +class SLF4JLoggerTest { private static final Object OBJ = new Object(); // Log4j objects @@ -265,4 +272,38 @@ void mdcCannotContainNullKey() { // expected } } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @Issue("https://github.com/apache/logging-log4j2/issues/3819") + void threadLocalUsage(boolean useThreadLocal) throws ReflectiveOperationException { + // Reset the static ThreadLocal in SLF4JLogger + getLogBuilderThreadLocal().remove(); + final org.slf4j.Logger slf4jLogger = context.getLogger(getClass()); + Logger logger = new SLF4JLogger(slf4jLogger.getName(), null, slf4jLogger, useThreadLocal); + LogBuilder builder1 = logger.atInfo(); + builder1.log("Test message"); + LogBuilder builder2 = logger.atInfo(); + builder2.log("Another test message"); + // Check if the same builder is reused based on the useThreadLocal flag + Assertions.assertThat(isThreadLocalPresent()) + .as("ThreadLocal should be present iff useThreadLocal is enabled") + .isEqualTo(useThreadLocal); + Assertions.assertThat(builder2 == builder1) + .as("Builder2 should be the same as Builder1 iff useThreadLocal is enabled") + .isEqualTo(useThreadLocal); + } + + private static boolean isThreadLocalPresent() throws ReflectiveOperationException { + Method isPresentMethod = ThreadLocal.class.getDeclaredMethod("isPresent"); + isPresentMethod.setAccessible(true); + ThreadLocal threadLocal = getLogBuilderThreadLocal(); + return (boolean) isPresentMethod.invoke(threadLocal); + } + + private static ThreadLocal getLogBuilderThreadLocal() throws ReflectiveOperationException { + Field logBuilderField = SLF4JLogger.class.getDeclaredField("logBuilder"); + logBuilderField.setAccessible(true); + return (ThreadLocal) logBuilderField.get(null); + } } diff --git a/log4j-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml b/log4j-to-slf4j/src/test/resources/org/apache/logging/slf4j/SLF4JLoggerTest.xml similarity index 100% rename from log4j-to-slf4j/src/test/resources/org/apache/logging/slf4j/LoggerTest.xml rename to log4j-to-slf4j/src/test/resources/org/apache/logging/slf4j/SLF4JLoggerTest.xml diff --git a/src/changelog/.2.x.x/3819_logback-builder-reuse.xml b/src/changelog/.2.x.x/3819_logback-builder-reuse.xml new file mode 100644 index 00000000000..ea67aa50d04 --- /dev/null +++ b/src/changelog/.2.x.x/3819_logback-builder-reuse.xml @@ -0,0 +1,12 @@ + + + + + Fix potential memory leak involving `LogBuilder` in Log4j API to Logback bridge. + +