From 95a2beb1590d38d44611e806ffcabfb550b82d21 Mon Sep 17 00:00:00 2001 From: Kamil Krzywanski Date: Mon, 20 Jul 2026 12:15:03 +0200 Subject: [PATCH 1/2] Fix italic and underline ANSI escape codes AnsiEscape had UNDERLINE mapped to SGR 3 (italic) and no ITALIC entry. Map italic to 3 and underline to 4, and cover both with regression tests. --- .../log4j/core/pattern/AnsiEscapeTest.java | 41 +++++++++++++++++++ .../core/pattern/JAnsiTextRendererTest.java | 5 +++ .../log4j/core/pattern/AnsiEscape.java | 7 +++- .../4105_fix_AnsiEscape_italic_underline.xml | 12 ++++++ .../ROOT/pages/manual/pattern-layout.adoc | 3 +- 5 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java create mode 100644 src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java new file mode 100644 index 00000000000..509298860d1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java @@ -0,0 +1,41 @@ +/* + * 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.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Regression tests for ANSI SGR attribute codes (GitHub issue #4105). + *

+ * Expected values are hard-coded SGR sequences so a wrong mapping cannot + * compare equal to itself. + *

+ */ +class AnsiEscapeTest { + + @Test + void italicIsSgr3() { + assertEquals("\u001B[3m", AnsiEscape.createSequence("italic")); + } + + @Test + void underlineIsSgr4() { + assertEquals("\u001B[4m", AnsiEscape.createSequence("underline")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java index 0595f68533d..09503974540 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java @@ -39,6 +39,11 @@ public static Stream testRendering() { "", "@|white key|@ = @|cyan,bold some value|@", "\u001b[37mkey\u001b[m = \u001b[36;1msome value\u001b[m"), + // GitHub issue #4105: italic = SGR 3, underline = SGR 4 + Arguments.of( + "", + "@|italic italic text|@ and @|underline underlined text|@", + "\u001b[3mitalic text\u001b[m and \u001b[4munderlined text\u001b[m"), // Return broken escapes as is Arguments.of("", "Hello @|crazy|@ world!", "Hello @|crazy|@ world!"), Arguments.of("", "Hello @|world!", "Hello @|world!")); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java index 34213373dfb..bcf24b63b27 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java @@ -77,10 +77,15 @@ public enum AnsiEscape { */ DIM("2"), + /** + * Italic general attribute. + */ + ITALIC("3"), + /** * Underline general attribute. */ - UNDERLINE("3"), + UNDERLINE("4"), /** * Blink general attribute. diff --git a/src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml b/src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml new file mode 100644 index 00000000000..ad018d2a2c0 --- /dev/null +++ b/src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml @@ -0,0 +1,12 @@ + + + + + Fix `AnsiEscape` so that `italic` maps to SGR 3 and `underline` maps to SGR 4 + + diff --git a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc index 4e3943066a7..6a5a8cdc80d 100644 --- a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc @@ -1465,7 +1465,7 @@ In EBNF form the syntax of a style expression is: | ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" - ::= "normal" | "bold" | "dim" | "underline" + ::= "normal" | "bold" | "dim" | "italic" | "underline" | "blink" | "reverse" | "hidden" | "black" | "bg_black" | "bright_black" | "bg_bright_black" | "red" | "bg_red" | "bright_red" | "bg_bright_red" @@ -1484,6 +1484,7 @@ The style specifiers have the following effects (see https://en.wikipedia.org/wi `normal`:: Reverts all parameters to their default value `bold`:: Increases the font weight or the color intensity `dim`:: Decreases the fond weight or the color intensity +`italic`:: Renders the text in italic on terminals that support it `underline`:: Underlines the text on some terminals `blink`:: Causes the text to blink `reverse`:: Swaps foreground and background colors From 6840385b1cce369343acd00d053416bf6898a92d Mon Sep 17 00:00:00 2001 From: Kamil Krzywanski Date: Wed, 12 Aug 2026 19:49:29 +0200 Subject: [PATCH 2/2] Align AnsiEscape with remaining Jansi style names Add missing AnsiRenderer.Code names (faint, blink_slow, etc.), extend SGR mapping tests, and drop the extra test comments from review. --- .../log4j/core/pattern/AnsiEscapeTest.java | 40 ++++++++--- .../core/pattern/JAnsiTextRendererTest.java | 5 +- .../log4j/core/pattern/AnsiEscape.java | 72 ++++++++++++++++++- .../4105_fix_AnsiEscape_italic_underline.xml | 2 +- .../ROOT/pages/manual/pattern-layout.adoc | 12 ++-- 5 files changed, 114 insertions(+), 17 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java index 509298860d1..6f1d355dd35 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/AnsiEscapeTest.java @@ -18,7 +18,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; /** * Regression tests for ANSI SGR attribute codes (GitHub issue #4105). @@ -29,13 +30,34 @@ */ class AnsiEscapeTest { - @Test - void italicIsSgr3() { - assertEquals("\u001B[3m", AnsiEscape.createSequence("italic")); - } - - @Test - void underlineIsSgr4() { - assertEquals("\u001B[4m", AnsiEscape.createSequence("underline")); + @ParameterizedTest + @CsvSource({ + // Log4j style names + "normal, 0", + "bold, 1", + "dim, 2", + "italic, 3", + "underline, 4", + "blink, 5", + "reverse, 7", + "hidden, 8", + // Jansi AnsiRenderer.Code names / aliases (post-#3070 parity) + "reset, 0", + "intensity_bold, 1", + "faint, 2", + "intensity_faint, 2", + "blink_slow, 5", + "blink_fast, 6", + "blink_off, 25", + "negative_on, 7", + "negative_off, 27", + "conceal_on, 8", + "conceal_off, 28", + "underline_double, 21", + "underline_off, 24", + "bg_default, 49", + }) + void styleMapsToSgr(final String name, final String sgrCode) { + assertEquals("\u001B[" + sgrCode + "m", AnsiEscape.createSequence(name)); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java index 09503974540..129829b1e0a 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/JAnsiTextRendererTest.java @@ -39,11 +39,14 @@ public static Stream testRendering() { "", "@|white key|@ = @|cyan,bold some value|@", "\u001b[37mkey\u001b[m = \u001b[36;1msome value\u001b[m"), - // GitHub issue #4105: italic = SGR 3, underline = SGR 4 Arguments.of( "", "@|italic italic text|@ and @|underline underlined text|@", "\u001b[3mitalic text\u001b[m and \u001b[4munderlined text\u001b[m"), + Arguments.of( + "", + "@|faint faint text|@ and @|blink_slow blinking text|@", + "\u001b[2mfaint text\u001b[m and \u001b[5mblinking text\u001b[m"), // Return broken escapes as is Arguments.of("", "Hello @|crazy|@ world!", "Hello @|crazy|@ world!"), Arguments.of("", "Hello @|world!", "Hello @|world!")); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java index bcf24b63b27..7b647f854df 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AnsiEscape.java @@ -58,6 +58,11 @@ public enum AnsiEscape { */ NORMAL("0"), + /** + * Reset general attribute (Jansi {@code AnsiRenderer.Code} name). + */ + RESET("0"), + /** * Bright general attribute. * @@ -72,11 +77,26 @@ public enum AnsiEscape { */ BOLD("1"), + /** + * Bold intensity attribute (Jansi {@code AnsiRenderer.Code} name). + */ + INTENSITY_BOLD("1"), + /** * Dim general attribute. */ DIM("2"), + /** + * Faint / dim general attribute (Jansi {@code AnsiRenderer.Code} name). + */ + FAINT("2"), + + /** + * Faint intensity attribute (Jansi {@code AnsiRenderer.Code} name). + */ + INTENSITY_FAINT("2"), + /** * Italic general attribute. */ @@ -92,16 +112,61 @@ public enum AnsiEscape { */ BLINK("5"), + /** + * Slow blink general attribute (Jansi {@code AnsiRenderer.Code} name). + */ + BLINK_SLOW("5"), + + /** + * Fast blink general attribute. + */ + BLINK_FAST("6"), + /** * Reverse general attribute. */ REVERSE("7"), /** - * Normal general attribute. + * Reverse / negative general attribute (Jansi {@code AnsiRenderer.Code} name). + */ + NEGATIVE_ON("7"), + + /** + * Conceal / hidden general attribute. */ HIDDEN("8"), + /** + * Conceal general attribute (Jansi {@code AnsiRenderer.Code} name). + */ + CONCEAL_ON("8"), + + /** + * Double underline general attribute. + */ + UNDERLINE_DOUBLE("21"), + + /** + * Turns underline off. + */ + UNDERLINE_OFF("24"), + + /** + * Turns blink off. + */ + BLINK_OFF("25"), + + /** + * Turns reverse / negative video off. + */ + NEGATIVE_OFF("27"), + + /** + * Turns conceal off. + */ + CONCEAL_OFF("28"), + /** * Black foreground color. */ @@ -232,6 +297,11 @@ public enum AnsiEscape { */ BG_WHITE("47"), + /** + * Default background color. + */ + BG_DEFAULT("49"), + /** * Bright black foreground color. */ diff --git a/src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml b/src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml index ad018d2a2c0..1452cb793e4 100644 --- a/src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml +++ b/src/changelog/.2.x.x/4105_fix_AnsiEscape_italic_underline.xml @@ -7,6 +7,6 @@ type="fixed"> - Fix `AnsiEscape` so that `italic` maps to SGR 3 and `underline` maps to SGR 4 + Fix `AnsiEscape` italic/underline SGR codes and restore missing Jansi `AnsiRenderer.Code` style names (for example `faint`, `blink_slow`) diff --git a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc index 6a5a8cdc80d..84781f1d552 100644 --- a/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/pattern-layout.adoc @@ -1465,8 +1465,8 @@ In EBNF form the syntax of a style expression is: | ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" - ::= "normal" | "bold" | "dim" | "italic" | "underline" - | "blink" | "reverse" | "hidden" + ::= "normal" | "reset" | "bold" | "dim" | "faint" | "italic" | "underline" + | "underline_double" | "blink" | "blink_slow" | "blink_fast" | "reverse" | "hidden" | "black" | "bg_black" | "bright_black" | "bg_bright_black" | "red" | "bg_red" | "bright_red" | "bg_bright_red" | "green" | "bg_green" | "bright_green" | "bg_bright_green" @@ -1481,12 +1481,14 @@ For example, you can use `underline blue bg_bright_yellow` to specify a blue und The style specifiers have the following effects (see https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters[Select Graphic Rendition] for details): -`normal`:: Reverts all parameters to their default value +`normal` / `reset`:: Reverts all parameters to their default value `bold`:: Increases the font weight or the color intensity -`dim`:: Decreases the fond weight or the color intensity +`dim` / `faint`:: Decreases the font weight or the color intensity `italic`:: Renders the text in italic on terminals that support it `underline`:: Underlines the text on some terminals -`blink`:: Causes the text to blink +`underline_double`:: Double-underlines the text on terminals that support it +`blink` / `blink_slow`:: Causes the text to blink +`blink_fast`:: Causes the text to blink rapidly on terminals that support it `reverse`:: Swaps foreground and background colors `hidden`:: Hides the text