diff --git a/log4j2-appender/src/main/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderBuilder.java b/log4j2-appender/src/main/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderBuilder.java index 3355965..3353fd8 100644 --- a/log4j2-appender/src/main/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderBuilder.java +++ b/log4j2-appender/src/main/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderBuilder.java @@ -101,7 +101,7 @@ public class LokiAppenderBuilder> extends Abstr ); int bufferSizeBytes = getBufferSizeMegabytes() * BYTES_IN_MEGABYTE; - if (TjahziInitializer.isCorrectSize(bufferSizeBytes)) { + if (!TjahziInitializer.isCorrectSize(bufferSizeBytes)) { LOGGER.warn("Invalid log buffer size {} - using nearest power of two greater than provided value, no less than 1MB. {}", bufferSizeBytes, GitHubDocs.LOG_BUFFER_SIZING.getLogMessage() diff --git a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LogLevelLabelConfigurationTest.java b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LogLevelLabelConfigurationTest.java index 36dddb2..219ce86 100644 --- a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LogLevelLabelConfigurationTest.java +++ b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LogLevelLabelConfigurationTest.java @@ -37,7 +37,7 @@ class LogLevelLabelConfigurationTest { .withExposedPorts(3100); @Test - void shouldWorkWIthNoLogLevelConfigured() throws URISyntaxException { + void shouldWorkWIthNoLogLevelConfigured() throws Exception { // Given System.setProperty("loki.host", loki.getHost()); System.setProperty("loki.port", loki.getFirstMappedPort().toString()); diff --git a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderMonitoringTest.java b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderMonitoringTest.java index 4bdbd95..fd255cb 100644 --- a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderMonitoringTest.java +++ b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderMonitoringTest.java @@ -21,6 +21,9 @@ class LokiAppenderMonitoringTest { @Test void shouldInjectMonitoringAndUseIt() throws URISyntaxException { // Given + System.setProperty("loki.host", "somewhere"); + System.setProperty("loki.port", "42"); + URI uri = getClass() .getClassLoader() .getResource("basic-appender-test-configuration.xml") diff --git a/log4j2-appender/src/test/resources/appender-test-large-batches.xml b/log4j2-appender/src/test/resources/appender-test-large-batches.xml index 222f1de..b418b5a 100644 --- a/log4j2-appender/src/test/resources/appender-test-large-batches.xml +++ b/log4j2-appender/src/test/resources/appender-test-large-batches.xml @@ -7,7 +7,7 @@ - + 1048576 ${sys:loki.host} diff --git a/logback-appender/pom.xml b/logback-appender/pom.xml index a8f1b14..0931358 100644 --- a/logback-appender/pom.xml +++ b/logback-appender/pom.xml @@ -27,6 +27,13 @@ 1.2.3 + + io.dropwizard.metrics + metrics-core + 4.1.17 + provided + + com.google.code.java-allocation-instrumenter java-allocation-instrumenter diff --git a/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/Label.java b/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/Label.java index dbb5028..25202c9 100644 --- a/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/Label.java +++ b/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/Label.java @@ -1,5 +1,6 @@ package pl.tkowalcz.tjahzi.logback; +import java.util.Map; import java.util.regex.Pattern; public class Label { @@ -9,6 +10,15 @@ public class Label { private String name; private String value; + // For Logback this is just a convenience method for testing + public static Label createLabel(String name, String value) { + Label result = new Label(); + result.setName(name); + result.setValue(value); + + return result; + } + public boolean hasValidName() { return hasValidName(getName()); } @@ -29,6 +39,11 @@ public class Label { this.value = value; } + // @VisibleForTests + public Map.Entry asEntry() { + return Map.entry(getName(), getValue()); + } + public static boolean hasValidName(String label) { return LABEL_NAME_PATTER.matcher(label).matches(); } diff --git a/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppender.java b/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppender.java index 7d1dbad..8a7f3cf 100644 --- a/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppender.java +++ b/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppender.java @@ -4,17 +4,21 @@ import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; import pl.tkowalcz.tjahzi.LoggingSystem; import pl.tkowalcz.tjahzi.TjahziLogger; +import pl.tkowalcz.tjahzi.stats.MonitoringModule; +import pl.tkowalcz.tjahzi.stats.MutableMonitoringModuleWrapper; import java.nio.ByteBuffer; import java.util.Collections; public class LokiAppender extends LokiAppenderConfigurator { + private PatternLayoutEncoder encoder; private LoggingSystem loggingSystem; private TjahziLogger logger; private String logLevelLabel; + private MutableMonitoringModuleWrapper monitoringModuleWrapper; public PatternLayoutEncoder getEncoder() { return encoder; @@ -24,6 +28,19 @@ public class LokiAppender extends LokiAppenderConfigurator { this.encoder = encoder; } + /** + * This is an entry point to set monitoring (statistics) hooks for this appender. This + * API is in beta and is subject to change (and probably will). + */ + public void setMonitoringModule(MonitoringModule monitoringModule) { + monitoringModuleWrapper.setMonitoringModule(monitoringModule); + } + + // @VisibleForTesting + public LoggingSystem getLoggingSystem() { + return loggingSystem; + } + @Override protected void append(ILoggingEvent event) { String logLevel = event.getLevel().toString(); @@ -50,6 +67,7 @@ public class LokiAppender extends LokiAppenderConfigurator { LokiAppenderFactory lokiAppenderFactory = new LokiAppenderFactory(this); loggingSystem = lokiAppenderFactory.createAppender(); logLevelLabel = lokiAppenderFactory.getLogLevelLabel(); + monitoringModuleWrapper = lokiAppenderFactory.getMonitoringModuleWrapper(); logger = loggingSystem.createLogger(); loggingSystem.start(); diff --git a/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppenderFactory.java b/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppenderFactory.java index 03428f4..1a4e961 100644 --- a/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppenderFactory.java +++ b/logback-appender/src/main/java/pl/tkowalcz/tjahzi/logback/LokiAppenderFactory.java @@ -19,6 +19,7 @@ public class LokiAppenderFactory { private final HashMap lokiLabels; private final String logLevelLabel; + private MutableMonitoringModuleWrapper monitoringModuleWrapper; public LokiAppenderFactory(LokiAppenderConfigurator configurator) { this.configurator = configurator; @@ -47,7 +48,7 @@ public class LokiAppenderFactory { .flatMap(header -> Stream.of(header.getName(), header.getValue())) .toArray(String[]::new); - MutableMonitoringModuleWrapper monitoringModuleWrapper = new MutableMonitoringModuleWrapper(); + monitoringModuleWrapper = new MutableMonitoringModuleWrapper(); monitoringModuleWrapper.setMonitoringModule(new StandardMonitoringModule()); NettyHttpClient httpClient = HttpClientFactory @@ -59,7 +60,7 @@ public class LokiAppenderFactory { ); int bufferSizeBytes = configurator.getBufferSizeMegabytes() * LokiAppenderConfigurator.BYTES_IN_MEGABYTE; - if (TjahziInitializer.isCorrectSize(bufferSizeBytes)) { + if (!TjahziInitializer.isCorrectSize(bufferSizeBytes)) { configurator.addWarn( String.format( "Invalid log buffer size %d - using nearest power of two greater than provided value, no less than 1MB. %s\n", @@ -83,4 +84,8 @@ public class LokiAppenderFactory { public String getLogLevelLabel() { return logLevelLabel; } + + public MutableMonitoringModuleWrapper getMonitoringModuleWrapper() { + return monitoringModuleWrapper; + } } diff --git a/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/BufferSizeConfigurationTest.java b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/BufferSizeConfigurationTest.java new file mode 100644 index 0000000..bacb406 --- /dev/null +++ b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/BufferSizeConfigurationTest.java @@ -0,0 +1,58 @@ +package pl.tkowalcz.tjahzi.logback; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; +import pl.tkowalcz.tjahzi.logback.util.TestUtil; + +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThat; + +class BufferSizeConfigurationTest { + + @Test + void shouldUseDefaultWhenLogBufferSizeIsNotSetViaConfiguration() throws Exception { + // Given + URI uri = getClass() + .getClassLoader() + .getResource("appender-test-with-buffer-size-megabytes-unset.xml") + .toURI(); + + // When + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + + context.reset(); + configurator.doConfigure(uri.toURL()); + + // Then + LokiAppender loki = TestUtil.getLokiAppender(context); + assertThat(loki.getLoggingSystem().getLogBufferSize()).isEqualTo(32 * 1024 * 1024); + } + + @Test + void shouldSetLogBufferSizeViaConfiguration() throws Exception { + // Given + URI uri = getClass() + .getClassLoader() + .getResource("appender-test-with-buffer-size-megabytes-set-to-64.xml") + .toURI(); + + // When + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + + context.reset(); + configurator.doConfigure(uri.toURL()); + + // Then + LokiAppender loki = TestUtil.getLokiAppender(context); + assertThat(loki.getLoggingSystem().getLogBufferSize()).isEqualTo(64 * 1024 * 1024); + } +} diff --git a/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LabelFactoryTest.java b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LabelFactoryTest.java new file mode 100644 index 0000000..20bad63 --- /dev/null +++ b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LabelFactoryTest.java @@ -0,0 +1,145 @@ +package pl.tkowalcz.tjahzi.logback; + +import ch.qos.logback.core.ConsoleAppender; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class LabelFactoryTest { + + @Test + void shouldRemoveDuplicatedLabels() { + // Given + Label thisIsADuplicateAndShouldBeDropped = Label.createLabel("ip", "127.0.0.1"); + Label thisShouldStay = Label.createLabel("ip", "10.0.2.34"); + Label thisTooShouldStay = Label.createLabel("hostname", "hostname"); + + LabelFactory labelFactory = new LabelFactory( + new ConsoleAppender<>(), + "log_level", + thisIsADuplicateAndShouldBeDropped, + thisShouldStay, + thisTooShouldStay + ); + + // When + HashMap actual = labelFactory.convertLabelsDroppingInvalid(); + + // Then + assertThat(actual).containsOnly( + thisShouldStay.asEntry(), + thisTooShouldStay.asEntry() + ); + } + + @Test + void shouldSkipLabelsWithInvalidName() { + // Given + Label thisShouldStay = Label.createLabel("ip", "10.0.2.34"); + Label thisShouldStayToo = Label.createLabel("region", "coruscant-west-2"); + Label invalidNameShouldBeDropped = Label.createLabel("host-name", "hostname"); + + LabelFactory labelFactory = new LabelFactory( + new ConsoleAppender<>(), + "log_level", + thisShouldStayToo, + thisShouldStay, + invalidNameShouldBeDropped + ); + + // When + HashMap actual = labelFactory.convertLabelsDroppingInvalid(); + + // Then + assertThat(actual).containsOnly( + thisShouldStay.asEntry(), + thisShouldStayToo.asEntry() + ); + } + + @Test + void shouldAcceptNullLogLevel() { + // Given + Label label1 = Label.createLabel("ip", "10.0.2.34"); + Label label2 = Label.createLabel("region", "coruscant-west-2"); + + LabelFactory labelFactory = new LabelFactory( + new ConsoleAppender<>(), + null, + label2, + label1 + ); + + // When + HashMap actual = labelFactory.convertLabelsDroppingInvalid(); + + // Then + assertThat(actual).containsOnly( + label1.asEntry(), + label2.asEntry() + ); + } + + @Test + void logLevelLabelShouldOverrideConflictingLabel() { + // Given + String logLevelLabel = "log_level"; + + Label thisShouldStay = Label.createLabel("ip", "10.0.2.34"); + Label thisShouldBeRemovedDueToConflict = Label.createLabel(logLevelLabel, "coruscant-west-2"); + + LabelFactory labelFactory = new LabelFactory( + new ConsoleAppender<>(), + logLevelLabel, + thisShouldBeRemovedDueToConflict, + thisShouldStay + ); + + HashMap labels = new HashMap<>( + Map.ofEntries( + thisShouldStay.asEntry(), + thisShouldBeRemovedDueToConflict.asEntry() + ) + ); + + // When + String actual = labelFactory.validateLogLevelLabel(labels); + + // Then + assertThat(labels).containsOnly( + thisShouldStay.asEntry() + ); + + assertThat(actual).isEqualTo(logLevelLabel); + } + + @Test + void shouldDisableLogLevelLabelIfItHasInvalidName() { + // Given + String logLevelLabel = "log-level"; + + LabelFactory labelFactory = new LabelFactory(new ConsoleAppender<>(), logLevelLabel); + + // When + String actual = labelFactory.validateLogLevelLabel(new HashMap<>()); + + // Then + assertThat(actual).isNull(); + } + + @Test + void shouldHandleDisabledLogLevelLabel() { + // Given + String logLevelLabel = null; + LabelFactory labelFactory = new LabelFactory(new ConsoleAppender<>(), logLevelLabel); + + // When + String actual = labelFactory.validateLogLevelLabel(new HashMap<>()); + + // Then + assertThat(actual).isNull(); + } +} diff --git a/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LabelTest.java b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LabelTest.java new file mode 100644 index 0000000..9195cdb --- /dev/null +++ b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LabelTest.java @@ -0,0 +1,21 @@ +package pl.tkowalcz.tjahzi.logback; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class LabelTest { + + @Test + void shouldValidateNamePattern() { + // Given + Label correctLabel = Label.createLabel("aaa_bbb_ccc", "fobar"); + Label tooShortLabel = Label.createLabel("", "foobar"); + Label labelWithInvalidChars = Label.createLabel("log-level", "foobar"); + + // When & Then + assertThat(correctLabel.hasValidName()).isTrue(); + assertThat(tooShortLabel.hasValidName()).isFalse(); + assertThat(labelWithInvalidChars.hasValidName()).isFalse(); + } +} diff --git a/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LogLevelLabelConfigurationTest.java b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LogLevelLabelConfigurationTest.java new file mode 100644 index 0000000..c2d1c1d --- /dev/null +++ b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LogLevelLabelConfigurationTest.java @@ -0,0 +1,141 @@ +package pl.tkowalcz.tjahzi.logback; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import io.restassured.parsing.Parser; +import org.awaitility.Awaitility; +import org.awaitility.Durations; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.net.URI; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; + +@Testcontainers +class LogLevelLabelConfigurationTest { + + @Container + public GenericContainer loki = new GenericContainer("grafana/loki:latest") + .withCommand("-config.file=/etc/loki-config.yaml") + .withClasspathResourceMapping("loki-config.yaml", + "/etc/loki-config.yaml", + BindMode.READ_ONLY) + .waitingFor( + Wait.forHttp("/ready") + .forPort(3100) + ) + .withExposedPorts(3100); + + @Test + void shouldWorkWIthNoLogLevelConfigured() throws Exception { + // Given + System.setProperty("loki.host", loki.getHost()); + System.setProperty("loki.port", loki.getFirstMappedPort().toString()); + + URI uri = getClass() + .getClassLoader() + .getResource("appender-test-with-log-label-unset.xml") + .toURI(); + + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + + context.reset(); + configurator.doConfigure(uri.toURL()); + + // When + Logger logger = context.getLogger(LokiAppenderLargeBatchesTest.class); + logger.info("Test"); + + // Then + RestAssured.port = loki.getFirstMappedPort(); + RestAssured.baseURI = "http://" + loki.getHost(); + RestAssured.registerParser("text/plain", Parser.JSON); + + Awaitility + .await() + .atMost(Durations.TEN_SECONDS) + .pollInterval(Durations.ONE_SECOND) + .ignoreExceptions() + .untilAsserted(() -> { + given() + .contentType(ContentType.URLENC) + .urlEncodingEnabled(false) + .formParam("query=%7Bserver%3D%22127.0.0.1%22%7D") + .when() + .get("/loki/api/v1/query_range") + .then() + .log() + .all() + .statusCode(200) + .body("status", equalTo("success")) + .body("data.result.size()", equalTo(1)) + .body("data.result[0].stream.server", equalTo("127.0.0.1")) + .body("data.result[0].stream.log_level", nullValue()); + }); + } + + @Test + void shouldSendLogLevelAsConfigured() throws Exception { + // Given + System.setProperty("loki.host", loki.getHost()); + System.setProperty("loki.port", loki.getFirstMappedPort().toString()); + + URI uri = getClass() + .getClassLoader() + .getResource("appender-test-with-log-label-set.xml") + .toURI(); + + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + + context.reset(); + configurator.doConfigure(uri.toURL()); + + // When + Logger logger = context.getLogger(LokiAppenderLargeBatchesTest.class); + logger.info("Test"); + + // Then + RestAssured.port = loki.getFirstMappedPort(); + RestAssured.baseURI = "http://" + loki.getHost(); + RestAssured.registerParser("text/plain", Parser.JSON); + + Awaitility + .await() + .atMost(Durations.TEN_SECONDS) + .pollInterval(Durations.ONE_SECOND) + .ignoreExceptions() + .untilAsserted(() -> { + given() + .contentType(ContentType.URLENC) + .urlEncodingEnabled(false) + .formParam("query=%7Bserver%3D%22127.0.0.1%22%7D") + .when() + .get("/loki/api/v1/query_range") + .then() + .log() + .all() + .statusCode(200) + .body("status", equalTo("success")) + .body("data.result.size()", equalTo(1)) + .body("data.result[0].stream.server", equalTo("127.0.0.1")) + .body("data.result[0].stream.log_level", equalTo("INFO")); + }); + } +} diff --git a/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LokiAppenderMonitoringTest.java b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LokiAppenderMonitoringTest.java new file mode 100644 index 0000000..68b28a6 --- /dev/null +++ b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LokiAppenderMonitoringTest.java @@ -0,0 +1,68 @@ +package pl.tkowalcz.tjahzi.logback; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import com.codahale.metrics.Counter; +import com.codahale.metrics.MetricRegistry; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; +import org.testcontainers.junit.jupiter.Testcontainers; +import pl.tkowalcz.tjahzi.logback.util.TestUtil; +import pl.tkowalcz.tjahzi.stats.DropwizardMonitoringModule; + +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +@Testcontainers +class LokiAppenderMonitoringTest { + + @Test + void shouldInjectMonitoringAndUseIt() throws Exception { + // Given + System.setProperty("loki.host", "somewhere"); + System.setProperty("loki.port", "42"); + + URI uri = getClass() + .getClassLoader() + .getResource("basic-appender-test-configuration.xml") + .toURI(); + + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + + context.reset(); + configurator.doConfigure(uri.toURL()); + + LokiAppender loki = TestUtil.getLokiAppender(context); + + MetricRegistry metricRegistry = new MetricRegistry(); + loki.setMonitoringModule( + new DropwizardMonitoringModule( + metricRegistry, + "appender.loki" + ) + ); + + // When + Logger logger = context.getLogger(LokiAppenderLargeBatchesTest.class); + logger.info("Test test test"); + + // Then + assertThat(metricRegistry.getMetrics()).isNotEmpty(); + + Counter connectAttemptsCounter = metricRegistry + .getCounters() + .get("appender.loki.httpConnectAttempts"); + assertThat(connectAttemptsCounter).isNotNull(); + + await().untilAsserted(() -> { + assertThat(connectAttemptsCounter.getCount()).isEqualTo(1); + } + ); + } +} diff --git a/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LokiAppenderShutdownTest.java b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LokiAppenderShutdownTest.java new file mode 100644 index 0000000..0942876 --- /dev/null +++ b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/LokiAppenderShutdownTest.java @@ -0,0 +1,84 @@ +package pl.tkowalcz.tjahzi.logback; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThat; + +@Testcontainers +class LokiAppenderShutdownTest { + + @Container + public GenericContainer loki = new GenericContainer("grafana/loki:latest") + .withCommand("-config.file=/etc/loki-config.yaml") + .withClasspathResourceMapping("loki-config.yaml", + "/etc/loki-config.yaml", + BindMode.READ_ONLY) + .waitingFor( + Wait.forHttp("/ready") + .forPort(3100) + ) + .withExposedPorts(3100); + + @Test + void shouldSendData() throws Exception { + // Given + System.setProperty("loki.host", loki.getHost()); + System.setProperty("loki.port", loki.getFirstMappedPort().toString()); + + URI uri = getClass() + .getClassLoader() + .getResource("basic-appender-test-configuration.xml") + .toURI(); + + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(context); + + context.reset(); + configurator.doConfigure(uri.toURL()); + + // Verify our assumptions that we can find threads started by Tjahzi + Awaitility.await().untilAsserted(() -> { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); + + assertThat(threadInfos) + .extracting(ThreadInfo::getThreadName) + .contains( + "ReadingLogBufferAndSendingHttp", + "tjahzi-worker" + ); + }); + + // When + context.stop(); + + // Then + Awaitility.await().untilAsserted(() -> { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); + + assertThat(threadInfos) + .extracting(ThreadInfo::getThreadName) + .doesNotContain( + "ReadingLogBufferAndSendingHttp", + "tjahzi-worker" + ); + }); + } +} diff --git a/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/util/TestUtil.java b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/util/TestUtil.java new file mode 100644 index 0000000..42abe13 --- /dev/null +++ b/logback-appender/src/test/java/pl/tkowalcz/tjahzi/logback/util/TestUtil.java @@ -0,0 +1,26 @@ +package pl.tkowalcz.tjahzi.logback.util; + +import ch.qos.logback.classic.LoggerContext; +import pl.tkowalcz.tjahzi.logback.LokiAppender; + +import java.util.Spliterators; +import java.util.stream.StreamSupport; + +public class TestUtil { + + public static LokiAppender getLokiAppender(LoggerContext context) { + return (LokiAppender) context + .getLoggerList() + .stream() + .flatMap( + logger -> + StreamSupport.stream( + Spliterators.spliteratorUnknownSize(logger.iteratorForAppenders(), 0), + false + ) + ) + .filter(appender -> appender instanceof LokiAppender) + .findAny() + .orElseThrow(() -> new AssertionError("Expected to find Loki appender")); + } +} diff --git a/logback-appender/src/test/resources/appender-test-large-batches.xml b/logback-appender/src/test/resources/appender-test-large-batches.xml index 6d87c8e..4590b57 100644 --- a/logback-appender/src/test/resources/appender-test-large-batches.xml +++ b/logback-appender/src/test/resources/appender-test-large-batches.xml @@ -2,7 +2,7 @@ 2 - 10000 + 10 1048576 ${loki.host}