You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2024/08/30 19:24:49 UTC

(logging-log4j-samples) 01/02: Add GraalVM test for each logging backend

This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/logging-log4j-samples.git

commit 94c819ecdef430640b8a5367eacfdffca9cd5dc3
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Fri Aug 30 20:55:14 2024 +0200

    Add GraalVM test for each logging backend
    
    Add a profile to `log4j-samples-graalvm` to generate
    binaries that use:
    
    - Log4j Core,
    - Logback,
    - `java.util.logging`,
    - Simple Logger
---
 log4j-samples-graalvm/README.adoc                  |  47 ++-
 log4j-samples-graalvm/pom.xml                      | 321 ++++++++++++++++++++-
 .../apache/logging/log4j/samples/graalvm/Main.java |  34 ++-
 .../src/main/resources/log4j2.simplelog.properties |  20 ++
 .../src/main/resources/log4j2.xml                  |  11 +-
 .../src/main/resources/{log4j2.xml => logback.xml} |  27 +-
 .../src/main/resources/logging.properties          |  22 ++
 .../src/reachability-metadata/jul/index.json       |   4 +
 .../reachability-metadata/jul/reflect-config.json  |  11 +
 .../reachability-metadata/jul/resource-config.json |  10 +
 .../minimal/log4j-core/2.24.0/reflect-config.json  |   4 +-
 .../minimal/log4j-core/2.24.0/resource-config.json |  33 ++-
 .../logging/log4j/samples/graalvm/JulIT.java       |  63 ++++
 .../logging/log4j/samples/graalvm/SimpleIT.java    |  52 ++++
 .../logging/log4j/samples/graalvm/StandardIT.java  |  60 ++++
 15 files changed, 649 insertions(+), 70 deletions(-)

diff --git a/log4j-samples-graalvm/README.adoc b/log4j-samples-graalvm/README.adoc
index 6ae0ec1..5e87fe1 100644
--- a/log4j-samples-graalvm/README.adoc
+++ b/log4j-samples-graalvm/README.adoc
@@ -22,19 +22,32 @@ This project shows how to generate a native image using GraalVM.
 In order to use it:
 
 . set the `JAVA_HOME` environment variable to the path of a GraalVM installation,
-. set the current working directory to the parent folder of this file,
+. set the current working directory to the folder containing this file,
 . generate the artifact locally with:
 +
 [source,shell]
 ----
-./mvnw -Pgraalvm -pl log4j-samples-graalvm -am \
-clean verify
+../mvnw clean verify -Puse-<backend>
 ----
++
+where `<backend>` can be:
+
+`log4j-core`::
+This profile uses Log4j Core as logging backend.
+
+`logback`::
+This profile uses Logback as logging backend.
+
+`jul`::
+This profile uses `java.util.logging` as logging backend.
+
+`simple`::
+This profile uses `SimpleLogger` as logging backend.
 . run the application with
 +
 [source,shell]
 ----
-log4j-samples-graalvm/target/log4j-samples-graalvm
+target/log4j-samples-graalvm
 ----
 
 == Reachability metadata
@@ -60,14 +73,14 @@ https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticM
 ----
 # Build application classpath
 export CLASSPATH=$(\
-./mvnw -q -Pgraalvm -pl log4j-samples-graalvm \
+../mvnw -q \
 dependency:build-classpath -DincludeScope=runtime -Dmdep.outputProperty=classpath \
 help:evaluate -Dexpression=classpath -DforceStdout \
 )
-CLASSPATH="log4j-samples-graalvm/target/log4j-samples-graalvm-0.0.0-SNAPSHOT.jar:$CLASSPATH"
+CLASSPATH="target/log4j-samples-graalvm-0.0.0-SNAPSHOT.jar:$CLASSPATH"
 # Run application using the GraalVM agent
 $JAVA_HOME/bin/java \
--agentlib:native-image-agent=config-output-dir=log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0 \
+-agentlib:native-image-agent=config-output-dir=src/reachability-metadata/minimal/log4j-core/2.24.0 \
 org.apache.logging.log4j.samples.graalvm.Main
 ----
 
@@ -76,7 +89,7 @@ These plugins are easy to spot, because they only have a `name` key.
 
 == Native binary size
 
-Using GraalVM 17.0.10 on Linux x64, the native binaries have the following size:
+Using GraalVM 21.0.4 on Linux x64, the native binaries have the following size:
 
 [CAUTION]
 ====
@@ -89,12 +102,18 @@ In a typical application those classes are already required, so the difference s
 |===
 | Configuration | Size
 
-| Without Log4j
-| 6.59 MiB
+| With Log4j Core (minimal reachability metadata)
+| 36.13 MiB
 
-| With minimal reachability metadata
-| 36.96 MiB
-
-| With full reachability metadata
+| With Log4j Core (full reachability metadata)
 | Unknown (≥ 38.5 MiB)
+
+| With Logback
+| 30.59 MiB
+
+| With `java.util.logging`
+| 14.03 MiB
+
+| With `SimpleLogger`
+| 13.84 MiB
 |===
diff --git a/log4j-samples-graalvm/pom.xml b/log4j-samples-graalvm/pom.xml
index 4b68efd..8341aa0 100644
--- a/log4j-samples-graalvm/pom.xml
+++ b/log4j-samples-graalvm/pom.xml
@@ -18,23 +18,100 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
-    <groupId>org.apache.logging.log4j.samples</groupId>
-    <artifactId>log4j-samples</artifactId>
-    <version>${revision}</version>
+    <groupId>org.apache.logging</groupId>
+    <artifactId>logging-parent</artifactId>
+    <version>11.2.0</version>
+    <relativePath />
   </parent>
 
   <artifactId>log4j-samples-graalvm</artifactId>
+  <version>${revision}</version>
   <name>Apache Log4j Samples: GraalVM native image</name>
 
   <properties>
+    <!-- Some samples require JDK 9+ -->
+    <minimalJavaBuildVersion>17</minimalJavaBuildVersion>
+    <maven.compiler.release>17</maven.compiler.release>
+    <maven.compiler.source />
+    <maven.compiler.target />
+
+    <!-- Skip `bnd-baseline-maven-plugin` since this is not deployed -->
+    <bnd.baseline.skip>true</bnd.baseline.skip>
+
+    <revision>0.0.0-SNAPSHOT</revision>
+
+    <!-- Dependencies (alphabetical) -->
+    <assertj.version>3.26.0</assertj.version>
+    <junit.version>5.11.0</junit.version>
     <log4j.version>2.24.0-SNAPSHOT</log4j.version>
+    <logback.version>1.5.7</logback.version>
+    <slf4j.version>2.0.16</slf4j.version>
+
+    <!-- IT to run for each profile -->
+    <integration-tests-include>none</integration-tests-include>
   </properties>
 
+  <dependencyManagement>
+    <dependencies>
+
+      <dependency>
+        <groupId>org.assertj</groupId>
+        <artifactId>assertj-bom</artifactId>
+        <version>${assertj.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+
+      <dependency>
+        <groupId>org.junit</groupId>
+        <artifactId>junit-bom</artifactId>
+        <version>${junit.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+
+      <dependency>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-bom</artifactId>
+        <version>${log4j.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>${slf4j.version}</version>
+      </dependency>
+
+      <dependency>
+        <groupId>ch.qos.logback</groupId>
+        <artifactId>logback-classic</artifactId>
+        <version>${logback.version}</version>
+      </dependency>
+
+    </dependencies>
+  </dependencyManagement>
+
   <dependencies>
+
     <dependency>
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-api</artifactId>
     </dependency>
+
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+
   </dependencies>
 
   <build>
@@ -57,11 +134,20 @@
         </executions>
       </plugin>
 
+      <!--
+        ~ Creates a native executable
+        -->
       <plugin>
         <groupId>org.graalvm.buildtools</groupId>
         <artifactId>native-maven-plugin</artifactId>
         <version>0.10.2</version>
         <extensions>true</extensions>
+        <configuration>
+          <mainClass>org.apache.logging.log4j.samples.graalvm.Main</mainClass>
+          <metadataRepository>
+            <enabled>false</enabled>
+          </metadataRepository>
+        </configuration>
         <executions>
           <execution>
             <id>build-native</id>
@@ -69,26 +155,129 @@
               <goal>compile-no-fork</goal>
             </goals>
             <phase>package</phase>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!--
+        ~ Cleans the logs directory before running the executable
+        -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-clean-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>remove-stale-log-files</id>
+            <goals>
+              <goal>clean</goal>
+            </goals>
+            <phase>pre-integration-test</phase>
             <configuration>
-              <mainClass>org.apache.logging.log4j.samples.graalvm.Main</mainClass>
-              <metadataRepository>
-                <enabled>true</enabled>
-                <localPath>${project.basedir}/src/reachability-metadata/minimal</localPath>
-              </metadataRepository>
+              <filesets>
+                <fileset>
+                  <directory>target/logs</directory>
+                  <includes>
+                    <include>*.log</include>
+                  </includes>
+                </fileset>
+              </filesets>
+              <excludeDefaultDirectories>true</excludeDefaultDirectories>
             </configuration>
           </execution>
         </executions>
       </plugin>
 
+      <!--
+        ~ Runs the native executable
+        -->
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>3.4.1</version>
+        <executions>
+          <execution>
+            <id>run-executable</id>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <phase>integration-test</phase>
+            <configuration>
+              <executable>target/log4j-samples-graalvm</executable>
+              <outputFile>target/logs/out.log</outputFile>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!--
+        ~ Verifies the content of the log file
+        -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-failsafe-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>integration-test</id>
+            <goals>
+              <goal>integration-test</goal>
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <includes>${integration-tests-include}</includes>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
   </build>
 
   <profiles>
+
+    <!--
+      ~ Enable this profile to use JUL as logging implementation.
+      -->
+    <profile>
+      <id>use-jul</id>
+
+      <properties>
+        <integration-tests-include>**/JulIT.class</integration-tests-include>
+      </properties>
+
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.logging.log4j</groupId>
+          <artifactId>log4j-to-jul</artifactId>
+          <scope>runtime</scope>
+        </dependency>
+      </dependencies>
+
+      <build>
+        <plugins>
+          <!--
+            ~ JUL requires reflection metadata for FileHandler
+            -->
+          <plugin>
+            <groupId>org.graalvm.buildtools</groupId>
+            <artifactId>native-maven-plugin</artifactId>
+            <configuration>
+              <buildArgs>
+                <arg>-H:ConfigurationFileDirectories=${basedir}/src/reachability-metadata/jul</arg>
+              </buildArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+
+    <!--
+      ~ Enable this profile to use Log4j Core as logging implementation.
+      -->
     <profile>
       <id>use-log4j-core</id>
-      <activation>
-        <activeByDefault>true</activeByDefault>
-      </activation>
+
+      <properties>
+        <integration-tests-include>**/StandardIT.class</integration-tests-include>
+      </properties>
 
       <dependencies>
         <dependency>
@@ -98,6 +287,116 @@
         </dependency>
       </dependencies>
 
+      <build>
+        <plugins>
+          <!--
+            ~ Log4j Core needs additional metadata
+            -->
+          <plugin>
+            <groupId>org.graalvm.buildtools</groupId>
+            <artifactId>native-maven-plugin</artifactId>
+            <configuration>
+              <metadataRepository>
+                <enabled>true</enabled>
+                <localPath>${project.basedir}/src/reachability-metadata/minimal</localPath>
+              </metadataRepository>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+
+    <!--
+      ~ Enable this profile to use Logback as logging implementation.
+      -->
+    <profile>
+      <id>use-logback</id>
+
+      <properties>
+        <integration-tests-include>**/StandardIT.class</integration-tests-include>
+      </properties>
+
+      <dependencies>
+
+        <dependency>
+          <groupId>org.apache.logging.log4j</groupId>
+          <artifactId>log4j-to-slf4j</artifactId>
+          <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+          <groupId>ch.qos.logback</groupId>
+          <artifactId>logback-classic</artifactId>
+          <scope>runtime</scope>
+        </dependency>
+
+      </dependencies>
+
+      <build>
+        <plugins>
+          <!--
+            ~ Logback needs additional metadata
+            -->
+          <plugin>
+            <groupId>org.graalvm.buildtools</groupId>
+            <artifactId>native-maven-plugin</artifactId>
+            <configuration>
+              <metadataRepository>
+                <enabled>true</enabled>
+              </metadataRepository>
+              <buildArgs>
+                <arg>-H:IncludeResources=logback.xml</arg>
+              </buildArgs>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+
+    <profile>
+      <id>use-simple</id>
+
+      <properties>
+        <integration-tests-include>**/SimpleIT.class</integration-tests-include>
+      </properties>
+
+      <build>
+        <plugins>
+          <!--
+            ~ SimpleLog needs a configuration file
+            -->
+          <plugin>
+            <groupId>org.graalvm.buildtools</groupId>
+            <artifactId>native-maven-plugin</artifactId>
+            <configuration>
+              <metadataRepository>
+                <enabled>true</enabled>
+              </metadataRepository>
+              <buildArgs>
+                <arg>-H:IncludeResources=log4j2.simplelog.properties</arg>
+              </buildArgs>
+            </configuration>
+          </plugin>
+          <!--
+            ~ SimpleLogger needs some system properties
+            -->
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>exec-maven-plugin</artifactId>
+            <version>3.4.1</version>
+            <executions>
+              <execution>
+                <id>run-executable</id>
+                <configuration>
+                  <arguments>
+                    <argument>-Dlog4j.provider=org.apache.logging.log4j.simple.SimpleProvider</argument>
+                  </arguments>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
     </profile>
   </profiles>
 </project>
diff --git a/log4j-samples-graalvm/src/main/java/org/apache/logging/log4j/samples/graalvm/Main.java b/log4j-samples-graalvm/src/main/java/org/apache/logging/log4j/samples/graalvm/Main.java
index 5265011..af58d49 100644
--- a/log4j-samples-graalvm/src/main/java/org/apache/logging/log4j/samples/graalvm/Main.java
+++ b/log4j-samples-graalvm/src/main/java/org/apache/logging/log4j/samples/graalvm/Main.java
@@ -16,25 +16,37 @@
  */
 package org.apache.logging.log4j.samples.graalvm;
 
-import java.time.Instant;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 public final class Main {
 
+    static {
+        try {
+            Files.createDirectories(Paths.get("target", "logs"));
+            java.util.logging.LogManager.getLogManager()
+                    .readConfiguration(Main.class.getResourceAsStream("/logging.properties"));
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private static final Logger logger = LogManager.getLogger(Main.class);
 
     private Main() {}
 
-    public static void main(String[] args) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(Instant.now())
-                .append(" [")
-                .append(Thread.currentThread().getName())
-                .append("] INFO ")
-                .append(Main.class.getName())
-                .append(" - Hello System.err!");
-        System.err.println(sb);
-        logger.info("Hello Log4j!");
+    public static void main(final String[] args) {
+        final List<Level> levels = new ArrayList<>(Arrays.asList(Level.values()));
+        levels.remove(Level.OFF);
+        levels.remove(Level.ALL);
+        levels.remove(Level.FATAL);
+        levels.stream().sorted().forEach(level -> logger.log(level, "Hello {}!", level));
     }
 }
diff --git a/log4j-samples-graalvm/src/main/resources/log4j2.simplelog.properties b/log4j-samples-graalvm/src/main/resources/log4j2.simplelog.properties
new file mode 100644
index 0000000..88f4373
--- /dev/null
+++ b/log4j-samples-graalvm/src/main/resources/log4j2.simplelog.properties
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+##
+log4j2.simplelogLevel = TRACE
+log4j2.simplelogShowlogname = true
+log4j2.simplelogShowShortLogname = false
diff --git a/log4j-samples-graalvm/src/main/resources/log4j2.xml b/log4j-samples-graalvm/src/main/resources/log4j2.xml
index 954a8f7..03a9c22 100644
--- a/log4j-samples-graalvm/src/main/resources/log4j2.xml
+++ b/log4j-samples-graalvm/src/main/resources/log4j2.xml
@@ -19,13 +19,14 @@
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-config-2.xsd">
   <Appenders>
-    <Console name="CONSOLE">
-      <PatternLayout pattern="%d [%t] %p %c - %m%n"/>
-    </Console>
+    <File name="FILE"
+          fileName="target/logs/file.log">
+      <PatternLayout pattern="[%t] %p %c - %m%n"/>
+    </File>
   </Appenders>
   <Loggers>
-    <Root level="INFO">
-      <AppenderRef ref="CONSOLE"/>
+    <Root level="TRACE">
+      <AppenderRef ref="FILE"/>
     </Root>
   </Loggers>
 </Configuration>
diff --git a/log4j-samples-graalvm/src/main/resources/log4j2.xml b/log4j-samples-graalvm/src/main/resources/logback.xml
similarity index 63%
copy from log4j-samples-graalvm/src/main/resources/log4j2.xml
copy to log4j-samples-graalvm/src/main/resources/logback.xml
index 954a8f7..31bf7de 100644
--- a/log4j-samples-graalvm/src/main/resources/log4j2.xml
+++ b/log4j-samples-graalvm/src/main/resources/logback.xml
@@ -15,17 +15,16 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<Configuration xmlns="https://logging.apache.org/xml/ns"
-               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-               xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-config-2.xsd">
-  <Appenders>
-    <Console name="CONSOLE">
-      <PatternLayout pattern="%d [%t] %p %c - %m%n"/>
-    </Console>
-  </Appenders>
-  <Loggers>
-    <Root level="INFO">
-      <AppenderRef ref="CONSOLE"/>
-    </Root>
-  </Loggers>
-</Configuration>
+<!DOCTYPE configuration>
+<configuration>
+    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+        <file>target/logs/file.log</file>
+        <encoder>
+            <pattern>[%t] %p %c - %m%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="TRACE">
+        <appender-ref ref="FILE" />
+    </root>
+</configuration>
diff --git a/log4j-samples-graalvm/src/main/resources/logging.properties b/log4j-samples-graalvm/src/main/resources/logging.properties
new file mode 100644
index 0000000..3070627
--- /dev/null
+++ b/log4j-samples-graalvm/src/main/resources/logging.properties
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+##
+handlers = java.util.logging.FileHandler
+.level = FINER
+java.util.logging.FileHandler.pattern = target/logs/file.log
+java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = %4$s %3$s - %5$s%n
diff --git a/log4j-samples-graalvm/src/reachability-metadata/jul/index.json b/log4j-samples-graalvm/src/reachability-metadata/jul/index.json
new file mode 100644
index 0000000..93a3dda
--- /dev/null
+++ b/log4j-samples-graalvm/src/reachability-metadata/jul/index.json
@@ -0,0 +1,4 @@
+[
+  "reflect-config.json",
+  "resource-config.json"
+]
\ No newline at end of file
diff --git a/log4j-samples-graalvm/src/reachability-metadata/jul/reflect-config.json b/log4j-samples-graalvm/src/reachability-metadata/jul/reflect-config.json
new file mode 100644
index 0000000..cedee01
--- /dev/null
+++ b/log4j-samples-graalvm/src/reachability-metadata/jul/reflect-config.json
@@ -0,0 +1,11 @@
+[
+  {
+    "name": "java.util.logging.FileHandler",
+    "methods": [
+      {
+        "name": "<init>",
+        "parameterTypes": []
+      }
+    ]
+  }
+]
diff --git a/log4j-samples-graalvm/src/reachability-metadata/jul/resource-config.json b/log4j-samples-graalvm/src/reachability-metadata/jul/resource-config.json
new file mode 100644
index 0000000..6f02a9c
--- /dev/null
+++ b/log4j-samples-graalvm/src/reachability-metadata/jul/resource-config.json
@@ -0,0 +1,10 @@
+{
+  "resources": {
+    "includes": [
+      {
+        "pattern": "\\Qlogging.properties\\E"
+      }
+    ]
+  },
+  "bundles": []
+}
diff --git a/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/reflect-config.json b/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/reflect-config.json
index 8ec5600..a419fe9 100644
--- a/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/reflect-config.json
+++ b/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/reflect-config.json
@@ -8,7 +8,7 @@
     "allDeclaredFields": true
   },
   {
-    "name": "org.apache.logging.log4j.core.appender.ConsoleAppender",
+    "name": "org.apache.logging.log4j.core.appender.FileAppender",
     "queryAllDeclaredMethods": true,
     "methods": [
       {
@@ -18,7 +18,7 @@
     ]
   },
   {
-    "name": "org.apache.logging.log4j.core.appender.ConsoleAppender$Builder",
+    "name": "org.apache.logging.log4j.core.appender.FileAppender$Builder",
     "allDeclaredFields": true
   },
   {
diff --git a/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/resource-config.json b/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/resource-config.json
index 61dfaf0..dd9527e 100644
--- a/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/resource-config.json
+++ b/log4j-samples-graalvm/src/reachability-metadata/minimal/log4j-core/2.24.0/resource-config.json
@@ -1,15 +1,22 @@
 {
-  "resources":{
-  "includes":[{
-    "pattern":"\\QMETA-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat\\E"
-  }, {
-    "pattern":"\\QMETA-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider\\E"
-  }, {
-    "pattern":"\\QMETA-INF/services/org.apache.logging.log4j.spi.Provider\\E"
-  }, {
-    "pattern":"\\QMETA-INF/services/org.apache.logging.log4j.util.PropertySource\\E"
-  }, {
-    "pattern":"\\Qlog4j2.xml\\E"
-  }]},
-  "bundles":[]
+  "resources": {
+    "includes": [
+      {
+        "pattern": "\\QMETA-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat\\E"
+      },
+      {
+        "pattern": "\\QMETA-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider\\E"
+      },
+      {
+        "pattern": "\\QMETA-INF/services/org.apache.logging.log4j.spi.Provider\\E"
+      },
+      {
+        "pattern": "\\QMETA-INF/services/org.apache.logging.log4j.util.PropertySource\\E"
+      },
+      {
+        "pattern": "\\Qlog4j2.xml\\E"
+      }
+    ]
+  },
+  "bundles": []
 }
diff --git a/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/JulIT.java b/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/JulIT.java
new file mode 100644
index 0000000..129573e
--- /dev/null
+++ b/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/JulIT.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.samples.graalvm;
+
+import static org.apache.logging.log4j.samples.graalvm.StandardIT.STANDARD_LEVELS;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test for JUL.
+ */
+public class JulIT {
+
+    private static final String[] JUL_LEVELS = {"SEVERE", "WARNING", "INFO", "FINE", "FINER"};
+
+    @Test
+    void verifyStdOut() {
+        final Path basePath = Paths.get(System.getProperty("basedir"), "target", "logs");
+        final Path logFile = basePath.resolve("out.log");
+
+        assertThat(logFile).exists().isEmptyFile();
+    }
+
+    @Test
+    void verifyLogFile() throws IOException {
+        final Path basePath = Paths.get(System.getProperty("basedir"), "target", "logs");
+        final Path logFile = basePath.resolve("file.log");
+
+        assertThat(logFile).exists().isNotEmptyFile();
+
+        final List<String> expected = IntStream.range(0, STANDARD_LEVELS.length)
+                .mapToObj(i ->
+                        String.format("%s %s - Hello %s!", JUL_LEVELS[i], Main.class.getName(), STANDARD_LEVELS[i]))
+                .toList();
+
+        try (final Stream<String> stream = Files.lines(logFile, StandardCharsets.UTF_8)) {
+            assertThat(stream).containsExactlyElementsOf(expected);
+        }
+    }
+}
diff --git a/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/SimpleIT.java b/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/SimpleIT.java
new file mode 100644
index 0000000..5a5ee65
--- /dev/null
+++ b/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/SimpleIT.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.samples.graalvm;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test to run for the SimpleLogger implementation.
+ */
+public class SimpleIT {
+
+    static final String[] STANDARD_LEVELS = {"ERROR", "WARN", "INFO", "DEBUG", "TRACE"};
+
+    @Test
+    void verifyStdOut() throws IOException {
+        final Path basePath = Paths.get(System.getProperty("basedir"), "target", "logs");
+        final Path logFile = basePath.resolve("out.log");
+
+        assertThat(logFile).exists().isNotEmptyFile();
+
+        final List<String> expected = Stream.of(STANDARD_LEVELS)
+                .map(l -> String.format("%s %s Hello %s!", l, Main.class.getName(), l))
+                .toList();
+
+        try (final Stream<String> stream = Files.lines(logFile, StandardCharsets.UTF_8)) {
+            assertThat(stream).containsExactlyElementsOf(expected);
+        }
+    }
+}
diff --git a/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/StandardIT.java b/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/StandardIT.java
new file mode 100644
index 0000000..f797413
--- /dev/null
+++ b/log4j-samples-graalvm/src/test/java/org/apache/logging/log4j/samples/graalvm/StandardIT.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.samples.graalvm;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Integration test to run for configurable implementations that can include the thread's name.
+ */
+public class StandardIT {
+
+    static final String[] STANDARD_LEVELS = {"ERROR", "WARN", "INFO", "DEBUG", "TRACE"};
+
+    @Test
+    void verifyStdOut() {
+        final Path basePath = Paths.get(System.getProperty("basedir"), "target", "logs");
+        final Path logFile = basePath.resolve("out.log");
+
+        assertThat(logFile).exists().isEmptyFile();
+    }
+
+    @Test
+    void verifyLogFile() throws IOException {
+        final Path basePath = Paths.get(System.getProperty("basedir"), "target", "logs");
+        final Path logFile = basePath.resolve("file.log");
+
+        assertThat(logFile).exists().isNotEmptyFile();
+
+        final List<String> expected = Stream.of(STANDARD_LEVELS)
+                .map(l -> String.format("[main] %s %s - Hello %s!", l, Main.class.getName(), l))
+                .toList();
+
+        try (final Stream<String> stream = Files.lines(logFile, StandardCharsets.UTF_8)) {
+            assertThat(stream).containsExactlyElementsOf(expected);
+        }
+    }
+}