Search code examples
javamavenlog4jslf4jgraalvm-native-image

GraalVM native-image build fails to find log4j appender class


I am tring to convert my .jar project into a native image since I need to run it in a device where Java is not supported. For that I installed GraalVM and all the required dependencies, and the native-image build works perfectly (or at least, seems to, as it doesn't give out any errors during the proccess).

The command that I'm using for the build is:

/usr/lib/jvm/graalvm/bin/native-image -jar MyApp.jar MyApp --enable-http --enable-https --no-fallback -H:+ReportExceptionStackTraces

The problem is, when I try to run the native file, I get an exception saying that the log4j class could not be found, and thus I have no application logs during execution:

log4j:ERROR Could not instantiate class [org.apache.log4j.RollingFileAppender].
java.lang.ClassNotFoundException: org.apache.log4j.RollingFileAppender
        at java.lang.Class.forName(DynamicHub.java:1338)
        at java.lang.Class.forName(DynamicHub.java:1313)
        at org.apache.log4j.helpers.Loader.loadClass(Loader.java:198)
        at org.apache.log4j.helpers.OptionConverter.instantiateByClassName(OptionConverter.java:327)
        at org.apache.log4j.helpers.OptionConverter.instantiateByKey(OptionConverter.java:124)
        at org.apache.log4j.PropertyConfigurator.parseAppender(PropertyConfigurator.java:785)
        at org.apache.log4j.PropertyConfigurator.parseCategory(PropertyConfigurator.java:768)
        at org.apache.log4j.PropertyConfigurator.configureRootCategory(PropertyConfigurator.java:648)
        at org.apache.log4j.PropertyConfigurator.doConfigure(PropertyConfigurator.java:514)
        at org.apache.log4j.PropertyConfigurator.doConfigure(PropertyConfigurator.java:580)
        at org.apache.log4j.helpers.OptionConverter.selectAndConfigure(OptionConverter.java:526)
        at org.apache.log4j.LogManager.<clinit>(LogManager.java:127)
        at org.slf4j.impl.Log4jLoggerFactory.<init>(Log4jLoggerFactory.java:66)
        at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:72)
        at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:45)
        at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
        at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
        at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:412)
        at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:357)
        at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:383)
        at com.test.MyApp.<clinit>(MyApp.java:40)
log4j:ERROR Could not instantiate appender named "file".
log4j:WARN No appenders could be found for logger (com.test.MyApp).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

When I run the .jar through java -jar, it works just fine. Extracting the jar, I can see all the log4j files in it, including the RollingFileAppender class which is giving the error (it's a shaded jar). The problem is not just with RollingFileAppender either, if I try using another different appender, I still get the same error for the other class. So I just can't figure out what is wrong with the build.

Here's all the dependencies in my pom.xml file:

    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>       
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>3.0.19.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <version>3.0.19.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-client</artifactId>
            <version>3.0.19.Final</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>

        (These last 2 were not in the original project, I tried adding them to see if it'd help but nothing changed, still get the same error)
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>apache-log4j-extras</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

And my maven-shade-plugin configuration:

          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.4</version>
            <executions>
              <execution>
                <phase>package</phase>
                <goals>
                  <goal>shade</goal>
                </goals>
              </execution>
            </executions>
          </plugin>

Any light on this matter would be greatly appreciated, I've been trying to fix this for days with no luck.

Thank you in advance!


Solution

  • Funnily enough, soon after posting this question, I found the answer to it. It had to do with the reflect configuration of the GraalVM. The fix was actually quite simple:

    First you run your jar using a special GraalVM option:

    <GRAALVM_HOME>/bin/java -agentlib:native-image-agent=config-output-dir=<DIRECTORY_YOU_WANT_THE_FILES_TO_BE_GERERATED_AT> -jar <JAR_FILE>.jar
    

    That will generate a bunch of .json files that will help configure the GraalVM native-image build:

    jni-config.json
    predefined-classes-config.json
    proxy-config.json
    reflect-config.json
    resource-config.json
    serialization-config.json
    

    Once you have those files, you can execute the build passing them as a parameter:

    <GRAALVM_HOME>/bin/native-image -jar <JAR_FILE>.jar <NATIVE_IMAGE_NAME> -H:ConfigurationFileDirectories=<DIRECTORY_WHERE_YOUR_JSON_FILES_ARE_AT>
    

    And that's it! Once the build finished, the native image ran perfectly without any issues, all the logs worked as expected.

    If you want, you can also add the .json files to a META-INF/native-image directory accessible from the classpath, for example within your src/main/resources directory. That way, you don't need to pass the directory as argument when calling the native-image build.

    My source for all this useful information was at: https://simply-how.com/fix-graalvm-native-image-compilation-issues

    I hope this helps someone else out there with the same issue :)