From a07cfd53e4a3122dc83c4ad36b96f6f6fc78375c Mon Sep 17 00:00:00 2001
From: Kevin Burke
Date: Fri, 25 Jul 2025 14:00:04 -0700
Subject: [PATCH 01/36] logback-core: fix spelling errors (#956)
---
.../main/java/ch/qos/logback/core/AsyncAppenderBase.java | 6 +++---
.../src/main/java/ch/qos/logback/core/util/ContextUtil.java | 6 +++---
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/logback-core/src/main/java/ch/qos/logback/core/AsyncAppenderBase.java b/logback-core/src/main/java/ch/qos/logback/core/AsyncAppenderBase.java
index a51f6efec3..ee577f85f7 100755
--- a/logback-core/src/main/java/ch/qos/logback/core/AsyncAppenderBase.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/AsyncAppenderBase.java
@@ -113,8 +113,8 @@ public void start() {
addInfo("Setting discardingThreshold to " + discardingThreshold);
worker.setDaemon(true);
worker.setName("AsyncAppender-Worker-" + getName());
- // make sure this instance is marked as "started" before staring the worker
- // Thread
+ // make sure this instance is marked as "started" before starting the
+ // worker Thread
super.start();
worker.start();
}
@@ -244,7 +244,7 @@ public boolean isNeverBlock() {
* BlockingQueue#remainingCapacity()}
*
* @return the remaining capacity
- *
+ *
*/
public int getRemainingCapacity() {
return blockingQueue.remainingCapacity();
diff --git a/logback-core/src/main/java/ch/qos/logback/core/util/ContextUtil.java b/logback-core/src/main/java/ch/qos/logback/core/util/ContextUtil.java
index 4ad3abb283..54883d9375 100755
--- a/logback-core/src/main/java/ch/qos/logback/core/util/ContextUtil.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/util/ContextUtil.java
@@ -85,10 +85,10 @@ public void addFrameworkPackage(List frameworkPackages, String packageNa
public void addOrReplaceShutdownHook(ShutdownHook hook) {
Runtime runtime = Runtime.getRuntime();
- Thread oldShutdownHookTread = (Thread) context.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
- if(oldShutdownHookTread != null) {
+ Thread oldShutdownHookThread = (Thread) context.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
+ if(oldShutdownHookThread != null) {
addInfo("Removing old shutdown hook from JVM runtime");
- runtime.removeShutdownHook(oldShutdownHookTread);
+ runtime.removeShutdownHook(oldShutdownHookThread);
}
Thread hookThread = new Thread(hook, "Logback shutdown hook [" + context.getName() + "]");
From 61f6a2544f36b3016e0efd434ee21f19269f1df7 Mon Sep 17 00:00:00 2001
From: ceki
Date: Mon, 29 Sep 2025 19:28:08 +0200
Subject: [PATCH 02/36] disallow new in if condition attribute in config files
Signed-off-by: ceki
---
.../blackboxInput/joran/conditional/ifNew.xml | 14 +++++++++++++
.../joran/conditional/IfThenElseTest.java | 11 +++++++++-
.../processor/conditional/IfModelHandler.java | 20 ++++++++++++++++---
3 files changed, 41 insertions(+), 4 deletions(-)
create mode 100644 logback-core-blackbox/src/test/blackboxInput/joran/conditional/ifNew.xml
diff --git a/logback-core-blackbox/src/test/blackboxInput/joran/conditional/ifNew.xml b/logback-core-blackbox/src/test/blackboxInput/joran/conditional/ifNew.xml
new file mode 100644
index 0000000000..fae14c7484
--- /dev/null
+++ b/logback-core-blackbox/src/test/blackboxInput/joran/conditional/ifNew.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/logback-core-blackbox/src/test/java/ch/qos/logback/core/blackbox/joran/conditional/IfThenElseTest.java b/logback-core-blackbox/src/test/java/ch/qos/logback/core/blackbox/joran/conditional/IfThenElseTest.java
index d993418962..72e9d66cda 100644
--- a/logback-core-blackbox/src/test/java/ch/qos/logback/core/blackbox/joran/conditional/IfThenElseTest.java
+++ b/logback-core-blackbox/src/test/java/ch/qos/logback/core/blackbox/joran/conditional/IfThenElseTest.java
@@ -51,7 +51,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Stack;
@@ -129,6 +128,16 @@ public void whenContextPropertyIsSet_IfThenBranchIsEvaluated() throws JoranExcep
verifyConfig(new String[] { "BEGIN", "a", "END" });
}
+ @Test
+ public void ifWithNew() throws JoranException {
+ context.putProperty(ki1, val1);
+ simpleConfigurator.doConfigure(CONDITIONAL_DIR_PREFIX + "ifNew.xml");
+ checker.containsMatch(Status.ERROR, IfModelHandler.NEW_OPERATOR_DISALLOWED_MSG);
+ checker.containsMatch(Status.ERROR, IfModelHandler.NEW_OPERATOR_DISALLOWED_SEE);
+ verifyConfig(new String[] { "BEGIN", "END" });
+ }
+
+
@Test
public void whenLocalPropertyIsSet_IfThenBranchIsEvaluated() throws JoranException {
simpleConfigurator.doConfigure(CONDITIONAL_DIR_PREFIX + "if_localProperty.xml");
diff --git a/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/IfModelHandler.java b/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/IfModelHandler.java
index 0b9a39a375..5bc101dbc7 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/IfModelHandler.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/IfModelHandler.java
@@ -1,6 +1,6 @@
/**
* Logback: the reliable, generic, fast and flexible logging framework.
- * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
+ * Copyright (C) 1999-2025, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
@@ -33,6 +33,9 @@ public class IfModelHandler extends ModelHandlerBase {
public static final String MISSING_JANINO_MSG = "Could not find Janino library on the class path. Skipping conditional processing.";
public static final String MISSING_JANINO_SEE = "See also " + CoreConstants.CODES_URL + "#ifJanino";
+ public static final String NEW_OPERATOR_DISALLOWED_MSG = "The 'condition' attribute may not contain the 'new' operator.";
+ public static final String NEW_OPERATOR_DISALLOWED_SEE = "See also " + CoreConstants.CODES_URL + "#conditionNew";
+
enum Branch {IF_BRANCH, ELSE_BRANCH; }
IfModel ifModel = null;
@@ -75,6 +78,13 @@ public void handle(ModelInterpretationContext mic, Model model) throws ModelHand
return;
}
+ // do not allow 'new' operator
+ if(hasNew(conditionStr)) {
+ addError(NEW_OPERATOR_DISALLOWED_MSG);
+ addError(NEW_OPERATOR_DISALLOWED_SEE);
+ return;
+ }
+
try {
PropertyEvalScriptBuilder pesb = new PropertyEvalScriptBuilder(mic);
pesb.setContext(context);
@@ -96,8 +106,12 @@ public void handle(ModelInterpretationContext mic, Model model) throws ModelHand
}
}
}
-
-
+
+ private boolean hasNew(String conditionStr) {
+ return conditionStr.contains("new ");
+ }
+
+
@Override
public void postHandle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
From c76fed3c01f389e4c18db914bcba1e72bccc2d1e Mon Sep 17 00:00:00 2001
From: Ralf Wiebicke
Date: Mon, 29 Sep 2025 19:28:57 +0200
Subject: [PATCH 03/36] ViewStatusMessagesServlet requires method POST for
button 'Clear' (#971)
The button 'Clear' has a side-effect and should not work with GET,
as GET is considered a Safe Method not taking an action other than retrieval.
https://www.rfc-editor.org/rfc/rfc2616#section-9.1.1
I'd like to restrict users from doing any changes by restricting them to method GET.
With that said one might consider this change as a security fix.
Signed-off-by: Ralf Wiebicke
---
.../qos/logback/core/status/ViewStatusMessagesServletBase.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/logback-core/src/main/java/ch/qos/logback/core/status/ViewStatusMessagesServletBase.java b/logback-core/src/main/java/ch/qos/logback/core/status/ViewStatusMessagesServletBase.java
index f7f1a88a1e..353ae6b8ef 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/status/ViewStatusMessagesServletBase.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/status/ViewStatusMessagesServletBase.java
@@ -62,7 +62,7 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws
output.append("");
output.append("\r\n");
- if (CLEAR.equalsIgnoreCase(req.getParameter(SUBMIT))) {
+ if ("POST".equals(req.getMethod()) && CLEAR.equalsIgnoreCase(req.getParameter(SUBMIT))) {
sm.clear();
sm.add(new InfoStatus("Cleared all status messages", this));
}
From 8d2262d3c5227f209905ac1705a3333ebd8a33c8 Mon Sep 17 00:00:00 2001
From: ceki
Date: Mon, 29 Sep 2025 22:52:11 +0200
Subject: [PATCH 04/36] soften warning on using ConsoleAppender
Signed-off-by: ceki
---
.../qos/logback/core/blackbox}/COWArrayListConcurrencyTest.java | 0
.../src/main/java/ch/qos/logback/core/ConsoleAppender.java | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename {logback-core/src/test/java/ch/qos/logback/core/util => logback-core-blackbox/src/test/java/ch/qos/logback/core/blackbox}/COWArrayListConcurrencyTest.java (100%)
diff --git a/logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListConcurrencyTest.java b/logback-core-blackbox/src/test/java/ch/qos/logback/core/blackbox/COWArrayListConcurrencyTest.java
similarity index 100%
rename from logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListConcurrencyTest.java
rename to logback-core-blackbox/src/test/java/ch/qos/logback/core/blackbox/COWArrayListConcurrencyTest.java
diff --git a/logback-core/src/main/java/ch/qos/logback/core/ConsoleAppender.java b/logback-core/src/main/java/ch/qos/logback/core/ConsoleAppender.java
index 8200b05015..eec96b5240 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/ConsoleAppender.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/ConsoleAppender.java
@@ -88,7 +88,7 @@ private void targetWarn(String val) {
@Override
public void start() {
- addInfo("BEWARE: Writing to the console can be very slow. Avoid logging to the ");
+ addInfo("NOTE: Writing to the console can be slow. Try to avoid logging to the ");
addInfo("console in production environments, especially in high volume systems.");
addInfo("See also "+CONSOLE_APPENDER_WARNING_URL);
OutputStream targetStream = target.getStream();
From 7f653409c95e40efd79b2b1bbeefde6dd649ceab Mon Sep 17 00:00:00 2001
From: ceki
Date: Mon, 29 Sep 2025 22:53:09 +0200
Subject: [PATCH 05/36] minor changes
Signed-off-by: ceki
---
.../logback/core/rolling/RenameUtilTest.java | 25 ++++++++++++++-----
1 file changed, 19 insertions(+), 6 deletions(-)
diff --git a/logback-core/src/test/java/ch/qos/logback/core/rolling/RenameUtilTest.java b/logback-core/src/test/java/ch/qos/logback/core/rolling/RenameUtilTest.java
index 1460ecd77f..e8fdf16728 100755
--- a/logback-core/src/test/java/ch/qos/logback/core/rolling/RenameUtilTest.java
+++ b/logback-core/src/test/java/ch/qos/logback/core/rolling/RenameUtilTest.java
@@ -21,8 +21,10 @@
import ch.qos.logback.core.testUtil.CoreTestConstants;
import ch.qos.logback.core.testUtil.RandomUtil;
import ch.qos.logback.core.status.testUtil.StatusChecker;
+import ch.qos.logback.core.util.EnvUtil;
import ch.qos.logback.core.util.StatusPrinter;
+import ch.qos.logback.core.util.StatusPrinter2;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -32,6 +34,7 @@
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.channels.FileLock;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -41,6 +44,7 @@ public class RenameUtilTest {
Encoder
*
- *
Nevertheless, it should also be useful in newly written code.
+ *
Properties are looked up in the following order:
*
+ *
+ *
In the local property container, usually the {@link ModelInterpretationContext}
+ *
in the logger context
+ *
system properties
+ *
environment variables
+ *
+ *
+ * @see OptionHelper#propertyLookup(String, PropertyContainer, PropertyContainer)
* @since 1.5.20
* @author Ceki Gülcü
*/
-abstract public class PropertyEvaluatorBase extends ContextAwareBase implements PropertyEvaluator {
+abstract public class PropertyConditionBase extends ContextAwareBase implements PropertyCondition {
/**
* Indicates whether this evaluator has been started.
@@ -39,14 +48,17 @@ abstract public class PropertyEvaluatorBase extends ContextAwareBase implements
*
The local property container used for property lookups.
*
*
Local properties correspond to the properties in the embedding
- * configurator.
+ * configurator, i.e. usually the {@link ModelInterpretationContext} instance.
*/
PropertyContainer localPropertyContainer;
/**
- * Returns the property container used by this evaluator.
+ * Returns the local property container used by this evaluator.
+ *
+ *
Local properties correspond to the properties in the embedding
+ * configurator, i.e. usually the {@link ModelInterpretationContext} instance.
*
- * @return the property container
+ * @return the local property container
*/
@Override
public PropertyContainer getLocalPropertyContainer() {
@@ -54,18 +66,25 @@ public PropertyContainer getLocalPropertyContainer() {
}
/**
- * Sets the property container for this evaluator.
+ * Sets the local property container for this evaluator.
+ *
+ *
Local properties correspond to the properties in the embedding
+ * configurator, i.e. usually the {@link ModelInterpretationContext} instance.
*
- * @param aPropertyContainer the property container to set
+ * @param aLocalPropertyContainer the local property container to set
*/
@Override
- public void setLocalPropertyContainer(PropertyContainer aPropertyContainer) {
- this.localPropertyContainer = aPropertyContainer;
+ public void setLocalPropertyContainer(PropertyContainer aLocalPropertyContainer) {
+ this.localPropertyContainer = aLocalPropertyContainer;
}
/**
* Checks if the property with the given key is null.
*
+ *
The property is looked up via the
+ * {@link OptionHelper#propertyLookup(String, PropertyContainer, PropertyContainer)} method.
+ * See above for the lookup order.
+ *
* @param k the property key
* @return true if the property is null, false otherwise
*/
@@ -77,6 +96,10 @@ public boolean isNull(String k) {
/**
* Checks if the property with the given key is defined (not null).
*
+ *
The property is looked up via the
+ * {@link OptionHelper#propertyLookup(String, PropertyContainer, PropertyContainer)} method.
+ * See above for the lookup order.
+ *
* @param k the property key
* @return true if the property is defined, false otherwise
*/
@@ -99,6 +122,10 @@ public String p(String k) {
/**
* Retrieves the property value for the given key, returning an empty string if null.
*
+ *
The property is looked up via the
+ * {@link OptionHelper#propertyLookup(String, PropertyContainer, PropertyContainer)} method.
+ * See above for the lookup order.
+ *
* @param k the property key
* @return the property value or an empty string
*/
diff --git a/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyEqualityCondition.java b/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyEqualityCondition.java
new file mode 100644
index 0000000000..e48fbcd5c4
--- /dev/null
+++ b/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyEqualityCondition.java
@@ -0,0 +1,117 @@
+/*
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * Copyright (C) 1999-2025, QOS.ch. All rights reserved.
+ *
+ * This program and the accompanying materials are dual-licensed under
+ * either the terms of the Eclipse Public License v1.0 as published by
+ * the Eclipse Foundation
+ *
+ * or (per the licensee's choosing)
+ *
+ * under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation.
+ */
+
+package ch.qos.logback.core.boolex;
+
+/**
+ * Condition that evaluates to {@code true} when a property
+ * equals a specified expected value.
+ *
+ *
The property named by {@link #key} is resolved using the
+ * inherited property lookup mechanism (see {@code PropertyConditionBase}).
+ * If the resolved property value equals {@link #value} (using
+ * {@link String#equals(Object)}), this condition evaluates to {@code true}.
+ *
+ * @since 1.5.20
+ */
+public class PropertyEqualityCondition extends PropertyConditionBase {
+
+ /**
+ * The property name (key) to look up. Must be set before starting.
+ */
+ String key;
+
+ /**
+ * The expected value to compare the resolved property against.
+ */
+ String value;
+
+ /**
+ * Start the component and validate required parameters.
+ * If either {@link #key} or {@link #value} is {@code null}, an error
+ * is reported and the component does not start.
+ */
+ public void start() {
+ if (key == null) {
+ addError("In PropertyEqualsValue 'key' parameter cannot be null");
+ return;
+ }
+ if (value == null) {
+ addError("In PropertyEqualsValue 'value' parameter cannot be null");
+ return;
+ }
+ super.start();
+ }
+
+ /**
+ * Return the configured expected value.
+ *
+ * @return the expected value, or {@code null} if not set
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Set the expected value that the resolved property must equal for
+ * this condition to evaluate to {@code true}.
+ *
+ * @param value the expected value
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Return the property key that will be looked up when evaluating the
+ * condition.
+ *
+ * @return the property key, or {@code null} if not set
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Set the property key to resolve during evaluation.
+ *
+ * @param key the property key
+ */
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ /**
+ * Evaluate the condition: resolve the property named by {@link #key}
+ * and compare it to {@link #value}.
+ *
+ * @return {@code true} if the resolved property equals the expected
+ * value; {@code false} otherwise
+ */
+ @Override
+ public boolean evaluate() {
+ if (key == null) {
+ addError("key cannot be null");
+ return false;
+ }
+
+ String val = p(key);
+ if (val == null)
+ return false;
+ else {
+ return val.equals(value);
+ }
+ }
+
+}
diff --git a/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyEqualsValue.java b/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyEqualsValue.java
deleted file mode 100644
index 72f779845a..0000000000
--- a/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyEqualsValue.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Logback: the reliable, generic, fast and flexible logging framework.
- * Copyright (C) 1999-2025, QOS.ch. All rights reserved.
- *
- * This program and the accompanying materials are dual-licensed under
- * either the terms of the Eclipse Public License v1.0 as published by
- * the Eclipse Foundation
- *
- * or (per the licensee's choosing)
- *
- * under the terms of the GNU Lesser General Public License version 2.1
- * as published by the Free Software Foundation.
- */
-
-package ch.qos.logback.core.boolex;
-
-public class PropertyEqualsValue extends PropertyEvaluatorBase {
-
-
- String key;
- String value;
-
- public void start() {
- if (key == null) {
- addError("In PropertyEqualsValue 'key' parameter cannot be null");
- return;
- }
- if (value == null) {
- addError("In PropertyEqualsValue 'value' parameter cannot be null");
- return;
- }
- super.start();
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(String value) {
- this.value = value;
- }
-
- public String getKey() {
- return key;
- }
-
- public void setKey(String key) {
- this.key = key;
- }
-
-
- @Override
- public boolean evaluate() {
- if (key == null) {
- addError("key cannot be null");
- return false;
- }
-
- String val = p(key);
- if (val == null)
- return false;
- else {
- return val.equals(value);
- }
- }
-
-
-}
diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/Condition.java b/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/Condition.java
index 250dbad672..8c6b417829 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/Condition.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/Condition.java
@@ -13,6 +13,26 @@
*/
package ch.qos.logback.core.joran.conditional;
+/**
+ *
A condition evaluated during Joran conditional processing.
+ *
+ *
Implementations of this interface encapsulate a boolean test that
+ * determines whether a conditional block in a Joran configuration should
+ * be processed.
+ *
+ *
Typical implementations evaluate configuration state, environment
+ * variables, or other runtime properties.
+ *
+ * @since 0.9.20
+ * @author Ceki Gülcü
+ */
public interface Condition {
+
+ /**
+ * Evaluate the condition.
+ *
+ * @return {@code true} if the condition is satisfied and the associated
+ * conditional block should be activated; {@code false} otherwise
+ */
boolean evaluate();
}
diff --git a/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/ByPropertiesConditionModelHandler.java b/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/ByPropertiesConditionModelHandler.java
index 12c0119e9a..89bbb3d1be 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/ByPropertiesConditionModelHandler.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/ByPropertiesConditionModelHandler.java
@@ -15,7 +15,7 @@
package ch.qos.logback.core.model.processor.conditional;
import ch.qos.logback.core.Context;
-import ch.qos.logback.core.boolex.PropertyEvaluator;
+import ch.qos.logback.core.boolex.PropertyCondition;
import ch.qos.logback.core.model.Model;
import ch.qos.logback.core.model.conditional.IfModel;
import ch.qos.logback.core.model.conditional.ByPropertiesConditionModel;
@@ -30,7 +30,7 @@
public class ByPropertiesConditionModelHandler extends ModelHandlerBase {
private boolean inError = false;
- PropertyEvaluator propertyEvaluator;
+ PropertyCondition propertyEvaluator;
public ByPropertiesConditionModelHandler(Context context) {
super(context);
@@ -61,8 +61,8 @@ public void handle(ModelInterpretationContext mic, Model model) throws ModelHand
try {
addInfo("About to instantiate PropertyEvaluator of type [" + className + "]");
- propertyEvaluator = (PropertyEvaluator) OptionHelper.instantiateByClassName(className,
- PropertyEvaluator.class, context);
+ propertyEvaluator = (PropertyCondition) OptionHelper.instantiateByClassName(className,
+ PropertyCondition.class, context);
propertyEvaluator.setContext(context);
propertyEvaluator.setLocalPropertyContainer(mic);
mic.pushObject(propertyEvaluator);
diff --git a/logback-core/src/main/java/ch/qos/logback/core/util/OptionHelper.java b/logback-core/src/main/java/ch/qos/logback/core/util/OptionHelper.java
index e38b8ebafe..c643a70892 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/util/OptionHelper.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/util/OptionHelper.java
@@ -113,6 +113,20 @@ public static String substVars(String input, PropertyContainer pc0, PropertyCont
}
+ /**
+ * Try to lookup the property in the following order:
+ *
This encoder supports extensive configuration through boolean flags to include or exclude specific fields
+ * in the output, allowing customization for different logging needs. For example, you can enable/disable
+ * sequence numbers, nanoseconds, thread names, logger context, markers, MDC, key-value pairs, arguments,
+ * and throwable information.
The encoder is designed for extensibility: subclasses can override protected methods (e.g.,
+ * {@link #appendLoggerContext}, {@link #appendThrowableProxy}, {@link #appendMarkers}) to customize
+ * how specific parts of the JSON are generated. Additionally, the {@link #appendCustomFields} hook
+ * allows appending custom top-level fields to the JSON object.
+ *
+ *
Configuration
+ *
Use the setter methods (e.g., {@link #setWithSequenceNumber}, {@link #setWithTimestamp}) to control
+ * which fields are included. By default, most fields are enabled except {@code withFormattedMessage}.
+ *
+ * @see JSON Lines
+ * @see RFC 8259 (The JavaScript Object Notation (JSON) Data Interchange Format)
+ * @see ch.qos.logback.core.encoder.EncoderBase
+ * @since 1.3.8/1.4.8
+ * @author Ceki Gülcü
*/
public class JsonEncoder extends EncoderBase {
static final boolean DO_NOT_ADD_QUOTE_KEY = false;
@@ -89,20 +124,20 @@ public class JsonEncoder extends EncoderBase {
public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
- private static final char OPEN_OBJ = '{';
- private static final char CLOSE_OBJ = '}';
- private static final char OPEN_ARRAY = '[';
- private static final char CLOSE_ARRAY = ']';
+ protected static final char OPEN_OBJ = '{';
+ protected static final char CLOSE_OBJ = '}';
+ protected static final char OPEN_ARRAY = '[';
+ protected static final char CLOSE_ARRAY = ']';
- private static final char QUOTE = DOUBLE_QUOTE_CHAR;
- private static final char SP = ' ';
- private static final char ENTRY_SEPARATOR = COLON_CHAR;
+ protected static final char QUOTE = DOUBLE_QUOTE_CHAR;
+ protected static final char SP = ' ';
+ protected static final char ENTRY_SEPARATOR = COLON_CHAR;
- private static final String COL_SP = ": ";
+ protected static final String COL_SP = ": ";
- private static final String QUOTE_COL = "\":";
+ protected static final String QUOTE_COL = "\":";
- private static final char VALUE_SEPARATOR = COMMA_CHAR;
+ protected static final char VALUE_SEPARATOR = COMMA_CHAR;
private boolean withSequenceNumber = true;
@@ -194,12 +229,34 @@ public byte[] encode(ILoggingEvent event) {
if (withThrowable)
appendThrowableProxy(sb, THROWABLE_ATTR_NAME, event.getThrowableProxy());
+ // allow subclasses to append custom top-level fields; default implementation is a no-op
+ appendCustomFields(sb, event);
+
sb.append(CLOSE_OBJ);
sb.append(CoreConstants.JSON_LINE_SEPARATOR);
return sb.toString().getBytes(UTF_8_CHARSET);
}
- void appendValueSeparator(StringBuilder sb, boolean... subsequentConditionals) {
+ /**
+ * Append a JSON value separator (a comma) to the provided {@link StringBuilder}
+ * when any of the supplied boolean flags indicate that a subsequent element
+ * is present.
+ *
+ *
Callers pass a sequence of booleans that represent whether subsequent
+ * JSON members will be written. If at least one of those booleans is
+ * {@code true}, this method appends a single comma (',') to separate JSON
+ * fields.
+ *
+ *
This method is protected so subclasses that extend the encoder can
+ * reuse or override the logic for inserting separators between generated
+ * JSON members.
+ *
+ * @param sb the {@link StringBuilder} to append the separator to; must not be {@code null}
+ * @param subsequentConditionals one or more booleans indicating whether
+ * subsequent JSON elements will be written.
+ * If any value is {@code true}, a comma is appended.
+ */
+ protected void appendValueSeparator(StringBuilder sb, boolean... subsequentConditionals) {
boolean enabled = false;
for (boolean subsequent : subsequentConditionals) {
if (subsequent) {
@@ -212,7 +269,7 @@ void appendValueSeparator(StringBuilder sb, boolean... subsequentConditionals) {
sb.append(VALUE_SEPARATOR);
}
- private void appendLoggerContext(StringBuilder sb, LoggerContextVO loggerContextVO) {
+ protected void appendLoggerContext(StringBuilder sb, LoggerContextVO loggerContextVO) {
sb.append(QUOTE).append(CONTEXT_ATTR_NAME).append(QUOTE_COL);
if (loggerContextVO == null) {
@@ -231,7 +288,7 @@ private void appendLoggerContext(StringBuilder sb, LoggerContextVO loggerContext
}
- private void appendMap(StringBuilder sb, String attrName, Map map) {
+ protected void appendMap(StringBuilder sb, String attrName, Map map) {
sb.append(QUOTE).append(attrName).append(QUOTE_COL);
if (map == null) {
sb.append(NULL_STR);
@@ -253,11 +310,11 @@ private void appendMap(StringBuilder sb, String attrName, Map ma
sb.append(CLOSE_OBJ);
}
- private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {
+ protected void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {
appendThrowableProxy(sb, attributeName, itp, true);
}
- private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp, boolean appendValueSeparator) {
+ protected void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp, boolean appendValueSeparator) {
if (appendValueSeparator)
sb.append(VALUE_SEPARATOR);
@@ -316,10 +373,16 @@ private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrow
}
- private void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArray, int commonFrames) {
+ protected void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArray, int commonFrames) {
sb.append(QUOTE).append(STEP_ARRAY_NAME_ATTRIBUTE).append(QUOTE_COL).append(OPEN_ARRAY);
- int len = stepArray != null ? stepArray.length : 0;
+ // If there are no stack trace elements, write an empty array and return early.
+ if (stepArray == null || stepArray.length == 0) {
+ sb.append(CLOSE_ARRAY);
+ return;
+ }
+
+ int len = stepArray.length;
if (commonFrames >= len) {
commonFrames = 0;
@@ -351,19 +414,36 @@ private void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArra
sb.append(CLOSE_ARRAY);
}
- private void appenderMember(StringBuilder sb, String key, String value) {
+ /**
+ * Hook allowing subclasses to append additional fields into the root JSON object.
+ * Default implementation is a no-op.
+ *
+ *
Subclasses may append additional top-level JSON members here. If a
+ * subclass writes additional members it should prepend them with
+ * {@link #VALUE_SEPARATOR} (a comma) if necessary to keep the JSON valid.
+ * Implementations must not close the root JSON object or write the final
+ * line separator; {@link JsonEncoder} handles those.
+ *
+ * @param sb the StringBuilder that accumulates the JSON output; never null
+ * @param event the logging event being encoded; never null
+ */
+ protected void appendCustomFields(StringBuilder sb, ILoggingEvent event) {
+ // no-op by default; subclasses may append VALUE_SEPARATOR then their fields
+ }
+
+ protected void appenderMember(StringBuilder sb, String key, String value) {
sb.append(QUOTE).append(key).append(QUOTE_COL).append(QUOTE).append(value).append(QUOTE);
}
- private void appenderMemberWithIntValue(StringBuilder sb, String key, int value) {
+ protected void appenderMemberWithIntValue(StringBuilder sb, String key, int value) {
sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
}
- private void appenderMemberWithLongValue(StringBuilder sb, String key, long value) {
+ protected void appenderMemberWithLongValue(StringBuilder sb, String key, long value) {
sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
}
- private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
+ protected void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
List kvpList = event.getKeyValuePairs();
if (kvpList == null || kvpList.isEmpty())
return;
@@ -382,7 +462,7 @@ private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
sb.append(CLOSE_ARRAY);
}
- private void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
+ protected void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
Object[] argumentArray = event.getArgumentArray();
if (argumentArray == null)
return;
@@ -399,7 +479,7 @@ private void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
sb.append(CLOSE_ARRAY);
}
- private void appendMarkers(StringBuilder sb, ILoggingEvent event) {
+ protected void appendMarkers(StringBuilder sb, ILoggingEvent event) {
List markerList = event.getMarkerList();
if (markerList == null)
return;
@@ -434,7 +514,7 @@ private String jsonEscape(String s) {
return jsonEscapeString(s);
}
- private void appendMDC(StringBuilder sb, ILoggingEvent event) {
+ protected void appendMDC(StringBuilder sb, ILoggingEvent event) {
Map map = event.getMDCPropertyMap();
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(MDC_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_OBJ);
@@ -452,7 +532,13 @@ private void appendMDC(StringBuilder sb, ILoggingEvent event) {
sb.append(CLOSE_OBJ);
}
- boolean isNotEmptyMap(Map map) {
+ /**
+ * Return {@code true} when the provided map is non-null and non-empty.
+ *
+ * @param map the map to check; may be null
+ * @return {@code true} if the map contains at least one entry
+ */
+ boolean isNotEmptyMap(Map, ?> map) {
if (map == null)
return false;
return !map.isEmpty();
@@ -464,7 +550,8 @@ public byte[] footerBytes() {
}
/**
- * @param withSequenceNumber
+ * Set whether the sequence number is included in each encoded event.
+ * @param withSequenceNumber {@code true} to include the sequence number in the output
* @since 1.5.0
*/
public void setWithSequenceNumber(boolean withSequenceNumber) {
@@ -472,7 +559,8 @@ public void setWithSequenceNumber(boolean withSequenceNumber) {
}
/**
- * @param withTimestamp
+ * Set whether the event timestamp is included in each encoded event.
+ * @param withTimestamp {@code true} to include the event timestamp in the output
* @since 1.5.0
*/
public void setWithTimestamp(boolean withTimestamp) {
@@ -480,55 +568,111 @@ public void setWithTimestamp(boolean withTimestamp) {
}
/**
- * @param withNanoseconds
+ * Set whether nanoseconds will be included in the timestamp output.
+ * @param withNanoseconds {@code true} to include nanoseconds in the timestamp output
* @since 1.5.0
*/
public void setWithNanoseconds(boolean withNanoseconds) {
this.withNanoseconds = withNanoseconds;
}
- public void setWithLevel(boolean withLevel) {
- this.withLevel = withLevel;
- }
-
- public void setWithThreadName(boolean withThreadName) {
- this.withThreadName = withThreadName;
- }
-
- public void setWithLoggerName(boolean withLoggerName) {
- this.withLoggerName = withLoggerName;
- }
-
- public void setWithContext(boolean withContext) {
- this.withContext = withContext;
- }
-
- public void setWithMarkers(boolean withMarkers) {
- this.withMarkers = withMarkers;
- }
-
- public void setWithMDC(boolean withMDC) {
- this.withMDC = withMDC;
- }
-
- public void setWithKVPList(boolean withKVPList) {
- this.withKVPList = withKVPList;
- }
-
- public void setWithMessage(boolean withMessage) {
- this.withMessage = withMessage;
- }
-
- public void setWithArguments(boolean withArguments) {
- this.withArguments = withArguments;
- }
-
- public void setWithThrowable(boolean withThrowable) {
- this.withThrowable = withThrowable;
- }
-
- public void setWithFormattedMessage(boolean withFormattedMessage) {
- this.withFormattedMessage = withFormattedMessage;
- }
-
-}
+ /**
+ * Enable or disable the inclusion of the log level in the encoded output.
+ *
+ * @param withLevel {@code true} to include the log level. Default is {@code true}.
+ */
+ public void setWithLevel(boolean withLevel) {
+ this.withLevel = withLevel;
+ }
+
+ /**
+ * Enable or disable the inclusion of the thread name in the encoded output.
+ *
+ * @param withThreadName {@code true} to include the thread name. Default is {@code true}.
+ */
+ public void setWithThreadName(boolean withThreadName) {
+ this.withThreadName = withThreadName;
+ }
+
+ /**
+ * Enable or disable the inclusion of the logger name in the encoded output.
+ *
+ * @param withLoggerName {@code true} to include the logger name. Default is {@code true}.
+ */
+ public void setWithLoggerName(boolean withLoggerName) {
+ this.withLoggerName = withLoggerName;
+ }
+
+ /**
+ * Enable or disable the inclusion of the logger context information.
+ *
+ * @param withContext {@code true} to include the logger context. Default is {@code true}.
+ */
+ public void setWithContext(boolean withContext) {
+ this.withContext = withContext;
+ }
+
+ /**
+ * Enable or disable the inclusion of markers in the encoded output.
+ *
+ * @param withMarkers {@code true} to include markers. Default is {@code true}.
+ */
+ public void setWithMarkers(boolean withMarkers) {
+ this.withMarkers = withMarkers;
+ }
+
+ /**
+ * Enable or disable the inclusion of MDC properties in the encoded output.
+ *
+ * @param withMDC {@code true} to include MDC properties. Default is {@code true}.
+ */
+ public void setWithMDC(boolean withMDC) {
+ this.withMDC = withMDC;
+ }
+
+ /**
+ * Enable or disable the inclusion of key-value pairs attached to the logging event.
+ *
+ * @param withKVPList {@code true} to include the key/value pairs list. Default is {@code true}.
+ */
+ public void setWithKVPList(boolean withKVPList) {
+ this.withKVPList = withKVPList;
+ }
+
+ /**
+ * Enable or disable the inclusion of the raw message text in the encoded output.
+ *
+ * @param withMessage {@code true} to include the message. Default is {@code true}.
+ */
+ public void setWithMessage(boolean withMessage) {
+ this.withMessage = withMessage;
+ }
+
+ /**
+ * Enable or disable the inclusion of the event argument array in the encoded output.
+ *
+ * @param withArguments {@code true} to include the argument array. Default is {@code true}.
+ */
+ public void setWithArguments(boolean withArguments) {
+ this.withArguments = withArguments;
+ }
+
+ /**
+ * Enable or disable the inclusion of throwable information in the encoded output.
+ *
+ * @param withThrowable {@code true} to include throwable/stacktrace information. Default is {@code true}.
+ */
+ public void setWithThrowable(boolean withThrowable) {
+ this.withThrowable = withThrowable;
+ }
+
+ /**
+ * Enable or disable the inclusion of the formatted message in the encoded output.
+ *
+ * @param withFormattedMessage {@code true} to include the formatted message. Default is {@code false}.
+ */
+ public void setWithFormattedMessage(boolean withFormattedMessage) {
+ this.withFormattedMessage = withFormattedMessage;
+ }
+
+ }
From 0091c0c853c6736bf86a19da667028bf4550d6a4 Mon Sep 17 00:00:00 2001
From: ceki
Date: Mon, 20 Oct 2025 19:51:17 +0200
Subject: [PATCH 20/36] fixes LOGBACK-427
Signed-off-by: ceki
---
.../qos/logback/classic/log4j/XMLLayout.java | 19 +++++++------------
1 file changed, 7 insertions(+), 12 deletions(-)
diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/log4j/XMLLayout.java b/logback-classic/src/main/java/ch/qos/logback/classic/log4j/XMLLayout.java
index 4bc04388a8..c4da9189e8 100644
--- a/logback-classic/src/main/java/ch/qos/logback/classic/log4j/XMLLayout.java
+++ b/logback-classic/src/main/java/ch/qos/logback/classic/log4j/XMLLayout.java
@@ -17,6 +17,7 @@
import java.util.Set;
import java.util.Map.Entry;
+import ch.qos.logback.classic.net.SMTPAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
@@ -37,10 +38,8 @@
*/
public class XMLLayout extends LayoutBase {
- private final int DEFAULT_SIZE = 256;
- private final int UPPER_LIMIT = 2048;
+ private static final int DEFAULT_SIZE = 256;
- private StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
private boolean locationInfo = false;
private boolean properties = false;
@@ -57,8 +56,10 @@ public void start() {
*
*
* If you are embedding this layout within an
- * {@link org.apache.log4j.net.SMTPAppender} then make sure to set the
- * LocationInfo option of that appender as well.
+ * {@link SMTPAppender} then make sure to set the
+ * {@link SMTPAppender#setIncludeCallerData(boolean) includeCallerData} option
+ * as well.
+ *
*/
public void setLocationInfo(boolean flag) {
locationInfo = flag;
@@ -96,13 +97,7 @@ public boolean getProperties() {
*/
public String doLayout(ILoggingEvent event) {
- // Reset working buffer. If the buffer is too large, then we need a new
- // one in order to avoid the penalty of creating a large array.
- if (buf.capacity() > UPPER_LIMIT) {
- buf = new StringBuilder(DEFAULT_SIZE);
- } else {
- buf.setLength(0);
- }
+ StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
// We yield to the \r\n heresy.
From 46bf3763bea53c1849501f3ebbe809c7202b031f Mon Sep 17 00:00:00 2001
From: ceki
Date: Mon, 20 Oct 2025 22:27:43 +0200
Subject: [PATCH 21/36] improved GZ compression
Signed-off-by: ceki
---
.../rolling/helper/CompressionStrategyBase.java | 2 +-
.../core/rolling/helper/GZCompressionStrategy.java | 14 ++++----------
2 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java
index f5ef07b3f6..b215564b52 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/CompressionStrategyBase.java
@@ -22,7 +22,7 @@
abstract public class CompressionStrategyBase extends ContextAwareBase implements CompressionStrategy {
- static final int BUFFER_SIZE = 8192;
+ static final int BUFFER_SIZE = 65536;
void createMissingTargetDirsIfNecessary(File file) {
boolean result = FileUtil.createMissingParentDirectories(file);
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java
index d7b205ef4c..231b5d49a0 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java
@@ -17,7 +17,6 @@
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.status.WarnStatus;
-import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -50,16 +49,11 @@ public void compress(String originalFileName, String compressedFileName, String
addInfo("GZ compressing [" + file2gz + "] as [" + gzedFile + "]");
createMissingTargetDirsIfNecessary(gzedFile);
+ System.out.println("GZ compressing [" + file2gz + "] as [" + gzedFile + "]");
+ try (FileInputStream fis = new FileInputStream(originalFileName);
+ GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(compressedFileName), BUFFER_SIZE)) {
- try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(originalFileName));
- GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(compressedFileName))) {
-
- byte[] inbuf = new byte[BUFFER_SIZE];
- int n;
-
- while ((n = bis.read(inbuf)) != -1) {
- gzos.write(inbuf, 0, n);
- }
+ fis.transferTo(gzos);
addInfo("Done GZ compressing [" + file2gz + "] as [" + gzedFile + "]");
} catch (Exception e) {
From f61f617b697d788451f5d21466172356b00f62d3 Mon Sep 17 00:00:00 2001
From: ceki
Date: Tue, 21 Oct 2025 11:41:10 +0200
Subject: [PATCH 22/36] revert to while read loop
Signed-off-by: ceki
---
.../core/rolling/helper/GZCompressionStrategy.java | 10 +++++++---
.../core/rolling/helper/XZCompressionStrategy.java | 3 ++-
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java
index 231b5d49a0..b5b04f240b 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/GZCompressionStrategy.java
@@ -49,11 +49,15 @@ public void compress(String originalFileName, String compressedFileName, String
addInfo("GZ compressing [" + file2gz + "] as [" + gzedFile + "]");
createMissingTargetDirsIfNecessary(gzedFile);
- System.out.println("GZ compressing [" + file2gz + "] as [" + gzedFile + "]");
try (FileInputStream fis = new FileInputStream(originalFileName);
- GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(compressedFileName), BUFFER_SIZE)) {
+ GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(compressedFileName), BUFFER_SIZE)) {
- fis.transferTo(gzos);
+ byte[] inbuf = new byte[BUFFER_SIZE];
+ int n;
+
+ while ((n = fis.read(inbuf)) != -1) {
+ gzos.write(inbuf, 0, n);
+ }
addInfo("Done GZ compressing [" + file2gz + "] as [" + gzedFile + "]");
} catch (Exception e) {
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java
index 95a539dc6b..b5dd05591d 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java
@@ -17,6 +17,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+
import org.tukaani.xz.LZMA2Options;
import org.tukaani.xz.XZOutputStream;
@@ -56,7 +57,7 @@ public void compress(String nameOfFile2xz, String nameOfxzedFile, String innerEn
createMissingTargetDirsIfNecessary(xzedFile);
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(nameOfFile2xz));
- XZOutputStream xzos = new XZOutputStream(new FileOutputStream(nameOfxzedFile), new LZMA2Options())) {
+ XZOutputStream xzos = new XZOutputStream(new FileOutputStream(nameOfxzedFile), new LZMA2Options())) {
byte[] inbuf = new byte[BUFFER_SIZE];
int n;
From 8411d007cbd9a65228041e6c4724b8c2738320f6 Mon Sep 17 00:00:00 2001
From: ceki
Date: Tue, 21 Oct 2025 12:06:47 +0200
Subject: [PATCH 23/36] minor improvements
Signed-off-by: ceki
---
.../main/java/ch/qos/logback/core/CoreConstants.java | 5 +++++
.../logback/core/rolling/helper/FileFilterUtil.java | 10 +++++++---
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java b/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java
index 47b96f5bbf..dde2d07b17 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/CoreConstants.java
@@ -13,6 +13,7 @@
*/
package ch.qos.logback.core;
+import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -145,6 +146,10 @@ public class CoreConstants {
* An empty Class array.
*/
public static final Class>[] EMPTY_CLASS_ARRAY = new Class[] {};
+
+
+ public static final File[] EMPTY_FILE_ARRAY = new File[0];
+
public static final String CAUSED_BY = "Caused by: ";
public static final String SUPPRESSED = "Suppressed: ";
public static final String WRAPPED_BY = "Wrapped by: ";
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileFilterUtil.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileFilterUtil.java
index 1502e55188..68a59a831e 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileFilterUtil.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/FileFilterUtil.java
@@ -20,8 +20,12 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static ch.qos.logback.core.CoreConstants.EMPTY_FILE_ARRAY;
+
public class FileFilterUtil {
+
+
public static void sortFileArrayByName(File[] fileArray) {
Arrays.sort(fileArray, new Comparator() {
public int compare(File o1, File o2) {
@@ -74,10 +78,10 @@ static public boolean isEmptyDirectory(File dir) {
public static File[] filesInFolderMatchingStemRegex(File file, final String stemRegex) {
if (file == null) {
- return new File[0];
+ return EMPTY_FILE_ARRAY;
}
- if (!file.exists() || !file.isDirectory()) {
- return new File[0];
+ if (!file.isDirectory()) {
+ return EMPTY_FILE_ARRAY;
}
// better compile the regex. See also LOGBACK-1409
From a8e6fa4dc1118d011901e71c8fae8014043a604c Mon Sep 17 00:00:00 2001
From: ceki
Date: Tue, 21 Oct 2025 17:54:18 +0200
Subject: [PATCH 24/36] drop BufferedInputStream to avoid axtra memory copies
Signed-off-by: ceki
---
.../logback/core/rolling/helper/XZCompressionStrategy.java | 4 ++--
.../core/rolling/helper/ZipCompressionStrategy.java | 7 +++----
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java
index b5dd05591d..70904c4f47 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/XZCompressionStrategy.java
@@ -56,13 +56,13 @@ public void compress(String nameOfFile2xz, String nameOfxzedFile, String innerEn
addInfo("XZ compressing [" + file2xz + "] as [" + xzedFile + "]");
createMissingTargetDirsIfNecessary(xzedFile);
- try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(nameOfFile2xz));
+ try (FileInputStream fis = new FileInputStream(nameOfFile2xz);
XZOutputStream xzos = new XZOutputStream(new FileOutputStream(nameOfxzedFile), new LZMA2Options())) {
byte[] inbuf = new byte[BUFFER_SIZE];
int n;
- while ((n = bis.read(inbuf)) != -1) {
+ while ((n = fis.read(inbuf)) != -1) {
xzos.write(inbuf, 0, n);
}
} catch (Exception e) {
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java
index 1ac2855ede..32730bc813 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/ZipCompressionStrategy.java
@@ -31,7 +31,6 @@
* @since 1.5.18
*/
public class ZipCompressionStrategy extends CompressionStrategyBase {
- static final int BUFFER_SIZE = 8192;
@Override
public void compress(String originalFileName, String compressedFileName, String innerEntryName) {
@@ -64,8 +63,8 @@ public void compress(String originalFileName, String compressedFileName, String
addInfo("ZIP compressing [" + file2zip + "] as [" + zippedFile + "]");
createMissingTargetDirsIfNecessary(zippedFile);
- try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(originalFileName));
- ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(compressedFileName))) {
+ try (FileInputStream fis = new FileInputStream(originalFileName);
+ ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(compressedFileName))) {
ZipEntry zipEntry = computeZipEntry(innerEntryName);
zos.putNextEntry(zipEntry);
@@ -73,7 +72,7 @@ public void compress(String originalFileName, String compressedFileName, String
byte[] inbuf = new byte[BUFFER_SIZE];
int n;
- while ((n = bis.read(inbuf)) != -1) {
+ while ((n = fis.read(inbuf)) != -1) {
zos.write(inbuf, 0, n);
}
From 26ea7ea6a0424956f185303c9f2ae32f2c43ae6a Mon Sep 17 00:00:00 2001
From: ceki
Date: Thu, 23 Oct 2025 10:54:20 +0200
Subject: [PATCH 25/36] add support for parameterized unit tests
Signed-off-by: ceki
---
...imeBasedFileNamingAndTriggeringPolicy.java | 5 +-
...asedFileNamingAndTriggeringPolicyBase.java | 8 ++-
.../helper/TimeBasedArchiveRemover.java | 9 ++-
.../ParentScaffoldingForRollingTests.java | 66 +++++++++++++++++++
pom.xml | 7 ++
5 files changed, 89 insertions(+), 6 deletions(-)
create mode 100644 logback-core/src/test/java/ch/qos/logback/core/rolling/testUtil/ParentScaffoldingForRollingTests.java
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/DefaultTimeBasedFileNamingAndTriggeringPolicy.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/DefaultTimeBasedFileNamingAndTriggeringPolicy.java
index 7b8612f042..8c05881328 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/DefaultTimeBasedFileNamingAndTriggeringPolicy.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/DefaultTimeBasedFileNamingAndTriggeringPolicy.java
@@ -13,6 +13,8 @@
import java.io.File;
import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.Date;
import ch.qos.logback.core.joran.spi.NoAutoStart;
@@ -54,7 +56,8 @@ public boolean isTriggeringEvent(File activeFile, final E event) {
long nextCheck = computeNextCheck(currentTime);
atomicNextCheck.set(nextCheck);
Instant instantOfElapsedPeriod = dateInCurrentPeriod;
- addInfo("Elapsed period: " + instantOfElapsedPeriod.toString());
+ ZonedDateTime ztd = instantOfElapsedPeriod.atZone(zoneId);
+ addInfo("Elapsed period: " + ztd.toString());
this.elapsedPeriodsFileName = tbrp.fileNamePatternWithoutCompSuffix.convert(instantOfElapsedPeriod);
setDateInCurrentPeriod(currentTime);
return true;
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/TimeBasedFileNamingAndTriggeringPolicyBase.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/TimeBasedFileNamingAndTriggeringPolicyBase.java
index 7961399ccf..864636903e 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/TimeBasedFileNamingAndTriggeringPolicyBase.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/TimeBasedFileNamingAndTriggeringPolicyBase.java
@@ -17,6 +17,7 @@
import java.io.File;
import java.time.Instant;
+import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicLong;
@@ -56,6 +57,8 @@ abstract public class TimeBasedFileNamingAndTriggeringPolicyBase extends Cont
protected boolean started = false;
protected boolean errorFree = true;
+ protected ZoneId zoneId = ZoneId.systemDefault();
+
public boolean isStarted() {
return started;
}
@@ -68,7 +71,8 @@ public void start() {
}
if (dtc.getZoneId() != null) {
- TimeZone tz = TimeZone.getTimeZone(dtc.getZoneId());
+ this.zoneId = dtc.getZoneId();
+ TimeZone tz = TimeZone.getTimeZone(zoneId);
rc = new RollingCalendar(dtc.getDatePattern(), tz, Locale.getDefault());
} else {
rc = new RollingCalendar(dtc.getDatePattern());
@@ -90,7 +94,7 @@ public void start() {
if (tbrp.getParentsRawFileProperty() != null) {
File currentFile = new File(tbrp.getParentsRawFileProperty());
- if (currentFile.exists() && currentFile.canRead()) {
+ if (currentFile.canRead()) {
timestamp = currentFile.lastModified();
setDateInCurrentPeriod(timestamp);
}
diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java
index d2ce45f8c1..9156ab416c 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java
@@ -96,6 +96,7 @@ public void cleanPeriod(Instant instantOfPeriodToClean) {
File[] matchingFileArray = getFilesInPeriod(instantOfPeriodToClean);
for (File f : matchingFileArray) {
+ addInfo("deleting historically stale " + f);
checkAndDeleteFile(f);
}
@@ -106,7 +107,7 @@ public void cleanPeriod(Instant instantOfPeriodToClean) {
}
private boolean checkAndDeleteFile(File f) {
- addInfo("deleting historically stale " + f);
+
if (f == null) {
addWarn("Cannot delete empty file");
return false;
@@ -126,7 +127,7 @@ void capTotalSize(Instant now) {
long totalSize = 0;
long totalRemoved = 0;
int successfulDeletions = 0;
- int failedDeletions = 0;
+ int failedDeletions = 0;
for (int offset = 0; offset < maxHistory; offset++) {
Instant instant = rc.getEndOfNextNthPeriod(now, -offset);
@@ -134,9 +135,11 @@ void capTotalSize(Instant now) {
descendingSort(matchingFileArray, instant);
for (File f : matchingFileArray) {
long size = f.length();
+ //System.out.println("File: " + f + " size=" + size);
totalSize += size;
if (totalSize > totalSizeCap) {
- addInfo("Deleting [" + f + "]" + " of size " + new FileSize(size) + " on account of totalSizeCap " + totalSizeCap);
+ //addInfo("Deleting [" + f + "]" + " of size " + new FileSize(size) + " on account of totalSizeCap " + totalSizeCap);
+ addInfo("Deleting [" + f + "]" + " of size " + size + " on account of totalSizeCap " + totalSizeCap);
boolean success = checkAndDeleteFile(f);
diff --git a/logback-core/src/test/java/ch/qos/logback/core/rolling/testUtil/ParentScaffoldingForRollingTests.java b/logback-core/src/test/java/ch/qos/logback/core/rolling/testUtil/ParentScaffoldingForRollingTests.java
new file mode 100644
index 0000000000..b50fc30c55
--- /dev/null
+++ b/logback-core/src/test/java/ch/qos/logback/core/rolling/testUtil/ParentScaffoldingForRollingTests.java
@@ -0,0 +1,66 @@
+/*
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * Copyright (C) 1999-2025, QOS.ch. All rights reserved.
+ *
+ * This program and the accompanying materials are dual-licensed under
+ * either the terms of the Eclipse Public License v1.0 as published by
+ * the Eclipse Foundation
+ *
+ * or (per the licensee's choosing)
+ *
+ * under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation.
+ */
+
+package ch.qos.logback.core.rolling.testUtil;
+
+import ch.qos.logback.core.Context;
+import ch.qos.logback.core.ContextBase;
+import ch.qos.logback.core.encoder.EchoEncoder;
+import ch.qos.logback.core.testUtil.CoreTestConstants;
+import ch.qos.logback.core.testUtil.RandomUtil;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+public class ParentScaffoldingForRollingTest {
+
+ protected EchoEncoder