From de24bf3ebd35fac7c90a67e2ccaa13ab3cdce365 Mon Sep 17 00:00:00 2001 From: devatherock Date: Thu, 12 May 2022 08:15:46 -0500 Subject: [PATCH] feat: Support for reading logback config from local or remote file --- .circleci/config.yml | 2 +- CHANGELOG.md | 3 + Dockerfile | 4 +- README.md | 2 +- build.gradle | 8 ++ docker-compose.yml | 1 + .../io/github/devatherock/Application.java | 32 ++----- .../logback/LogbackConfigInitializer.java | 89 +++++++++++++++++++ .../artifactory-badge/native-image.properties | 2 - src/main/resources/graal/access-filter.json | 1 + .../LogbackConfigInitializerSpec.groovy | 71 +++++++++++++++ src/test/resources/logback-invalid.xml | 7 ++ 12 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 src/main/java/io/github/devatherock/logback/LogbackConfigInitializer.java create mode 100644 src/test/groovy/io/github/devatherock/logback/LogbackConfigInitializerSpec.groovy create mode 100644 src/test/resources/logback-invalid.xml diff --git a/.circleci/config.yml b/.circleci/config.yml index 5aaef3a..ad3d488 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ pr_filter: &pr_filter executors: jdk-executor: docker: - - image: devatherock/graalvm:0.2.0 + - image: devatherock/graalvm:ol8-java11-22.1.0-2 auth: username: $DOCKER_USERNAME password: $DOCKER_PASSWORD diff --git a/CHANGELOG.md b/CHANGELOG.md index 20988ca..3db4d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## [Unreleased] +### Added +- Support for reading logback config from local or remote file + ### Changed - Used integration test config from `gradle-includes` diff --git a/Dockerfile b/Dockerfile index b9ff043..991f46a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM devatherock/graalvm:ol7-java11-20.3.4-1 as graalvm +FROM ghcr.io/graalvm/native-image:ol8-java11-22.1.0 as graalvm COPY . /home/app/micronaut-graal-app WORKDIR /home/app/micronaut-graal-app -RUN native-image --no-server -cp build/libs/*-all.jar +RUN native-image -cp build/libs/*-all.jar diff --git a/README.md b/README.md index c2ada82..e700b22 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ docker run --rm \ -p 8080:8080 \ -e ARTIFACTORY_URL=https://some/url \ -e ARTIFACTORY_API_KEY=xyz \ - devatherock/artifactory-badge:0.5.0 + devatherock/artifactory-badge:1.0.0 ``` ### Configurable properties diff --git a/build.gradle b/build.gradle index 67ad370..5be3337 100644 --- a/build.gradle +++ b/build.gradle @@ -101,6 +101,14 @@ ext.jacoco = [ exclusions: [ 'io/github/devatherock/Application.class', 'io/github/devatherock/test/ArtifactoryController.class' + ], + coverageThresholds: [ + 'io.github.devatherock.logback.LogbackConfigInitializer': [ + 'BRANCH': 0.75, + 'COMPLEXITY': 0.62, + 'INSTRUCTION': 0.89, + 'LINE': 0.88 + ] ] ] apply from: 'https://raw.githubusercontent.com/devatherock/gradle-includes/master/integration.gradle' diff --git a/docker-compose.yml b/docker-compose.yml index 83f04d8..cc4336e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,5 +8,6 @@ services: - ARTIFACTORY_URL=http://localhost:8081 - ARTIFACTORY_API_KEY=dummyKey - ARTIFACTORY_BADGE_SHIELDS_IO_URL=http://localhost:8081 + - LOGBACK_CONFIGURATION_FILE=https://raw.githubusercontent.com/devatherock/artifactory-badge/master/src/main/resources/logback-json.xml ports: - "8080:8080" \ No newline at end of file diff --git a/src/main/java/io/github/devatherock/Application.java b/src/main/java/io/github/devatherock/Application.java index dc62a1d..4b192de 100644 --- a/src/main/java/io/github/devatherock/Application.java +++ b/src/main/java/io/github/devatherock/Application.java @@ -1,41 +1,21 @@ package io.github.devatherock; -import org.slf4j.LoggerFactory; +import java.io.IOException; + +import io.github.devatherock.logback.LogbackConfigInitializer; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.joran.JoranConfigurator; -import ch.qos.logback.classic.util.ContextInitializer; -import ch.qos.logback.core.joran.spi.JoranException; -import ch.qos.logback.core.util.StatusPrinter; import io.micronaut.runtime.Micronaut; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.servers.Server; -@OpenAPIDefinition(info = @Info(title = "artifactory-badge", version = "1.0.0"), servers = { +@OpenAPIDefinition(info = @Info(title = "artifactory-badge", version = "1.1.0"), servers = { @Server(url = "http://localhost:8080", description = "The server where the application is hosted. Defaulted to localhost") }) public class Application { - private static final String ENV_LOGBACK_CONFIG = "LOGBACK_CONFIGURATION_FILE"; - - public static void main(String[] args) { - if (System.getProperty(ContextInitializer.CONFIG_FILE_PROPERTY) == null && - System.getenv(ENV_LOGBACK_CONFIG) != null) { - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - - try { - JoranConfigurator configurator = new JoranConfigurator(); - configurator.setContext(context); - context.reset(); - configurator.doConfigure( - Application.class.getClassLoader().getResourceAsStream(System.getenv( - ENV_LOGBACK_CONFIG))); - } catch (JoranException je) { - // StatusPrinter will handle this - } - StatusPrinter.printInCaseOfErrorsOrWarnings(context); - } + public static void main(String[] args) throws IOException { + LogbackConfigInitializer.initializeConfig(); Micronaut.run(Application.class); } } \ No newline at end of file diff --git a/src/main/java/io/github/devatherock/logback/LogbackConfigInitializer.java b/src/main/java/io/github/devatherock/logback/LogbackConfigInitializer.java new file mode 100644 index 0000000..9c52283 --- /dev/null +++ b/src/main/java/io/github/devatherock/logback/LogbackConfigInitializer.java @@ -0,0 +1,89 @@ +package io.github.devatherock.logback; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.slf4j.LoggerFactory; + +import io.github.devatherock.Application; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.classic.util.ContextInitializer; +import ch.qos.logback.core.joran.spi.JoranException; +import ch.qos.logback.core.util.StatusPrinter; + +/** + * Initializes logging config using an environment variable. Needed for native + * images + * + * @author devaprasadh + * + */ +public class LogbackConfigInitializer { + private static final String ENV_LOGBACK_CONFIG = "LOGBACK_CONFIGURATION_FILE"; + + /** + * Initializes logging config using an environment variable + * + * @throws IOException + */ + public static void initializeConfig() throws IOException { + if (System.getProperty(ContextInitializer.CONFIG_FILE_PROPERTY) == null && + System.getenv(ENV_LOGBACK_CONFIG) != null) { + initializeConfig(System.getenv(ENV_LOGBACK_CONFIG)); + } + } + + /** + * Initializes logging config using the specified config file + * + * @throws IOException + */ + private static void initializeConfig(String configFile) throws IOException { + InputStream config = readConfig(configFile); + + if (null != config) { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + try { + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + context.reset(); + configurator.doConfigure(config); + } catch (JoranException exception) { + // StatusPrinter will handle this + } finally { + config.close(); + } + StatusPrinter.printInCaseOfErrorsOrWarnings(context); + } + } + + /** + * Read the config file contents as an {@link InputStream} + * + * @param configFile + * @return an inputstream + */ + private static InputStream readConfig(String configFile) { + InputStream stream = Application.class.getClassLoader().getResourceAsStream(configFile); // From classpath + + if (null == stream) { + try { + stream = new FileInputStream(configFile); // From local file + } catch (FileNotFoundException exception) { + try { + stream = new URL(configFile).openStream(); // From remote URL + } catch (IOException ioException) { + // no op + } + } + } + + return stream; + } +} diff --git a/src/main/resources/META-INF/native-image/io.github.devatherock/artifactory-badge/native-image.properties b/src/main/resources/META-INF/native-image/io.github.devatherock/artifactory-badge/native-image.properties index f21a84f..0b71c37 100644 --- a/src/main/resources/META-INF/native-image/io.github.devatherock/artifactory-badge/native-image.properties +++ b/src/main/resources/META-INF/native-image/io.github.devatherock/artifactory-badge/native-image.properties @@ -2,10 +2,8 @@ Args = -H:ResourceConfigurationFiles=build/generated/resources/graalvm/resource- -H:Name=micronautgraalapp \ -H:ReflectionConfigurationFiles=build/graal/reflect-config.json,build/resources/main/graal/reflect-config.json \ --no-fallback \ - --allow-incomplete-classpath \ --report-unsupported-elements-at-runtime \ --initialize-at-build-time=sun.instrument.InstrumentationImpl \ --initialize-at-run-time=io.micronaut.aop.internal.intercepted.KotlinInterceptedMethod \ -H:EnableURLProtocols=http,https \ - --enable-all-security-services \ -H:Class=io.github.devatherock.Application diff --git a/src/main/resources/graal/access-filter.json b/src/main/resources/graal/access-filter.json index bcacd7a..7412cbd 100644 --- a/src/main/resources/graal/access-filter.json +++ b/src/main/resources/graal/access-filter.json @@ -1,5 +1,6 @@ { "rules": [ + {"excludeClasses": "io.github.devatherock.logback.LogbackConfigInitializerSpec"}, {"excludeClasses": "io.github.devatherock.artifactory.config.AppConfigSpec"}, {"excludeClasses": "io.github.devatherock.artifactory.config.ArtifactoryPropertiesSpec"}, {"excludeClasses": "io.github.devatherock.artifactory.config.ShieldsIOPropertiesSpec"}, diff --git a/src/test/groovy/io/github/devatherock/logback/LogbackConfigInitializerSpec.groovy b/src/test/groovy/io/github/devatherock/logback/LogbackConfigInitializerSpec.groovy new file mode 100644 index 0000000..e7dd1c7 --- /dev/null +++ b/src/test/groovy/io/github/devatherock/logback/LogbackConfigInitializerSpec.groovy @@ -0,0 +1,71 @@ +package io.github.devatherock.logback + +import java.nio.file.Paths + +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Test class for {@link LogbackConfigInitializer} + */ +class LogbackConfigInitializerSpec extends Specification { + + void 'test initialize config - no args'() { + when: + LogbackConfigInitializer.initializeConfig() + + then: + noExceptionThrown() + } + + @Unroll + void 'test read config - #configFile'() { + when: + InputStream stream = LogbackConfigInitializer.readConfig(configFile) + + then: + stream + + cleanup: + stream?.close() + + where: + configFile << [ + 'logback.xml', + Paths.get(System.properties['user.dir'], 'src/main/resources/logback.xml').toString(), + 'https://raw.githubusercontent.com/devatherock/artifactory-badge/master/src/main/resources/logback-json.xml' + ] + } + + @Unroll + void 'test read config - non-existent/invalid path - #configFile'() { + expect: + !LogbackConfigInitializer.readConfig(configFile) + + where: + configFile << [ + 'classpath:logback.xml', + 'dummy.xml', + Paths.get(System.properties['user.dir'], 'src/main/resources/logbackx.xml').toString(), + 'https://raw.githubusercontent.com/devatherock/artifactory-badge/master/src/main/resour/logback-json.xml' + ] + } + + @Unroll + void 'test initialize config - #configFile'() { + when: + LogbackConfigInitializer.initializeConfig(configFile) + + then: + noExceptionThrown() + + where: + configFile << [ + 'logback.xml', + Paths.get(System.properties['user.dir'], 'src/main/resources/logback.xml').toString(), + 'https://raw.githubusercontent.com/devatherock/artifactory-badge/master/src/main/resources/logback-json.xml', + 'classpath:logback.xml', + 'logback-invalid.xml' + ] + } +} diff --git a/src/test/resources/logback-invalid.xml b/src/test/resources/logback-invalid.xml new file mode 100644 index 0000000..84a69c3 --- /dev/null +++ b/src/test/resources/logback-invalid.xml @@ -0,0 +1,7 @@ + + + + + + +