1

tl;dr: I'd like to filter out some unneeded logs (and leave other) from pdfbox library. This library uses commons-logging facade.

Longer version

I'd like to filter out some unneeded logs (and leave other) from pdfbox library. It has in its own dependencies commons-logging, which is (as I understand it) a facade similar to slf4j. I also have another dependency (picocli), which also has dependency to commons-logging albeit with a newer version. There is a project called jcl-over-slf4j which redirects logs coming from commons-logging to slf4j facade. I decided to use it together with logback-classic, a logger that natively implements slf4j. I also wrote a custom filter to filter out logs I don't need. Everything works fine when I run junit test (from IntelliJ and using CLI - by running mvn clean package). But running the final (fat) jar including my own code and all the dependencies results in a message printed out to console saying that I don't have any supported logger on classpath:

SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.

Why is that? It seems that everything works fine during test execution and fails to work when using fat jar.

UPDATE

As this really seems important, I will describe the fat jar issue and how I run my application. To create fat jar I use the maven shade plugin together with the configuration seen below. I create fat jar by executing mvn clean package and run the application by java -jar myapp.jar.

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-shade-plugin</artifactId>
   <version>3.2.4</version>
   <configuration>
      <createDependencyReducedPom>false</createDependencyReducedPom>
      <transformers>
         <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>com.github.menteith.pdfexample.Main</mainClass>
            <manifestEntries>
               <Built-By />
            </manifestEntries>
         </transformer>
      </transformers>
      <filters>
         <filter>
            <artifact>*:*</artifact>
            <excludes>
               <exclude>META-INF/**</exclude>
               <exclude>module-info.class</exclude>
            </excludes>
         </filter>
      </filters>
   </configuration>
   <executions>
      <execution>
         <phase>package</phase>
         <goals>
            <goal>shade</goal>
         </goals>
      </execution>
   </executions>
</plugin>

Here's my custom Logback filter:

class LogbackTurboFilter
    extends TurboFilter {

   @Override
   public FilterReply decide(final Marker marker, final Logger logger, final Level level,
       final String format, final Object[] params, final Throwable t) {

      System.out.println("In decide!!!"); // this is printed during tests, but not when final jar is run

      if ( !isStarted() ) {
         return FilterReply.NEUTRAL;
      }

      if ( level == Level.WARN
           && logger.getName().startsWith("org.apache.pdfbox") ) {
         return FilterReply.DENY;
      } else {
         return FilterReply.NEUTRAL;
      }
   }
}

Here's my logback.xml:

<configuration>
    <turboFilter class="com.github.menteith.pdfexample.LogbackTurboFilter"/>
    <appender name="STDOUT"
            class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%-4relative [%thread] %-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

Relevant parts of pom.xml:

<dependency>
    <artifactId>pdfbox</artifactId>
    <groupId>org.apache.pdfbox</groupId>
    <version>2.0.27</version>
    <!-- picocli has never artifact -->
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>info.picocli</groupId>
    <artifactId>picocli</artifactId>
    <version>4.7.1</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>2.0.6</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.5</version>
</dependency>

And dependency tree from maven:

+- org.apache.pdfbox:pdfbox:jar:2.0.27:compile
|  \- org.apache.pdfbox:fontbox:jar:2.0.27:compile
+- info.picocli:picocli:jar:4.7.1:compile
+- org.slf4j:jcl-over-slf4j:jar:2.0.6:compile
|  \- org.slf4j:slf4j-api:jar:2.0.6:compile
+- ch.qos.logback:logback-classic:jar:1.4.5:compile
|  \- ch.qos.logback:logback-core:jar:1.4.5:compile
+- org.bouncycastle:bcprov-jdk15on:jar:1.70:compile
+- org.bouncycastle:bcmail-jdk15on:jar:1.70:compile
|  +- org.bouncycastle:bcutil-jdk15on:jar:1.70:compile
|  \- org.bouncycastle:bcpkix-jdk15on:jar:1.70:compile
+- com.github.jai-imageio:jai-imageio-jpeg2000:jar:1.4.0:compile
|  \- com.github.jai-imageio:jai-imageio-core:jar:1.4.0:compile
+- org.projectlombok:lombok:jar:1.18.24:provided
+- org.junit.jupiter:junit-jupiter-engine:jar:5.9.2:test
|  +- org.junit.platform:junit-platform-engine:jar:1.9.2:test
|  |  +- org.opentest4j:opentest4j:jar:1.2.0:test
|  |  \- org.junit.platform:junit-platform-commons:jar:1.9.2:test
|  +- org.junit.jupiter:junit-jupiter-api:jar:5.9.2:test
|  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
\- org.assertj:assertj-core:jar:3.24.0:test
   \- net.bytebuddy:byte-buddy:jar:1.12.20:test
menteith
  • 596
  • 14
  • 51

1 Answers1

0
  1. create a separate "Hello World" project without dependencies
  2. add your fat jar as a single dependency
  3. invoke fat jar main class from "Hello World" main class
  4. set breakpoint in org.slf4j.LoggerFactory#bind method
  5. debug fat jar issues with class loading
ursa
  • 4,404
  • 1
  • 24
  • 38