├── integration-tests ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.properties │ │ │ └── logback.xml │ │ └── java │ │ │ └── io │ │ │ └── quarkiverse │ │ │ └── logging │ │ │ └── logback │ │ │ └── it │ │ │ └── LoggingLogbackResource.java │ └── test │ │ └── java │ │ └── io │ │ └── quarkiverse │ │ └── logging │ │ └── logback │ │ └── it │ │ ├── NativeLoggingLogbackResourceIT.java │ │ └── LoggingLogbackResourceTest.java └── pom.xml ├── docs ├── modules │ └── ROOT │ │ ├── nav.adoc │ │ └── pages │ │ ├── index.adoc │ │ └── config.adoc └── antora.yml ├── impl ├── deployment │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── quarkus-logback-version.txt │ │ │ └── java │ │ │ └── io │ │ │ └── quarkiverse │ │ │ └── logging │ │ │ └── logback │ │ │ └── deployment │ │ │ └── LoggingLogbackProcessor.java │ └── pom.xml ├── runtime │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── quarkus-extension.yaml │ │ │ └── java │ │ │ └── io │ │ │ └── quarkiverse │ │ │ └── logback │ │ │ └── runtime │ │ │ ├── DelayedStart.java │ │ │ ├── events │ │ │ ├── EndSub.java │ │ │ ├── BodySub.java │ │ │ ├── EventSub.java │ │ │ ├── StartSub.java │ │ │ ├── LocatorImpl.java │ │ │ ├── EventSubstitution.java │ │ │ └── AttributesImpl.java │ │ │ ├── PackagingDataSubstitutions.java │ │ │ ├── LoggingEventWrapper.java │ │ │ └── LogbackRecorder.java │ └── pom.xml └── pom.xml ├── .github ├── project.yml ├── dependabot.yml ├── CODEOWNERS └── workflows │ ├── pre-release.yml │ ├── build.yml │ ├── quarkus-snapshot.yaml │ └── release.yml ├── runtime ├── src │ └── main │ │ └── resources │ │ └── META-INF │ │ └── quarkus-extension.yaml └── pom.xml ├── deployment ├── src │ └── test │ │ ├── resources │ │ ├── quarkus-logback.xml │ │ ├── quarkus-logback-expressions.xml │ │ ├── quarkus-logback-build-system.xml │ │ └── quarkus-logback-TimeBasedRollingPolicy.xml │ │ └── java │ │ └── io │ │ └── quarkiverse │ │ └── logging │ │ └── logback │ │ └── test │ │ ├── LoggingLogbackTest.java │ │ ├── LoggingLogbackTimeBasedRollingPolicyTest.java │ │ ├── LoggingLogbackDevModeTest.java │ │ ├── LoggingLogbackExpressionsDevModeTest.java │ │ └── BuildSystemPropertiesTest.java └── pom.xml ├── .gitignore ├── .all-contributorsrc ├── README.md ├── pom.xml └── LICENSE /integration-tests/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Quarkus - Logging Logback] 2 | -------------------------------------------------------------------------------- /impl/deployment/src/main/resources/quarkus-logback-version.txt: -------------------------------------------------------------------------------- 1 | ${logback.version} -------------------------------------------------------------------------------- /.github/project.yml: -------------------------------------------------------------------------------- 1 | release: 2 | current-version: 1.1.2 3 | next-version: 999-SNAPSHOT 4 | 5 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: quarkus-logging-logback 2 | title: Logging Logback 3 | version: dev 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /impl/runtime/src/main/resources/META-INF/quarkus-extension.yaml: -------------------------------------------------------------------------------- 1 | artifact: ${project.groupId}:${project.artifactId}:${project.version} 2 | name: Logging Logback Implementation 3 | metadata: 4 | unlisted: true -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/DelayedStart.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime; 2 | 3 | public interface DelayedStart { 4 | 5 | void doQuarkusDelayedStart(); 6 | } 7 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/io/quarkiverse/logging/logback/it/NativeLoggingLogbackResourceIT.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.it; 2 | 3 | import io.quarkus.test.junit.QuarkusIntegrationTest; 4 | 5 | @QuarkusIntegrationTest 6 | public class NativeLoggingLogbackResourceIT extends LoggingLogbackResourceTest { 7 | } 8 | -------------------------------------------------------------------------------- /runtime/src/main/resources/META-INF/quarkus-extension.yaml: -------------------------------------------------------------------------------- 1 | artifact: ${project.groupId}:${project.artifactId}:${project.version} 2 | name: Logging Logback 3 | description: logback.xml support for logback appenders 4 | metadata: 5 | keywords: 6 | - logging 7 | - logback 8 | categories: 9 | - "logging" 10 | status: "experimental" -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/events/EndSub.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime.events; 2 | 3 | import org.xml.sax.Locator; 4 | 5 | public class EndSub extends EventSub { 6 | public EndSub(String namespaceURI, String localName, String qName, Locator locator) { 7 | super(namespaceURI, localName, qName, locator); 8 | } 9 | 10 | public EndSub() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /deployment/src/test/resources/quarkus-logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | target/tests.log 5 | false 6 | 7 | LOGBACK- %-5level %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /integration-tests/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | target/tests.log 5 | false 6 | 7 | LOGBACK- %-5level %msg %n 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /deployment/src/test/resources/quarkus-logback-expressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | target/tests.log 5 | false 6 | 7 | ${lg.pre}- %-5level %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /deployment/src/test/resources/quarkus-logback-build-system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | target/tests.log 5 | false 6 | 7 | ${project.version}- %-5level %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/events/BodySub.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime.events; 2 | 3 | import org.xml.sax.Locator; 4 | 5 | public class BodySub extends EventSub { 6 | public String text; 7 | 8 | public BodySub(String namespaceURI, String localName, String qName, Locator locator, String text) { 9 | super(namespaceURI, localName, qName, locator); 10 | this.text = text; 11 | } 12 | 13 | public BodySub() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "org.apache.maven.plugins:maven-compiler-plugin" 14 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/PackagingDataSubstitutions.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime; 2 | 3 | import com.oracle.svm.core.annotate.Substitute; 4 | import com.oracle.svm.core.annotate.TargetClass; 5 | 6 | import ch.qos.logback.classic.spi.PackagingDataCalculator; 7 | import ch.qos.logback.classic.spi.StackTraceElementProxy; 8 | 9 | @TargetClass(PackagingDataCalculator.class) 10 | public final class PackagingDataSubstitutions { 11 | 12 | @Substitute 13 | void populateFrames(StackTraceElementProxy[] stepArray) { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/events/EventSub.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime.events; 2 | 3 | import org.xml.sax.Locator; 4 | 5 | public class EventSub { 6 | 7 | public String namespaceURI; 8 | public String localName; 9 | public String qName; 10 | public Locator locator; 11 | 12 | public EventSub(String namespaceURI, String localName, String qName, Locator locator) { 13 | this.namespaceURI = namespaceURI; 14 | this.localName = localName; 15 | this.qName = qName; 16 | this.locator = locator; 17 | } 18 | 19 | public EventSub() { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 5 | 6 | # The '*' pattern is global owners. 7 | 8 | # Order is important. The last matching pattern has the most precedence. 9 | # The folders are ordered as follows: 10 | 11 | # In each subsection folders are ordered first by depth, then alphabetically. 12 | # This should make it easy to add new rules without breaking existing ones. 13 | 14 | * @quarkiverse/quarkiverse-logging-logback 15 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Pre Release 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/project.yml' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | name: pre release 12 | 13 | steps: 14 | - uses: radcortez/project-metadata-action@master 15 | name: retrieve project metadata 16 | id: metadata 17 | with: 18 | github-token: ${{secrets.GITHUB_TOKEN}} 19 | metadata-file-path: '.github/project.yml' 20 | 21 | - name: Validate version 22 | if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT') 23 | run: | 24 | echo '::error::Cannot release a SNAPSHOT version.' 25 | exit 1 -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/events/StartSub.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime.events; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.xml.sax.Locator; 7 | 8 | public class StartSub extends EventSub { 9 | public ArrayList partList; 10 | public AttributesImpl attributes; 11 | 12 | public StartSub(String namespaceURI, String localName, String qName, Locator locator, AttributesImpl attributes, 13 | List partList) { 14 | super(namespaceURI, localName, qName, locator); 15 | this.attributes = attributes; 16 | this.partList = new ArrayList<>(partList); 17 | } 18 | 19 | public StartSub() { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /impl/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.logback 6 | quarkus-logging-logback-parent 7 | 999-SNAPSHOT 8 | 9 | quarkus-logging-logback-impl-parent 10 | 999-SNAPSHOT 11 | pom 12 | Quarkus - Logging Logback Impl- Parent 13 | 14 | deployment 15 | runtime 16 | 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # Eclipse 26 | .project 27 | .classpath 28 | .settings/ 29 | bin/ 30 | 31 | # IntelliJ 32 | .idea 33 | *.ipr 34 | *.iml 35 | *.iws 36 | 37 | # NetBeans 38 | nb-configuration.xml 39 | 40 | # Visual Studio Code 41 | .vscode 42 | .factorypath 43 | 44 | # OSX 45 | .DS_Store 46 | 47 | # Vim 48 | *.swp 49 | *.swo 50 | 51 | # patch 52 | *.orig 53 | *.rej 54 | 55 | # Gradle 56 | .gradle/ 57 | build/ 58 | 59 | # Maven 60 | target/ 61 | pom.xml.tag 62 | pom.xml.releaseBackup 63 | pom.xml.versionsBackup 64 | release.properties -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "stuartwdouglas", 10 | "name": "Stuart Douglas", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/328571?v=4", 12 | "profile": "https://github.com/stuartwdouglas", 13 | "contributions": [ 14 | "code", 15 | "maintenance" 16 | ] 17 | }, 18 | { 19 | "login": "gsmet", 20 | "name": "Guillaume Smet", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/1279749?v=4", 22 | "profile": "https://lesincroyableslivres.fr/", 23 | "contributions": [ 24 | "code" 25 | ] 26 | } 27 | ], 28 | "contributorsPerLine": 7, 29 | "projectName": "quarkus-logging-logback", 30 | "projectOwner": "quarkiverse", 31 | "repoType": "github", 32 | "repoHost": "https://github.com", 33 | "skipCi": true, 34 | "commitConvention": "angular" 35 | } 36 | -------------------------------------------------------------------------------- /integration-tests/src/test/java/io/quarkiverse/logging/logback/it/LoggingLogbackResourceTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.it; 2 | 3 | import static io.restassured.RestAssured.given; 4 | import static org.hamcrest.Matchers.is; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import io.quarkus.test.junit.QuarkusTest; 14 | 15 | @QuarkusTest 16 | public class LoggingLogbackResourceTest { 17 | 18 | @Test 19 | public void testHelloEndpoint() throws IOException { 20 | //test that the app is working properly 21 | given() 22 | .when().get("/logging-logback") 23 | .then() 24 | .statusCode(200) 25 | .body(is("Hello logging-logback")); 26 | 27 | //now test that logging config has been picked up 28 | String line = Files.readAllLines(Paths.get("target/tests.log")).get(0); 29 | Assertions.assertTrue(line.startsWith("LOGBACK")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /deployment/src/test/java/io/quarkiverse/logging/logback/test/LoggingLogbackTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.test; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Paths; 6 | import java.util.List; 7 | 8 | import org.jboss.shrinkwrap.api.ShrinkWrap; 9 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.RegisterExtension; 13 | 14 | import io.quarkus.test.QuarkusUnitTest; 15 | 16 | public class LoggingLogbackTest { 17 | 18 | // Start unit test with your extension loaded 19 | @RegisterExtension 20 | static final QuarkusUnitTest unitTest = new QuarkusUnitTest() 21 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 22 | .addAsResource("quarkus-logback.xml", "logback.xml")); 23 | 24 | @Test 25 | public void testLogFile() throws IOException { 26 | List strings = Files.readAllLines(Paths.get("target/tests.log")); 27 | Assertions.assertFalse(strings.isEmpty()); 28 | for (String line : strings) { 29 | Assertions.assertTrue(line.startsWith("LOGBACK")); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths-ignore: 8 | - '.gitignore' 9 | - 'CODEOWNERS' 10 | - 'LICENSE' 11 | - '*.md' 12 | - '*.adoc' 13 | - '*.txt' 14 | - '.all-contributorsrc' 15 | pull_request: 16 | paths-ignore: 17 | - '.gitignore' 18 | - 'CODEOWNERS' 19 | - 'LICENSE' 20 | - '*.md' 21 | - '*.adoc' 22 | - '*.txt' 23 | - '.all-contributorsrc' 24 | 25 | jobs: 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | 33 | - name: Set up JDK 17 34 | uses: actions/setup-java@v1 35 | with: 36 | java-version: 17 37 | 38 | - name: Get Date 39 | id: get-date 40 | run: | 41 | echo "::set-output name=date::$(/bin/date -u "+%Y-%m")" 42 | shell: bash 43 | - name: Cache Maven Repository 44 | id: cache-maven 45 | uses: actions/cache@v2 46 | with: 47 | path: ~/.m2/repository 48 | # refresh cache every month to avoid unlimited growth 49 | key: maven-repo-${{ runner.os }}-${{ steps.get-date.outputs.date }} 50 | 51 | - name: Build with Maven 52 | run: mvn -B formatter:validate verify --file pom.xml 53 | 54 | -------------------------------------------------------------------------------- /integration-tests/src/main/java/io/quarkiverse/logging/logback/it/LoggingLogbackResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.logback.it; 18 | 19 | import jakarta.enterprise.context.ApplicationScoped; 20 | import jakarta.ws.rs.GET; 21 | import jakarta.ws.rs.Path; 22 | 23 | @Path("/logging-logback") 24 | @ApplicationScoped 25 | public class LoggingLogbackResource { 26 | 27 | @GET 28 | public String hello() { 29 | return "Hello logging-logback"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /deployment/src/test/java/io/quarkiverse/logging/logback/test/LoggingLogbackTimeBasedRollingPolicyTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.test; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Paths; 6 | import java.util.List; 7 | 8 | import org.jboss.shrinkwrap.api.ShrinkWrap; 9 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.RegisterExtension; 13 | 14 | import io.quarkus.test.QuarkusUnitTest; 15 | 16 | public class LoggingLogbackTimeBasedRollingPolicyTest { 17 | 18 | @RegisterExtension 19 | static final QuarkusUnitTest unitTest = new QuarkusUnitTest() 20 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 21 | .addAsResource("quarkus-logback-TimeBasedRollingPolicy.xml", "logback.xml")); 22 | 23 | @Test 24 | public void testLogFile() throws IOException { 25 | List strings = Files.readAllLines(Paths.get("target/tests-TimeBasedRollingPolicy.log")); 26 | Assertions.assertFalse(strings.isEmpty()); 27 | for (String line : strings) { 28 | Assertions.assertTrue(line.startsWith("LOGBACK TimeBasedRollingPolicy")); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /deployment/src/test/resources/quarkus-logback-TimeBasedRollingPolicy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [%d{dd-MM-yyyy HH:mm:ss:SSS}] [%.20thread] [%level] [%logger{0}] %msg%n %xException{short} 5 | 6 | 7 | 8 | 9 | target/tests-TimeBasedRollingPolicy.log 10 | 11 | target/tests-TimeBasedRollingPolicy-%d{yyyy-MM-dd}.%i.log 12 | 13 | 14 | 10MB 15 | 16 | 17 | 5 18 | 19 | 20 | TRACE 21 | 22 | 23 | LOGBACK TimeBasedRollingPolicy [%level] [%d{dd-MM-yyyy HH:mm:ss:SSS}] %msg%n 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/events/LocatorImpl.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime.events; 2 | 3 | import org.xml.sax.Locator; 4 | 5 | import io.quarkus.runtime.annotations.RecordableConstructor; 6 | 7 | public class LocatorImpl implements Locator { 8 | 9 | public final String publicId; 10 | public final String systemId; 11 | public final int lineNumber; 12 | public final int columnNumber; 13 | 14 | @RecordableConstructor 15 | public LocatorImpl(String publicId, String systemId, int lineNumber, int columnNumber) { 16 | this.publicId = publicId; 17 | this.systemId = systemId; 18 | this.lineNumber = lineNumber; 19 | this.columnNumber = columnNumber; 20 | } 21 | 22 | public LocatorImpl(Locator locator) { 23 | this.publicId = locator.getPublicId(); 24 | this.systemId = locator.getSystemId(); 25 | this.lineNumber = locator.getLineNumber(); 26 | this.columnNumber = locator.getColumnNumber(); 27 | } 28 | 29 | @Override 30 | public String getPublicId() { 31 | return publicId; 32 | } 33 | 34 | @Override 35 | public String getSystemId() { 36 | return systemId; 37 | } 38 | 39 | @Override 40 | public int getLineNumber() { 41 | return lineNumber; 42 | } 43 | 44 | @Override 45 | public int getColumnNumber() { 46 | return columnNumber; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/quarkus-snapshot.yaml: -------------------------------------------------------------------------------- 1 | name: "Quarkus ecosystem CI" 2 | on: 3 | workflow_dispatch: 4 | watch: 5 | types: [started] 6 | 7 | # For this CI to work, ECOSYSTEM_CI_TOKEN needs to contain a GitHub with rights to close the Quarkus issue that the user/bot has opened, 8 | # while 'ECOSYSTEM_CI_REPO_PATH' needs to be set to the corresponding path in the 'quarkusio/quarkus-ecosystem-ci' repository 9 | 10 | env: 11 | ECOSYSTEM_CI_REPO: quarkusio/quarkus-ecosystem-ci 12 | ECOSYSTEM_CI_REPO_FILE: context.yaml 13 | JAVA_VERSION: 17 14 | 15 | ######################### 16 | # Repo specific setting # 17 | ######################### 18 | 19 | ECOSYSTEM_CI_REPO_PATH: quarkiverse-logging-logback 20 | 21 | jobs: 22 | build: 23 | name: "Build against latest Quarkus snapshot" 24 | runs-on: ubuntu-latest 25 | # Allow to manually launch the ecosystem CI in addition to the bots 26 | if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == '' 27 | 28 | steps: 29 | - name: Set up Java 30 | uses: actions/setup-java@v1 31 | with: 32 | java-version: ${{ env.JAVA_VERSION }} 33 | 34 | - name: Checkout repo 35 | uses: actions/checkout@v2 36 | with: 37 | path: current-repo 38 | 39 | - name: Checkout Ecosystem 40 | uses: actions/checkout@v2 41 | with: 42 | repository: ${{ env.ECOSYSTEM_CI_REPO }} 43 | path: ecosystem-ci 44 | 45 | - name: Setup and Run Tests 46 | run: ./ecosystem-ci/setup-and-test 47 | env: 48 | ECOSYSTEM_CI_TOKEN: ${{ secrets.ECOSYSTEM_CI_TOKEN }} 49 | -------------------------------------------------------------------------------- /deployment/src/test/java/io/quarkiverse/logging/logback/test/LoggingLogbackDevModeTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.test; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Paths; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import io.quarkus.test.QuarkusDevModeTest; 14 | import io.restassured.RestAssured; 15 | 16 | public class LoggingLogbackDevModeTest { 17 | 18 | // Start hot reload (DevMode) test with your extension loaded 19 | @RegisterExtension 20 | static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() 21 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 22 | .addAsResource("quarkus-logback.xml", "logback.xml")); 23 | 24 | @Test 25 | public void testLogbackXmlChanges() throws IOException { 26 | for (String line : Files.readAllLines(Paths.get("target/tests.log"))) { 27 | Assertions.assertTrue(line.startsWith("LOGBACK")); 28 | } 29 | devModeTest.modifyResourceFile("logback.xml", s -> s.replaceAll("LOGBACK", "MODIFIED")); 30 | RestAssured.get(); //trigger hot reload 31 | boolean foundMod = false; 32 | for (String line : Files.readAllLines(Paths.get("target/tests.log"))) { 33 | if (line.startsWith("MODIFIED")) { 34 | foundMod = true; 35 | break; 36 | } 37 | } 38 | Assertions.assertTrue(foundMod, "Log changes did not take effect"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /deployment/src/test/java/io/quarkiverse/logging/logback/test/LoggingLogbackExpressionsDevModeTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.test; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Paths; 6 | 7 | import org.jboss.shrinkwrap.api.ShrinkWrap; 8 | import org.jboss.shrinkwrap.api.asset.StringAsset; 9 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 10 | import org.junit.jupiter.api.Assertions; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.RegisterExtension; 13 | 14 | import io.quarkus.test.QuarkusDevModeTest; 15 | import io.restassured.RestAssured; 16 | 17 | public class LoggingLogbackExpressionsDevModeTest { 18 | 19 | // Start hot reload (DevMode) test with your extension loaded 20 | @RegisterExtension 21 | static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() 22 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 23 | .addAsResource(new StringAsset("lg.pre=EXP"), "application.properties") 24 | .addAsResource("quarkus-logback-expressions.xml", "logback.xml")); 25 | 26 | @Test 27 | public void testLogbackXmlChanges() throws IOException { 28 | for (String line : Files.readAllLines(Paths.get("target/tests.log"))) { 29 | Assertions.assertTrue(line.startsWith("EXP")); 30 | } 31 | devModeTest.modifyResourceFile("application.properties", s -> s.replaceAll("EXP", "MODIFIED")); 32 | RestAssured.get(); //trigger hot reload 33 | boolean foundMod = false; 34 | for (String line : Files.readAllLines(Paths.get("target/tests.log"))) { 35 | if (line.startsWith("MODIFIED")) { 36 | foundMod = true; 37 | break; 38 | } 39 | } 40 | Assertions.assertTrue(foundMod, "Log changes did not take effect"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /impl/deployment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.logback 6 | quarkus-logging-logback-impl-parent 7 | 999-SNAPSHOT 8 | 9 | quarkus-logging-logback-impl-deployment 10 | Quarkus - Logging Logback Impl - Deployment 11 | The deployment module of the implementation part of the Logback extension 12 | 13 | 14 | io.quarkus 15 | quarkus-arc-deployment 16 | 17 | 18 | io.smallrye.common 19 | smallrye-common-version 20 | 21 | 22 | io.quarkiverse.logging.logback 23 | quarkus-logging-logback-impl 24 | 25 | 26 | io.smallrye.common 27 | smallrye-common-version 28 | 29 | 30 | 31 | 32 | 33 | src/main/resources 34 | true 35 | 36 | 37 | 38 | 39 | maven-compiler-plugin 40 | 41 | 42 | 43 | io.quarkus 44 | quarkus-extension-processor 45 | ${quarkus.version} 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quarkus - Logging Logback 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | [![Version](https://img.shields.io/maven-central/v/io.quarkiverse.logging.logback/quarkus-logging-logback?logo=apache-maven&style=flat-square)](https://search.maven.org/artifact/io.quarkiverse.logging.logback/quarkus-logging-logback) 7 | 8 | This extension allows you to use `logback.xml` to configure logback appenders and control log levels. 9 | 10 | Documentation is at https://quarkiverse.github.io/quarkiverse-docs/quarkus-logging-logback/dev/index.html 11 | ## Contributors ✨ 12 | 13 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Stuart Douglas
Stuart Douglas

💻 🚧
Guillaume Smet
Guillaume Smet

💻
26 | 27 | 28 | 29 | 30 | 31 | 32 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 33 | -------------------------------------------------------------------------------- /deployment/src/test/java/io/quarkiverse/logging/logback/test/BuildSystemPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.test; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | import java.util.List; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import org.jboss.shrinkwrap.api.ShrinkWrap; 12 | import org.jboss.shrinkwrap.api.spec.JavaArchive; 13 | import org.junit.jupiter.api.Assertions; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.extension.RegisterExtension; 16 | 17 | import io.quarkus.test.QuarkusUnitTest; 18 | 19 | public class BuildSystemPropertiesTest { 20 | 21 | // Start unit test with your extension loaded 22 | @RegisterExtension 23 | static final QuarkusUnitTest unitTest = new QuarkusUnitTest() 24 | .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) 25 | .addAsResource("quarkus-logback-build-system.xml", "logback.xml")); 26 | 27 | @Test 28 | public void testProjectVersion() throws IOException { 29 | //bit of a hack, grab the current version from the pom 30 | File f = new File("."); 31 | String version = null; 32 | for (;;) { 33 | File pom = new File(f, "pom.xml"); 34 | if (pom.exists()) { 35 | Matcher m = Pattern.compile("(.*?)").matcher(Files.readString(pom.toPath())); 36 | if (!m.find()) { 37 | throw new RuntimeException("Could not resolve project version"); 38 | } 39 | version = m.group(1); 40 | break; 41 | } 42 | f = f.getParentFile(); 43 | if (f == null) { 44 | throw new RuntimeException("Could not resolve project pom"); 45 | } 46 | } 47 | List strings = Files.readAllLines(Paths.get("target/tests.log")); 48 | Assertions.assertFalse(strings.isEmpty()); 49 | for (String line : strings) { 50 | Assertions.assertTrue(line.startsWith(version)); 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /runtime/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.logback 6 | quarkus-logging-logback-parent 7 | 999-SNAPSHOT 8 | 9 | quarkus-logging-logback 10 | Quarkus - Logging Logback - Runtime 11 | Logback support for Quarkus 12 | 13 | 14 | io.quarkus 15 | quarkus-core 16 | 17 | 18 | io.quarkiverse.logging.logback 19 | quarkus-logging-logback-impl 20 | true 21 | 22 | 23 | 24 | 25 | 26 | io.quarkus 27 | quarkus-extension-maven-plugin 28 | ${quarkus.version} 29 | 30 | 31 | compile 32 | 33 | extension-descriptor 34 | 35 | 36 | ${project.groupId}:${project.artifactId}-deployment:${project.version} 37 | 38 | 39 | 40 | 41 | 42 | maven-compiler-plugin 43 | 44 | 45 | 46 | io.quarkus 47 | quarkus-extension-processor 48 | ${quarkus.version} 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /deployment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.logback 6 | quarkus-logging-logback-parent 7 | 999-SNAPSHOT 8 | 9 | quarkus-logging-logback-deployment 10 | Quarkus - Logging Logback - Deployment 11 | Logback support for Quarkus, deployment module 12 | 13 | 14 | io.quarkiverse.logging.logback 15 | quarkus-logging-logback 16 | 17 | 18 | io.quarkus 19 | quarkus-core-deployment 20 | 21 | 22 | io.quarkiverse.logging.logback 23 | quarkus-logging-logback-impl-deployment 24 | true 25 | 26 | 27 | io.quarkus 28 | quarkus-junit5-internal 29 | test 30 | 31 | 32 | io.quarkus 33 | quarkus-vertx-http-deployment 34 | test 35 | 36 | 37 | io.rest-assured 38 | rest-assured 39 | test 40 | 41 | 42 | 43 | 44 | 45 | maven-compiler-plugin 46 | 47 | 48 | 49 | io.quarkus 50 | quarkus-extension-processor 51 | ${quarkus.version} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /impl/runtime/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.logback 6 | quarkus-logging-logback-impl-parent 7 | 999-SNAPSHOT 8 | 9 | quarkus-logging-logback-impl 10 | Quarkus - Logging Logback Impl - Runtime 11 | The implementation part of the Logback extension 12 | 13 | 14 | io.quarkus 15 | quarkus-arc 16 | 17 | 18 | org.graalvm.sdk 19 | graal-sdk 20 | provided 21 | 22 | 23 | ch.qos.logback 24 | logback-classic 25 | 26 | 27 | 28 | 29 | 30 | io.quarkus 31 | quarkus-extension-maven-plugin 32 | ${quarkus.version} 33 | 34 | 35 | compile 36 | 37 | extension-descriptor 38 | 39 | 40 | ${project.groupId}:${project.artifactId}-deployment:${project.version} 41 | 42 | io.quarkus:quarkus-core 43 | 44 | 45 | 46 | 47 | 48 | 49 | maven-compiler-plugin 50 | 51 | 52 | 53 | io.quarkus 54 | quarkus-extension-processor 55 | ${quarkus.version} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = Quarkus - Logging Logback 2 | :extension-status: preview 3 | 4 | This extension allows you to use Logback to configure Quarkus logging via `logback.xml`. This extension parses the config 5 | file at build time, and then bridges the resulting config with jboss-logmanager. 6 | 7 | == Installation 8 | 9 | If you want to use this extension, you need to add the `io.quarkiverse.logging.logback:quarkus-logging-logback` extension first. 10 | In your `pom.xml` file, add: 11 | 12 | [source,xml] 13 | ---- 14 | 15 | io.quarkiverse.logging.logback 16 | quarkus-logging-logback 17 | 18 | ---- 19 | 20 | == Technical Details 21 | 22 | This extension does not actually replace jboss-logmanager as the default logging engine, instead it bridges to the two, 23 | to allow them to co-exist. JBoss Logging is still the core logmanager for Quarkus, however any logging events it receives 24 | are also sent to the logback and will be sent through any logback appenders. 25 | 26 | Log levels for loggers are also parsed, and are used to set the default log level for the corresponding jboss-logmanager 27 | loggers. These defaults can still be overriden however, so if you set a log level in `application.properties` it will take 28 | precidence. 29 | 30 | If a console appender is detected in `logback.xml` then the standard Quarkus console logging is disabled, to prevent double 31 | logging of messages, otherwise it is left enabled and will work as normal. 32 | 33 | == Common Problems 34 | 35 | === Duplicate SLF4J bindings 36 | 37 | You may see an error similar to below: 38 | 39 | ``` 40 | SLF4J: Class path contains multiple SLF4J bindings. 41 | SLF4J: Found binding in [jar:file:/home/stuart/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] 42 | SLF4J: Found binding in [jar:file:/home/stuart/.m2/repository/org/jboss/slf4j/slf4j-jboss-logmanager/1.1.0.Final/slf4j-jboss-logmanager-1.1.0.Final.jar!/org/slf4j/impl/StaticLoggerBinder.class] 43 | SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. 44 | SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder] 45 | 46 | ``` 47 | 48 | To get around this you need to exclude the `ch.qos.logback:logback-classic` artifact from your project. Quarkus will automatically 49 | bring in a modified version at runtime that has had the SLF4J binding removed. Run `mvn dependency:tree` to figure out what is 50 | bringing in the artifact, and then exclude it from the `pom.xml`. -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Release 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | paths: 7 | - '.github/project.yml' 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | defaults: 14 | run: 15 | shell: bash 16 | 17 | jobs: 18 | release: 19 | runs-on: ubuntu-latest 20 | name: release 21 | if: ${{github.event.pull_request.merged == true}} 22 | 23 | steps: 24 | - uses: radcortez/project-metadata-action@main 25 | name: Retrieve project metadata 26 | id: metadata 27 | with: 28 | github-token: ${{secrets.GITHUB_TOKEN}} 29 | metadata-file-path: '.github/project.yml' 30 | 31 | - uses: actions/checkout@v4 32 | 33 | - name: Set up JDK 11 34 | uses: actions/setup-java@v4 35 | with: 36 | distribution: temurin 37 | java-version: 17 38 | cache: 'maven' 39 | server-id: ossrh 40 | server-username: MAVEN_USERNAME 41 | server-password: MAVEN_PASSWORD 42 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} 43 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 44 | 45 | - name: Configure Git author 46 | run: | 47 | git config --local user.email "action@github.com" 48 | git config --local user.name "GitHub Action" 49 | 50 | - name: Update latest release version in docs 51 | run: | 52 | mvn -B -ntp -pl docs -am generate-resources -Denforcer.skip -Dformatter.skip -Dimpsort.skip 53 | if ! git diff --quiet docs/modules/ROOT/pages/includes/attributes.adoc; then 54 | git add docs/modules/ROOT/pages/includes/attributes.adoc 55 | git commit -m "Update the latest release version ${{steps.metadata.outputs.current-version}} in documentation" 56 | fi 57 | 58 | - name: Maven release ${{steps.metadata.outputs.current-version}} 59 | run: | 60 | mvn -B release:prepare -Prelease -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} 61 | mvn -B release:perform -Darguments=-DperformRelease -DperformRelease -Prelease 62 | env: 63 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 64 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 65 | MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 66 | 67 | - name: Push changes to ${{github.base_ref}} branch 68 | run: | 69 | git push 70 | git push origin ${{steps.metadata.outputs.current-version}} 71 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/events/EventSubstitution.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime.events; 2 | 3 | import java.lang.reflect.Constructor; 4 | 5 | import org.xml.sax.Attributes; 6 | import org.xml.sax.Locator; 7 | 8 | import ch.qos.logback.core.joran.event.BodyEvent; 9 | import ch.qos.logback.core.joran.event.EndEvent; 10 | import ch.qos.logback.core.joran.event.SaxEvent; 11 | import ch.qos.logback.core.joran.event.StartEvent; 12 | import ch.qos.logback.core.joran.spi.ElementPath; 13 | import io.quarkus.runtime.ObjectSubstitution; 14 | 15 | public class EventSubstitution implements ObjectSubstitution { 16 | 17 | static final Constructor START; 18 | static final Constructor BODY; 19 | static final Constructor END; 20 | 21 | static { 22 | try { 23 | START = StartEvent.class.getDeclaredConstructor(ElementPath.class, String.class, String.class, String.class, 24 | Attributes.class, Locator.class); 25 | BODY = BodyEvent.class.getDeclaredConstructor(String.class, Locator.class); 26 | END = EndEvent.class.getDeclaredConstructor(String.class, String.class, String.class, Locator.class); 27 | START.setAccessible(true); 28 | BODY.setAccessible(true); 29 | END.setAccessible(true); 30 | } catch (NoSuchMethodException e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | 35 | @Override 36 | public EventSub serialize(SaxEvent obj) { 37 | if (obj instanceof StartEvent) { 38 | StartEvent s = (StartEvent) obj; 39 | return new StartSub(obj.namespaceURI, obj.localName, obj.qName, new LocatorImpl(obj.locator), 40 | new AttributesImpl(s.attributes), s.elementPath.getCopyOfPartList()); 41 | } else if (obj instanceof BodyEvent) { 42 | return new BodySub(obj.namespaceURI, obj.localName, obj.qName, new LocatorImpl(obj.locator), 43 | ((BodyEvent) obj).getText()); 44 | } else if (obj instanceof EndEvent) { 45 | return new EndSub(obj.namespaceURI, obj.localName, obj.qName, new LocatorImpl(obj.locator)); 46 | } 47 | throw new RuntimeException("Unknown event type"); 48 | } 49 | 50 | @Override 51 | public SaxEvent deserialize(EventSub obj) { 52 | try { 53 | if (obj instanceof StartSub) { 54 | StartSub s = (StartSub) obj; 55 | return START.newInstance(new ElementPath(s.partList), s.namespaceURI, s.localName, s.qName, s.attributes, 56 | s.locator); 57 | } else if (obj instanceof BodySub) { 58 | return BODY.newInstance(((BodySub) obj).text, obj.locator); 59 | } else if (obj instanceof EndSub) { 60 | EndSub e = (EndSub) obj; 61 | return END.newInstance(e.namespaceURI, e.localName, e.qName, e.locator); 62 | } 63 | throw new RuntimeException("Unknown event type"); 64 | } catch (Exception e) { 65 | throw new RuntimeException(e); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.logback 6 | quarkus-logging-logback-parent 7 | 999-SNAPSHOT 8 | 9 | quarkus-logging-logback-integration-tests 10 | Quarkus - Logging Logback - Integration Tests 11 | 12 | 13 | io.quarkus 14 | quarkus-resteasy 15 | 16 | 17 | io.quarkiverse.logging.logback 18 | quarkus-logging-logback 19 | ${project.version} 20 | 21 | 22 | io.quarkus 23 | quarkus-junit5 24 | test 25 | 26 | 27 | io.rest-assured 28 | rest-assured 29 | test 30 | 31 | 32 | 33 | 34 | 35 | io.quarkus 36 | quarkus-maven-plugin 37 | 38 | 39 | 40 | build 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | native-image 50 | 51 | 52 | native 53 | 54 | 55 | 56 | 57 | 58 | maven-surefire-plugin 59 | 60 | ${native.surefire.skip} 61 | 62 | 63 | 64 | maven-failsafe-plugin 65 | 66 | 67 | 68 | integration-test 69 | verify 70 | 71 | 72 | 73 | ${project.build.directory}/${project.build.finalName}-runner 74 | org.jboss.logmanager.LogManager 75 | ${maven.home} 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | native 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/events/AttributesImpl.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime.events; 2 | 3 | import java.util.Objects; 4 | 5 | import org.xml.sax.Attributes; 6 | 7 | import io.quarkus.runtime.annotations.RecordableConstructor; 8 | 9 | public class AttributesImpl implements Attributes { 10 | 11 | public Attribute[] attributes; 12 | 13 | public AttributesImpl(Attributes at) { 14 | attributes = new Attribute[at.getLength()]; 15 | for (int i = 0; i < at.getLength(); ++i) { 16 | attributes[i] = new Attribute(at.getLocalName(i), at.getValue(i), at.getType(i), at.getURI(i), at.getQName(i)); 17 | } 18 | } 19 | 20 | public AttributesImpl() { 21 | 22 | } 23 | 24 | @Override 25 | public int getLength() { 26 | return attributes.length; 27 | } 28 | 29 | @Override 30 | public String getURI(int index) { 31 | return attributes[index].uri; 32 | } 33 | 34 | @Override 35 | public String getLocalName(int index) { 36 | return attributes[index].localName; 37 | } 38 | 39 | @Override 40 | public String getQName(int index) { 41 | return attributes[index].qName; 42 | } 43 | 44 | @Override 45 | public String getType(int index) { 46 | return attributes[index].type; 47 | } 48 | 49 | @Override 50 | public String getValue(int index) { 51 | return attributes[index].value; 52 | } 53 | 54 | @Override 55 | public int getIndex(String uri, String localName) { 56 | for (int i = 0; i < attributes.length; ++i) { 57 | Attribute at = attributes[i]; 58 | if (Objects.equals(uri, at.uri) && Objects.equals(localName, at.localName)) { 59 | return i; 60 | } 61 | } 62 | return -1; 63 | } 64 | 65 | @Override 66 | public int getIndex(String qName) { 67 | for (int i = 0; i < attributes.length; ++i) { 68 | Attribute at = attributes[i]; 69 | if (Objects.equals(qName, at.qName)) { 70 | return i; 71 | } 72 | } 73 | return -1; 74 | } 75 | 76 | @Override 77 | public String getType(String uri, String localName) { 78 | int index = getIndex(uri, localName); 79 | if (index == -1) { 80 | return null; 81 | } 82 | return attributes[index].type; 83 | } 84 | 85 | @Override 86 | public String getType(String qName) { 87 | int index = getIndex(qName); 88 | if (index == -1) { 89 | return null; 90 | } 91 | return attributes[index].type; 92 | } 93 | 94 | @Override 95 | public String getValue(String uri, String localName) { 96 | int index = getIndex(uri, localName); 97 | if (index == -1) { 98 | return null; 99 | } 100 | return attributes[index].value; 101 | } 102 | 103 | @Override 104 | public String getValue(String qName) { 105 | int index = getIndex(qName); 106 | if (index == -1) { 107 | return null; 108 | } 109 | return attributes[index].value; 110 | } 111 | 112 | public static class Attribute { 113 | public String localName; 114 | public String value; 115 | public String type; 116 | public String uri; 117 | public String qName; 118 | 119 | @RecordableConstructor 120 | public Attribute(String localName, String value, String type, String uri, String qName) { 121 | this.localName = localName; 122 | this.value = value; 123 | this.type = type; 124 | this.uri = uri; 125 | this.qName = qName; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse 6 | quarkiverse-parent 7 | 18 8 | 9 | io.quarkiverse.logging.logback 10 | quarkus-logging-logback-parent 11 | 999-SNAPSHOT 12 | pom 13 | Quarkus - Logging Logback - Parent 14 | 15 | impl 16 | runtime 17 | deployment 18 | 19 | 20 | scm:git:git@github.com:quarkiverse/quarkus-logging-logback.git 21 | scm:git:git@github.com:quarkiverse/quarkus-logging-logback.git 22 | https://github.com/quarkiverse/quarkus-logging-logback 23 | HEAD 24 | 25 | 26 | 3.11.0 27 | true 28 | 11 29 | 11 30 | UTF-8 31 | UTF-8 32 | 3.6.6 33 | 1.4.12 34 | 35 | 36 | 37 | 38 | io.quarkus 39 | quarkus-bom 40 | ${quarkus.version} 41 | pom 42 | import 43 | 44 | 45 | ch.qos.logback 46 | logback-classic 47 | ${logback.version} 48 | 49 | 50 | io.quarkiverse.logging.logback 51 | quarkus-logging-logback 52 | ${project.version} 53 | 54 | 55 | io.quarkiverse.logging.logback 56 | quarkus-logging-logback-deployment 57 | ${project.version} 58 | 59 | 60 | io.quarkiverse.logging.logback 61 | quarkus-logging-logback-impl 62 | ${project.version} 63 | 64 | 65 | io.quarkiverse.logging.logback 66 | quarkus-logging-logback-impl-deployment 67 | ${project.version} 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | io.quarkus 76 | quarkus-maven-plugin 77 | ${quarkus.version} 78 | 79 | 80 | maven-compiler-plugin 81 | ${compiler-plugin.version} 82 | 83 | 84 | 85 | 86 | 87 | src/main/resources 88 | true 89 | 90 | 91 | 92 | 93 | 94 | it 95 | 96 | 97 | performRelease 98 | !true 99 | 100 | 101 | 102 | integration-tests 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/config.adoc: -------------------------------------------------------------------------------- 1 | // 2 | // This content is generated using mvn compile and copied manually to here 3 | // 4 | [.configuration-legend] 5 | icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime 6 | [.configuration-reference.searchable, cols="80,.^10,.^10"] 7 | |=== 8 | 9 | h|[[quarkus-freemarker_configuration]]link:#quarkus-freemarker_configuration[Configuration property] 10 | 11 | h|Type 12 | h|Default 13 | 14 | a|icon:lock[title=Fixed at build time] [[quarkus-freemarker_quarkus.freemarker.resource-paths]]`link:#quarkus-freemarker_quarkus.freemarker.resource-paths[quarkus.freemarker.resource-paths]` 15 | 16 | [.description] 17 | -- 18 | Comma-separated list of absolute resource paths to scan recursively for templates. All tree folder from 'resource-paths' will be added as a resource. Unprefixed locations or locations starting with classpath will be processed in the same way. 19 | --|list of string 20 | |`freemarker/templates` 21 | 22 | 23 | a| [[quarkus-freemarker_quarkus.freemarker.file-paths]]`link:#quarkus-freemarker_quarkus.freemarker.file-paths[quarkus.freemarker.file-paths]` 24 | 25 | [.description] 26 | -- 27 | Comma-separated of file system paths where freemarker templates are located 28 | --|list of string 29 | | 30 | 31 | 32 | a| [[quarkus-freemarker_quarkus.freemarker.default-encoding]]`link:#quarkus-freemarker_quarkus.freemarker.default-encoding[quarkus.freemarker.default-encoding]` 33 | 34 | [.description] 35 | -- 36 | Set the preferred charset template files are stored in. 37 | --|string 38 | | 39 | 40 | 41 | a| [[quarkus-freemarker_quarkus.freemarker.template-exception-handler]]`link:#quarkus-freemarker_quarkus.freemarker.template-exception-handler[quarkus.freemarker.template-exception-handler]` 42 | 43 | [.description] 44 | -- 45 | Sets how errors will appear. rethrow, debug, html-debug, ignore. 46 | --|string 47 | | 48 | 49 | 50 | a| [[quarkus-freemarker_quarkus.freemarker.log-template-exceptions]]`link:#quarkus-freemarker_quarkus.freemarker.log-template-exceptions[quarkus.freemarker.log-template-exceptions]` 51 | 52 | [.description] 53 | -- 54 | If false, don't log exceptions inside FreeMarker that it will be thrown at you anyway. 55 | --|boolean 56 | | 57 | 58 | 59 | a| [[quarkus-freemarker_quarkus.freemarker.wrap-unchecked-exceptions]]`link:#quarkus-freemarker_quarkus.freemarker.wrap-unchecked-exceptions[quarkus.freemarker.wrap-unchecked-exceptions]` 60 | 61 | [.description] 62 | -- 63 | Wrap unchecked exceptions thrown during template processing into TemplateException-s. 64 | --|boolean 65 | | 66 | 67 | 68 | a| [[quarkus-freemarker_quarkus.freemarker.fallback-on-null-loop-variable]]`link:#quarkus-freemarker_quarkus.freemarker.fallback-on-null-loop-variable[quarkus.freemarker.fallback-on-null-loop-variable]` 69 | 70 | [.description] 71 | -- 72 | If false, do not fall back to higher scopes when reading a null loop variable. 73 | --|boolean 74 | | 75 | 76 | 77 | a| [[quarkus-freemarker_quarkus.freemarker.boolean-format]]`link:#quarkus-freemarker_quarkus.freemarker.boolean-format[quarkus.freemarker.boolean-format]` 78 | 79 | [.description] 80 | -- 81 | The string value for the boolean `true` and `false` values, usually intended for human consumption (not for a computer language), separated with comma. 82 | --|string 83 | | 84 | 85 | 86 | a| [[quarkus-freemarker_quarkus.freemarker.number-format]]`link:#quarkus-freemarker_quarkus.freemarker.number-format[quarkus.freemarker.number-format]` 87 | 88 | [.description] 89 | -- 90 | Sets the default number format used to convert numbers to strings. 91 | --|string 92 | | 93 | 94 | 95 | a| [[quarkus-freemarker_quarkus.freemarker.object-wrapper-expose-fields]]`link:#quarkus-freemarker_quarkus.freemarker.object-wrapper-expose-fields[quarkus.freemarker.object-wrapper-expose-fields]` 96 | 97 | [.description] 98 | -- 99 | If true, the object wrapper will be configured to expose fields. 100 | --|boolean 101 | | 102 | 103 | 104 | a|icon:lock[title=Fixed at build time] [[quarkus-freemarker_quarkus.freemarker.directive-directive]]`link:#quarkus-freemarker_quarkus.freemarker.directive-directive[quarkus.freemarker.directive]` 105 | 106 | [.description] 107 | -- 108 | List of directives to register with format name=classname 109 | --|`Map` 110 | | 111 | 112 | |=== 113 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/LoggingEventWrapper.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.logging.Formatter; 6 | 7 | import org.jboss.logmanager.ExtLogRecord; 8 | import org.slf4j.Marker; 9 | import org.slf4j.event.KeyValuePair; 10 | 11 | import ch.qos.logback.classic.Level; 12 | import ch.qos.logback.classic.spi.ILoggingEvent; 13 | import ch.qos.logback.classic.spi.IThrowableProxy; 14 | import ch.qos.logback.classic.spi.LoggerContextVO; 15 | import ch.qos.logback.classic.spi.ThrowableProxy; 16 | 17 | public class LoggingEventWrapper implements ILoggingEvent { 18 | 19 | private StackTraceElement[] callerData; 20 | final ExtLogRecord logRecord; 21 | final Formatter formatter; 22 | 23 | public LoggingEventWrapper(ExtLogRecord logRecord, Formatter formatter) { 24 | this.logRecord = logRecord; 25 | this.formatter = formatter; 26 | } 27 | 28 | @Override 29 | public String getThreadName() { 30 | return logRecord.getThreadName(); 31 | } 32 | 33 | @Override 34 | public Level getLevel() { 35 | if (logRecord.getLevel().intValue() >= org.jboss.logmanager.Level.ERROR.intValue()) { 36 | return Level.ERROR; 37 | } else if (logRecord.getLevel().intValue() >= org.jboss.logmanager.Level.WARNING.intValue()) { 38 | return Level.WARN; 39 | } else if (logRecord.getLevel().intValue() >= org.jboss.logmanager.Level.INFO.intValue()) { 40 | return Level.INFO; 41 | } else if (logRecord.getLevel().intValue() >= org.jboss.logmanager.Level.DEBUG.intValue()) { 42 | return Level.DEBUG; 43 | } else if (logRecord.getLevel().intValue() >= org.jboss.logmanager.Level.TRACE.intValue()) { 44 | return Level.TRACE; 45 | } 46 | return Level.OFF; 47 | } 48 | 49 | @Override 50 | public String getMessage() { 51 | return logRecord.getMessage(); 52 | } 53 | 54 | @Override 55 | public Object[] getArgumentArray() { 56 | return logRecord.getParameters(); 57 | } 58 | 59 | @Override 60 | public String getFormattedMessage() { 61 | return logRecord.getFormattedMessage(); 62 | } 63 | 64 | @Override 65 | public String getLoggerName() { 66 | return logRecord.getLoggerName(); 67 | } 68 | 69 | @Override 70 | public LoggerContextVO getLoggerContextVO() { 71 | return LogbackRecorder.defaultLoggerContext.getLoggerContextRemoteView(); 72 | } 73 | 74 | @Override 75 | public IThrowableProxy getThrowableProxy() { 76 | if (logRecord.getThrown() != null) { 77 | return new ThrowableProxy(logRecord.getThrown()); 78 | } 79 | return null; 80 | } 81 | 82 | @Override 83 | public StackTraceElement[] getCallerData() { 84 | if (callerData == null) { 85 | callerData = new StackTraceElement[] { 86 | new StackTraceElement( 87 | null, 88 | logRecord.getSourceModuleName(), 89 | logRecord.getSourceModuleVersion(), 90 | logRecord.getSourceClassName(), 91 | logRecord.getSourceMethodName(), 92 | logRecord.getSourceFileName(), 93 | logRecord.getSourceLineNumber()) 94 | }; 95 | } 96 | return callerData; 97 | } 98 | 99 | @Override 100 | public boolean hasCallerData() { 101 | return getCallerData() != null; 102 | } 103 | 104 | @Override 105 | public Marker getMarker() { 106 | return (Marker) logRecord.getMarker(); 107 | } 108 | 109 | @Override 110 | public Map getMDCPropertyMap() { 111 | return logRecord.getMdcCopy(); 112 | } 113 | 114 | @Override 115 | public Map getMdc() { 116 | return getMDCPropertyMap(); 117 | } 118 | 119 | @Override 120 | public long getTimeStamp() { 121 | return logRecord.getMillis(); 122 | } 123 | 124 | @Override 125 | public void prepareForDeferredProcessing() { 126 | 127 | } 128 | 129 | @Override 130 | public List getMarkerList() { 131 | Marker marker = getMarker(); 132 | 133 | if (marker == null) { 134 | return List.of(); 135 | } 136 | 137 | return List.of(marker); 138 | } 139 | 140 | @Override 141 | public int getNanoseconds() { 142 | return 0; 143 | } 144 | 145 | @Override 146 | public long getSequenceNumber() { 147 | return logRecord.getSequenceNumber(); 148 | } 149 | 150 | @Override 151 | public List getKeyValuePairs() { 152 | return List.of(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /impl/runtime/src/main/java/io/quarkiverse/logback/runtime/LogbackRecorder.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logback.runtime; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Optional; 7 | import java.util.Set; 8 | import java.util.logging.Handler; 9 | 10 | import org.jboss.logmanager.ExtHandler; 11 | import org.jboss.logmanager.ExtLogRecord; 12 | import org.slf4j.helpers.Util; 13 | import org.xml.sax.InputSource; 14 | import org.xml.sax.helpers.AttributesImpl; 15 | 16 | import ch.qos.logback.classic.Logger; 17 | import ch.qos.logback.classic.LoggerContext; 18 | import ch.qos.logback.classic.joran.JoranConfigurator; 19 | import ch.qos.logback.core.joran.event.BodyEvent; 20 | import ch.qos.logback.core.joran.event.SaxEvent; 21 | import ch.qos.logback.core.joran.event.SaxEventRecorder; 22 | import ch.qos.logback.core.joran.event.StartEvent; 23 | import ch.qos.logback.core.joran.spi.JoranException; 24 | import ch.qos.logback.core.status.StatusUtil; 25 | import ch.qos.logback.core.util.StatusPrinter; 26 | import io.quarkiverse.logback.runtime.events.BodySub; 27 | import io.quarkiverse.logback.runtime.events.EventSubstitution; 28 | import io.quarkus.runtime.RuntimeValue; 29 | import io.quarkus.runtime.ShutdownContext; 30 | import io.quarkus.runtime.annotations.Recorder; 31 | import io.smallrye.common.expression.Expression; 32 | import io.smallrye.config.ConfigValue; 33 | import io.smallrye.config.SmallRyeConfig; 34 | import io.smallrye.config.SmallRyeConfigProviderResolver; 35 | 36 | @Recorder 37 | public class LogbackRecorder { 38 | 39 | public static final String DELAYED = "$$delayed"; 40 | static volatile LoggerContext defaultLoggerContext; 41 | 42 | public static final List DELAYED_START_HANDLERS = new ArrayList<>(); 43 | private static volatile boolean started; 44 | 45 | public static void addDelayed(DelayedStart delayedStart) { 46 | if (started) { 47 | delayedStart.doQuarkusDelayedStart(); 48 | } else { 49 | DELAYED_START_HANDLERS.add(delayedStart); 50 | } 51 | } 52 | 53 | public void init(List originalEvents, Set delayedStartClasses, ShutdownContext context, 54 | Map buildSystemProps) { 55 | EventSubstitution substitution = new EventSubstitution(); 56 | if (defaultLoggerContext == null) { 57 | SmallRyeConfig config = (SmallRyeConfig) SmallRyeConfigProviderResolver.instance().getConfig(); 58 | List configEvents = new ArrayList<>(); 59 | for (SaxEvent i : originalEvents) { 60 | if (i instanceof StartEvent) { 61 | AttributesImpl impl = (AttributesImpl) ((StartEvent) i).attributes; 62 | int index = impl.getIndex("class"); 63 | if (index > -1) { 64 | String val = impl.getValue(index); 65 | if (delayedStartClasses.contains(val)) { 66 | impl.setValue(index, val + DELAYED); 67 | } 68 | } 69 | for (int j = 1; j <= impl.getLength(); ++j) { 70 | String val = impl.getValue(index); 71 | if (val != null && val.contains("${")) { 72 | final String expanded = doExpand(config, val, buildSystemProps); 73 | impl.setValue(j, expanded); 74 | } 75 | } 76 | configEvents.add(i); 77 | } else if (i instanceof BodyEvent) { 78 | String val = ((BodyEvent) i).getText(); 79 | if (val.contains("${")) { 80 | final String expanded = doExpand(config, val, buildSystemProps); 81 | configEvents.add(substitution.deserialize( 82 | new BodySub(i.getNamespaceURI(), i.getLocalName(), i.getQName(), i.getLocator(), expanded))); 83 | } else { 84 | configEvents.add(i); 85 | } 86 | } else { 87 | configEvents.add(i); 88 | } 89 | } 90 | defaultLoggerContext = new LoggerContext(); 91 | try { 92 | JoranConfigurator configurator = new JoranConfigurator() { 93 | 94 | @Override 95 | public SaxEventRecorder populateSaxEventRecorder(InputSource inputSource) throws JoranException { 96 | SaxEventRecorder recorder = new SaxEventRecorder(context) { 97 | public List getSaxEventList() { 98 | return configEvents; 99 | } 100 | }; 101 | return recorder; 102 | }; 103 | }; 104 | configurator.setContext(defaultLoggerContext); 105 | configurator.doConfigure((InputSource) null); 106 | // logback-292 107 | if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { 108 | StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); 109 | } 110 | } catch (Exception t) { // see LOGBACK-1159 111 | Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t); 112 | } 113 | context.addLastShutdownTask(new Runnable() { 114 | @Override 115 | public void run() { 116 | defaultLoggerContext.stop(); 117 | defaultLoggerContext = null; 118 | started = false; 119 | } 120 | }); 121 | } 122 | } 123 | 124 | private String doExpand(SmallRyeConfig config, String val, Map buildSystemProps) { 125 | Expression expression = Expression.compile(val); 126 | final String expanded = expression.evaluate((resolveContext, stringBuilder) -> { 127 | final ConfigValue resolve = config.getConfigValue(resolveContext.getKey()); 128 | if (resolve.getValue() != null) { 129 | stringBuilder.append(resolve.getValue()); 130 | } else if (buildSystemProps.containsKey(resolveContext.getKey())) { 131 | stringBuilder.append(buildSystemProps.get(resolveContext.getKey())); 132 | } else if (resolveContext.hasDefault()) { 133 | resolveContext.expandDefault(); 134 | } else { 135 | stringBuilder.append("${" + resolveContext.getKey() + "}"); 136 | } 137 | }); 138 | return expanded; 139 | } 140 | 141 | public RuntimeValue> createHandler() { 142 | started = true; 143 | for (DelayedStart i : DELAYED_START_HANDLERS) { 144 | i.doQuarkusDelayedStart(); 145 | } 146 | DELAYED_START_HANDLERS.clear(); 147 | return new RuntimeValue<>(Optional.of(new ExtHandler() { 148 | 149 | @Override 150 | public final void doPublish(final ExtLogRecord record) { 151 | if (defaultLoggerContext == null) { 152 | return; 153 | } 154 | Logger logger = defaultLoggerContext.getLogger(record.getLoggerName()); 155 | logger.callAppenders(new LoggingEventWrapper(record, getFormatter())); 156 | } 157 | 158 | @Override 159 | public void flush() { 160 | 161 | } 162 | 163 | @Override 164 | public void close() throws SecurityException { 165 | 166 | } 167 | })); 168 | 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /impl/deployment/src/main/java/io/quarkiverse/logging/logback/deployment/LoggingLogbackProcessor.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.logback.deployment; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.URL; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Objects; 14 | import java.util.Properties; 15 | import java.util.Set; 16 | import java.util.concurrent.atomic.AtomicReference; 17 | import java.util.function.Function; 18 | import java.util.stream.Collectors; 19 | 20 | import org.jboss.logging.Logger; 21 | import org.xml.sax.InputSource; 22 | 23 | import ch.qos.logback.classic.LoggerContext; 24 | import ch.qos.logback.classic.joran.JoranConfigurator; 25 | import ch.qos.logback.classic.util.ContextInitializer; 26 | import ch.qos.logback.core.joran.event.BodyEvent; 27 | import ch.qos.logback.core.joran.event.EndEvent; 28 | import ch.qos.logback.core.joran.event.SaxEvent; 29 | import ch.qos.logback.core.joran.event.SaxEventRecorder; 30 | import ch.qos.logback.core.joran.event.StartEvent; 31 | import ch.qos.logback.core.joran.spi.JoranException; 32 | import ch.qos.logback.core.joran.spi.NoAutoStart; 33 | import ch.qos.logback.core.spi.LifeCycle; 34 | import ch.qos.logback.core.util.Loader; 35 | import io.quarkiverse.logback.runtime.DelayedStart; 36 | import io.quarkiverse.logback.runtime.LogbackRecorder; 37 | import io.quarkiverse.logback.runtime.events.BodySub; 38 | import io.quarkiverse.logback.runtime.events.EndSub; 39 | import io.quarkiverse.logback.runtime.events.EventSubstitution; 40 | import io.quarkiverse.logback.runtime.events.StartSub; 41 | import io.quarkus.bootstrap.model.AppArtifactKey; 42 | import io.quarkus.deployment.GeneratedClassGizmoAdaptor; 43 | import io.quarkus.deployment.annotations.BuildProducer; 44 | import io.quarkus.deployment.annotations.BuildStep; 45 | import io.quarkus.deployment.annotations.ExecutionTime; 46 | import io.quarkus.deployment.annotations.Record; 47 | import io.quarkus.deployment.builditem.FeatureBuildItem; 48 | import io.quarkus.deployment.builditem.GeneratedClassBuildItem; 49 | import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; 50 | import io.quarkus.deployment.builditem.LogHandlerBuildItem; 51 | import io.quarkus.deployment.builditem.RemovedResourceBuildItem; 52 | import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; 53 | import io.quarkus.deployment.builditem.ShutdownContextBuildItem; 54 | import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; 55 | import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; 56 | import io.quarkus.deployment.recording.RecorderContext; 57 | import io.quarkus.gizmo.ClassCreator; 58 | import io.quarkus.gizmo.MethodCreator; 59 | import io.quarkus.gizmo.MethodDescriptor; 60 | import io.smallrye.common.version.VersionScheme; 61 | 62 | class LoggingLogbackProcessor { 63 | 64 | private static final Logger log = Logger.getLogger(LoggingLogbackProcessor.class); 65 | 66 | private static final String FEATURE = "logging-logback"; 67 | public static final String PROJECT_VERSION = "project.version"; 68 | 69 | @BuildStep 70 | FeatureBuildItem feature() { 71 | return new FeatureBuildItem(FEATURE); 72 | } 73 | 74 | @BuildStep 75 | RemovedResourceBuildItem removeSlf4jBinding() { 76 | return new RemovedResourceBuildItem(new AppArtifactKey("ch.qos.logback", "logback-classic", null, "jar"), 77 | Collections.singleton("org/slf4j/impl/StaticLoggerBinder.class")); 78 | } 79 | 80 | @BuildStep 81 | HotDeploymentWatchedFileBuildItem watchLogback() { 82 | return new HotDeploymentWatchedFileBuildItem("logback.xml"); 83 | } 84 | 85 | @Record(ExecutionTime.STATIC_INIT) 86 | @BuildStep 87 | void init(LogbackRecorder recorder, RecorderContext context, 88 | BuildProducer runTimeConfigurationDefaultBuildItemBuildProducer, 89 | BuildProducer generatedClasses, 90 | OutputTargetBuildItem outputTargetBuildItem, 91 | CurateOutcomeBuildItem curateOutcomeBuildItem, 92 | ShutdownContextBuildItem shutdownContextBuildItem) 93 | throws Exception { 94 | //first check the versions 95 | doVersionCheck(); 96 | 97 | URL url = getUrl(); 98 | if (url == null) { 99 | return; 100 | } 101 | context.registerSubstitution(StartEvent.class, StartSub.class, (Class) EventSubstitution.class); 102 | context.registerSubstitution(BodyEvent.class, BodySub.class, (Class) EventSubstitution.class); 103 | context.registerSubstitution(EndEvent.class, EndSub.class, (Class) EventSubstitution.class); 104 | final AtomicReference> events = new AtomicReference<>(); 105 | 106 | JoranConfigurator configurator = new JoranConfigurator() { 107 | 108 | @Override 109 | public SaxEventRecorder populateSaxEventRecorder(InputSource inputSource) throws JoranException { 110 | SaxEventRecorder recorder = super.populateSaxEventRecorder(inputSource); 111 | events.set(recorder.getSaxEventList()); 112 | return recorder; 113 | }; 114 | }; 115 | configurator.setContext(new LoggerContext()); 116 | configurator.doConfigure(url); 117 | 118 | List loggerPath = Arrays.asList("configuration", "logger"); 119 | List rootPath = Arrays.asList("configuration", "root"); 120 | String rootLevel = null; 121 | Map levels = new HashMap<>(); 122 | Set allClasses = new HashSet<>(); 123 | for (SaxEvent i : events.get()) { 124 | if (i instanceof StartEvent) { 125 | StartEvent s = ((StartEvent) i); 126 | if (Objects.equals(loggerPath, s.elementPath.getCopyOfPartList())) { 127 | String level = s.attributes.getValue("level"); 128 | if (level != null) { 129 | levels.put(s.attributes.getValue("name"), level); 130 | } 131 | } else if (Objects.equals(rootPath, s.elementPath.getCopyOfPartList())) { 132 | String level = s.attributes.getValue("level"); 133 | if (level != null) { 134 | rootLevel = level; 135 | } 136 | } 137 | int classIndex = s.attributes.getIndex("class"); 138 | if (classIndex != -1) { 139 | allClasses.add(s.attributes.getValue(classIndex)); 140 | } 141 | } 142 | } 143 | 144 | boolean disableConsole = false; 145 | Set delayedClasses = new HashSet<>(); 146 | for (String i : allClasses) { 147 | if (i.equals("ch.qos.logback.core.ConsoleAppender")) { 148 | disableConsole = true; 149 | } 150 | try { 151 | Class c = Thread.currentThread().getContextClassLoader().loadClass(i); 152 | if (LifeCycle.class.isAssignableFrom(c) && c.getAnnotation(NoAutoStart.class) == null) { 153 | delayedClasses.add(i); 154 | } 155 | } catch (ClassNotFoundException exception) { 156 | throw new RuntimeException(exception); 157 | } 158 | } 159 | if (disableConsole) { 160 | runTimeConfigurationDefaultBuildItemBuildProducer 161 | .produce(new RunTimeConfigurationDefaultBuildItem("quarkus.log.console.enable", "false")); 162 | } 163 | 164 | for (String i : delayedClasses) { 165 | try (ClassCreator c = new ClassCreator( 166 | new GeneratedClassGizmoAdaptor(generatedClasses, 167 | (Function) s -> s.substring(s.length() - LogbackRecorder.DELAYED.length())), 168 | i + LogbackRecorder.DELAYED, null, i, DelayedStart.class.getName())) { 169 | MethodCreator start = c.getMethodCreator("start", void.class); 170 | start.invokeStaticMethod( 171 | MethodDescriptor.ofMethod(LogbackRecorder.class, "addDelayed", void.class, DelayedStart.class), 172 | start.getThis()); 173 | start.returnValue(null); 174 | MethodCreator method = c.getMethodCreator("doQuarkusDelayedStart", void.class); 175 | method.invokeSpecialMethod(MethodDescriptor.ofMethod(i, "start", void.class), method.getThis()); 176 | method.returnValue(null); 177 | } 178 | } 179 | 180 | if (rootLevel != null) { 181 | runTimeConfigurationDefaultBuildItemBuildProducer 182 | .produce(new RunTimeConfigurationDefaultBuildItem("quarkus.log.level", rootLevel)); 183 | } 184 | for (Map.Entry e : levels.entrySet()) { 185 | runTimeConfigurationDefaultBuildItemBuildProducer.produce(new RunTimeConfigurationDefaultBuildItem( 186 | "quarkus.log.category.\"" + e.getKey() + "\".level", e.getValue())); 187 | } 188 | 189 | Map buildProperties = new HashMap<>(outputTargetBuildItem.getBuildSystemProperties() 190 | .entrySet().stream().collect(Collectors.toMap(Object::toString, Object::toString))); 191 | buildProperties.put(PROJECT_VERSION, curateOutcomeBuildItem.getApplicationModel().getAppArtifact().getVersion()); 192 | recorder.init(events.get(), delayedClasses, shutdownContextBuildItem, buildProperties); 193 | } 194 | 195 | private void doVersionCheck() throws IOException { 196 | //if the versions are wrong you get really hard to understand errors 197 | //easier to just verify this ourselves 198 | String compiledVersion; 199 | String coreVersion = null; 200 | String classicVersion = null; 201 | 202 | try (InputStream in = getClass().getClassLoader().getResourceAsStream("quarkus-logback-version.txt")) { 203 | compiledVersion = new String(in.readAllBytes(), StandardCharsets.UTF_8); 204 | } 205 | try (InputStream in = getClass().getClassLoader() 206 | .getResourceAsStream("META-INF/maven/ch.qos.logback/logback-core/pom.properties")) { 207 | if (in != null) { 208 | Properties p = new Properties(); 209 | p.load(in); 210 | coreVersion = p.getProperty("version"); 211 | } 212 | } 213 | try (InputStream in = getClass().getClassLoader() 214 | .getResourceAsStream("META-INF/maven/ch.qos.logback/logback-classic/pom.properties")) { 215 | if (in != null) { 216 | Properties p = new Properties(); 217 | p.load(in); 218 | classicVersion = p.getProperty("version"); 219 | } 220 | } 221 | if (coreVersion != null) { 222 | if (VersionScheme.MAVEN.compare(coreVersion, compiledVersion) < 0) { 223 | throw new RuntimeException("ch.qos.logback:logback-core version " + coreVersion 224 | + " is not compatible with quarkus-logback which requires at least " + compiledVersion 225 | + " please use the correct logback version"); 226 | } 227 | if (classicVersion != null) { 228 | if (VersionScheme.MAVEN.compare(classicVersion, coreVersion) != 0) { 229 | throw new RuntimeException("logback-core(" + coreVersion + ") and logback-classic(" + classicVersion 230 | + ") versions must match"); 231 | } 232 | } 233 | } else { 234 | log.warn("Could not determine logback version on class path"); 235 | } 236 | } 237 | 238 | @BuildStep 239 | @Record(ExecutionTime.RUNTIME_INIT) 240 | LogHandlerBuildItem handler(LogbackRecorder recorder) { 241 | return new LogHandlerBuildItem(recorder.createHandler()); 242 | } 243 | 244 | private URL getUrl() { 245 | URL url = Loader.getResource(ContextInitializer.TEST_AUTOCONFIG_FILE, Thread.currentThread().getContextClassLoader()); 246 | if (url != null) { 247 | return url; 248 | } 249 | return Loader.getResource(ContextInitializer.AUTOCONFIG_FILE, Thread.currentThread().getContextClassLoader()); 250 | } 251 | } 252 | --------------------------------------------------------------------------------