-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Logback Configuration for KSML
Added tests
- Loading branch information
1 parent
7b2668c
commit aac6f80
Showing
8 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
ksml-runner/src/main/java/io/axual/ksml/runner/logging/KSMLLogbackConfigurator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
ksml-runner/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
io.axual.ksml.runner.logging.KSMLLogbackConfigurator |
106 changes: 106 additions & 0 deletions
106
ksml-runner/src/test/java/io/axual/ksml/runner/logging/KSMLLogbackConfiguratorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |
35 changes: 35 additions & 0 deletions
35
ksml-runner/src/test/java/io/axual/ksml/runner/logging/MockAppender.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters