Tony
Tony

Reputation: 438

GraalVM (native-image) can not compile logback dependencies

I'm using the current version of community edition: GraalVM/native-image 22.1.0

My project has a dependency to the logging framework logback (version 1.2.3)

If I want to compile my "all-in-one" jar with graalVM the native-image complains:

Error: java.util.concurrent.ExecutionException: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of ch.qos.logback.classic.Logger are allowed in the image heap as this class should be initialized at image runtime. To see how this object got instantiated use --trace-object-instantiation=ch.qos.logback.classic.Logger.

I played around with many different permutations of settings e.g. --initialize-at-run-time=\<complete list of logback classes\> and --initialize-at-run-time

I tried to generate a reflection configuration file with

java -agentlib:native-image-agent=config-output-dir=META-INF/native-image -jar build/myjar-all.jar

and added it to the config file with

-H:ReflectionConfigurationFiles=reflect-config.json

But without success. Always getting different error messages all related to logback.

So my question:

Has anyone got logback successfully compiled with graalvm before?

Upvotes: 23

Views: 9701

Answers (2)

Vicky
Vicky

Reputation: 889

Maybe I am late to answer but this may help someone. I was also facing issues with logback.xml initialisation.

While searching for solution I found that spring suggests to name the file as logback-spring.xml instead of logback.xml

See here https://docs.spring.io/spring-boot/docs/2.1.13.RELEASE/reference/html/boot-features-logging.html

enter image description here

Upvotes: 0

Thach Van
Thach Van

Reputation: 1539

Short answer - you should add ch.qos.logback to --initialize-at-build-time:

--initialize-at-build-time=ch.qos.logback

For details, I would like to post what I have done to make logback working with GraalVM.

In the build.gradle file, I added instructions about what should be included at build time, at run time and also path to the file contains reflection configuration:

graalvmNative {
  binaries {
    all {
      resources.autodetect()
    }
    main {
      imageName.set('app') 
      buildArgs.add('--verbose')
      buildArgs.add('--add-opens=java.base/java.nio=ALL-UNNAMED')
      buildArgs.add('--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED')
      buildArgs.add('--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED')
      buildArgs.add('--trace-class-initialization=ch.qos.logback.classic.Logger')
      buildArgs.add('--trace-object-instantiation=ch.qos.logback.core.AsyncAppenderBase$Worker')
      buildArgs.add('--initialize-at-build-time=org.slf4j.LoggerFactory,ch.qos.logback')
      buildArgs.add('--initialize-at-run-time=io.netty')
    }
  }
}

nativeBuild {
  buildArgs('-H:ReflectionConfigurationFiles=../../../src/main/resources/reflection-config.json')
}

Here is the content of reflection-config.json file:

[
  {
    "name": "ch.qos.logback.classic.AsyncAppender",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.classic.encoder.PatternLayoutEncoder",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.classic.pattern.DateConverter",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.classic.pattern.LevelConverter",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.classic.pattern.LineSeparatorConverter",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.classic.pattern.LoggerConverter",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.classic.pattern.MessageConverter",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.classic.pattern.ThreadConverter",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.core.ConsoleAppender",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  },
  {
    "name": "ch.qos.logback.core.FileAppender",
    "allDeclaredConstructors": true,
    "allPublicConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredClasses": true,
    "allPublicClasses": true
  }
]

I use this logback.xml file:

<configuration scan="true" scanPeriod="150 seconds">
  <property name="LOG_DIR" value="logs" />

  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender" target="System.out">
    <encoder>
      <charset>UTF-8</charset>
      <pattern>%d{"yyyy-MM-dd'T'HH:mm:ss.SSSXXX", UTC} {%thread} [%-5level] %logger{0} - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_DIR}/app.log</file>
    <encoder>
      <charset>UTF-8</charset>
      <pattern>%d{"yyyy-MM-dd'T'HH:mm:ss.SSSXXX", UTC} {%thread} [%-5level] %logger - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="ASYNC_CONSOLE" class="ch.qos.logback.classic.AsyncAppender">
    <discardingThreshold>0</discardingThreshold> <!-- default 20, means drop lower event when has 20% capacity remaining -->
    <appender-ref ref="CONSOLE" />
    <queueSize>1024</queueSize> <!-- default 256 -->
    <includeCallerData>false</includeCallerData> <!-- default false -->
    <neverBlock>false</neverBlock> <!-- default false, set to true to cause the Appender not block the application and just drop the messages -->
  </appender>

  <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
    <discardingThreshold>0</discardingThreshold> <!-- default 20, means drop lower event when has 20% capacity remaining -->
    <appender-ref ref="FILE" />
    <queueSize>1024</queueSize> <!-- default 256 -->
    <includeCallerData>false</includeCallerData> <!-- default false -->
    <neverBlock>false</neverBlock> <!-- default false, set to true to cause the Appender not block the application and just drop the messages -->
  </appender>

  <root level="all">
    <appender-ref ref="ASYNC_CONSOLE" />
    <appender-ref ref="ASYNC_FILE" />
  </root>
</configuration>

Upvotes: 28

Related Questions