Skip to content

Commit

Permalink
Implement Logback Configuration for KSML
Browse files Browse the repository at this point in the history
Added tests
  • Loading branch information
richard-axual committed Apr 16, 2024
1 parent 7b2668c commit aac6f80
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 0 deletions.
13 changes: 13 additions & 0 deletions ksml-runner/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand All @@ -81,6 +86,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- Needed for JUnit Pioneer -->
<configuration>
<argLine>
${surefireArgLine}
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package io.axual.ksml.runner.logging;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Set;

import ch.qos.logback.classic.ClassicConstants;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ConfiguratorRank;
import ch.qos.logback.classic.util.DefaultJoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.status.InfoStatus;
import ch.qos.logback.core.status.StatusManager;
import ch.qos.logback.core.status.WarnStatus;
import ch.qos.logback.core.util.Loader;
import ch.qos.logback.core.util.OptionHelper;

@ConfiguratorRank(value = ConfiguratorRank.CUSTOM_HIGH_PRIORITY)
public class KSMLLogbackConfigurator extends DefaultJoranConfigurator {
public static final String CONFIG_FILE_ENV_PROPERTY = "LOGBACK_CONFIGURATION_FILE";
public static final String CONFIG_FILE_SYS_PROPERTY = ClassicConstants.CONFIG_FILE_PROPERTY;

@Override
public ExecutionStatus configure(LoggerContext context) {
ClassLoader classLoader = Loader.getClassLoaderOfObject(this);
// System properties should take precedence
URL url = findConfigFileURLFromSystemProperties(classLoader);
if (url == null) {
// Get the configuration from environment variables, if set
url = findConfigFileURLFromEnvironmentVariables(classLoader);
}
if (url != null) {
try {
configureByResource(url);
} catch (JoranException e) {
context.getStatusManager().add(new WarnStatus("Could not configure KSML logging", this, e));
}

return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
}

// No environment variable URL found, use DefaultJoran logic
return super.configure(context);
}

private URL findConfigFileURLFromEnvironmentVariables(ClassLoader classLoader) {
return findConfigFileURL(classLoader, OptionHelper.getEnv(CONFIG_FILE_ENV_PROPERTY));
}

private URL findConfigFileURLFromSystemProperties(ClassLoader classLoader) {
return findConfigFileURL(classLoader, OptionHelper.getSystemProperty(CONFIG_FILE_SYS_PROPERTY));
}

private URL findConfigFileURL(ClassLoader classLoader, String logbackConfigFile) {
if (logbackConfigFile != null && !logbackConfigFile.isBlank()) {
URL url = null;
try {
url = new URI(logbackConfigFile.trim()).toURL();
return url;
} catch (URISyntaxException | MalformedURLException | IllegalArgumentException e) {
// so, resource is not a URL:
// attempt to get the resource from the class path
url = Loader.getResource(logbackConfigFile, classLoader);
if (url != null) {
return url;
}
// OK, check if the config is a file?
File f = new File(logbackConfigFile);
if (f.exists() && f.isFile()) {
try {
url = f.toURI().toURL();
return url;
} catch (MalformedURLException e1) {
// Eat exception
}
}
} finally {
StatusManager sm = context.getStatusManager();
if (url == null) {
// Information, could not find the resource
sm.add(new InfoStatus("Could NOT find resource [" + logbackConfigFile + "]", context));
} else {
// OK, a resource url was found
sm.add(new InfoStatus("Found resource [" + logbackConfigFile + "] at [" + url + "]", context));
Set<URL> urlSet = null;
try {
// Get all resources with the name
urlSet = Loader.getResources(logbackConfigFile, classLoader);
} catch (IOException e) {
// Error on getting the resources
addError("Failed to get url list for resource [" + logbackConfigFile + "]", e);
}
if (urlSet != null && urlSet.size() > 1) {
// Multiple resources found, raise general warning and for each of the resources
addWarn("Resource [" + logbackConfigFile + "] occurs multiple times on the classpath.");
for (URL urlFromSet : urlSet) {
addWarn("Resource [" + logbackConfigFile + "] occurs at [" + urlFromSet.toString() + "]");
}
}
}
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.axual.ksml.runner.logging.KSMLLogbackConfigurator
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package io.axual.ksml.runner.logging;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.ClearEnvironmentVariable;
import org.junitpioneer.jupiter.RestoreEnvironmentVariables;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
import org.junitpioneer.jupiter.SetSystemProperty;

import java.lang.reflect.Field;
import java.net.URL;
import java.util.Collection;
import java.util.Map;

import ch.qos.logback.classic.LoggerContext;
import lombok.SneakyThrows;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

class KSMLLogbackConfiguratorTest {

LoggerContext spiedContext = new LoggerContext();

@Test
@DisplayName("The configuration file is loaded from an environment variable reference pointing to a resource")
@SetEnvironmentVariable(key = "LOGBACK_CONFIGURATION_FILE", value = "logback-custom-testing.xml")
@SetSystemProperty(key = "logback.test.id", value = "testEnvToResource")
void configureWithEnvironmentVariableToResource() {
KSMLLogbackConfigurator configurator = new KSMLLogbackConfigurator();
configurator.setContext(spiedContext);
configurator.configure(spiedContext);
Collection<MockAppender> appender = MockAppender.APPENDERS.get("testEnvToResource");
assertNotNull(appender);
assertEquals(1, appender.size());
}

@Test
@DisplayName("The configuration file is loaded from an environment variable reference in URL format")
@RestoreEnvironmentVariables
@SetSystemProperty(key = "logback.test.id", value = "testEnvToResourceUrl")
void configureWithEnvironmentVariableToResourceURL() {
// Get value for environment variable
URL resourceUrl = getClass().getClassLoader().getResource("logback-custom-testing.xml");
assertNotNull(resourceUrl);
setEnvVar("LOGBACK_CONFIGURATION_FILE", resourceUrl.toExternalForm());

// Run test
KSMLLogbackConfigurator configurator = new KSMLLogbackConfigurator();
configurator.setContext(spiedContext);
configurator.configure(spiedContext);
Collection<MockAppender> appender = MockAppender.APPENDERS.get("testEnvToResourceUrl");
assertNotNull(appender);
assertEquals(1, appender.size());
}

@Test
@DisplayName("The configuration file is loaded from an environment variable reference as an absolute filepath")
@RestoreEnvironmentVariables
@SetSystemProperty(key = "logback.test.id", value = "testEnvToFile")
void configureWithEnvironmentVariableToFile() {
// Get value for environment variable
URL resourceUrl = getClass().getClassLoader().getResource("logback-custom-testing.xml");
assertNotNull(resourceUrl);
setEnvVar("LOGBACK_CONFIGURATION_FILE", resourceUrl.getPath());

// Run test
KSMLLogbackConfigurator configurator = new KSMLLogbackConfigurator();
configurator.setContext(spiedContext);
configurator.configure(spiedContext);
Collection<MockAppender> appender = MockAppender.APPENDERS.get("testEnvToFile");
assertNotNull(appender);
assertEquals(1, appender.size());
}

@Test
@DisplayName("The configuration file should not be loaded, but fall back to the default setting")
@ClearEnvironmentVariable(key = "LOGBACK_CONFIGURATION_FILE")
@SetSystemProperty(key = "logback.test.id", value = "shouldNotAppear")
void configureWithoutEnvironmentVariable() {
KSMLLogbackConfigurator configurator = new KSMLLogbackConfigurator();
configurator.setContext(spiedContext);
configurator.configure(spiedContext);

// This id comes from the logback-test.xml, which should be loaded now and is hardcoded
Collection<MockAppender> appender = MockAppender.APPENDERS.get("fixed-from-standard-joran-lookup");
assertNotNull(appender);
assertEquals(1, appender.size());

// This id is set, but since the default logback-test.xml is used it should never be set
appender = MockAppender.APPENDERS.get("shouldNotAppear");
assertNotNull(appender);
assertEquals(0, appender.size());
}

@SneakyThrows
@SuppressWarnings("unchecked")
void setEnvVar(String key, String value) {
Class<?> classOfMap = System.getenv().getClass();
Field field = classOfMap.getDeclaredField("m");
field.setAccessible(true);
Map<String, String> writeableEnvironmentVariables = (Map<String, String>) field.get(System.getenv());
writeableEnvironmentVariables.put(key, value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.axual.ksml.runner.logging;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

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

public class MockAppender extends AppenderBase<ILoggingEvent> {

public static final Multimap<String, MockAppender> APPENDERS = HashMultimap.create();

public MockAppender() {
super();
}

@Override
protected void append(ILoggingEvent eventObject) {
// Eat it
}

private String testId;


public String getTestId() {
return testId;
}

public void setTestId(String testId) {
if (testId != null) {
APPENDERS.put(testId, this);
}
this.testId = testId;
}
}
17 changes: 17 additions & 0 deletions ksml-runner/src/test/resources/logback-custom-testing.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false">
<appender name="MOCK"
class="io.axual.ksml.runner.logging.MockAppender">
<encoder>
<pattern>%date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX", UTC} %-5level %-36logger{36} %msg%n
</pattern>
</encoder>
<testId>${logback.test.id:-unknown}</testId>
</appender>

<root level="INFO">
<appender-ref ref="MOCK"/>
</root>

<logger name="io.axual.ksml" level="TRACE"/>
</configuration>
17 changes: 17 additions & 0 deletions ksml-runner/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false">
<appender name="MOCK"
class="io.axual.ksml.runner.logging.MockAppender">
<encoder>
<pattern>%date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX", UTC} %-5level %-36logger{36} %msg%n
</pattern>
</encoder>
<testId>fixed-from-standard-joran-lookup</testId>
</appender>

<root level="INFO">
<appender-ref ref="MOCK"/>
</root>

<logger name="io.axual.ksml" level="TRACE"/>
</configuration>
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<hamcrest.version>2.2</hamcrest.version>
<mockito-core.version>5.7.0</mockito-core.version>
<mockito-junit-jupiter.version>5.7.0</mockito-junit-jupiter.version>
<junit-pioneer.version>2.2.0</junit-pioneer.version>

<!-- project and java version settings -->
<java.source.version>21</java.source.version>
Expand Down Expand Up @@ -319,6 +320,12 @@
<version>${jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>${junit-pioneer.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
Expand Down

0 comments on commit aac6f80

Please sign in to comment.