Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package slf4j for openSUSE:Factory checked in at 2026-10-01 16:59:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/slf4j (Old) and /work/SRC/openSUSE:Factory/.slf4j.new.1253 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "slf4j" Thu Oct 1 16:59:54 2026 rev:36 rq:1381649 version:2.0.20 Changes: -------- --- /work/SRC/openSUSE:Factory/slf4j/slf4j.changes 2026-07-15 16:45:25.254257134 +0200 +++ /work/SRC/openSUSE:Factory/.slf4j.new.1253/slf4j.changes 2026-10-01 17:00:08.842941287 +0200 @@ -1,0 +2,30 @@ +Tue Sep 29 11:05:22 UTC 2026 - Fridrich Strba <[email protected]> + +- Upgrade to 2.0.20 + * Changes + + Marker instances are slated to become immutable in future + releases. As such, methods in the Marker interface + adding/removing children are now marked as deprecated. +- Changes of version 2.0.19 + * Changes + + When the fluent API was used with XLogger/LoggerWrapper in the + slf4j-ext module, caller location was incorrectly reported. To + correct this, LoggerWrapper now delegates + makeLoggingEventBuilder() to the wrapped logger so that the + application caller can be correctly extracted. + + In slf4j-api/DefaultLoggingEventBuilder, a failing toString() + invocation on a key-value value, including StackOverflowError, + no longer aborts logging. The builder now substitutes [FAILED + toString()], matching the existing behaviour of + MessageFormatter for message arguments. + + The isLoggable() method in SLF4JPlatformLogger now maps + System.Logger.Level.ALL and Level.OFF the same way as log(), + that is ALL as TRACE and OFF as ERROR, instead of treating + both as always loggable. + + Published JAR files now package each module's own LICENSE.txt + under META-INF/. Previously the parent POM copied the + repository-root MIT license into every module, so Apache-2.0 + modules such as jcl-over-slf4j and log4j-over-slf4j shipped + the wrong license text. + +------------------------------------------------------------------- Old: ---- slf4j-2.0.18.tar.xz New: ---- slf4j-2.0.20.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ slf4j.spec ++++++ --- /var/tmp/diff_new_pack.iuzI85/_old 2026-10-01 17:00:11.025032692 +0200 +++ /var/tmp/diff_new_pack.iuzI85/_new 2026-10-01 17:00:11.029032860 +0200 @@ -18,7 +18,7 @@ Name: slf4j -Version: 2.0.18 +Version: 2.0.20 Release: 0 Summary: Simple Logging Facade for Java # the log4j-over-slf4j and jcl-over-slf4j submodules are ASL 2.0, rest is MIT ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.iuzI85/_old 2026-10-01 17:00:11.095035624 +0200 +++ /var/tmp/diff_new_pack.iuzI85/_new 2026-10-01 17:00:11.099035792 +0200 @@ -1,6 +1,6 @@ -mtime: 1783942362 -commit: 0421d53bb61b789b2e722848e1cfb1df567986661edf959920be1c727b785119 +mtime: 1790680239 +commit: 754f14deb2c29730a9a6be8ec0219a254cab268b51864c07d0398323181ed0c6 url: https://src.opensuse.org/java-packages/slf4j -revision: 0421d53bb61b789b2e722848e1cfb1df567986661edf959920be1c727b785119 +revision: 754f14deb2c29730a9a6be8ec0219a254cab268b51864c07d0398323181ed0c6 projectscmsync: https://src.opensuse.org/java-packages/_ObsPrj ++++++ _service ++++++ --- /var/tmp/diff_new_pack.iuzI85/_old 2026-10-01 17:00:11.126036923 +0200 +++ /var/tmp/diff_new_pack.iuzI85/_new 2026-10-01 17:00:11.130037091 +0200 @@ -2,7 +2,7 @@ <service name="tar_scm" mode="disabled"> <param name="scm">git</param> <param name="url">https://github.com/qos-ch/slf4j.git</param> - <param name="revision">v_2.0.18</param> + <param name="revision">v_2.0.20</param> <param name="match-tag">v_*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v_(.*)</param> ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-09-29 13:10:39.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ slf4j-2.0.18.tar.xz -> slf4j-2.0.20.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/integration/pom.xml new/slf4j-2.0.20/integration/pom.xml --- old/slf4j-2.0.18/integration/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/integration/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/jcl-over-slf4j/pom.xml new/slf4j-2.0.20/jcl-over-slf4j/pom.xml --- old/slf4j-2.0.18/jcl-over-slf4j/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/jcl-over-slf4j/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -5,7 +5,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/jul-to-slf4j/pom.xml new/slf4j-2.0.20/jul-to-slf4j/pom.xml --- old/slf4j-2.0.18/jul-to-slf4j/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/jul-to-slf4j/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/log4j-over-slf4j/pom.xml new/slf4j-2.0.20/log4j-over-slf4j/pom.xml --- old/slf4j-2.0.18/log4j-over-slf4j/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/log4j-over-slf4j/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/osgi-over-slf4j/pom.xml new/slf4j-2.0.20/osgi-over-slf4j/pom.xml --- old/slf4j-2.0.18/osgi-over-slf4j/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/osgi-over-slf4j/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/parent/pom.xml new/slf4j-2.0.20/parent/pom.xml --- old/slf4j-2.0.18/parent/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/parent/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-bom</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../pom.xml</relativePath> </parent> @@ -26,7 +26,7 @@ <properties> <!-- yyyy-MM-dd'T'HH:mm:ss'Z' --> - <project.build.outputTimestamp>2026-05-12T18:15:00Z</project.build.outputTimestamp> + <project.build.outputTimestamp>2026-09-22T14:37:00Z</project.build.outputTimestamp> <latest.1.version>1.7.36</latest.1.version> <!-- java.util.ServiceLoader requires Java 6 --> <jdk.version>8</jdk.version> @@ -95,8 +95,10 @@ <filtering>true</filtering> </resource> + <!-- Use each module's own LICENSE.txt (e.g. Apache-2.0 for jcl-over-slf4j + and log4j-over-slf4j) rather than the repo-root MIT LICENSE. --> <resource> - <directory>..</directory> + <directory>${project.basedir}</directory> <targetPath>META-INF</targetPath> <includes> <include>LICENSE.txt</include> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/pom.xml new/slf4j-2.0.20/pom.xml --- old/slf4j-2.0.18/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -6,7 +6,7 @@ <groupId>org.slf4j</groupId> <artifactId>slf4j-bom</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <packaging>pom</packaging> <url>http://www.slf4j.org</url> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-api/pom.xml new/slf4j-2.0.20/slf4j-api/pom.xml --- old/slf4j-2.0.18/slf4j-api/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-api/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-api/src/main/java/org/slf4j/Marker.java new/slf4j-2.0.20/slf4j-api/src/main/java/org/slf4j/Marker.java --- old/slf4j-2.0.18/slf4j-api/src/main/java/org/slf4j/Marker.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-api/src/main/java/org/slf4j/Marker.java 2026-09-22 16:37:49.000000000 +0200 @@ -71,6 +71,8 @@ * a reference to another marker * @throws IllegalArgumentException * if 'reference' is null + * + * @deprecated Markers are now immutable and no longer support children. */ public void add(Marker reference); @@ -80,11 +82,13 @@ * @param reference * the marker reference to remove * @return true if reference could be found and removed, false otherwise. + * + * @deprecated Markers are now immutable and no longer support children. */ public boolean remove(Marker reference); /** - * @deprecated Replaced by {@link #hasReferences()}. + * @deprecated Markers are now immutable and no longer support children. */ @Deprecated public boolean hasChildren(); @@ -93,6 +97,7 @@ * Does this marker have any references? * * @return true if this marker has one or more references, false otherwise. + * @deprecated Markers are now immutable and no longer support children. */ public boolean hasReferences(); @@ -101,6 +106,7 @@ * marker. An empty iterator is returned when this marker has no references. * * @return Iterator over the references of this marker + * @deprecated Markers are now immutable and no longer support children. */ public Iterator<Marker> iterator(); @@ -114,6 +120,7 @@ * @throws IllegalArgumentException * if 'other' is null * @return Whether this marker contains the other marker. + * @deprecated Markers are now immutable and no longer support children. */ public boolean contains(Marker other); @@ -124,6 +131,7 @@ * * @param name The marker name to test for inclusion. * @return Whether this marker contains the other marker. + * @deprecated Markers are now immutable and no longer support children. */ public boolean contains(String name); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java new/slf4j-2.0.20/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java --- old/slf4j-2.0.18/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-api/src/main/java/org/slf4j/helpers/BasicMarker.java 2026-09-22 16:37:49.000000000 +0200 @@ -53,6 +53,11 @@ return name; } + /** + * @deprecated Markers are now immutable and no longer support children. + */ + @Override + @Deprecated public void add(Marker reference) { if (reference == null) { throw new IllegalArgumentException("A null value cannot be added to a Marker as reference."); @@ -70,23 +75,48 @@ } } + /** + * @deprecated Markers are now immutable and no longer support children. + */ + @Override + @Deprecated public boolean hasReferences() { return (referenceList.size() > 0); } + /** + * @deprecated Markers are now immutable and no longer support children. + */ + @Override @Deprecated public boolean hasChildren() { return hasReferences(); } + /** + * @deprecated Markers are now immutable and no longer support children. + */ + @Override + @Deprecated public Iterator<Marker> iterator() { return referenceList.iterator(); } + + /** + * @deprecated Markers are now immutable and no longer support children. + */ + @Override + @Deprecated public boolean remove(Marker referenceToRemove) { return referenceList.remove(referenceToRemove); } + /** + * @deprecated Markers are now immutable and no longer support children. + */ + @Override + @Deprecated public boolean contains(Marker other) { if (other == null) { throw new IllegalArgumentException("Other cannot be null"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-api/src/main/java/org/slf4j/spi/DefaultLoggingEventBuilder.java new/slf4j-2.0.20/slf4j-api/src/main/java/org/slf4j/spi/DefaultLoggingEventBuilder.java --- old/slf4j-2.0.18/slf4j-api/src/main/java/org/slf4j/spi/DefaultLoggingEventBuilder.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-api/src/main/java/org/slf4j/spi/DefaultLoggingEventBuilder.java 2026-09-22 16:37:49.000000000 +0200 @@ -32,6 +32,7 @@ import org.slf4j.event.KeyValuePair; import org.slf4j.event.Level; import org.slf4j.event.LoggingEvent; +import org.slf4j.helpers.Reporter; /** * Default implementation of {@link LoggingEventBuilder} @@ -254,12 +255,33 @@ for(KeyValuePair kvp : keyValuePairList) { sb.append(kvp.key); sb.append('='); - sb.append(kvp.value); + // Same protection as MessageFormatter.safeObjectAppend: a failing + // toString() (including StackOverflowError) must not abort logging. + // See https://github.com/qos-ch/slf4j/issues/448 + safeObjectAppend(sb, kvp.value); sb.append(' '); } return sb; } + /** + * Append {@code o} to {@code sb}, catching any {@link Throwable} thrown by + * {@link Object#toString()} and substituting {@code [FAILED toString()]}. + * Mirrors {@code MessageFormatter.safeObjectAppend}. + */ + private static void safeObjectAppend(StringBuilder sb, Object o) { + if (o == null) { + sb.append("null"); + return; + } + try { + sb.append(o.toString()); + } catch (Throwable t) { + Reporter.error("Failed toString() invocation on an object of type [" + o.getClass().getName() + "]", t); + sb.append("[FAILED toString()]"); + } + } + private String mergeMessage(String msg, StringBuilder sb) { if(sb != null) { sb.append(msg); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-ext/pom.xml new/slf4j-2.0.20/slf4j-ext/pom.xml --- old/slf4j-2.0.18/slf4j-ext/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-ext/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-ext/src/main/java/org/slf4j/ext/LoggerWrapper.java new/slf4j-2.0.20/slf4j-ext/src/main/java/org/slf4j/ext/LoggerWrapper.java --- old/slf4j-2.0.18/slf4j-ext/src/main/java/org/slf4j/ext/LoggerWrapper.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-ext/src/main/java/org/slf4j/ext/LoggerWrapper.java 2026-09-22 16:37:49.000000000 +0200 @@ -28,7 +28,9 @@ import org.slf4j.Marker; //import org.slf4j.helpers.FormattingTuple; //import org.slf4j.helpers.MessageFormatter; +import org.slf4j.event.Level; import org.slf4j.spi.LocationAwareLogger; +import org.slf4j.spi.LoggingEventBuilder; /** * A helper class wrapping an {@link org.slf4j.Logger} instance preserving @@ -62,6 +64,16 @@ } /** + * Delegate fluent-API builder creation to the wrapped logger so that caller + * location is extracted correctly. See also + * https://github.com/qos-ch/slf4j/issues/464 + */ + @Override + public LoggingEventBuilder makeLoggingEventBuilder(Level level) { + return logger.makeLoggingEventBuilder(level); + } + + /** * Delegate to the appropriate method of the underlying logger. */ public boolean isTraceEnabled() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-ext/src/test/java/org/slf4j/dummyExt/XLoggerTest.java new/slf4j-2.0.20/slf4j-ext/src/test/java/org/slf4j/dummyExt/XLoggerTest.java --- old/slf4j-2.0.18/slf4j-ext/src/test/java/org/slf4j/dummyExt/XLoggerTest.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-ext/src/test/java/org/slf4j/dummyExt/XLoggerTest.java 2026-09-22 16:37:49.000000000 +0200 @@ -1,7 +1,7 @@ /** * Copyright (c) 2004-2011 QOS.ch * All rights reserved. - * + * <p> * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + * <p> * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -151,16 +151,29 @@ assertEquals("" + (line + 1), li.getLineNumber()); } } - + @Test public void testNoDoubleSubstitution_Bug421() { XLogger logger = XLoggerFactory.getXLogger("UnitTest"); logger.error("{},{}", "foo", "[{}]"); verify(listAppender.list.get(0), "foo,[{}]"); - + logger.error("{},{}", "[{}]", "foo"); verify(listAppender.list.get(1), "[{}],foo"); } - - + + @Test + public void callerExtraction_issue464() { + XLogger logger = XLoggerFactory.getXLogger("com.foo.callerExtraction_issue464"); + logger.atInfo().log("hello"); + assertEquals(1, listAppender.list.size()); + + + LoggingEvent e = listAppender.list.get(0); + LocationInfo li = e.getLocationInformation(); + assertEquals(this.getClass().getName(), li.getClassName()); + assertEquals("callerExtraction_issue464", li.getMethodName()); + + } + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-jdk-platform-logging/pom.xml new/slf4j-2.0.20/slf4j-jdk-platform-logging/pom.xml --- old/slf4j-2.0.18/slf4j-jdk-platform-logging/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-jdk-platform-logging/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -8,7 +8,7 @@ <parent> <artifactId>slf4j-parent</artifactId> <groupId>org.slf4j</groupId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-jdk-platform-logging/src/main/java/org/slf4j/jdk/platform/logging/SLF4JPlatformLogger.java new/slf4j-2.0.20/slf4j-jdk-platform-logging/src/main/java/org/slf4j/jdk/platform/logging/SLF4JPlatformLogger.java --- old/slf4j-2.0.18/slf4j-jdk-platform-logging/src/main/java/org/slf4j/jdk/platform/logging/SLF4JPlatformLogger.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-jdk-platform-logging/src/main/java/org/slf4j/jdk/platform/logging/SLF4JPlatformLogger.java 2026-09-22 16:37:49.000000000 +0200 @@ -1,7 +1,7 @@ /** * Copyright (c) 2004-2021 QOS.ch * All rights reserved. - * + * <p> * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + * <p> * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -42,7 +42,7 @@ class SLF4JPlatformLogger implements System.Logger { static private final String PRESUMED_CALLER_BOUNDARY = System.Logger.class.getName(); - + private final Logger slf4jLogger; public SLF4JPlatformLogger(Logger logger) { @@ -59,12 +59,12 @@ @Override public boolean isLoggable(Level jplLevel) { - if (jplLevel == Level.ALL) - return true; - if (jplLevel == Level.OFF) - return true; + // If called via log method, fixExtremeLevels has been called. System.Logger.Level.ALL and Level.OFF have + // been respectively mapped to Level.TRACE and Level.ERROR. + // However, if isLoggable is called directly, we must fix the extreme levels here as well. + final Level jplLevelReduced = fixExtremeLevels(jplLevel); - org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevel); + org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevelReduced); return slf4jLogger.isEnabledForLevel(slf4jLevel); } @@ -72,27 +72,27 @@ /** * Transform a {@link Level} to {@link org.slf4j.event.Level}. - * + * * This method assumes that Level.ALL or Level.OFF never reach this method. - * + * * @param jplLevel * @return */ private org.slf4j.event.Level jplLevelToSLF4JLevel(Level jplLevel) { - switch (jplLevel) { - case TRACE: - return org.slf4j.event.Level.TRACE; - case DEBUG: - return org.slf4j.event.Level.DEBUG; - case INFO: - return org.slf4j.event.Level.INFO; - case WARNING: - return org.slf4j.event.Level.WARN; - case ERROR: - return org.slf4j.event.Level.ERROR; - default: - reportUnknownLevel(jplLevel); - return null; + switch(jplLevel) { + case TRACE: + return org.slf4j.event.Level.TRACE; + case DEBUG: + return org.slf4j.event.Level.DEBUG; + case INFO: + return org.slf4j.event.Level.INFO; + case WARNING: + return org.slf4j.event.Level.WARN; + case ERROR: + return org.slf4j.event.Level.ERROR; + default: + reportUnknownLevel(jplLevel); + return null; } } @@ -108,8 +108,8 @@ /** * Single point of processing taking all possible parameters. - * - * @param jplLevel + * + * @param jplLevel * @param bundle * @param msg * @param thrown @@ -122,7 +122,7 @@ org.slf4j.event.Level slf4jLevel = jplLevelToSLF4JLevel(jplLevelReduced); boolean isEnabled = slf4jLogger.isEnabledForLevel(slf4jLevel); - if (isEnabled) { + if(isEnabled) { performLog(slf4jLevel, bundle, msg, thrown, params); } } @@ -139,10 +139,10 @@ * @return */ private Level fixExtremeLevels(Level jplLevel) { - if (jplLevel == Level.OFF) - return Level.ERROR; + if(jplLevel == Level.OFF) + return Level.ERROR; - if (jplLevel == Level.ALL) + if(jplLevel == Level.ALL) return Level.TRACE; return jplLevel; @@ -151,18 +151,18 @@ private void performLog(org.slf4j.event.Level slf4jLevel, ResourceBundle bundle, String msg, Throwable thrown, Object... params) { String message = getResourceStringOrMessage(bundle, msg); LoggingEventBuilder leb = slf4jLogger.makeLoggingEventBuilder(slf4jLevel); - if (thrown != null) { + if(thrown != null) { leb = leb.setCause(thrown); } - if (params != null && params.length > 0) { + if(params != null && params.length > 0) { // add the arguments to the logging event for possible processing by the backend - for (Object p : params) { + for(Object p : params) { leb = leb.addArgument(p); } // The JDK uses a different formatting convention. We must invoke it now. message = MessageFormat.format(message, params); } - if (leb instanceof CallerBoundaryAware) { + if(leb instanceof CallerBoundaryAware) { CallerBoundaryAware cba = (CallerBoundaryAware) leb; cba.setCallerBoundary(PRESUMED_CALLER_BOUNDARY); } @@ -176,7 +176,7 @@ } private static String getResourceStringOrMessage(ResourceBundle bundle, String msg) { - if (bundle == null || msg == null) + if(bundle == null || msg == null) return msg; // ResourceBundle::getString throws: // diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-jdk-platform-logging/src/test/java/org/slf4j/jdk/platform/logging/test/SLF4JPlatformLoggingTest.java new/slf4j-2.0.20/slf4j-jdk-platform-logging/src/test/java/org/slf4j/jdk/platform/logging/test/SLF4JPlatformLoggingTest.java --- old/slf4j-2.0.18/slf4j-jdk-platform-logging/src/test/java/org/slf4j/jdk/platform/logging/test/SLF4JPlatformLoggingTest.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-jdk-platform-logging/src/test/java/org/slf4j/jdk/platform/logging/test/SLF4JPlatformLoggingTest.java 2026-09-22 16:37:49.000000000 +0200 @@ -24,9 +24,6 @@ */ package org.slf4j.jdk.platform.logging.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.io.IOException; import java.io.PrintStream; import java.lang.System.Logger; @@ -41,6 +38,8 @@ import org.junit.Test; import org.slf4j.LoggerFactory; +import static org.junit.Assert.*; + /** * The present test is fragile in the sense that it sets up SimpleLogger * with a StringPrintStream and reverts to the old stream when done. @@ -129,7 +128,19 @@ List<String> results = SPS.stringList; assertEquals(1, results.size()); assertEquals("ERROR extremeLevels - hello", results.get(0)); - } + @Test + public void extremeLevels_isLoggableTest() throws IOException { + LoggerFinder finder = System.LoggerFinder.getLoggerFinder(); + assertEquals(EXPECTED_FINDER_CLASS, finder.getClass().getName()); + Logger systemLogger = finder.getLogger("extremeLevels", null); + + boolean offResult = systemLogger.isLoggable(Level.OFF); + assertTrue(offResult); + + + boolean allResult = systemLogger.isLoggable(Level.ALL); + assertFalse(allResult); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-jdk14/pom.xml new/slf4j-2.0.20/slf4j-jdk14/pom.xml --- old/slf4j-2.0.18/slf4j-jdk14/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-jdk14/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-jdk14/src/test/java/org/slf4j/issue/CallerInfoTest.java new/slf4j-2.0.20/slf4j-jdk14/src/test/java/org/slf4j/issue/CallerInfoTest.java --- old/slf4j-2.0.18/slf4j-jdk14/src/test/java/org/slf4j/issue/CallerInfoTest.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-jdk14/src/test/java/org/slf4j/issue/CallerInfoTest.java 2026-09-22 16:37:49.000000000 +0200 @@ -77,6 +77,7 @@ LogRecord logRecod = recordList.get(0); assertEquals(this.getClass().getName(), logRecod.getSourceClassName()); + assertEquals("testCallerInfoWithFluentAPI", logRecod.getSourceMethodName()); } @Test diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-jdk14/src/test/java/org/slf4j/jul/FluentApiInvocationTest.java new/slf4j-2.0.20/slf4j-jdk14/src/test/java/org/slf4j/jul/FluentApiInvocationTest.java --- old/slf4j-2.0.18/slf4j-jdk14/src/test/java/org/slf4j/jul/FluentApiInvocationTest.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-jdk14/src/test/java/org/slf4j/jul/FluentApiInvocationTest.java 2026-09-22 16:37:49.000000000 +0200 @@ -130,6 +130,35 @@ } + /** + * Regression for https://github.com/qos-ch/slf4j/issues/448: + * a toString() that throws (including StackOverflowError) on an + * addKeyValue value must not abort logging; same as message args. + */ + @Test + public void keyValuePairWithFailingToString() { + Object bad = new Object() { + @Override + public String toString() { + throw new IllegalStateException("boom"); + } + }; + logger.atDebug().addKeyValue("key", bad).log("msg with key/value"); + assertLogMessage("key=[FAILED toString()] msg with key/value", 0); + } + + @Test + public void keyValuePairWithStackOverflowInToString() { + Object overflow = new Object() { + @Override + public String toString() { + return super.toString() + this.toString(); + } + }; + logger.atDebug().addKeyValue("key", overflow).log("msg with key/value"); + assertLogMessage("key=[FAILED toString()] msg with key/value", 0); + } + private void assertLogMessage(String expected, int index) { LogRecord logRecord = listHandler.recordList.get(index); Assert.assertNotNull(logRecord); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-log4j12/pom.xml new/slf4j-2.0.20/slf4j-log4j12/pom.xml --- old/slf4j-2.0.18/slf4j-log4j12/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-log4j12/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -8,7 +8,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> @@ -24,7 +24,7 @@ <relocation> <groupId>org.slf4j</groupId> <artifactId>slf4j-reload4j</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> </relocation> </distributionManagement> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-migrator/pom.xml new/slf4j-2.0.20/slf4j-migrator/pom.xml --- old/slf4j-2.0.18/slf4j-migrator/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-migrator/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-nop/pom.xml new/slf4j-2.0.20/slf4j-nop/pom.xml --- old/slf4j-2.0.18/slf4j-nop/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-nop/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-reload4j/pom.xml new/slf4j-2.0.20/slf4j-reload4j/pom.xml --- old/slf4j-2.0.18/slf4j-reload4j/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-reload4j/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-simple/pom.xml new/slf4j-2.0.20/slf4j-simple/pom.xml --- old/slf4j-2.0.18/slf4j-simple/pom.xml 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-simple/pom.xml 2026-09-22 16:37:49.000000000 +0200 @@ -7,7 +7,7 @@ <parent> <groupId>org.slf4j</groupId> <artifactId>slf4j-parent</artifactId> - <version>2.0.18</version> + <version>2.0.20</version> <relativePath>../parent/pom.xml</relativePath> </parent> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-simple/src/test/java/org/slf4j/simple/AcceptanceTest.java new/slf4j-2.0.20/slf4j-simple/src/test/java/org/slf4j/simple/AcceptanceTest.java --- old/slf4j-2.0.18/slf4j-simple/src/test/java/org/slf4j/simple/AcceptanceTest.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-simple/src/test/java/org/slf4j/simple/AcceptanceTest.java 1970-01-01 01:00:00.000000000 +0100 @@ -1,50 +0,0 @@ -package org.slf4j.simple; - -import org.junit.Ignore; -import org.slf4j.Logger; -import org.slf4j.event.Level; - -import java.io.PrintStream; - -@Ignore -public class AcceptanceTest extends LoggerTestSuite { - - @Override - public Logger createLogger(ListAppendingOutputStream outputStream, Level level) { - SimpleLogger.CONFIG_PARAMS.outputChoice = new OutputChoice(new PrintStream(outputStream)); - - SimpleLogger logger = new SimpleLogger("TestSuiteLogger"); - logger.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(level.toString()); - return logger; - } - - @Override - public String extractMessage(String message) { - return message - .split("\n")[0] - .split("- ")[1]; - } - - @Override - public String extractExceptionMessage(String message) { - String[] logLines = message.split("\n"); - - if (logLines.length < 2) { - return null; - } - String exceptionLine = logLines[1]; - return exceptionLine.split(": ")[1]; - } - - @Override - public String extractExceptionType(String message) { - String[] logLines = message.split("\n"); - - if (logLines.length < 2) { - return null; - } - String exceptionLine = logLines[1]; - return exceptionLine.split(": ")[0]; - } - -} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-simple/src/test/java/org/slf4j/simple/LoggerTestSuite.java new/slf4j-2.0.20/slf4j-simple/src/test/java/org/slf4j/simple/LoggerTestSuite.java --- old/slf4j-2.0.18/slf4j-simple/src/test/java/org/slf4j/simple/LoggerTestSuite.java 2026-05-12 20:32:21.000000000 +0200 +++ new/slf4j-2.0.20/slf4j-simple/src/test/java/org/slf4j/simple/LoggerTestSuite.java 1970-01-01 01:00:00.000000000 +0100 @@ -1,250 +0,0 @@ -package org.slf4j.simple; - - -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.event.Level; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - - -public abstract class LoggerTestSuite { - - private ListAppendingOutputStream prepareSink(List<String> outputList) { - return new ListAppendingOutputStream(outputList); - } - - @Test - public void testTrace() { - ArrayList<String> outputList = new ArrayList<>(); - Logger configuredLogger = createLogger(prepareSink(outputList), Level.TRACE); - - assertTrue("Trace level should be enabled for this test", configuredLogger.isTraceEnabled()); - configuredLogger.trace("Simple trace message"); - - assertEquals("Trace message should've been captured", 1, outputList.size()); - assertTrue("Message should be logged in trace level", isTraceMessage(outputList.get(0))); - assertEquals("Supplied trace message wasn't found in the log", - "Simple trace message", - extractMessage(outputList.get(0))); - - outputList.clear(); - - configuredLogger.debug("Simple debug message"); - configuredLogger.info("Simple info message"); - configuredLogger.warn("Simple warn message"); - configuredLogger.error("Simple error message"); - assertEquals("The other levels should have been captured", 4, outputList.size()); - - } - - @Test - public void testDebug() { - ArrayList<String> outputList = new ArrayList<>(); - Logger configuredLogger = createLogger(prepareSink(outputList), Level.DEBUG); - - configuredLogger.trace("Simple trace message"); - assertEquals("Lower levels should have been ignored", 0, outputList.size()); - - assertTrue("Debug level should be enabled for this test", configuredLogger.isDebugEnabled()); - configuredLogger.debug("Simple debug message"); - - assertEquals("Debug message should've been captured", 1, outputList.size()); - assertTrue("Message should be logged in debug level", isDebugMessage(outputList.get(0))); - assertEquals("Supplied debug message wasn't found in the log", - "Simple debug message", - extractMessage(outputList.get(0))); - - outputList.clear(); - - configuredLogger.info("Simple info message"); - configuredLogger.warn("Simple warn message"); - configuredLogger.error("Simple error message"); - assertEquals("The other levels should have been captured", 3, outputList.size()); - } - - - @Test - public void testInfo() { - ArrayList<String> outputList = new ArrayList<>(); - Logger configuredLogger = createLogger(prepareSink(outputList), Level.INFO); - - configuredLogger.trace("Simple trace message"); - configuredLogger.debug("Simple debug message"); - assertEquals("Lower levels should have been ignored", 0, outputList.size()); - - assertTrue("Info level should be enabled for this test", configuredLogger.isInfoEnabled()); - configuredLogger.info("Simple info message"); - - assertEquals("Info message should've been captured", 1, outputList.size()); - assertTrue("Message should be logged in debug level", isInfoMessage(outputList.get(0))); - assertEquals("Supplied info message wasn't found in the log", - "Simple info message", - extractMessage(outputList.get(0))); - - outputList.clear(); - - configuredLogger.warn("Simple warn message"); - configuredLogger.error("Simple error message"); - assertEquals("The other levels should have been captured", 2, outputList.size()); - } - - @Test - public void testWarn() { - ArrayList<String> outputList = new ArrayList<>(); - Logger configuredLogger = createLogger(prepareSink(outputList), Level.WARN); - - configuredLogger.trace("Simple trace message"); - configuredLogger.debug("Simple debug message"); - configuredLogger.info("Simple info message"); - assertEquals("Lower levels should have been ignored", 0, outputList.size()); - - assertTrue("Warn level should be enabled for this test", configuredLogger.isWarnEnabled()); - configuredLogger.warn("Simple warn message"); - - assertEquals("Warn message should've been captured", 1, outputList.size()); - assertTrue("Message should be logged in warn level", isWarnMessage(outputList.get(0))); - assertEquals("Supplied warn message wasn't found in the log", - "Simple warn message", - extractMessage(outputList.get(0))); - - outputList.clear(); - - configuredLogger.error("Simple error message"); - assertEquals("The other levels should have been captured", 1, outputList.size()); - } - - @Test - public void testError() { - ArrayList<String> outputList = new ArrayList<>(); - Logger configuredLogger = createLogger(prepareSink(outputList), Level.ERROR); - - configuredLogger.trace("Simple trace message"); - configuredLogger.debug("Simple debug message"); - configuredLogger.info("Simple info message"); - configuredLogger.warn("Simple warn message"); - assertEquals("Lower levels should have been ignored", 0, outputList.size()); - - assertTrue("Error level should be enabled for this test", configuredLogger.isErrorEnabled()); - configuredLogger.error("Simple error message"); - - assertEquals("Error message should've been captured", 1, outputList.size()); - assertTrue("Message should be logged in error level", isErrorMessage(outputList.get(0))); - assertEquals("Supplied error message wasn't found in the log", - "Simple error message", - extractMessage(outputList.get(0))); - } - - @Test - public void testFormatting() { - ArrayList<String> outputList = new ArrayList<>(); - Logger configuredLogger = createLogger(prepareSink(outputList), Level.INFO); - - configuredLogger.info("Some {} string", "formatted"); - assertEquals("The formatted message should've been captured", 1, outputList.size()); - assertEquals("Message should've been formatted", "Some formatted string", extractMessage(outputList.get(0))); - } - - @Test - public void testException() { - ArrayList<String> outputList = new ArrayList<>(); - Logger configuredLogger = createLogger(prepareSink(outputList), Level.INFO); - - Exception exception = new RuntimeException("My error"); - - configuredLogger.info("Logging with an exception", exception); - assertEquals("The formatted message should've been captured", 1, outputList.size()); - assertEquals("Message should've been formatted", - "My error", - extractExceptionMessage(outputList.get(0))); - - assertEquals("Message should've been formatted", - "java.lang.RuntimeException", - extractExceptionType(outputList.get(0))); - } - - - /** - * Allows tests to check whether the log message contains a trace message. - * Override if needed. - * @param message String containing the full log message - * @return whether it is a trace message or not - */ - protected boolean isTraceMessage(String message) { - return message.toLowerCase().contains("trace"); - } - - /** - * Allows tests to check whether the log message contains a debug message. - * Override if needed. - * @param message String containing the full log message - * @return whether it is a debug message or not - */ - protected boolean isDebugMessage(String message) { - return message.toLowerCase().contains("debug"); - } - - /** - * Allows tests to check whether the log message contains an info message. - * Override if needed. - * @param message String containing the full log message - * @return whether it is an info message or not - */ - protected boolean isInfoMessage(String message) { - return message.toLowerCase().contains("info"); - } - - /** - * Allows tests to check whether the log message contains a warn message. - * Override if needed. - * @param message String containing the full log message - * @return whether it is a warn message or not - */ - protected boolean isWarnMessage(String message) { - return message.toLowerCase().contains("warn"); - } - - /** - * Allows tests to check whether the log message contains an error message. - * Override if needed. - * @param message String containing the full log message - * @return whether it is an error message or not - */ - protected boolean isErrorMessage(String message) { - return message.toLowerCase().contains("error"); - } - - /** - * Extracts only the part of the log string that should represent the `message` string. - * @param message the full log message - * @return only the supplied message - */ - public abstract String extractMessage(String message); - - /** - * Extracts only the part of the log string that should represent the supplied exception message, if any. - * @param message the full log message - * @return only the supplied exception message - */ - public abstract String extractExceptionMessage(String message); - - /** - * Extracts only the part of the log string that should represent the supplied exception type. - * @param message the full log message - * @return only the supplied exception type name - */ - public abstract String extractExceptionType(String message); - - /** - * Configures the logger for running the tests. - * @param outputStream The output stream for logs to be written to - * @param level The expected level the tests will run for this logger - * @return a configured logger able to run the tests - */ - public abstract Logger createLogger(ListAppendingOutputStream outputStream, Level level); - -} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/slf4j-2.0.18/slf4j-simple/src/test/java/org/slf4j/simple/SimpleLoggerAcceptanceTest.java new/slf4j-2.0.20/slf4j-simple/src/test/java/org/slf4j/simple/SimpleLoggerAcceptanceTest.java --- old/slf4j-2.0.18/slf4j-simple/src/test/java/org/slf4j/simple/SimpleLoggerAcceptanceTest.java 1970-01-01 01:00:00.000000000 +0100 +++ new/slf4j-2.0.20/slf4j-simple/src/test/java/org/slf4j/simple/SimpleLoggerAcceptanceTest.java 2026-09-22 16:37:49.000000000 +0200 @@ -0,0 +1,358 @@ +package org.slf4j.simple; + + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.event.Level; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +/** + * Acceptance tests for the slf4j-simple SimpleLogger implementation. + * + * <p>This concrete JUnit test class exercises logging at different levels + * (TRACE, DEBUG, INFO, WARN, ERROR), verifies message formatting, and + * checks exception logging behavior. The class contains helper methods to + * create a logger that writes into a supplied {@link ListAppendingOutputStream} + * and to extract message and exception details from produced log lines.</p> + * + * <p>Important: these tests are predicated on the fact that {@code SimpleLogger} + * invokes {@code flush()} on the provided output stream after writing each + * log entry and that each logging event is accumulated into a single entry of + * the backing {@code List<String>} fiels in {@link ListAppendingOutputStream}. + * </p> + * + * <p>Each logging event — including events that contain a {@code Throwable} + * and produce stacktrace lines — is accumulated into the {@code targetList} + * field of {@link ListAppendingOutputStream} as a single entry representing + * the entire event. Tests expect one list element per logging event.</p> + * + */ +public class SimpleLoggerAcceptanceTest { + + /** + * Create a {@link ListAppendingOutputStream} backed by the supplied list. + * + * <p>This helper returns an output stream that accumulates written text + * lines into the provided list. Tests rely on the {@code SimpleLogger} + * implementation to invoke {@code flush()} on the provided stream after + * writing each log entry so that bytes are converted into list entries and + * visible to assertions. </p> + * + * <p>Each logging event (including a {@code Throwable} and its stacktrace) + * is collected by {@code ListAppendingOutputStream} into a single element + * in its {@code targetList} field; tests expect one string element per event.</p> + * + * @param outputList the list which will receive captured log lines + * @return a new ListAppendingOutputStream writing into the supplied list + */ + private ListAppendingOutputStream prepareSink(List<String> outputList) { + return new ListAppendingOutputStream(outputList); + } + + /** + * Extracts only the part of the log string that should represent the `message` string. + * + * @param message the full log message + * @return only the supplied message + */ + public String extractMessage(String message) { + // Example log line: + // [main] INFO TestSuiteLogger - An exception occurred: 99 + return message + .split("\r?\n")[0] + .split("- ")[1]; + } + + /** + * Extracts only the part of the log string that should represent the supplied exception message, if any. + * + * @param message the full log message + * @return only the supplied exception message + */ + public String extractExceptionMessage(String message) { + // Example log lines: + // [main] INFO TestSuiteLogger - An exception occurred: 99 + // java.lang.IllegalArgumentException: Invalid argument + // at org.slf4j.simple.SimpleLoggerAcceptanceTest.testExceptionParameterFormatting(SimpleLoggerAcceptanceTest.java:274) + + String[] logLines = message.split("\r?\n"); + + if(logLines.length < 2) { + return null; + } + String exceptionLine = logLines[1]; + return exceptionLine.split(": ")[1]; + } + + /** + * Extracts only the part of the log string that should represent the supplied exception type. + * + * @param message the full log message + * @return only the supplied exception type name + */ + public String extractExceptionType(String message) { + String[] logLines = message.split("\n"); + + if(logLines.length < 2) { + return null; + } + String exceptionLine = logLines[1]; + return exceptionLine.split(": ")[0]; + } + + /** + * Configures the logger for running the tests. + * + * @param outputStream The output stream for logs to be written to + * @param level The expected level the tests will run for this logger + * @return a configured logger able to run the tests + */ + public Logger createLogger(ListAppendingOutputStream outputStream, Level level) { + SimpleLogger.CONFIG_PARAMS.outputChoice = new OutputChoice(new PrintStream(outputStream)); + + SimpleLogger logger = new SimpleLogger("TestSuiteLogger"); + logger.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(level.toString()); + return logger; + } + + @Test + public void testTrace() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.TRACE); + + assertTrue("Trace level should be enabled for this test", configuredLogger.isTraceEnabled()); + configuredLogger.trace("Simple trace message"); + + assertEquals("Trace message should've been captured", 1, outputList.size()); + assertTrue("Message should be logged in trace level", isTraceMessage(outputList.get(0))); + assertEquals("Supplied trace message wasn't found in the log", + "Simple trace message", + extractMessage(outputList.get(0))); + + outputList.clear(); + + configuredLogger.debug("Simple debug message"); + configuredLogger.info("Simple info message"); + configuredLogger.warn("Simple warn message"); + configuredLogger.error("Simple error message"); + assertEquals("The other levels should have been captured", 4, outputList.size()); + + } + + @Test + public void testDebug() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.DEBUG); + + configuredLogger.trace("Simple trace message"); + assertEquals("Lower levels should have been ignored", 0, outputList.size()); + + assertTrue("Debug level should be enabled for this test", configuredLogger.isDebugEnabled()); + configuredLogger.debug("Simple debug message"); + + assertEquals("Debug message should've been captured", 1, outputList.size()); + assertTrue("Message should be logged in debug level", isDebugMessage(outputList.get(0))); + assertEquals("Supplied debug message wasn't found in the log", + "Simple debug message", extractMessage(outputList.get(0))); + + outputList.clear(); + + configuredLogger.info("Simple info message"); + configuredLogger.warn("Simple warn message"); + configuredLogger.error("Simple error message"); + assertEquals("The other levels should have been captured", 3, outputList.size()); + } + + + @Test + public void testInfo() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.INFO); + + configuredLogger.trace("Simple trace message"); + configuredLogger.debug("Simple debug message"); + assertEquals("Lower levels should have been ignored", 0, outputList.size()); + + assertTrue("Info level should be enabled for this test", configuredLogger.isInfoEnabled()); + configuredLogger.info("Simple info message"); + + assertEquals("Info message should've been captured", 1, outputList.size()); + assertTrue("Message should be logged in debug level", isInfoMessage(outputList.get(0))); + assertEquals("Supplied info message wasn't found in the log", + "Simple info message", + extractMessage(outputList.get(0))); + + outputList.clear(); + + configuredLogger.warn("Simple warn message"); + configuredLogger.error("Simple error message"); + assertEquals("The other levels should have been captured", 2, outputList.size()); + } + + @Test + public void testWarn() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.WARN); + + configuredLogger.trace("Simple trace message"); + configuredLogger.debug("Simple debug message"); + configuredLogger.info("Simple info message"); + assertEquals("Lower levels should have been ignored", 0, outputList.size()); + + assertTrue("Warn level should be enabled for this test", configuredLogger.isWarnEnabled()); + configuredLogger.warn("Simple warn message"); + + assertEquals("Warn message should've been captured", 1, outputList.size()); + assertTrue("Message should be logged in warn level", isWarnMessage(outputList.get(0))); + assertEquals("Supplied warn message wasn't found in the log", + "Simple warn message", + extractMessage(outputList.get(0))); + + outputList.clear(); + + configuredLogger.error("Simple error message"); + assertEquals("The other levels should have been captured", 1, outputList.size()); + } + + @Test + public void testError() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.ERROR); + + configuredLogger.trace("Simple trace message"); + configuredLogger.debug("Simple debug message"); + configuredLogger.info("Simple info message"); + configuredLogger.warn("Simple warn message"); + assertEquals("Lower levels should have been ignored", 0, outputList.size()); + + assertTrue("Error level should be enabled for this test", configuredLogger.isErrorEnabled()); + configuredLogger.error("Simple error message"); + + assertEquals("Error message should've been captured", 1, outputList.size()); + assertTrue("Message should be logged in error level", isErrorMessage(outputList.get(0))); + assertEquals("Supplied error message wasn't found in the log", + "Simple error message", + extractMessage(outputList.get(0))); + } + + @Test + public void testFormatting() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.INFO); + + configuredLogger.info("Some {} string", "formatted"); + assertEquals("The formatted message should've been captured", 1, outputList.size()); + assertEquals("Message should've been formatted", "Some formatted string", extractMessage(outputList.get(0))); + } + + @Test + public void testException() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.INFO); + + Exception exception = new RuntimeException("My error"); + + configuredLogger.info("Logging with an exception", exception); + assertEquals("The formatted message should've been captured", 1, outputList.size()); + assertEquals("Message should've been formatted", + "My error", + extractExceptionMessage(outputList.get(0))); + + assertEquals("Message should've been formatted", + "java.lang.RuntimeException", + extractExceptionType(outputList.get(0))); + } + + + @Test + public void testExceptionParameterFormatting() { + ArrayList<String> outputList = new ArrayList<>(); + Logger configuredLogger = createLogger(prepareSink(outputList), Level.INFO); + + Exception exception = new IllegalArgumentException("Invalid argument"); + + configuredLogger.info("An exception occurred: {}", 99, exception); + assertEquals("The formatted message should've been captured", 1, outputList.size()); + assertEquals("Message should've been formatted", + "An exception occurred: 99", + extractMessage(outputList.get(0))); + + assertEquals("Exception type should've been captured", + "java.lang.IllegalArgumentException", + extractExceptionType(outputList.get(0))); + + + configuredLogger.info("Another exception occurred: {} {}", exception, 99); + assertEquals("The formatted message should've been captured", 2, outputList.size()); + assertEquals("Message should've been formatted", + "Another exception occurred: java.lang.IllegalArgumentException: Invalid argument 99", + extractMessage(outputList.get(1))); + + AtomicInteger atomicInteger = new AtomicInteger(); + outputList.forEach(s -> System.out.println("x Log line " + (atomicInteger.getAndIncrement()) + ": " + s)); + } + + /** + * Allows tests to check whether the log message contains a trace message. + * Override if needed. + * + * @param message String containing the full log message + * @return whether it is a trace message or not + */ + protected boolean isTraceMessage(String message) { + return message.toLowerCase().contains("trace"); + } + + /** + * Allows tests to check whether the log message contains a debug message. + * Override if needed. + * + * @param message String containing the full log message + * @return whether it is a debug message or not + */ + protected boolean isDebugMessage(String message) { + return message.toLowerCase().contains("debug"); + } + + /** + * Allows tests to check whether the log message contains an info message. + * Override if needed. + * + * @param message String containing the full log message + * @return whether it is an info message or not + */ + protected boolean isInfoMessage(String message) { + return message.toLowerCase().contains("info"); + } + + /** + * Allows tests to check whether the log message contains a warn message. + * Override if needed. + * + * @param message String containing the full log message + * @return whether it is a warn message or not + */ + protected boolean isWarnMessage(String message) { + return message.toLowerCase().contains("warn"); + } + + /** + * Allows tests to check whether the log message contains an error message. + * Override if needed. + * + * @param message String containing the full log message + * @return whether it is an error message or not + */ + protected boolean isErrorMessage(String message) { + return message.toLowerCase().contains("error"); + } + +} ++++++ slf4j-build.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/common.xml new/common.xml --- old/common.xml 2026-05-13 17:06:23.519700208 +0200 +++ new/common.xml 2026-09-29 13:01:10.194716031 +0200 @@ -3,7 +3,7 @@ <project name="common" basedir="."> <property file="build.properties"/> - <property name="project.version" value="2.0.18"/> + <property name="project.version" value="2.0.20"/> <property name="project.groupId" value="org.slf4j"/> <property name="project.url" value="http://www.slf4j.org/"/>
