diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/http/BootstrapUtil.java b/core/src/main/java/pl/tkowalcz/tjahzi/http/BootstrapUtil.java index f65d7e2..ee8d124 100644 --- a/core/src/main/java/pl/tkowalcz/tjahzi/http/BootstrapUtil.java +++ b/core/src/main/java/pl/tkowalcz/tjahzi/http/BootstrapUtil.java @@ -15,8 +15,6 @@ import javax.net.ssl.SSLException; public class BootstrapUtil { - public static final int HTTPS_PORT = 443; - public static ChannelFuture initConnection( EventLoopGroup group, ClientConfiguration clientConfiguration, @@ -25,7 +23,7 @@ public class BootstrapUtil { Bootstrap bootstrap = new Bootstrap(); SslContext sslContext = null; - if (isSsl(clientConfiguration)) { + if (clientConfiguration.isUseSSL()) { sslContext = createSslContext(); } @@ -49,10 +47,6 @@ public class BootstrapUtil { ).connect(); } - private static boolean isSsl(ClientConfiguration clientConfiguration) { - return clientConfiguration.getPort() == HTTPS_PORT; - } - private static SslContext createSslContext() { try { return SslContextBuilder.forClient() diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfiguration.java b/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfiguration.java index 68242e7..185749b 100644 --- a/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfiguration.java +++ b/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfiguration.java @@ -5,6 +5,7 @@ public class ClientConfiguration { private final String logEndpoint; private final String host; private final int port; + private final boolean useSSL; private final String username; private final String password; @@ -19,6 +20,7 @@ public class ClientConfiguration { String logEndpoint, String host, int port, + boolean useSSL, String username, String password, int connectionTimeoutMillis, @@ -28,6 +30,7 @@ public class ClientConfiguration { this.logEndpoint = logEndpoint; this.host = host; this.port = port; + this.useSSL = useSSL; this.username = username; this.password = password; @@ -51,6 +54,10 @@ public class ClientConfiguration { return port; } + public boolean isUseSSL() { + return useSSL; + } + public String getUsername() { return username; } diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfigurationBuilder.java b/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfigurationBuilder.java index f0fc73c..8fad5f0 100644 --- a/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfigurationBuilder.java +++ b/core/src/main/java/pl/tkowalcz/tjahzi/http/ClientConfigurationBuilder.java @@ -1,7 +1,15 @@ package pl.tkowalcz.tjahzi.http; +import java.net.MalformedURLException; +import java.net.URL; + public class ClientConfigurationBuilder { + public static final int HTTPS_PORT = 443; + public static final int HTTP_PORT = 80; + public static final String HTTPS_STRING = "https"; + public static final String HTTP_STRING = "http"; + public static final String DEFAULT_LOG_ENDPOINT = "/loki/api/v1/push"; public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 5_000; @@ -11,10 +19,15 @@ public class ClientConfigurationBuilder { public static final int DEFAULT_MAX_RETRIES = 0; - private String logEndpoint = DEFAULT_LOG_ENDPOINT; + private String logEndpoint; + + private String url; + private String host; private int port; + private boolean useSSL; + private String username; private String password; @@ -39,6 +52,11 @@ public class ClientConfigurationBuilder { return this; } + public ClientConfigurationBuilder withUseSSL(boolean useSSL) { + this.useSSL = useSSL; + return this; + } + public ClientConfigurationBuilder withUsername(String username) { this.username = username; return this; @@ -69,15 +87,29 @@ public class ClientConfigurationBuilder { return this; } + public ClientConfigurationBuilder withUrl(String url) { + this.url = url; + return this; + } + public ClientConfiguration build() { if (maxRequestsInFlight <= 0) { throw new IllegalArgumentException("Property maxRequestsInFlight must be greater than 0"); } - return new ClientConfiguration( - logEndpoint, + ConnectionParams connectionParams = ConnectionParamsFactory.create( + url, host, port, + logEndpoint, + useSSL + ); + + return new ClientConfiguration( + connectionParams.getLogEndpoint(), + connectionParams.getHost(), + connectionParams.getPort(), + connectionParams.isUseSSL(), username, password, connectionTimeoutMillis, diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/http/ConnectionParams.java b/core/src/main/java/pl/tkowalcz/tjahzi/http/ConnectionParams.java new file mode 100644 index 0000000..484f354 --- /dev/null +++ b/core/src/main/java/pl/tkowalcz/tjahzi/http/ConnectionParams.java @@ -0,0 +1,36 @@ +package pl.tkowalcz.tjahzi.http; + +import java.net.MalformedURLException; +import java.net.URL; + +public class ConnectionParams { + + private final String host; + private final int port; + + private final String logEndpoint; + private final boolean useSSL; + + public ConnectionParams(String host, int port, String logEndpoint, boolean useSSL) { + this.host = host; + this.port = port; + this.logEndpoint = logEndpoint; + this.useSSL = useSSL; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getLogEndpoint() { + return logEndpoint; + } + + public boolean isUseSSL() { + return useSSL; + } +} diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/http/ConnectionParamsFactory.java b/core/src/main/java/pl/tkowalcz/tjahzi/http/ConnectionParamsFactory.java new file mode 100644 index 0000000..47ab96b --- /dev/null +++ b/core/src/main/java/pl/tkowalcz/tjahzi/http/ConnectionParamsFactory.java @@ -0,0 +1,110 @@ +package pl.tkowalcz.tjahzi.http; + +import java.net.MalformedURLException; +import java.net.URL; + +public class ConnectionParamsFactory { + + public static final int HTTPS_PORT = 443; + public static final int HTTP_PORT = 80; + public static final String HTTPS_STRING = "https"; + public static final String HTTP_STRING = "http"; + + public static final String DEFAULT_LOG_ENDPOINT = "/loki/api/v1/push"; + + public static ConnectionParams create(String url, String host, int port, String logEndpoint, boolean useSSL) { + if (url != null && host != null) { + throw new IllegalArgumentException("Only one of 'url' or 'host' can be configured. " + + "Current configuration sets url to '" + url + "' and host to '" + host + "'"); + } + + if (url != null && port != 0) { + throw new IllegalArgumentException("Only one of 'url' or 'port' can be configured. " + + "If using url you must configure port as part of url string. Current configuration sets url to '" + url + "' and port to '" + port + "'"); + } + + if (url != null && useSSL) { + throw new IllegalArgumentException("Only one of 'url' or 'useSSL' can be configured. " + + "Please use 'https' prefix in url to enable SSL. Current configuration sets url to '" + url + "' and useSSL to '" + useSSL + "'"); + } + + if (url == null && host == null) { + throw new IllegalArgumentException("One of 'url' or 'host' must be configured."); + } + + if (host != null) { + return validateAndConfigureConnectionParametersNoUrl(host, port, logEndpoint, useSSL); + } + + return validateAndConfigureConnectionParametersWithUrl(url, logEndpoint, useSSL); + } + + private static ConnectionParams validateAndConfigureConnectionParametersNoUrl(String host, int port, String logEndpoint, boolean useSSL) { + if (logEndpoint == null) { + logEndpoint = DEFAULT_LOG_ENDPOINT; + } + + if (!useSSL) { + useSSL = port == HTTPS_PORT; + } + + return new ConnectionParams(host, port, logEndpoint, useSSL); + } + + private static ConnectionParams validateAndConfigureConnectionParametersWithUrl(String url, String logEndpoint, boolean useSSL) { + try { + URL parsedUrl = new URL(url); + String host = parsedUrl.getHost(); + int port = configurePort(parsedUrl); + + useSSL = validateProtocolSchemeAndConfigureSSL(parsedUrl); + logEndpoint = configureLogEndpoint(parsedUrl, logEndpoint); + + return new ConnectionParams(host, port, logEndpoint, useSSL); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Creating configuration with Loki URL failed, url: '" + url + "'", e); + } + } + + private static String configureLogEndpoint(URL parsedUrl, String logEndpoint) { + if (logEndpoint != null) { + throw new IllegalArgumentException("Only one of 'url' or 'logEndpoint' can be configured. " + + "Embed log endpoint path into url. " + + "Log endpoint: '" + logEndpoint + "', url: '" + parsedUrl + "'"); + } + + if (parsedUrl.getPath() != null && !parsedUrl.getPath().isEmpty()) { + return parsedUrl.getPath(); + } else { + return DEFAULT_LOG_ENDPOINT; + } + } + + private static int configurePort(URL parsedUrl) { + int port = parsedUrl.getPort(); + + if (port == -1) { + if (HTTPS_STRING.equalsIgnoreCase(parsedUrl.getProtocol())) { + return HTTPS_PORT; + } else { + return HTTP_PORT; + } + } + + return port; + } + + private static boolean validateProtocolSchemeAndConfigureSSL(URL parsedUrl) { + String protocolScheme = parsedUrl.getProtocol(); + + if (HTTPS_STRING.equalsIgnoreCase(protocolScheme)) { + return true; + } + + if (HTTP_STRING.equalsIgnoreCase(protocolScheme)) { + return false; + } + + throw new IllegalArgumentException("Unknown protocol scheme, must be one of 'https' or 'http' (case insensitive). Provided: '" + protocolScheme + "'"); + } +} diff --git a/core/src/test/java/pl/tkowalcz/tjahzi/http/ClientConfigurationBuilderTest.java b/core/src/test/java/pl/tkowalcz/tjahzi/http/ClientConfigurationBuilderTest.java new file mode 100644 index 0000000..516e869 --- /dev/null +++ b/core/src/test/java/pl/tkowalcz/tjahzi/http/ClientConfigurationBuilderTest.java @@ -0,0 +1,141 @@ +package pl.tkowalcz.tjahzi.http; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class ClientConfigurationBuilderTest { + + static Stream validConfigOptions() { + return Stream.of( + Arguments.of( + "Should take arguments from url", + "https://example.net:8443/monitoring/loki/api/v1/push", + "example.net", + 8443, + "/monitoring/loki/api/v1/push", + true + ), + Arguments.of( + "Should not enable ssl if prefix is not https", + "http://example.net:8443/monitoring/loki/api/v1/push", + "example.net", + 8443, + "/monitoring/loki/api/v1/push", + false + ), + Arguments.of( + "Should use default logEndpoint if none is provided", + "http://example.net:8443", + "example.net", + 8443, + "/loki/api/v1/push", + false + ), + Arguments.of( + "Should use default http port if none is provided", + "http://example.net", + "example.net", + 80, + "/loki/api/v1/push", + false + ), + Arguments.of( + "Should use default https port if none is provided", + "https://example.net", + "example.net", + 443, + "/loki/api/v1/push", + true + ) + ); + } + + static Stream invalidConfigOptions() { + return Stream.of( + Arguments.of( + "Should reject unknown protocol", + ClientConfiguration.builder().withUrl("file://example.net"), + "Unknown protocol scheme, must be one of 'https' or 'http' (case insensitive). Provided: 'file'" + ), + Arguments.of( + "Should not allow url and host", + ClientConfiguration.builder().withUrl("file://example.net").withHost("blah"), + "Only one of 'url' or 'host' can be configured. Current configuration sets url to 'file://example.net' and host to 'blah'" + ), + Arguments.of( + "Should not allow url and port", + ClientConfiguration.builder().withUrl("file://example.net").withPort(10), + "Only one of 'url' or 'port' can be configured. If using url you must configure port as part of url string. Current configuration sets url to 'file://example.net' and port to '10'" + ), + Arguments.of( + "Should not allow url and useSSL to be defined", + ClientConfiguration.builder().withUrl("file://example.net").withUseSSL(true), + "Only one of 'url' or 'useSSL' can be configured. Please use 'https' prefix in url to enable SSL. Current configuration sets url to 'file://example.net' and useSSL to 'true'" + ), + Arguments.of( + "Should require max requests in flight to be positive", + ClientConfiguration.builder().withHost("example.net").withMaxRequestsInFlight(-10), + "Property maxRequestsInFlight must be greater than 0" + ), + Arguments.of( + "Should require at least url or host to be specified", + ClientConfiguration.builder(), + "One of 'url' or 'host' must be configured." + ), + Arguments.of( + "Should not allow url and logEndpoint to be specified at the same time", + ClientConfiguration.builder().withUrl("http://example.net").withLogEndpoint("blah") , + "Only one of 'url' or 'logEndpoint' can be configured. Embed log endpoint path into url. Log endpoint: 'blah', url: 'http://example.net'" + ) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("validConfigOptions") + void shouldCorrectlyInterpretUrl( + String testCase, + String url, + String expectedHost, + int expectedPort, + String expectedLogEndpoint, + boolean expectedUseSSL + ) { + // Given + ClientConfigurationBuilder configurationBuilder = new ClientConfigurationBuilder(); + + // When + ClientConfiguration configuration = configurationBuilder + .withUrl(url) + .build(); + + // Then + assertThat(configuration.getHost()).isEqualTo(expectedHost); + assertThat(configuration.getPort()).isEqualTo(expectedPort); + assertThat(configuration.getLogEndpoint()).isEqualTo(expectedLogEndpoint); + assertThat(configuration.isUseSSL()).isEqualTo(expectedUseSSL); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("invalidConfigOptions") + void shouldRejectUnknownProtocolScheme( + String testCase, + ClientConfigurationBuilder clientConfigurationBuilder, + String expectedExceptionMessage + ) { + // When + IllegalArgumentException exception = Assertions.assertThrows( + IllegalArgumentException.class, + clientConfigurationBuilder::build + ); + + // Then + assertThat(exception).hasMessage(expectedExceptionMessage); + } +} diff --git a/core/src/test/java/pl/tkowalcz/tjahzi/http/HttpHeadersFactoryTest.java b/core/src/test/java/pl/tkowalcz/tjahzi/http/HttpHeadersFactoryTest.java index 444102b..efa7112 100644 --- a/core/src/test/java/pl/tkowalcz/tjahzi/http/HttpHeadersFactoryTest.java +++ b/core/src/test/java/pl/tkowalcz/tjahzi/http/HttpHeadersFactoryTest.java @@ -12,6 +12,7 @@ class HttpHeadersFactoryTest { void shouldAddAuthHeaderIfCredentialsAreProvided_NoAdditionalHeaders() { // Given ClientConfiguration configuration = ClientConfiguration.builder() + .withHost("blah") .withUsername("foob") .withPassword("ar") .build(); @@ -36,6 +37,7 @@ class HttpHeadersFactoryTest { void shouldAddAuthHeaderIfCredentialsAreProvided_WithAdditionalHeaders() { // Given ClientConfiguration configuration = ClientConfiguration.builder() + .withHost("blah") .withUsername("foob") .withPassword("ar") .build(); @@ -65,6 +67,7 @@ class HttpHeadersFactoryTest { void shouldNotAddAuthHeader_NoAdditionalHeaders() { // Given ClientConfiguration configuration = ClientConfiguration.builder() + .withHost("blah") .build(); String[] additionalHeaders = new String[0]; @@ -84,6 +87,7 @@ class HttpHeadersFactoryTest { void shouldNOtAddAuthHeader_WithAdditionalHeaders() { // Given ClientConfiguration configuration = ClientConfiguration.builder() + .withHost("blah") .build(); String[] additionalHeaders = new String[]{ diff --git a/log4j2-appender/README.md b/log4j2-appender/README.md index ffdefb1..9eec9c7 100644 --- a/log4j2-appender/README.md +++ b/log4j2-appender/README.md @@ -54,7 +54,7 @@ Log4j2 appender seemed like a good first. ### Note on Loki HTTP endpoint and host/port configuration -Tjahzi sends `POST` requests to `/loki/api/v1/push` HTTP endpoint. Specifying +Tjahzi by default sends `POST` requests to `/loki/api/v1/push` HTTP endpoint. Specifying e.g. `loki.mydomain.com3100` will configure the appender to call to URL: `http://loki.mydomain.com:3100/loki/api/v1/push`. @@ -114,6 +114,58 @@ attribute of configuration so that the appender can be found. ``` +### Configuring connection parameters individually and using URL + +At a minimum Tjahzi needs host and port configuration to connect to Loki: + +```xml +example.com +3100 +``` + +If port is equal to `443` then SSL will be used. You can also configure SSL manually: + +```xml +example.com +3100 + +true +``` + +You can also override the default endpoint to which Tjahzi sends data. This can be useful if Loki is behind reverse proxy and +additional path mapping is used: + +```xml +example.com +3100 + +/monitoring/loki/api/v1/push +``` + +All these parameters can be configured in one place using URL: + +```xml +https://example.com:56654/monitoring/loki/api/v1/push +``` + +Note that all other tags (host, port, useSSL, logEndpoint) cannot be used when using URL. + +| Section | Default | Comment | +|----------|----------------------------|-------------------------------------------------------------------------------------| +| Protocol | None (must be provided) | Supported protocols are `http` and `https`. Https is equvalent to setting `useUSSL` | +| Host | None (must be provided) | | +| Port | 80 for http, 443 for https | You can use any port and SSL will still be used if protocol is set to https | +| Path | '/loki/api/v1/push' | | + +Some examples of correct URLs: + +```xml +http://example.com +https://example.com:56654 +http://example.com/monitoring/loki/api/v1/push +https://example.com:3100/monitoring/foo/bar +``` + ### Lookups / variable substitution Contents of the properties @@ -190,21 +242,34 @@ appender.loki.labels[1].value=log4j Let's go through the example config used in previous sections and analyze configuration options (**Note: Tags are case-insensitive**). -#### Host (required) +#### Host (required unless URL is specified) Network host address of Loki instance. Either IP address or host name. It will be passed to Netty and end up being resolved by call to `InetSocketAddress.createUnresolved`. -#### Port (required) +#### Port (required unless URL is specified) Port used for connecting to running Loki. Tjahzi by default uses plain HTTP but if the port is `443` then it will automatically switch to HTTPS. -#### Username +#### useSSL (optional) + +Enable secure (HTTPS) communication regardless of configured port number. + +#### logEndpoint (optional) + +Overrides the default endpoint to which Tjahzi sends data. This can be useful if Loki is behind reverse proxy and +additional path mapping is used. + +#### URL (optional - replaces usage of host, port, useSSL, logEndpoint) + +Configure connection in one place instead of using host, port etc. See [this section](#configuring-connection-parameters-individually-and-using-URL). + +#### Username (optional) Username for HTTP basic auth. -#### Password +#### Password (optional) Password for HTTP basic auth. diff --git a/log4j2-appender/pom.xml b/log4j2-appender/pom.xml index 1165b64..1010ed7 100644 --- a/log4j2-appender/pom.xml +++ b/log4j2-appender/pom.xml @@ -102,6 +102,12 @@ 1.14.3 test + + org.testcontainers + nginx + 1.14.3 + test + 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 6f515fd..1518fe1 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 @@ -4,7 +4,6 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.status.StatusLogger; import pl.tkowalcz.tjahzi.LoggingSystem; import pl.tkowalcz.tjahzi.TjahziInitializer; @@ -37,13 +36,20 @@ public class LokiAppenderBuilder> extends Abstr private static final int BYTES_IN_KILOBYTE = 1024; @PluginBuilderAttribute - @Required(message = "No Loki address provided for LokiAppender") + private String url; + + @PluginBuilderAttribute + private String logEndpoint; + + @PluginBuilderAttribute private String host; @PluginBuilderAttribute - @Required(message = "No Loki port provided for LokiAppender") private int port; + @PluginBuilderAttribute + private boolean useSSL; + @PluginBuilderAttribute private String username; @@ -95,8 +101,11 @@ public class LokiAppenderBuilder> extends Abstr @Override public LokiAppender build() { ClientConfiguration configurationBuilder = ClientConfiguration.builder() + .withUrl(url) + .withLogEndpoint(logEndpoint) .withHost(host) .withPort(port) + .withUseSSL(useSSL) .withUsername(username) .withPassword(password) .withConnectionTimeoutMillis(connectTimeoutMillis) @@ -168,6 +177,46 @@ public class LokiAppenderBuilder> extends Abstr return host; } + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getLogEndpoint() { + return logEndpoint; + } + + public void setLogEndpoint(String logEndpoint) { + this.logEndpoint = logEndpoint; + } + + public boolean isUseSSL() { + return useSSL; + } + + public void setUseSSL(boolean useSSL) { + this.useSSL = useSSL; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + public int getConnectTimeoutMillis() { return connectTimeoutMillis; } diff --git a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/BufferSizeConfigurationTest.java b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/BufferSizeConfigurationTest.java index ce47eb7..91cd288 100644 --- a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/BufferSizeConfigurationTest.java +++ b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/BufferSizeConfigurationTest.java @@ -3,15 +3,13 @@ package pl.tkowalcz.tjahzi.log4j2; import org.apache.logging.log4j.core.LoggerContext; import org.junit.jupiter.api.Test; -import java.net.URISyntaxException; - import static org.assertj.core.api.Assertions.assertThat; import static pl.tkowalcz.tjahzi.log4j2.infra.IntegrationTest.loadConfig; class BufferSizeConfigurationTest { @Test - void shouldUseDefaultWhenLogBufferSizeIsNotSetViaConfiguration() throws URISyntaxException { + void shouldUseDefaultWhenLogBufferSizeIsNotSetViaConfiguration() { // When LoggerContext context = loadConfig("appender-test-with-buffer-size-megabytes-unset.xml"); @@ -22,7 +20,7 @@ class BufferSizeConfigurationTest { } @Test - void shouldSetLogBufferSizeViaConfiguration() throws URISyntaxException { + void shouldSetLogBufferSizeViaConfiguration() { // When LoggerContext context = loadConfig("appender-test-with-buffer-size-megabytes-set-to-64.xml"); diff --git a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderHttpsCustomEndpointTest.java b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderHttpsCustomEndpointTest.java new file mode 100644 index 0000000..12cd200 --- /dev/null +++ b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderHttpsCustomEndpointTest.java @@ -0,0 +1,75 @@ +package pl.tkowalcz.tjahzi.log4j2; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.NginxContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.junit.jupiter.Container; +import pl.tkowalcz.tjahzi.log4j2.infra.IntegrationTest; +import pl.tkowalcz.tjahzi.log4j2.infra.LokiAssert; + +import static org.hamcrest.CoreMatchers.*; + +class LokiAppenderHttpsCustomEndpointTest extends IntegrationTest { + + @Container + public NginxContainer nginx = new NginxContainer<>("nginx:latest") + .withNetwork(network) + .withClasspathResourceMapping("loki.reverse.nginx.conf", + "/etc/nginx/conf.d/loki.reverse.conf", + BindMode.READ_ONLY + ) + .withClasspathResourceMapping("nginx/nginx-selfsigned.crt", + "/etc/nginx/nginx-selfsigned.crt", + BindMode.READ_ONLY + ) + .withClasspathResourceMapping("nginx/nginx-selfsigned.key", + "/etc/nginx/nginx-selfsigned.key", + BindMode.READ_ONLY + ) + .withClasspathResourceMapping("nginx/passwords", + "/etc/nginx/passwords", + BindMode.READ_ONLY + ) + .waitingFor(new HttpWaitStrategy()) + .withExposedPorts(81); + + @Override + @BeforeEach + public void setUp() { + System.setProperty("loki.host", nginx.getHost()); + System.setProperty("loki.port", nginx.getFirstMappedPort().toString()); + } + + @Test + void shouldSendData() { + // Given + loadConfig("appender-test-custom-endpoint-configuration.xml"); + Logger logger = LogManager.getLogger(LokiAppenderHttpsCustomEndpointTest.class); + + String expectedLogLine = "Hello World"; + + // When + logger.info(expectedLogLine); + + // Then + LokiAssert.assertThat(loki) + .returns(response -> response + .body("data.result.size()", equalTo(1)) + .body("data.result[0].stream.server", equalTo("127.0.0.1")) + .body( + "data.result.values", + hasItems( + hasItems( + hasItems( + containsString("INFO LokiAppenderHttpsCustomEndpointTest - " + expectedLogLine) + ) + ) + ) + ) + ); + } +} diff --git a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderHttpsUrlConnectionReverseProxyTest.java b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderHttpsUrlConnectionReverseProxyTest.java new file mode 100644 index 0000000..d5d2dd3 --- /dev/null +++ b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/LokiAppenderHttpsUrlConnectionReverseProxyTest.java @@ -0,0 +1,75 @@ +package pl.tkowalcz.tjahzi.log4j2; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.NginxContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.junit.jupiter.Container; +import pl.tkowalcz.tjahzi.log4j2.infra.IntegrationTest; +import pl.tkowalcz.tjahzi.log4j2.infra.LokiAssert; + +import static org.hamcrest.CoreMatchers.*; + +class LokiAppenderHttpsUrlConnectionReverseProxyTest extends IntegrationTest { + + @Container + public NginxContainer nginx = new NginxContainer<>("nginx:latest") + .withNetwork(network) + .withClasspathResourceMapping("loki.reverse.nginx.conf", + "/etc/nginx/conf.d/loki.reverse.conf", + BindMode.READ_ONLY + ) + .withClasspathResourceMapping("nginx/nginx-selfsigned.crt", + "/etc/nginx/nginx-selfsigned.crt", + BindMode.READ_ONLY + ) + .withClasspathResourceMapping("nginx/nginx-selfsigned.key", + "/etc/nginx/nginx-selfsigned.key", + BindMode.READ_ONLY + ) + .withClasspathResourceMapping("nginx/passwords", + "/etc/nginx/passwords", + BindMode.READ_ONLY + ) + .waitingFor(new HttpWaitStrategy()) + .withExposedPorts(81); + + @Override + @BeforeEach + public void setUp() { + System.setProperty("loki.host", nginx.getHost()); + System.setProperty("loki.port", nginx.getFirstMappedPort().toString()); + } + + @Test + void shouldSendData() { + // Given + loadConfig("appender-test-url-configuration.xml"); + Logger logger = LogManager.getLogger(LokiAppenderHttpsUrlConnectionReverseProxyTest.class); + + String expectedLogLine = "Hello World"; + + // When + logger.info(expectedLogLine); + + // Then + LokiAssert.assertThat(loki) + .returns(response -> response + .body("data.result.size()", equalTo(1)) + .body("data.result[0].stream.server", equalTo("127.0.0.1")) + .body( + "data.result.values", + hasItems( + hasItems( + hasItems( + containsString("INFO LokiAppenderHttpsUrlConnectionReverseProxyTest - " + expectedLogLine) + ) + ) + ) + ) + ); + } +} diff --git a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/infra/IntegrationTest.java b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/infra/IntegrationTest.java index b2cf198..e6e1055 100644 --- a/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/infra/IntegrationTest.java +++ b/log4j2-appender/src/test/java/pl/tkowalcz/tjahzi/log4j2/infra/IntegrationTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -17,8 +18,12 @@ import java.net.URL; @Testcontainers public class IntegrationTest { + public static Network network = Network.newNetwork(); + @Container public GenericContainer loki = new GenericContainer("grafana/loki:2.3.0") + .withNetwork(network) + .withNetworkAliases("loki") .withCommand("-config.file=/etc/loki-config.yaml") .withClasspathResourceMapping("loki-config.yaml", "/etc/loki-config.yaml", @@ -31,7 +36,7 @@ public class IntegrationTest { .withExposedPorts(3100); @BeforeEach - void setUp() { + public void setUp() { System.setProperty("loki.host", loki.getHost()); System.setProperty("loki.port", loki.getFirstMappedPort().toString()); } diff --git a/log4j2-appender/src/test/resources/appender-test-custom-endpoint-configuration.xml b/log4j2-appender/src/test/resources/appender-test-custom-endpoint-configuration.xml new file mode 100644 index 0000000..0024917 --- /dev/null +++ b/log4j2-appender/src/test/resources/appender-test-custom-endpoint-configuration.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + ${sys:loki.host} + ${sys:loki.port} + + true + /monitoring/loki/api/v1/push + + tjahzi + tjahzi + + + + + [%t] %d{DEFAULT} %5p %c{1} - %m%n%exception{full} + + +
+