diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c6e1d4be36..6e93655bd8 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,18 @@
-tidelift: maven/ch.qos.logback:logback-classic
-github: qos-ch
\ No newline at end of file
+github: qos-ch
+tidelift: maven/ch.qos.logback:logback-core
+thanks-dev: gh/ceki
+
+
+#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+#patreon: # Replace with a single Patreon username
+#open_collective: # Replace with a single Open Collective username
+#ko_fi: # Replace with a single Ko-fi username
+#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+#liberapay: # Replace with a single Liberapay username
+#issuehunt: # Replace with a single IssueHunt username
+#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+#polar: # Replace with a single Polar username
+#buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
+#thanks_dev: # Replace with a single thanks.dev username
+#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
\ No newline at end of file
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7b75aea8d4..303add2fd6 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -39,6 +39,7 @@ jobs:
with:
distribution: 'temurin'
java-version: ${{ matrix.jdk }}
+ cache: maven # This enables Maven dependency caching
- name: Install
# download dependencies, etc, so test log looks better
run: mvn -B install
diff --git a/FUNDING.yml b/FUNDING.yml
deleted file mode 100644
index 18dbb1166a..0000000000
--- a/FUNDING.yml
+++ /dev/null
@@ -1 +0,0 @@
-github: qos-ch
\ No newline at end of file
diff --git a/README.md b/README.md
index c1d13079f3..93745d6971 100755
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ who can quickly answer your questions.
# Urgent issues
For urgent issues do not hesitate to [champion a
-release](https://github.com/sponsors/qos-ch/sponsorships?tier_id=77436).
+release](https://github.com/sponsors/qos-ch/sponsorships?tier_id=543501).
In principle, most championed issues are solved within 3 business days
followed up by a release.
diff --git a/logback-access/pom.xml b/logback-access/pom.xml
index f719baaa3a..3e83462237 100644
--- a/logback-access/pom.xml
+++ b/logback-access/pom.xml
@@ -8,7 +8,7 @@
It is used by isXYZEnabled() methods such as {@link #isDebugEnabled()}, + * {@link #isInfoEnabled()} etc. + *
+ * + *It returns the typical FilterReply values: ACCEPT, NEUTRAL or DENY. + *
* @param level * @return the reply given by the TurboFilters */ @@ -756,6 +763,24 @@ private FilterReply callTurboFilters(Marker marker, Level level) { return loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, null, null, null); } + /** + * Method that calls the attached TurboFilter objects based on this logger and + * {@link org.slf4j.event.LoggingEvent LoggingEvent}. + * + *This method is typically called by + * {@link #log(org.slf4j.event.LoggingEvent) log(LoggingEvent)} method.
+ * + *It returns {@link FilterReply} values: ACCEPT, NEUTRAL or DENY. + *
+ * + * @param slf4jEvent the SLF4J LoggingEvent + * @return the reply given by the TurboFilters + */ + private FilterReply callTurboFilters(org.slf4j.event.LoggingEvent slf4jEvent) { + return loggerContext.getTurboFilterChainDecision(this, slf4jEvent); + } + + /** * Return the context for this logger. * @@ -782,7 +807,9 @@ public void log(Marker marker, String fqcn, int levelInt, String message, Object /** * Support SLF4J interception during initialization as introduced in SLF4J - * version 1.7.15 + * version 1.7.15. Alternatively, this method can be called by SLF4J's fluent API, i.e. by + * {@link LoggingEventBuilder}. + * * * @since 1.1.4 * @param slf4jEvent @@ -791,10 +818,16 @@ public void log(org.slf4j.event.LoggingEvent slf4jEvent) { org.slf4j.event.Level slf4jLevel = slf4jEvent.getLevel(); Level logbackLevel = Level.convertAnSLF4JLevel(slf4jLevel); - // By default, assume this class was the caller. This happens during - // initialization. - // However, it is possible that the caller is some other library, e.g. - // slf4j-jdk-platform-logging + // invoke turbo filters. See also https://github.com/qos-ch/logback/issues/871 + final FilterReply decision = loggerContext.getTurboFilterChainDecision(this, slf4jEvent); + // the ACCEPT and NEUTRAL cases falls through as there are no further level checks to be done + if (decision == FilterReply.DENY) { + return; + } + + // By default, assume this class was the caller. In some cases, {@link SubstituteLogger} can also be a caller. + // + // It is possible that the caller is some other library, e.g. slf4j-jdk-platform-logging String callerBoundary = slf4jEvent.getCallerBoundary(); if (callerBoundary == null) { diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/LoggerContext.java b/logback-classic/src/main/java/ch/qos/logback/classic/LoggerContext.java index 8faed3ed3c..be8e7870fb 100755 --- a/logback-classic/src/main/java/ch/qos/logback/classic/LoggerContext.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/LoggerContext.java @@ -13,24 +13,6 @@ */ package ch.qos.logback.classic; -import static ch.qos.logback.core.CoreConstants.EVALUATOR_MAP; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.locks.ReentrantLock; - -import ch.qos.logback.classic.util.LogbackMDCAdapter; -import ch.qos.logback.core.status.ErrorStatus; -import ch.qos.logback.core.status.InfoStatus; -import org.slf4j.ILoggerFactory; -import org.slf4j.Marker; - import ch.qos.logback.classic.spi.LoggerComparator; import ch.qos.logback.classic.spi.LoggerContextListener; import ch.qos.logback.classic.spi.LoggerContextVO; @@ -45,8 +27,16 @@ import ch.qos.logback.core.status.StatusListener; import ch.qos.logback.core.status.StatusManager; import ch.qos.logback.core.status.WarnStatus; +import org.slf4j.ILoggerFactory; +import org.slf4j.Marker; import org.slf4j.spi.MDCAdapter; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +import static ch.qos.logback.core.CoreConstants.EVALUATOR_MAP; + /** * LoggerContext glues many of the logback-classic components together. In * principle, every logback-classic component instance is attached either @@ -281,12 +271,20 @@ final FilterReply getTurboFilterChainDecision_1(final Marker marker, final Logge final FilterReply getTurboFilterChainDecision_2(final Marker marker, final Logger logger, final Level level, final String format, final Object param1, final Object param2, final Throwable t) { - if (turboFilterList.size() == 0) { + if (turboFilterList.isEmpty()) { return FilterReply.NEUTRAL; } return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, new Object[] { param1, param2 }, t); } + final FilterReply getTurboFilterChainDecision(final Logger logger, final org.slf4j.event.LoggingEvent slf4jEvent) { + if (turboFilterList.isEmpty()) { + return FilterReply.NEUTRAL; + } + return turboFilterList.getTurboFilterChainDecision(logger, slf4jEvent); + } + + // === start listeners ============================================== public void addListener(LoggerContextListener listener) { loggerContextListenerList.add(listener); diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java b/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java index 15b7becc95..d2cc244f4c 100644 --- a/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java @@ -42,11 +42,16 @@ public class PatternLayout extends PatternLayoutBaseThis 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.
* - * https://jsonlines.org/ https://datatracker.ietf.org/doc/html/rfc8259 + *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.
+ * + *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}.
+ * + *{@code
+ *
+ *
+ * false
+ * false
+ * false
+ *
+ *
+ * }
+ *
+ * This produces output similar to the following (on a single line): + *
+ * + *{"timestamp":1640995200000,"level":"INFO","loggerName":"com.example.MyClass","context":{"name":"default","birthdate":1640995200000,"properties":{}},"message":"Hello World"}
+ *
+ * @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 EncoderBaseCallers 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, MapSubclasses 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* 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. diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/spi/Configurator.java b/logback-classic/src/main/java/ch/qos/logback/classic/spi/Configurator.java index bd2c2e145e..48073d60f7 100644 --- a/logback-classic/src/main/java/ch/qos/logback/classic/spi/Configurator.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/spi/Configurator.java @@ -14,22 +14,18 @@ package ch.qos.logback.classic.spi; import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.core.Context; import ch.qos.logback.core.spi.ContextAware; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; /** - * Allows programmatic initialization and configuration of Logback. The + *Allows programmatic initialization and configuration of Logback. The * ServiceLoader is typically used to instantiate implementations and thus * implementations will need to follow the guidelines of the ServiceLoader, - * in particular the no-arg constructor requirement. + * in particular the no-arg constructor requirement.
* - * The return type of {@link #configure(LoggerContext) configure} was changed from 'void' to + *The return type of {@link #configure(LoggerContext) configure} was changed from 'void' to * {@link ExecutionStatus) in logback version 1.3.0. + *
*/ public interface Configurator extends ContextAware { diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java b/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java index be4cd2ecee..e7d146a850 100755 --- a/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java @@ -138,7 +138,7 @@ public LoggingEvent(String fqcn, Logger logger, Level level, String message, Thr } if (throwable == null) { - throwable = extractThrowableAnRearrangeArguments(argArray); + throwable = extractThrowableAndRearrangeArguments(argArray); } if (throwable != null) { @@ -158,7 +158,7 @@ void initTmestampFields(Instant instant) { this.timeStamp = (epochSecond * 1000) + (milliseconds); } - private Throwable extractThrowableAnRearrangeArguments(Object[] argArray) { + private Throwable extractThrowableAndRearrangeArguments(Object[] argArray) { Throwable extractedThrowable = EventArgUtil.extractThrowable(argArray); if (EventArgUtil.successfulExtraction(extractedThrowable)) { this.argumentArray = EventArgUtil.trimmedCopy(argArray); diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/spi/TurboFilterList.java b/logback-classic/src/main/java/ch/qos/logback/classic/spi/TurboFilterList.java index 698354dca3..70264a98dc 100644 --- a/logback-classic/src/main/java/ch/qos/logback/classic/spi/TurboFilterList.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/spi/TurboFilterList.java @@ -1,13 +1,13 @@ /** * Logback: the reliable, generic, fast and flexible logging framework. * Copyright (C) 1999-2015, 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) - * + *
+ * 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.
*/
@@ -24,7 +24,7 @@
/**
* Implementation of TurboFilterAttachable.
- *
+ *
* @author Ceki Gülcü
*/
final public class TurboFilterList extends CopyOnWriteArrayList
* For more information about turbo filters, please refer to the online manual
- * at http://logback.qos.ch/manual/filters.html#TurboFilter
- *
+ * at https://logback.qos.ch/manual/filters.html#TurboFilter
+ *
* @author Ceki Gulcu
*/
public abstract class TurboFilter extends ContextAwareBase implements LifeCycle {
@@ -53,6 +57,50 @@ public abstract class TurboFilter extends ContextAwareBase implements LifeCycle
public abstract FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params,
Throwable t);
+
+ /**
+ * This method is intended to be called via SLF4J's fluent API and more specifically by
+ * {@link Logger#log(org.slf4j.event.LoggingEvent slf4jEvent)}. Derived classes are strongly
+ * encouraged to override this method with a better suited and more specialized
+ * implementation.
+ * The present default implementation translates the given SLF4J {@code LoggingEvent} into the
+ * set of parameters required by {@link #decide(Marker, Logger, Level, String, Object[], Throwable)}
+ * and delegate the decision to that method.
+ * Concretely, this method:
+ * Returns the {@link ch.qos.logback.core.spi.FilterReply} produced by
+ * {@code decide(...)}, which should be one of DENY, NEUTRAL or ACCEPT.
+ *
+ * Derived classes are strongly encouraged to override this method with a
+ * better suited and more specialized implementation. Implementations are typically configured and managed by a LoggerContext.
+ * The type parameter E represents the event type the appender consumes (for
+ * example a log event object). Implementations should honor lifecycle methods
+ * from {@link LifeCycle} and may be {@link ContextAware} and
+ * {@link FilterAttachable} to support contextual information and filtering. Concurrency: appenders are generally invoked by multiple threads. Implementations
+ * must ensure thread-safety where applicable (for example when writing to shared
+ * resources). The {@link #doAppend(Object)} method may be called concurrently. Implementations should apply any configured filters before outputting
+ * the event. Implementations should avoid throwing runtime exceptions;
+ * if an error occurs that cannot be handled internally, a {@link LogbackException}
+ * (or a subtype) may be thrown to indicate a failure during append. The default implementation returns a no-op guard produced by
+ * {@link ReentryGuardFactory#makeGuard(ch.qos.logback.core.util.ReentryGuardFactory.GuardType)}
+ * using {@code GuardType.NOP}. Subclasses that require actual re-entry
+ * protection (for example using a thread-local or lock-based guard) should
+ * override this method to return an appropriate {@link ReentryGuard}
+ * implementation. Contract/expectations:
+ *
+ *
+ *
+ * System.out or
@@ -88,7 +90,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();
@@ -100,6 +102,14 @@ public void start() {
super.start();
}
+ /**
+ * Create a ThreadLocal ReentryGuard to prevent recursive appender invocations.
+ * @return a ReentryGuard instance of type {@link ReentryGuardFactory.GuardType#THREAD_LOCAL THREAD_LOCAL}.
+ */
+ protected ReentryGuard buildReentryGuard() {
+ return ReentryGuardFactory.makeGuard(ReentryGuardFactory.GuardType.THREAD_LOCAL);
+ }
+
private OutputStream wrapWithJansi(OutputStream targetStream) {
try {
addInfo("Enabling JANSI AnsiPrintStream for the console.");
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/UnsynchronizedAppenderBase.java b/logback-core/src/main/java/ch/qos/logback/core/UnsynchronizedAppenderBase.java
index 79ea30e18b..668863478f 100644
--- a/logback-core/src/main/java/ch/qos/logback/core/UnsynchronizedAppenderBase.java
+++ b/logback-core/src/main/java/ch/qos/logback/core/UnsynchronizedAppenderBase.java
@@ -20,6 +20,8 @@
import ch.qos.logback.core.spi.FilterAttachableImpl;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.status.WarnStatus;
+import ch.qos.logback.core.util.ReentryGuard;
+import ch.qos.logback.core.util.ReentryGuardFactory;
/**
* Similar to {@link AppenderBase} except that derived appenders need to handle thread
@@ -31,16 +33,13 @@
abstract public class UnsynchronizedAppenderBase
+ *
+ *
This condition expects a property name to be provided via + * {@link #setKey(String)}. When {@link #evaluate()} is called it returns + * {@code true} if the named property is defined and {@code false} + * otherwise. + */ +public class IsPropertyDefinedCondition extends PropertyConditionBase { + + /** + * The property name to check for definition. Must be set before + * starting this evaluator. + */ + String key; + + /** + * Start the evaluator. If the required {@link #key} is not set an + * error is reported and startup is aborted. + */ + public void start() { + if (key == null) { + addError("In IsPropertyDefinedEvaluator 'key' parameter cannot be null"); + return; + } + super.start(); + } + + /** + * Return the configured property name (key) that this evaluator will + * test for definition. + * + * @return the property key, or {@code null} if not set + */ + public String getKey() { + return key; + } + + /** + * Set the property name (key) to be checked by this evaluator. + * + * @param key the property name to check; must not be {@code null} + */ + public void setKey(String key) { + this.key = key; + } + + + /** + * Evaluate whether the configured property is defined. + * + * @return {@code true} if the property named by {@link #key} is + * defined, {@code false} otherwise + */ + @Override + public boolean evaluate() { + return isDefined(key); + } +} diff --git a/logback-core/src/main/java/ch/qos/logback/core/boolex/IsPropertyNullCondition.java b/logback-core/src/main/java/ch/qos/logback/core/boolex/IsPropertyNullCondition.java new file mode 100644 index 0000000000..2bd024808a --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/boolex/IsPropertyNullCondition.java @@ -0,0 +1,42 @@ +/* + * 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 IsPropertyNullCondition extends PropertyConditionBase { + + String key; + + public void start() { + if (key == null) { + addError("In IsPropertyNullCondition 'key' parameter cannot be null"); + return; + } + super.start(); + } + + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public boolean evaluate() { + return isNull(key); + } +} \ No newline at end of file diff --git a/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyCondition.java b/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyCondition.java new file mode 100644 index 0000000000..2a4e298026 --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyCondition.java @@ -0,0 +1,61 @@ +/** + * 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; + +import ch.qos.logback.core.Context; +import ch.qos.logback.core.joran.conditional.Condition; +import ch.qos.logback.core.spi.ContextAware; +import ch.qos.logback.core.spi.LifeCycle; +import ch.qos.logback.core.spi.PropertyContainer; + +/** + * Interface for evaluating conditions based on properties during the conditional processing + * of Logback configuration files. This interface is intended to provide an + * alternative to legacy Janino-based evaluation. + *
+ * Implementations of this interface can access both global properties from the {@link Context} + * and local properties specific to the embedding configurator instance. This allows for fine-grained + * and context-aware evaluation of configuration conditions. + *
+ * + *+ * Typical usage involves implementing this interface to provide custom logic for evaluating + * whether certain configuration blocks should be included or excluded based on property values. + *
+ * + * @since 1.5.20 + * @author Ceki Gülcü + */ +public interface PropertyCondition extends Condition, ContextAware, LifeCycle { + + /** + * Returns the local {@link PropertyContainer} used for property lookups specific to the embedding configurator. + * This is distinct from the global {@link Context} property container. + * + * @return the local property container, or null if not set + */ + public PropertyContainer getLocalPropertyContainer(); + + /** + * Sets a {@link PropertyContainer} specific to the embedding configurator, which is used for property lookups + * in addition to the global {@link Context} properties. This allows for overriding or supplementing global properties + * with local values during evaluation. + * + * @param aPropertyContainer the local property container to use for lookups + */ + public void setLocalPropertyContainer(PropertyContainer aPropertyContainer); + + +} \ No newline at end of file diff --git a/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyConditionBase.java b/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyConditionBase.java new file mode 100644 index 0000000000..343e451315 --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/boolex/PropertyConditionBase.java @@ -0,0 +1,162 @@ +/* + * 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; + +import ch.qos.logback.core.model.processor.ModelInterpretationContext; +import ch.qos.logback.core.spi.ContextAwareBase; +import ch.qos.logback.core.spi.PropertyContainer; +import ch.qos.logback.core.util.OptionHelper; + +/** + *Abstract base class provides some scaffolding. It is intended to ease migration + * from legacy conditional processing in configuration files + * (e.g. <if>, <then>, <else>) using the Janino library. Nevertheless, + * it should also be useful in newly written code.
+ * + *Properties are looked up in the following order:
+ * + *The local property container used for property lookups.
+ * + *Local properties correspond to the properties in the embedding + * configurator, i.e. usually the {@link ModelInterpretationContext} instance.
+ */ + PropertyContainer localPropertyContainer; + + /** + * 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 local property container + */ + @Override + public PropertyContainer getLocalPropertyContainer() { + return localPropertyContainer; + } + + /** + * 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 aLocalPropertyContainer the local property container to set + */ + @Override + 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 + */ + public boolean isNull(String k) { + String val = OptionHelper.propertyLookup(k, localPropertyContainer, getContext()); + return (val == null); + } + + /** + * 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 + */ + public boolean isDefined(String k) { + String val = OptionHelper.propertyLookup(k, localPropertyContainer, getContext()); + return (val != null); + } + + /** + * Retrieves the property value for the given key, returning an empty string if null. + * This is a shorthand for {@link #property(String)}. + * + * @param k the property key + * @return the property value or an empty string + */ + public String p(String k) { + return property(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 + */ + public String property(String k) { + String val = OptionHelper.propertyLookup(k, localPropertyContainer, getContext()); + if (val != null) + return val; + else + return ""; + } + + /** + * Checks if this evaluator has been started. + * + * @return true if started, false otherwise + */ + public boolean isStarted() { + return started; + } + + /** + * Starts this evaluator. + */ + public void start() { + started = true; + } + + /** + * Stops this evaluator. + */ + public void stop() { + started = false; + } +} 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/hook/DefaultShutdownHook.java b/logback-core/src/main/java/ch/qos/logback/core/hook/DefaultShutdownHook.java index c67a391a4c..30380d3ac6 100755 --- a/logback-core/src/main/java/ch/qos/logback/core/hook/DefaultShutdownHook.java +++ b/logback-core/src/main/java/ch/qos/logback/core/hook/DefaultShutdownHook.java @@ -35,9 +35,14 @@ public class DefaultShutdownHook extends ShutdownHookBase { */ private Duration delay = DEFAULT_DELAY; + + /** + * Creates a DefaultShutdownHook using the default delay ({@link #DEFAULT_DELAY}). + */ public DefaultShutdownHook() { } + public Duration getDelay() { return delay; } diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java b/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java index 662a9161b3..973505c882 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/JoranConfiguratorBase.java @@ -14,9 +14,7 @@ package ch.qos.logback.core.joran; import ch.qos.logback.core.joran.action.*; -import ch.qos.logback.core.joran.conditional.ElseAction; -import ch.qos.logback.core.joran.conditional.IfAction; -import ch.qos.logback.core.joran.conditional.ThenAction; +import ch.qos.logback.core.joran.conditional.*; import ch.qos.logback.core.joran.sanity.AppenderWithinAppenderSanityChecker; import ch.qos.logback.core.joran.sanity.SanityChecker; import ch.qos.logback.core.joran.spi.ElementSelector; @@ -77,6 +75,7 @@ protected void addElementSelectorAndActionAssociations(RuleStore rs) { rs.addRule(new ElementSelector("*/param"), ParamAction::new); // add if-then-else support + rs.addRule(new ElementSelector("*/condition"), ByPropertiesConditionAction::new); rs.addRule(new ElementSelector("*/if"), IfAction::new); rs.addTransparentPathPart("if"); rs.addRule(new ElementSelector("*/if/then"), ThenAction::new); diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java b/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java index 8c3ece450b..8ffb02f5c9 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/ModelClassToModelHandlerLinkerBase.java @@ -16,10 +16,12 @@ import ch.qos.logback.core.Context; import ch.qos.logback.core.model.*; +import ch.qos.logback.core.model.conditional.ByPropertiesConditionModel; import ch.qos.logback.core.model.conditional.ElseModel; import ch.qos.logback.core.model.conditional.IfModel; import ch.qos.logback.core.model.conditional.ThenModel; import ch.qos.logback.core.model.processor.*; +import ch.qos.logback.core.model.processor.conditional.ByPropertiesConditionModelHandler; import ch.qos.logback.core.model.processor.conditional.ElseModelHandler; import ch.qos.logback.core.model.processor.conditional.IfModelHandler; import ch.qos.logback.core.model.processor.conditional.ThenModelHandler; @@ -62,6 +64,8 @@ public void link(DefaultProcessor defaultProcessor) { defaultProcessor.addHandler(StatusListenerModel.class, StatusListenerModelHandler::makeInstance); defaultProcessor.addHandler(ImplicitModel.class, ImplicitModelHandler::makeInstance); + + defaultProcessor.addHandler(ByPropertiesConditionModel.class, ByPropertiesConditionModelHandler::makeInstance); defaultProcessor.addHandler(IfModel.class, IfModelHandler::makeInstance); defaultProcessor.addHandler(ThenModel.class, ThenModelHandler::makeInstance); defaultProcessor.addHandler(ElseModel.class, ElseModelHandler::makeInstance); diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/action/SequenceNumberGeneratorAction.java b/logback-core/src/main/java/ch/qos/logback/core/joran/action/SequenceNumberGeneratorAction.java index 96ccc3ee46..9d57e54258 100755 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/action/SequenceNumberGeneratorAction.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/action/SequenceNumberGeneratorAction.java @@ -1,6 +1,6 @@ /** * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2015, 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 diff --git a/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/ByPropertiesConditionAction.java b/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/ByPropertiesConditionAction.java new file mode 100644 index 0000000000..48ececdaaf --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/ByPropertiesConditionAction.java @@ -0,0 +1,43 @@ +/* + * 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.joran.conditional; + +import ch.qos.logback.core.joran.action.BaseModelAction; +import ch.qos.logback.core.joran.action.PreconditionValidator; +import ch.qos.logback.core.joran.spi.SaxEventInterpretationContext; +import ch.qos.logback.core.model.Model; +import ch.qos.logback.core.model.conditional.ByPropertiesConditionModel; +import org.xml.sax.Attributes; + +public class ByPropertiesConditionAction extends BaseModelAction { + + + @Override + protected Model buildCurrentModel(SaxEventInterpretationContext interpretationContext, String name, + Attributes attributes) { + ByPropertiesConditionModel sngm = new ByPropertiesConditionModel(); + sngm.setClassName(attributes.getValue(CLASS_ATTRIBUTE)); + return sngm; + } + + @Override + protected boolean validPreconditions(SaxEventInterpretationContext seic, String name, Attributes attributes) { + PreconditionValidator validator = new PreconditionValidator(this, seic, name, attributes); + validator.validateClassAttribute(); + return validator.isValid(); + } + +} + + 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/joran/conditional/IfAction.java b/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/IfAction.java index be77e803fb..c328704d92 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/IfAction.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/conditional/IfAction.java @@ -29,7 +29,7 @@ public class IfAction extends BaseModelAction { @Override protected boolean validPreconditions(SaxEventInterpretationContext interpcont, String name, Attributes attributes) { PreconditionValidator pv = new PreconditionValidator(this, interpcont, name, attributes); - pv.validateGivenAttribute(CONDITION_ATTRIBUTE); + //pv.validateGivenAttribute(CONDITION_ATTRIBUTE); return pv.isValid(); } diff --git a/logback-core/src/main/java/ch/qos/logback/core/model/conditional/ByPropertiesConditionModel.java b/logback-core/src/main/java/ch/qos/logback/core/model/conditional/ByPropertiesConditionModel.java new file mode 100644 index 0000000000..299580d827 --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/model/conditional/ByPropertiesConditionModel.java @@ -0,0 +1,29 @@ +/* + * 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.model.conditional; + +import ch.qos.logback.core.model.ComponentModel; + +public class ByPropertiesConditionModel extends ComponentModel { + + private static final long serialVersionUID = -1788292310734560420L; + + @Override + protected ByPropertiesConditionModel makeNewInstance() { + return new ByPropertiesConditionModel(); + } + + +} 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 new file mode 100644 index 0000000000..89bbb3d1be --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/model/processor/conditional/ByPropertiesConditionModelHandler.java @@ -0,0 +1,99 @@ +/* + * 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.model.processor.conditional; + +import ch.qos.logback.core.Context; +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; +import ch.qos.logback.core.model.processor.ModelHandlerBase; +import ch.qos.logback.core.model.processor.ModelHandlerException; +import ch.qos.logback.core.model.processor.ModelInterpretationContext; +import ch.qos.logback.core.util.OptionHelper; + +import static ch.qos.logback.core.model.conditional.IfModel.BranchState.ELSE_BRANCH; +import static ch.qos.logback.core.model.conditional.IfModel.BranchState.IF_BRANCH; + +public class ByPropertiesConditionModelHandler extends ModelHandlerBase { + + private boolean inError = false; + PropertyCondition propertyEvaluator; + + public ByPropertiesConditionModelHandler(Context context) { + super(context); + } + + @Override + protected ClassImplements the {@link Supplier} interface in order to cater for legacy code using the class name + * of a converter. + *
+ *Should not be used in non-legacy code.
+ */ public class ConverterSupplierByClassName extends ContextAwareBase implements SupplierImplementations are used by appenders and other components that must avoid + * recursively calling back into themselves (for example when an error causes + * logging while handling a logging event). Typical usage: check {@link #isLocked()} + * before proceeding and call {@link #lock()} / {@link #unlock()} around the + * guarded region.
+ * + *Concurrency: guards operate on a per-thread basis; callers should treat the + * guard as thread-local state. Implementations must document their semantics; + * the provided {@link ReentryGuardImpl} uses a {@link ThreadLocal} to track the + * locked state for the current thread.
+* + * @since 1.5.21 + */ +public interface ReentryGuard { + + /** + * Return true if the current thread holds the guard (i.e. is inside a guarded region). + * + *Implementations typically return {@code false} if the current thread has not + * previously called {@link #lock()} or if the stored value is {@code null}.
+ * + * @return {@code true} if the guard is locked for the current thread, {@code false} otherwise + */ + boolean isLocked(); + + /** + * Mark the guard as locked for the current thread. + * + *Callers must ensure {@link #unlock()} is invoked in a finally block to + * avoid leaving the guard permanently locked for the thread.
+ */ + void lock(); + + /** + * Release the guard for the current thread. + * + *After calling {@code unlock()} the {@link #isLocked()} should return + * {@code false} for the current thread (unless {@code lock()} is called again).
+ */ + void unlock(); + + + /** + * Default per-thread implementation backed by a {@link ThreadLocalSemantics: a value of {@link Boolean#TRUE} indicates the current thread + * is inside a guarded region. If the ThreadLocal has no value ({@code null}), + * {@link #isLocked()} treats this as unlocked (returns {@code false}).
+ * + *Note: this implementation intentionally uses {@code ThreadLocal
+ * if (!guard.isLocked()) {
+ * guard.lock();
+ * try {
+ * // guarded work
+ * } finally {
+ * guard.unlock();
+ * }
+ * }
+ *
+ *
+ */
+ class ReentryGuardImpl implements ReentryGuard {
+
+ private ThreadLocal{@link #isLocked()} always returns {@code false}. {@link #lock()} and + * {@link #unlock()} are no-ops.
+ * + *Use this implementation when the caller explicitly wants to disable + * reentrancy protection (for example in tests or in environments where the + * cost of thread-local checks is undesirable and re-entrancy cannot occur).
+ * + */ + class NOPRentryGuard implements ReentryGuard { + @Override + public boolean isLocked() { + return false; + } + + @Override + public void lock() { + // NOP + } + + @Override + public void unlock() { + // NOP + } + } + +} diff --git a/logback-core/src/main/java/ch/qos/logback/core/util/ReentryGuardFactory.java b/logback-core/src/main/java/ch/qos/logback/core/util/ReentryGuardFactory.java new file mode 100644 index 0000000000..1689b5437d --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/util/ReentryGuardFactory.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.util; + +/** + * Factory that creates {@link ReentryGuard} instances according to a requested type. + * + *This class centralizes creation of the built-in guard implementations. + * Consumers can use the factory to obtain either a per-thread guard or a no-op + * guard depending on their needs.
+ * + * @since 1.5.21 + */ +public class ReentryGuardFactory { + + /** + * Types of guards that can be produced by this factory. + * + * THREAD_LOCAL - returns a {@link ReentryGuard.ReentryGuardImpl} backed by a ThreadLocal. + * NOP - returns a {@link ReentryGuard.NOPRentryGuard} which never locks. + */ + public enum GuardType { + THREAD_LOCAL, + NOP + } + + + /** + * Create a {@link ReentryGuard} for the given {@link GuardType}. + * + *Returns a fresh instance of the requested guard implementation. The + * factory does not cache instances; callers may obtain separate instances + * as required.
+ * + *Thread-safety: this method is stateless and may be called concurrently + * from multiple threads.
+ * + * @param guardType the type of guard to create; must not be {@code null} + * @return a new {@link ReentryGuard} instance implementing the requested semantics + * @throws NullPointerException if {@code guardType} is {@code null} + * @throws IllegalArgumentException if an unknown guard type is provided + * @since 1.5.21 + */ + public static ReentryGuard makeGuard(GuardType guardType) { + switch (guardType) { + case THREAD_LOCAL: + return new ReentryGuard.ReentryGuardImpl(); + case NOP: + return new ReentryGuard.NOPRentryGuard(); + default: + throw new IllegalArgumentException("Unknown GuardType: " + guardType); + } + } +} diff --git a/logback-core/src/test/java/ch/qos/logback/core/rolling/JVMExitBeforeCompressionISDoneTest.java b/logback-core/src/test/java/ch/qos/logback/core/rolling/JVMExitBeforeCompressionISDoneTest.java index cc80c5c372..ba3d2f2a61 100755 --- a/logback-core/src/test/java/ch/qos/logback/core/rolling/JVMExitBeforeCompressionISDoneTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/rolling/JVMExitBeforeCompressionISDoneTest.java @@ -1,7 +1,13 @@ package ch.qos.logback.core.rolling; +import java.net.URL; +import java.net.URLClassLoader; import java.util.Date; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.hook.ShutdownHook; +import ch.qos.logback.core.hook.ShutdownHookBase; +import ch.qos.logback.core.status.Status; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -17,11 +23,18 @@ import org.junit.jupiter.api.Test; @Disabled +/** + * This test is disabled because it is intended to be run manually as it is difficult + * to unit test shutdown hooks. + * + * To run this test, enable it and execute it as a JUnit test. Observe the + * console output to see if the compression completes before the JVM exits. + */ public class JVMExitBeforeCompressionISDoneTest extends ScaffoldingForRollingTests { RollingFileAppender