0

How can I write my log messages to database in my quarkus application?

I tried to create a ExtHandler like this:

package org.myorg;

import io.agroal.api.AgroalDataSource;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logmanager.ExtHandler;
import org.jboss.logmanager.ExtLogRecord;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

@ApplicationScoped
public class DatabaseLogHandler extends ExtHandler {

  @Inject
  private AgroalDataSource dataSource;

  @Override
  protected void doPublish(final ExtLogRecord record) {
    try (final Connection conn = this.dataSource.getConnection();
         final PreparedStatement ps = conn.prepareStatement(
             "INSERT INTO logs(timestamp, level, logger, message) VALUES (?, ?, ?, ?)")) {

      ps.setLong(1, record.getMillis());
      ps.setString(2, record.getLevel().toString());
      ps.setString(3, record.getLoggerName());
      ps.setString(4, record.getMessage());
      ps.executeUpdate();

    } catch (final SQLException e) {
      e.printStackTrace();
    }
  }
}

and added

quarkus.log.handler.database=DatabaseLogHandler

to my application.properties but the code is not being executed.

3
  • Are you sure is it possibile di add a new handler in that way? I've checked documentation but I haven't found anything about custom handler support... Commented Nov 11 at 7:48
  • Have you considered migrating the underlying logging to SLF4J + LOG4J2 or SLF4J + LOGBACK? We would use the Appender of LOG4J2 or LOGBACK to write logs to the DB (using a JDBC Appender or Cassandra Appender). Commented Nov 11 at 14:11
  • I'm not sure if it is possible that way, just read that extending ExtHandler could work... I do not mind migrating to another logger. Do you have examples for Quarkus? Commented Nov 11 at 14:58

2 Answers 2

1

This is a demonstration of proof-of-concept testing.

Create Quarkus Application

mvn io.quarkus.platform:quarkus-maven-plugin:create \
    -DprojectGroupId=org.myorg \
    -DprojectArtifactId=quarkus-demo-database-log-handler \
    -DclassName="org.myorg.GreetingResource" \
    -Dpath="/hello"

Add extensions

Add Support for PostgreSQL

cd quarkus-demo-database-log-handler

mvn quarkus:add-extension -Dextensions="quarkus-jdbc-postgresql"

You can use the following command to search for available extensions.

mvn quarkus:list-extensions

Database - PostgreSQL

init_postgresql_custom.sql

CREATE TABLE app_log (
    id       BIGSERIAL PRIMARY KEY,
    log_time TIMESTAMPTZ DEFAULT NOW() NOT NULL,   -- now()
    level    VARCHAR(20) NOT NULL,        -- event.getLevel().toString()
    logger   VARCHAR(255) NOT NULL,       -- event.getLoggerName()
    message  TEXT NOT NULL                -- event.getFormattedMessage()
);
CREATE INDEX idx_app_log_time ON app_log(log_time DESC);
CREATE INDEX idx_app_log_level ON app_log(level);

Use Docker to provide the PostgreSQL Server

docker run \
  --name my-postgres-quarkus-log \
  -e POSTGRES_USER=demouser \
  -e POSTGRES_PASSWORD=Passw0rd! \
  -e POSTGRES_DB=demodb \
  -v $(pwd)/init_postgresql_custom.sql:/docker-entrypoint-initdb.d/init.sql \
  -p 5432:5432 \
  -d \
  postgres:18.0-alpine3.22

application.properties

  • add datasource name: logging
  • add config like quarkus.datasource.logging.xxx.yyy.zzz=aaa
# --- Logging DataSource ---
quarkus.datasource.logging.db-kind=postgresql
quarkus.datasource.logging.jdbc.url=jdbc:postgresql://localhost:5432/demodb
quarkus.datasource.logging.username=demouser
quarkus.datasource.logging.password=Passw0rd!

# Logging DataSource Agroal pool
quarkus.datasource.logging.jdbc.max-size=20
quarkus.datasource.logging.jdbc.min-size=2
quarkus.datasource.logging.jdbc.acquisition-timeout=5

quarkus.log.level=INFO
quarkus.log.category."org.myorg".level=DEBUG


# --- App DataSource ---
# quarkus.datasource.db-kind=postgresql
# quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/demodb
# quarkus.datasource.username=demouser
# quarkus.datasource.password=Passw0rd!

# App DataSource Agroal pool
# quarkus.datasource.jdbc.max-size=20
# quarkus.datasource.jdbc.min-size=2
# quarkus.datasource.jdbc.acquisition-timeout=5

Appfile

quarkus-demo-database-log-handler/src/main/java/org/myorg
├── DatabaseLogHandler.java
├── DatabaseLogHandlerInitializer.java
└── GreetingResource.java

DatabaseLogHandler.java

  • change Field Name: log_time (Not timestamp ) - init_postgresql_custom.sql
  • change SQL command, use now() to fill into log_time.
  • change record.getMessage()
  • it will get error content '%s %s (powered by Quarkus %s) started in %ss. %s'
  • change it to record.getFormattedMessage().
package org.myorg;

import io.agroal.api.AgroalDataSource;
import io.quarkus.agroal.DataSource;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logmanager.ExtHandler;
import org.jboss.logmanager.ExtLogRecord;

import java.sql.Connection;
import java.sql.PreparedStatement;

@ApplicationScoped
public class DatabaseLogHandler extends ExtHandler {

    private final AgroalDataSource dataSource;

    public DatabaseLogHandler(@DataSource("logging") AgroalDataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    protected void doPublish(ExtLogRecord record) {
        try (Connection conn = dataSource.getConnection()) {
            PreparedStatement ps = conn.prepareStatement(
             "INSERT INTO app_log(level, logger, message, log_time) VALUES (?, ?, ?, now())"
            );
            // "INSERT INTO logs(timestamp, level, logger, message) VALUES (?, ?, ?, ?)"))
            // ps.setLong(1, record.getMillis()); <- now()

            ps.setString(1, record.getLevel().getName());
            ps.setString(2, record.getLoggerName());
            //ps.setString(4, record.getMessage()); //get '%s %s (powered by Quarkus %s) started in %ss. %s'
            ps.setString(3, record.getFormattedMessage());
            ps.executeUpdate();
        } catch (Exception e) {
            reportError("DB logging failed", e, java.util.logging.ErrorManager.WRITE_FAILURE);
        }
    }

    @Override
    public void flush() {
        // not required
    }

    @Override
    public void close() throws SecurityException {
        // not required
    }
}

DatabaseLogHandlerInitializer.java

Register DatabaseLogHandler.

  • Inject DataSource from application.properties - quarkus.datasource.logging.xxxx=yyy
package org.myorg;

import io.agroal.api.AgroalDataSource;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import org.jboss.logmanager.LogManager;
import org.jboss.logmanager.Logger;

@ApplicationScoped
public class DatabaseLogHandlerInitializer {
    @Inject
    @io.quarkus.agroal.DataSource("logging")
    AgroalDataSource dataSource;

    void onStart(@Observes StartupEvent ev) {
        DatabaseLogHandler dbHandler = new DatabaseLogHandler(dataSource);
        Logger root = (Logger) LogManager.getLogManager().getLogger("");
        root.addHandler(dbHandler);
        System.err.println(">>> [DatabaseLogHandlerInitializer] DatabaseLogHandler attached. handler count = " + root.getHandlers().length);
    }
}

GreetingResource.java

  • add slf4j Logger and LoggerFactory.
package org.myorg;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/hello")
public class GreetingResource {
    private static final Logger logger = LoggerFactory.getLogger(GreetingResource.class);

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        logger.info("call hello().");
        logger.debug("DDDDD - call hello().");
        return "Hello from Quarkus REST";
    }

    // helloWithParameter - PathParam
    // /hello/John
    @GET
    @Path("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public String helloWithParameter(@PathParam("name") String name) {
        logger.info("Received name: {}", name);
        logger.debug("DDDDD - Received name: {}", name);
        return "Hello, " + name + "!";
    }

    // /hello/with-query?name=John
    @GET
    @Path("/with-query")
    @Produces(MediaType.TEXT_PLAIN)
    public String helloWithQuery(@QueryParam("name") String name) {
        logger.info("Received query name: {}", name);
        logger.debug("DDDDD - query name: {}", name);
        return "Hello, " + name + "!";
    }
}

pom.xml

  • Remove dependencies related to "test".

Final Project tree

remove test

quarkus-demo-database-log-handler
├── pom.xml                     <-- Change  
├── init_postgresql_custom.sql  <-- New
├── README.md
└── src
    └── main
        ├── docker
            ....
        ├── java
        │   └── org
        │       └── myorg
        │           ├── DatabaseLogHandlerInitializer.java <-- New
        │           ├── DatabaseLogHandler.java            <-- Change
        │           └── GreetingResource.java              <-- Change
        └── resources
            └── application.properties <-- Change        

Build For JVM

mvn clean package

Run

java -jar target/quarkus-app/quarkus-run.jar

Test

curl http://localhost:8080/hello

curl http://localhost:8080/hello/John

curl http://localhost:8080/hello/with-query?name=John

Verify - Table contents

Using database tools, connect to PostgreSQL, inspect the table, and see if there are any records of the logs we just wrote.

enter image description here.

Console Output:

$ java -jar target/quarkus-app/quarkus-run.jar
>>> [DatabaseLogHandlerInitializer] DatabaseLogHandler attached. handler count = 2
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2025-11-13 19:08:46,974 INFO  [io.quarkus] (main) quarkus-demo-database-log-handler 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.29.2) started in 0.913s. Listening on: http://0.0.0.0:8080
2025-11-13 19:08:47,476 INFO  [io.quarkus] (main) Profile prod activated. 
2025-11-13 19:08:47,478 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, jdbc-postgresql, narayana-jta, rest, smallrye-context-propagation, vertx]
2025-11-13 19:09:07,730 INFO  [org.myo.GreetingResource] (executor-thread-1) call hello().
2025-11-13 19:09:07,733 DEBUG [org.myo.GreetingResource] (executor-thread-1) DDDDD - call hello().
2025-11-13 19:09:07,760 INFO  [org.myo.GreetingResource] (executor-thread-1) Received name: John
2025-11-13 19:09:07,763 DEBUG [org.myo.GreetingResource] (executor-thread-1) DDDDD - Received name: John
2025-11-13 19:09:07,783 INFO  [org.myo.GreetingResource] (executor-thread-1) Received query name: John
2025-11-13 19:09:07,785 DEBUG [org.myo.GreetingResource] (executor-thread-1) DDDDD - query name: John

Build For Native

mvn clean package -Dnative

Run

./target/quarkus-demo-database-log-handler-1.0.0-SNAPSHOT-runner

Test

curl http://localhost:8080/hello

curl http://localhost:8080/hello/John-2

curl http://localhost:8080/hello/with-query?name=John-3

Console Output:

$ ./target/quarkus-demo-database-log-handler-1.0.0-SNAPSHOT-runner
>>> [DatabaseLogHandlerInitializer] DatabaseLogHandler attached. handler count = 2
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2025-11-13 19:34:10,414 INFO  [io.quarkus] (main) quarkus-demo-database-log-handler 1.0.0-SNAPSHOT native (powered by Quarkus 3.29.2) started in 0.087s. Listening on: http://0.0.0.0:8080
2025-11-13 19:34:10,469 INFO  [io.quarkus] (main) Profile prod activated. 
2025-11-13 19:34:10,471 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, jdbc-postgresql, narayana-jta, rest, smallrye-context-propagation, vertx]
2025-11-13 19:34:36,330 INFO  [org.myo.GreetingResource] (executor-thread-1) call hello().
2025-11-13 19:34:36,333 DEBUG [org.myo.GreetingResource] (executor-thread-1) DDDDD - call hello().
2025-11-13 19:34:36,366 INFO  [org.myo.GreetingResource] (executor-thread-1) Received name: John-2
2025-11-13 19:34:36,368 DEBUG [org.myo.GreetingResource] (executor-thread-1) DDDDD - Received name: John-2
2025-11-13 19:34:36,399 INFO  [org.myo.GreetingResource] (executor-thread-1) Received query name: John-3
2025-11-13 19:34:36,401 DEBUG [org.myo.GreetingResource] (executor-thread-1) DDDDD - query name: John-3

Verify

Using database tools, connect to PostgreSQL, inspect the table, and see if there are any records of the logs we just wrote.

enter image description here.

Sign up to request clarification or add additional context in comments.

Comments

0

This is a demonstration of proof-of-concept testing.

  • Concept 1: involves using SLF4J + LOGBACK.
  • Concept 2: involves using LOGBACK to output logs to the database.
  • Concept 3: uses PostgreSQL as the database.

Create Quarkus Application

mvn io.quarkus.platform:quarkus-maven-plugin:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=quarkus-demo \
    -DclassName="com.example.GreetingResource" \
    -Dpath="/hello"

Add extensions

Add Support for PostgreSQL and Logback.

cd quarkus-demo
mvn quarkus:add-extension -Dextensions="logging-logback"
mvn quarkus:add-extension -Dextensions="quarkus-jdbc-postgresql"

You can use the following command to search for available extensions.

mvn quarkus:list-extensions

Round 1 - Use Logback's built-in DBAppender

https://logback.qos.ch/manual/appenders.html

As of logback version 1.2.8 DBAppender no longer ships with logback-classic. However, DBAppender for logback-classic is available under the following Maven coordinates:

ch.qos.logback.db:logback-classic-db:1.2.11.1

Round 1 : Fail.

Round 2 - Use ch.qos.logback.db:logback-classic-db:1.2.11.1 DBAppender

The logback version referenced by logback-classic-db is incompatible with the version referenced by quarkus logback.

Round 2 : Fail.

Round 3 - Write your own custom Logback appender.

quarkus-demo/src/main/java/com/example/log
├── AsyncPostgresAppender.java
└── PostgresAppender.java

PostgresAppender.java

package com.example.log;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class PostgresAppender extends AppenderBase<ILoggingEvent> {

    private String url;
    private String user;
    private String password;

    @Override
    protected void append(ILoggingEvent event) {
        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            PreparedStatement stmt = conn.prepareStatement(
                    "INSERT INTO app_log(level, logger, message, ts) VALUES (?, ?, ?, now())"
            );
            stmt.setString(1, event.getLevel().toString());
            stmt.setString(2, event.getLoggerName());
            stmt.setString(3, event.getFormattedMessage());
            stmt.executeUpdate();
        } catch (Exception e) {
            addError("Failed to write log to DB", e);
        }
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

AsyncPostgresAppender.java

package com.example.log;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class AsyncPostgresAppender extends AppenderBase<ILoggingEvent> {

    private String url;
    private String user;
    private String password;
    private int queueSize = 1024;

    private BlockingQueue<ILoggingEvent> queue;
    private Thread worker;
    private volatile boolean running = true;

    @Override
    public void start() {
        queue = new ArrayBlockingQueue<>(queueSize);

        worker = new Thread(() -> {
            while (running || !queue.isEmpty()) {
                try {
                    // Wait Event
                    ILoggingEvent event = queue.take();
                    writeToDB(event);
                } catch (InterruptedException ignored) {
                } catch (Exception e) {
                    addError("Error writing log to DB", e);
                }
            }
        }, "AsyncPostgresAppender-Worker");

        worker.setDaemon(true);
        worker.start();
        super.start();
    }

    @Override
    protected void append(ILoggingEvent event) {
        if (!running) return;
        if (!queue.offer(event)) {
            addError("Queue full, dropping log: " + event.getFormattedMessage());
        }
    }

    private void writeToDB(ILoggingEvent event) {
        try (Connection conn = DriverManager.getConnection(url, user, password);
             PreparedStatement stmt = conn.prepareStatement(
                     "INSERT INTO app_log(level, logger, message, ts) VALUES (?, ?, ?, now())")) {
            stmt.setString(1, event.getLevel().toString());
            stmt.setString(2, event.getLoggerName());
            stmt.setString(3, event.getFormattedMessage());
            stmt.executeUpdate();
        } catch (Exception e) {
            addError("Failed to write log to DB", e);
        }
    }

    @Override
    public void stop() {
        running = false;
        if (worker != null) {
            worker.interrupt();
            try {
                worker.join(5000);
            } catch (InterruptedException ignored) {}
        }
        super.stop();
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setQueueSize(int queueSize) {
        this.queueSize = queueSize;
    }
}

Database

init_logback_my.sql

CREATE TABLE app_log (
    id       BIGSERIAL PRIMARY KEY,
    ts       TIMESTAMPTZ DEFAULT NOW(),   -- now()
    level    VARCHAR(16) NOT NULL,        -- event.getLevel().toString()
    logger   VARCHAR(255) NOT NULL,       -- event.getLoggerName()
    message  TEXT NOT NULL                -- event.getFormattedMessage()
);
CREATE INDEX idx_app_log_ts ON app_log(ts DESC);
CREATE INDEX idx_app_log_level ON app_log(level);

Use Docker to provide the PostgreSQL Server

docker run \
  --name my-postgres-log \
  -e POSTGRES_USER=demouser \
  -e POSTGRES_PASSWORD=Passw0rd! \
  -e POSTGRES_DB=demodb \
  -v $(pwd)/init_logback_my.sql:/docker-entrypoint-initdb.d/init.sql \
  -p 5432:5432 \
  -d \
  postgres:18.0-alpine3.22  

Logback Config File

quarkus-demo/src/main/resources
├── application.properties
└── logback.xml

logback.xml

<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d %-5level %logger{36} - ***>>> %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>app.log</file> 
        <encoder>
            <pattern>%d %-5level %logger{36} - ***>>> %msg%n</pattern>
        </encoder>
        <append>true</append> 
    </appender>


    <appender name="DB" class="com.example.log.PostgresAppender">
        <url>jdbc:postgresql://localhost:5432/demodb</url>
        <user>demouser</user>
        <password>Passw0rd!</password>
    </appender>

<!--
    <appender name="ASYNC_DB" class="com.example.log.AsyncPostgresAppender">
        <url>jdbc:postgresql://localhost:5432/demodb</url>
        <user>demouser</user>
        <password>Passw0rd!</password>
        <queueSize>2048</queueSize>
    </appender>
-->

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="DB"/>

<!--
        <appender-ref ref="ASYNC_DB"/>
-->
    </root>
</configuration>

Appfile

quarkus-demo/src/main/java/com/example
├── GreetingResource.java
└── log
    ├── AsyncPostgresAppender.java
    └── PostgresAppender.java

GreetingResource.java

  • add slf4j Logger and LoggerFactory.
package com.example;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/hello")
public class GreetingResource {
    private static final Logger logger = LoggerFactory.getLogger(GreetingResource.class);

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        logger.info("call hello().");
        return "Hello from Quarkus REST";
    }

    // helloWithParameter - PathParam
    // /hello/John
    @GET
    @Path("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public String helloWithParameter(@PathParam("name") String name) {
        logger.info("Received name: {}", name);
        return "Hello, " + name + "!";
    }

    // /hello/with-query?name=John
    @GET
    @Path("/with-query")
    @Produces(MediaType.TEXT_PLAIN)
    public String helloWithQuery(@QueryParam("name") String name) {
        logger.info("Received query name: {}", name);
        return "Hello, " + name + "!";
    }
}

pom.xml

  • Remove dependencies related to "test".
  • Confirm the following content has been added:
        <dependency>
            <groupId>io.quarkiverse.logging.logback</groupId>
            <artifactId>quarkus-logging-logback</artifactId>
            <version>1.1.2</version>
        </dependency>

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jdbc-postgresql</artifactId>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.14</version>
            <scope>compile</scope>
        </dependency>

full pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>quarkus-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <compiler-plugin.version>3.14.1</compiler-plugin.version>
        <maven.compiler.release>17</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>3.29.2</quarkus.platform.version>
        <skipITs>true</skipITs>
        <surefire-plugin.version>3.5.4</surefire-plugin.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest</artifactId>
        </dependency>

        <dependency>
            <groupId>io.quarkiverse.logging.logback</groupId>
            <artifactId>quarkus-logging-logback</artifactId>
            <version>1.1.2</version>
        </dependency>

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jdbc-postgresql</artifactId>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.14</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>${quarkus.platform.version}</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                            <goal>generate-code</goal>
                            <goal>generate-code-tests</goal>
                            <goal>native-image-agent</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin.version}</version>
                <configuration>
                    <parameters>true</parameters>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <configuration>
                    <argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
                    <systemPropertyVariables>
                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                        <maven.home>${maven.home}</maven.home>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
                    <systemPropertyVariables>
                        <native.image.path>${project.build.directory}/${project.build.finalName}-runner
                        </native.image.path>
                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                        <maven.home>${maven.home}</maven.home>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <profiles>
        <profile>
            <id>native</id>
            <activation>
                <property>
                    <name>native</name>
                </property>
            </activation>
            <properties>
                <quarkus.package.jar.enabled>false</quarkus.package.jar.enabled>
                <skipITs>false</skipITs>
                <quarkus.native.enabled>true</quarkus.native.enabled>
            </properties>
        </profile>
    </profiles>
</project>

Final

quarkus-demo
├── pom.xml             <-- Change
├── README.md
├── init_logback_my.sql <-- New
└── src
    └── main
        ├── docker
        ...
        ├── java
        │   └── com
        │       └── example
        │           ├── GreetingResource.java          <-- Change
        │           └── log
        │               ├── AsyncPostgresAppender.java <-- New
        │               └── PostgresAppender.java      <-- New
        └── resources
            ├── application.properties
            └── logback.xml             <-- New

Build For JVM

mvn clean package

Run

java -jar target/quarkus-app/quarkus-run.jar

Test

curl http://localhost:8080/hello

curl http://localhost:8080/hello/John

curl http://localhost:8080/hello/with-query?name=John

Verify

Using database tools, connect to PostgreSQL, inspect the table, and see if there are any records of the logs we just wrote.

enter image description here.

Build For Native

mvn clean package -Dnative

Run

./target/quarkus-demo-1.0.0-SNAPSHOT-runner

Test

curl http://localhost:8080/hello

curl http://localhost:8080/hello/John

curl http://localhost:8080/hello/with-query?name=John

Console Output:

$ ./target/quarkus-demo-1.0.0-SNAPSHOT-runner
2025-11-12 15:47:43,128 INFO  io.quarkus - ***>>> quarkus-demo 1.0.0-SNAPSHOT native (powered by Quarkus 3.29.2) started in 0.085s. Listening on: http://0.0.0.0:8080
2025-11-12 15:47:43,187 INFO  io.quarkus - ***>>> Profile prod activated. 
2025-11-12 15:47:43,237 INFO  io.quarkus - ***>>> Installed features: [agroal, cdi, jdbc-postgresql, logging-logback, narayana-jta, rest, smallrye-context-propagation, vertx]
2025-11-12 15:47:59,785 INFO  com.example.GreetingResource - ***>>> call hello().
2025-11-12 15:47:59,864 INFO  com.example.GreetingResource - ***>>> Received name: John
2025-11-12 15:47:59,939 INFO  com.example.GreetingResource - ***>>> Received query name: John

Verify

Using database tools, connect to PostgreSQL, inspect the table, and see if there are any records of the logs we just wrote.

enter image description here.

The above is executable. It's not perfect, but it's executable.

There are many more test combinations (Round 4, 5, 6,....), but they all failed.

Only this imperfect but executable example is retained.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.