diff --git a/.yamllint b/.yamllint
index 9d1b12e..c9f12ef 100644
--- a/.yamllint
+++ b/.yamllint
@@ -5,6 +5,3 @@ rules:
# no need for document start
document-start: disable
-
- # line length is not important
- line-length: disable
diff --git a/README.md b/README.md
index 9cf902e..620914e 100644
--- a/README.md
+++ b/README.md
@@ -1,176 +1,17 @@
# Micronaut Logging support
-This branch is for Micronaut 2.x, for 3.x see wip branch [3.x](../../tree/3.x).
-
## Features
-### Default logback.xml
+* [set log level based on MDC values](docs/features/logback_mdc_level.md)
+* [add default xml](docs/features/logback_default.md)
+* [preconfigured appender for different environments](docs/features/logback_appender.md)
+* [set log level based on HTTP request header](docs/features/http_log_level.md)
+* [add HTTP headers to MDC](docs/features/http_mdc_headers.md)
+* [add authentication information from HTTP request to MDC](docs/features/http_mdc_authentication.md)
-If no `logback.xml` by user is provided a default [logback.xml](src/main/resources/io/kokuwa/logback/logback-default.xml) is loaded. Otherwise use custom [logback.xml](src/main/resources/io/kokuwa/logback/logback-example.xml):
+## Development
-```xml
-
-
-
-
-
-
-
-
-```
-
-### Available Appender
-
-* console with jansi for developers
-* gcp logging format (with support for error reporting)
-* json
-
-### AutoSelect appender logback.xml
-
-1. if `LOGBACK_APPENDER` is set this appender will be used
-2. if GCP is detected gcp appender will be used
-3. if Kubernetes is detected json appender will be used
-4. console appender else
-
-*IMPORTENT*: only works without custom `logback.xml`
-
-### Set log level based on MDC values
-
-Configuration:
-
-* *enabled*: enable MDC filter (`true` is default)
-* *key*: MDC key, is optional (will use name instead, see example `user` below)
-* *level*: log level to use (`TRACE` is default)
-* *loggers*: passlist of logger names, matches all loggers if empty
-* *values*: values for matching MDC key, matches all values if empty
-
-Example for setting different values for different values/logger:
-
-```yaml
-logger:
- levels:
- io.kokuwa: INFO
- mdc:
- gateway-debug:
- key: gateway
- level: DEBUG
- loggers:
- - io.kokuwa
- values:
- - 6a1bae7f-eb6c-4c81-af9d-dc15396584e2
- - fb3318f1-2c73-48e9-acd4-a2be3c9f9256
- gateway-trace:
- key: gateway
- level: TRACE
- loggers:
- - io.kokuwa
- - io.micronaut
- values:
- - 257802b2-22fe-4dcc-bb99-c1db2a47861f
-```
-
-Example for omiting level and key:
-
-```yaml
-logger:
- levels:
- io.kokuwa: INFO
- mdc:
- gateway:
- loggers:
- - io.kokuwa
- values:
- - 257802b2-22fe-4dcc-bb99-c1db2a47861f
- - 0a44738b-0c3a-4798-8210-2495485f10b2
-```
-
-Example for minimal configuration:
-
-```yaml
-logger:
- levels:
- io.kokuwa: INFO
- mdc:
- user: {}
-```
-
-### Set log level based on HTTP request header
-
-Configuration for server filter (prefixed with *logger.request.filter*):
-
-* *enabled*: enable HTTP server filter (`true` is default)
-* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/core/src/main/java/io/micronaut/core/order/Ordered.java) (highest is default)
-* *path*: filter path (`/**` is default)
-* *header*: name of HTTP header (`x-log-level` is default)
-
-Configuration for client filter for propagation (prefixed with *logger.request.propagation*):
-
-* *enabled*: enable HTTP client filter (`true` is default)
-* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/core/src/main/java/io/micronaut/core/order/Ordered.java) (tracing is default)
-* *path*: filter path (`/**` is default)
-* *header*: name of HTTP header (server header is default)
-
-Example with default configuration:
-
-```yaml
-logger:
- request:
- filter:
- enabled: true
- order: -2147483648
- path: /**
- header: x-log-level
- propagation:
- enabled: true
- order: 19000
- path: /**
- header: ${logger.request.header.header-name}
-```
-
-### Add principal for request to MDC
-
-Configuration:
-
-* *enabled*: enable HTTP principal filter (`true` is default)
-* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/core/src/main/java/io/micronaut/core/order/Ordered.java) ([ServerFilterPhase.SECURITY.after()](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/http/src/main/java/io/micronaut/http/filter/ServerFilterPhase.java#L54) is default)
-* *path*: filter path (`/**` is default)
-* *key*: name of MDC header (`principal` is default)
-
-Example with default configuration:
-
-```yaml
-logger:
- request:
- principal:
- enabled: true
- order: 39250
- path: /**
- key: principal
-```
-
-## Build & Release
-
-### Dependency updates
-
-Display dependency updates:
-
-```sh
-mvn versions:display-property-updates -U
-```
-
-Update dependencies:
-
-```sh
-mvn versions:update-properties
-```
-
-### Release locally
-
-Run:
-
-```sh
-mvn release:prepare release:perform release:clean -B -DreleaseProfiles=oss-release
-```
+* [build](docs/build.md)
## Open Topics
diff --git a/docs/build.md b/docs/build.md
new file mode 100644
index 0000000..4d5c72e
--- /dev/null
+++ b/docs/build.md
@@ -0,0 +1,23 @@
+# Build & Release
+
+## Dependency updates
+
+Display dependency updates:
+
+```sh
+mvn versions:display-parent-updates versions:display-property-updates -U
+```
+
+Update dependencies:
+
+```sh
+mvn versions:update-parent versions:update-properties
+```
+
+## Release locally
+
+Run:
+
+```sh
+mvn release:prepare release:perform release:clean -B -DreleaseProfiles=oss-release
+```
diff --git a/docs/features/http_log_level.md b/docs/features/http_log_level.md
new file mode 100644
index 0000000..59b960d
--- /dev/null
+++ b/docs/features/http_log_level.md
@@ -0,0 +1,35 @@
+# Set log level based on HTTP request header
+
+With this features it is possible to set the log level while processing a request by adding the http header `x-log-level` with value `TRACE`. This log level is propagated to HTTP client requests.
+
+## Properties
+
+Property | Description | Default
+-------- | ----------- | -------
+`logger.http.level.enabled` | filter enabled? | `true`
+`logger.http.level.path` | filter path | `/**`
+`logger.http.level.order` | order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/core/src/main/java/io/micronaut/core/order/Ordered.java) | [ServerFilterPhase.FIRST.before()](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/http/src/main/java/io/micronaut/http/filter/ServerFilterPhase.java#L34)
+`logger.http.level.header` | name of HTTP header | `x-log-level`
+`logger.http.level.propagation.enabled` | propagation enabled? | `true`
+`logger.http.level.propagation.path` | propagation path | `/**`
+`logger.http.level.propagation.order` | order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/core/src/main/java/io/micronaut/core/order/Ordered.java) | [Order.HIGHEST_PRECEDENCE](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/core/src/main/java/io/micronaut/core/order/Ordered.java#L30)
+`logger.http.level.propagation.header` | name of HTTP header | see `logger.http.level.header`
+
+## Examples
+
+Default configuration:
+
+```yaml
+logger:
+ http:
+ level:
+ enabled: true
+ order: -1000
+ path: /**
+ header: x-log-level
+ propagation:
+ enabled: true
+ order: 2147483648
+ path: /**
+ header: ${logger.http.level.header}
+```
diff --git a/docs/features/http_mdc_authentication.md b/docs/features/http_mdc_authentication.md
new file mode 100644
index 0000000..ff4c4e9
--- /dev/null
+++ b/docs/features/http_mdc_authentication.md
@@ -0,0 +1,29 @@
+# Add authentication information to MDC
+
+This only applies to HTTP requests with successful security authentication.
+
+## Properties
+
+Property | Description | Default
+-------- | ----------- | -------
+`logger.http.authentication.enabled` | filter enabled? | `true`
+`logger.http.authentication.path` | filter path | `/**`
+`logger.http.authentication.order` | order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/core/src/main/java/io/micronaut/core/order/Ordered.java) | [ServerFilterPhase.SECURITY.after()](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/http/src/main/java/io/micronaut/http/filter/ServerFilterPhase.java#L54)
+`logger.http.authentication.prefix` | prefix to MDC key | ``
+`logger.http.authentication.name` | MDC key of authentication name | `principal`
+`logger.http.authentication.attributes` | authentication attributes to add to MDC, | `[]`
+
+## Examples
+
+Configuration for adding some jwt claims:
+
+```yaml
+logger:
+ http:
+ authentication:
+ prefix: jwt.
+ name: sub
+ attributes:
+ - aud
+ - azp
+```
diff --git a/docs/features/http_mdc_headers.md b/docs/features/http_mdc_headers.md
new file mode 100644
index 0000000..b6c031a
--- /dev/null
+++ b/docs/features/http_mdc_headers.md
@@ -0,0 +1,28 @@
+# Add HTTP headers to MDC
+
+## Properties
+
+Property | Description | Default
+-------- | ----------- | -------
+`logger.http.headers.enabled` | filter enabled? | `true`
+`logger.http.headers.path` | filter path | `/**`
+`logger.http.headers.order` | order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/core/src/main/java/io/micronaut/core/order/Ordered.java) | [ServerFilterPhase.FIRST.before()](https://github.com/micronaut-projects/micronaut-core/blob/v3.2.0/http/src/main/java/io/micronaut/http/filter/ServerFilterPhase.java#L34)
+`logger.http.headers.prefix` | prefix to MDC key | ``
+`logger.http.headers.names` | http header names to add to MDC | `[]`
+
+## Examples
+
+Configuration for b3-propagation:
+
+```yaml
+logger:
+ http:
+ headers:
+ prefix: header.
+ names:
+ - x-request-id
+ - x-b3-traceId
+ - x-b3-parentspanid
+ - x-b3-spanid
+ - x-b3-sampled
+```
diff --git a/docs/features/logback_appender.md b/docs/features/logback_appender.md
new file mode 100644
index 0000000..1975e8b
--- /dev/null
+++ b/docs/features/logback_appender.md
@@ -0,0 +1,16 @@
+# Appender
+
+## Available Appender
+
+* console with jansi for developers
+* gcp logging format (with support for error reporting)
+* json
+
+## AutoSelect appender
+
+1. if `LOGBACK_APPENDER` is set this appender will be used
+2. if GCP is detected gcp appender will be used
+3. if Kubernetes is detected json appender will be used
+4. console appender else
+
+*IMPORTENT*: only works without custom `logback.xml`
diff --git a/docs/features/logback_default.md b/docs/features/logback_default.md
new file mode 100644
index 0000000..98ccbc5
--- /dev/null
+++ b/docs/features/logback_default.md
@@ -0,0 +1,18 @@
+# Add default logback.xml
+
+If no `logback.xml` by user is provided a default [logback.xml](../../src/main/resources/io/kokuwa/logback/logback-default.xml) is loaded. Otherwise use custom [logback.xml](../../src/main/resources/io/kokuwa/logback/logback-example.xml):
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
diff --git a/docs/features/logback_mdc_level.md b/docs/features/logback_mdc_level.md
new file mode 100644
index 0000000..53887d5
--- /dev/null
+++ b/docs/features/logback_mdc_level.md
@@ -0,0 +1,66 @@
+# Set log level based on MDC values
+
+This can be used to change the log level based on MDC valus. E.g. change log levels for specific users/services etc.
+
+## Properties
+
+Property | Description | Default
+-------- | ----------- | -------
+`logger.mdc.enabled` | MDC enabled? | `true`
+`logger.mdc.` | MDC key to use |
+`logger.mdc..key` | MDC key override, see complex example below for usage | ``
+`logger.mdc..level` | log level to use | `TRACE`
+`logger.mdc..loggers` | passlist of logger names, matches all loggers if empty | `[]`
+`logger.mdc..values` | values for matching MDC key, matches all values if empty | `[]`
+
+## Examples
+
+Minimal configuration that logs everything with `TRACE` if MDC `principal` is present:
+
+```yaml
+logger:
+ levels:
+ io.kokuwa: INFO
+ mdc:
+ principal: {}
+```
+
+Configuration that logs everything with `TRACE` for logger `io.kokuwa` if MDC `gateway` matches one value:
+
+```yaml
+logger:
+ levels:
+ io.kokuwa: INFO
+ mdc:
+ gateway:
+ loggers:
+ - io.kokuwa
+ values:
+ - 257802b2-22fe-4dcc-bb99-c1db2a47861f
+ - 0a44738b-0c3a-4798-8210-2495485f10b2
+```
+
+Complex example with setting different values for different values/logger:
+
+```yaml
+logger:
+ levels:
+ io.kokuwa: INFO
+ mdc:
+ gateway-debug:
+ key: gateway
+ level: DEBUG
+ loggers:
+ - io.kokuwa
+ values:
+ - 6a1bae7f-eb6c-4c81-af9d-dc15396584e2
+ - fb3318f1-2c73-48e9-acd4-a2be3c9f9256
+ gateway-trace:
+ key: gateway
+ level: TRACE
+ loggers:
+ - io.kokuwa
+ - io.micronaut
+ values:
+ - 257802b2-22fe-4dcc-bb99-c1db2a47861f
+```
diff --git a/pom.xml b/pom.xml
index de101f4..ed38ada 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
io.kokuwa
maven-parent
- 0.5.4
+ 0.5.5
@@ -49,7 +49,7 @@
0.1.5
- 3.1.3
+ 3.2.0
@@ -92,6 +92,11 @@
micronaut-runtime
provided
+
+ io.micronaut.security
+ micronaut-security
+ provided
+
io.micronaut.test
micronaut-test-junit5
diff --git a/pom.xml.versionsBackup b/pom.xml.versionsBackup
new file mode 100644
index 0000000..5a30895
--- /dev/null
+++ b/pom.xml.versionsBackup
@@ -0,0 +1,169 @@
+
+
+ 4.0.0
+
+
+ io.kokuwa
+ maven-parent
+ 0.5.4
+
+
+
+ io.kokuwa.micronaut
+ micronaut-logging
+ 3.0.0-SNAPSHOT
+
+ Logging support for Micronaut
+ Enhanced logging using MDC or request header.
+ https://github.com/kokuwaio/micronaut-logging
+ 2020
+
+
+ Apache License 2.0
+ https://www.apache.org/licenses/LICENSE-2.0
+
+
+
+
+
+ Stephan Schnabel
+ https://github.com/stephanschnabel
+
+
+
+
+ https://github.com/kokuwaio/micronaut-logging
+ scm:git:https://github.com/kokuwaio/micronaut-logging.git
+ scm:git:https://github.com/kokuwaio/micronaut-logging.git
+ HEAD
+
+
+ github
+ https://github.com/kokuwaio/micronaut-logging/issues
+
+
+
+
+
+
+
+
+ 0.1.5
+ 3.2.0
+
+
+
+
+
+
+
+
+ io.micronaut
+ micronaut-bom
+ ${version.io.micronaut}
+ pom
+ import
+
+
+
+
+ ch.qos.logback.contrib
+ logback-json-classic
+ ${version.ch.qos.logback.contrib}
+
+
+ ch.qos.logback.contrib
+ logback-json-core
+ ${version.ch.qos.logback.contrib}
+
+
+ ch.qos.logback.contrib
+ logback-jackson
+ ${version.ch.qos.logback.contrib}
+
+
+
+
+
+
+
+
+ io.micronaut
+ micronaut-runtime
+ provided
+
+
+ io.micronaut.security
+ micronaut-security
+ provided
+
+
+ io.micronaut.test
+ micronaut-test-junit5
+ test
+
+
+ io.micronaut
+ micronaut-http-client
+ test
+
+
+ io.micronaut
+ micronaut-http-server-netty
+ test
+
+
+ io.micronaut.security
+ micronaut-security-jwt
+ test
+
+
+
+
+ com.google.code.findbugs
+ jsr305
+ provided
+
+
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ ch.qos.logback.contrib
+ logback-jackson
+
+
+ ch.qos.logback.contrib
+ logback-json-classic
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+ ${version.org.projectlombok}
+
+
+ io.micronaut
+ micronaut-inject-java
+ ${version.io.micronaut}
+
+
+
+
+
+
+
+
diff --git a/src/main/java/io/kokuwa/micronaut/logging/configurator/DefaultConfigurator.java b/src/main/java/io/kokuwa/micronaut/logging/configurator/DefaultConfigurator.java
index 1ebef70..1d808a9 100644
--- a/src/main/java/io/kokuwa/micronaut/logging/configurator/DefaultConfigurator.java
+++ b/src/main/java/io/kokuwa/micronaut/logging/configurator/DefaultConfigurator.java
@@ -1,5 +1,6 @@
package io.kokuwa.micronaut.logging.configurator;
+import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.Configurator;
import ch.qos.logback.core.joran.spi.JoranException;
@@ -29,5 +30,7 @@ public class DefaultConfigurator extends ContextAwareBase implements Configurato
} catch (JoranException e) {
addError("Failed to load logback.xml from io.kokuwa:micronaut-logging", e);
}
+
+ loggerContext.getLogger("io.micronaut.logging.PropertiesLoggingLevelsConfigurer").setLevel(Level.WARN);
}
}
diff --git a/src/main/java/io/kokuwa/micronaut/logging/http/AbstractMdcFilter.java b/src/main/java/io/kokuwa/micronaut/logging/http/AbstractMdcFilter.java
new file mode 100644
index 0000000..777d363
--- /dev/null
+++ b/src/main/java/io/kokuwa/micronaut/logging/http/AbstractMdcFilter.java
@@ -0,0 +1,47 @@
+package io.kokuwa.micronaut.logging.http;
+
+import java.util.Map;
+
+import org.reactivestreams.Publisher;
+import org.slf4j.MDC;
+
+import io.micronaut.core.async.publisher.Publishers;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.MutableHttpResponse;
+import io.micronaut.http.filter.HttpServerFilter;
+import io.micronaut.http.filter.ServerFilterChain;
+
+/**
+ * Base for all MDC related http filters.
+ *
+ * @author Stephan Schnabel
+ */
+public abstract class AbstractMdcFilter implements HttpServerFilter {
+
+ private final int order;
+
+ public AbstractMdcFilter(Integer order) {
+ this.order = order;
+ }
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+
+ protected Publisher> doFilter(
+ HttpRequest> request,
+ ServerFilterChain chain,
+ Map mdc) {
+
+ if (mdc.isEmpty()) {
+ return chain.proceed(request);
+ }
+
+ mdc.forEach(MDC::put);
+ return Publishers.map(chain.proceed(request), response -> {
+ mdc.keySet().forEach(MDC::remove);
+ return response;
+ });
+ }
+}
diff --git a/src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingClientHttpFilter.java b/src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelClientFilter.java
similarity index 62%
rename from src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingClientHttpFilter.java
rename to src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelClientFilter.java
index 463a070..511829d 100644
--- a/src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingClientHttpFilter.java
+++ b/src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelClientFilter.java
@@ -1,4 +1,4 @@
-package io.kokuwa.micronaut.logging.request;
+package io.kokuwa.micronaut.logging.http.level;
import java.util.Optional;
@@ -13,30 +13,29 @@ import io.micronaut.http.annotation.Filter;
import io.micronaut.http.context.ServerRequestContext;
import io.micronaut.http.filter.ClientFilterChain;
import io.micronaut.http.filter.HttpClientFilter;
-import io.micronaut.http.filter.ServerFilterPhase;
/**
- * Http request logging filter.
+ * Propagates log-level from server request to client.
*
* @author Stephan Schnabel
*/
-@Requires(beans = HeaderLoggingServerHttpFilter.class)
-@Requires(property = HeaderLoggingClientHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
-@Filter("${" + HeaderLoggingClientHttpFilter.PREFIX + ".path:/**}")
-public class HeaderLoggingClientHttpFilter implements HttpClientFilter {
+@Requires(beans = LogLevelServerFilter.class)
+@Requires(property = LogLevelClientFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
+@Filter("${" + LogLevelClientFilter.PREFIX + ".path:/**}")
+public class LogLevelClientFilter implements HttpClientFilter {
- public static final String PREFIX = "logger.request.propagation";
- public static final int DEFAULT_ORDER = ServerFilterPhase.TRACING.order();
+ public static final String PREFIX = "logger.http.level.propagation";
+ public static final int DEFAULT_ORDER = HIGHEST_PRECEDENCE;
private final String serverHeader;
private final String propagationHeader;
private final int order;
- public HeaderLoggingClientHttpFilter(
- @Value("${" + HeaderLoggingServerHttpFilter.PREFIX + ".header}") Optional serverHeader,
+ public LogLevelClientFilter(
+ @Value("${" + LogLevelServerFilter.PREFIX + ".header}") Optional serverHeader,
@Value("${" + PREFIX + ".header}") Optional propagationHeader,
@Value("${" + PREFIX + ".order}") Optional order) {
- this.serverHeader = serverHeader.orElse(HeaderLoggingServerHttpFilter.DEFAULT_HEADER);
+ this.serverHeader = serverHeader.orElse(LogLevelServerFilter.DEFAULT_HEADER);
this.propagationHeader = propagationHeader.orElse(this.serverHeader);
this.order = order.orElse(DEFAULT_ORDER);
}
diff --git a/src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingServerHttpFilter.java b/src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelServerFilter.java
similarity index 67%
rename from src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingServerHttpFilter.java
rename to src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelServerFilter.java
index f0afce3..67357ed 100644
--- a/src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingServerHttpFilter.java
+++ b/src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelServerFilter.java
@@ -1,4 +1,4 @@
-package io.kokuwa.micronaut.logging.request;
+package io.kokuwa.micronaut.logging.http.level;
import java.util.Optional;
@@ -10,6 +10,7 @@ import org.slf4j.MDC;
import ch.qos.logback.classic.turbo.TurboFilter;
import io.kokuwa.micronaut.logging.LogbackUtil;
+import io.kokuwa.micronaut.logging.http.AbstractMdcFilter;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.annotation.Value;
import io.micronaut.core.async.publisher.Publishers;
@@ -17,54 +18,46 @@ import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
-import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import io.micronaut.http.filter.ServerFilterPhase;
-import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.runtime.context.scope.Refreshable;
/**
* Http request logging filter.
*
* @author Stephan Schnabel
*/
-@Requires(beans = EmbeddedServer.class)
-@Requires(property = HeaderLoggingServerHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
-@Filter("${" + HeaderLoggingServerHttpFilter.PREFIX + ".path:/**}")
-public class HeaderLoggingServerHttpFilter implements HttpServerFilter {
-
- public static final String PREFIX = "logger.request.filter";
- public static final String MDC_FILTER = PREFIX;
- public static final String MDC_KEY = "level";
+@Refreshable
+@Requires(property = LogLevelServerFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
+@Filter("${" + LogLevelServerFilter.PREFIX + ".path:/**}")
+public class LogLevelServerFilter extends AbstractMdcFilter {
+ public static final String PREFIX = "logger.http.level";
public static final String DEFAULT_HEADER = "x-log-level";
public static final int DEFAULT_ORDER = ServerFilterPhase.FIRST.before();
+ public static final String MDC_KEY = "level";
+ public static final String MDC_FILTER = PREFIX;
private final LogbackUtil logback;
private final String header;
- private final int order;
- public HeaderLoggingServerHttpFilter(
+ public LogLevelServerFilter(
LogbackUtil logback,
@Value("${" + PREFIX + ".header}") Optional header,
@Value("${" + PREFIX + ".order}") Optional order) {
+ super(order.orElse(DEFAULT_ORDER));
this.logback = logback;
this.header = header.orElse(DEFAULT_HEADER);
- this.order = order.orElse(DEFAULT_ORDER);
}
@PostConstruct
void startTurbofilter() {
- logback.getTurboFilter(HeaderLoggingTurboFilter.class, MDC_FILTER, HeaderLoggingTurboFilter::new).start();
+ logback.getTurboFilter(LogLevelTurboFilter.class, MDC_FILTER, LogLevelTurboFilter::new).start();
}
@PreDestroy
void stopTurbofilter() {
- logback.getTurboFilter(HeaderLoggingTurboFilter.class, MDC_FILTER).ifPresent(TurboFilter::stop);
- }
-
- @Override
- public int getOrder() {
- return order;
+ logback.getTurboFilter(LogLevelTurboFilter.class, MDC_FILTER).ifPresent(TurboFilter::stop);
}
@Override
diff --git a/src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingTurboFilter.java b/src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelTurboFilter.java
similarity index 79%
rename from src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingTurboFilter.java
rename to src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelTurboFilter.java
index c1a8dc3..57df67f 100644
--- a/src/main/java/io/kokuwa/micronaut/logging/request/HeaderLoggingTurboFilter.java
+++ b/src/main/java/io/kokuwa/micronaut/logging/http/level/LogLevelTurboFilter.java
@@ -1,4 +1,4 @@
-package io.kokuwa.micronaut.logging.request;
+package io.kokuwa.micronaut.logging.http.level;
import org.slf4j.MDC;
import org.slf4j.Marker;
@@ -13,7 +13,7 @@ import ch.qos.logback.core.spi.FilterReply;
*
* @author Stephan Schnabel
*/
-public class HeaderLoggingTurboFilter extends TurboFilter {
+public class LogLevelTurboFilter extends TurboFilter {
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
@@ -22,7 +22,7 @@ public class HeaderLoggingTurboFilter extends TurboFilter {
return FilterReply.NEUTRAL;
}
- var value = MDC.get(HeaderLoggingServerHttpFilter.MDC_KEY);
+ var value = MDC.get(LogLevelServerFilter.MDC_KEY);
if (value == null) {
return FilterReply.NEUTRAL;
}
diff --git a/src/main/java/io/kokuwa/micronaut/logging/http/mdc/AuthenticationMdcFilter.java b/src/main/java/io/kokuwa/micronaut/logging/http/mdc/AuthenticationMdcFilter.java
new file mode 100644
index 0000000..f1c4a14
--- /dev/null
+++ b/src/main/java/io/kokuwa/micronaut/logging/http/mdc/AuthenticationMdcFilter.java
@@ -0,0 +1,77 @@
+package io.kokuwa.micronaut.logging.http.mdc;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+
+import org.reactivestreams.Publisher;
+import org.slf4j.MDC;
+
+import io.kokuwa.micronaut.logging.http.AbstractMdcFilter;
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.context.annotation.Value;
+import io.micronaut.core.util.StringUtils;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.MutableHttpResponse;
+import io.micronaut.http.annotation.Filter;
+import io.micronaut.http.filter.ServerFilterChain;
+import io.micronaut.http.filter.ServerFilterPhase;
+import io.micronaut.runtime.context.scope.Refreshable;
+import io.micronaut.security.authentication.Authentication;
+
+/**
+ * Filter to add claims from authentication to MDC.
+ *
+ * @author Stephan Schnabel
+ */
+@Refreshable
+@Requires(classes = Authentication.class)
+@Requires(property = AuthenticationMdcFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
+@Filter("${" + AuthenticationMdcFilter.PREFIX + ".path:/**}")
+public class AuthenticationMdcFilter extends AbstractMdcFilter {
+
+ public static final String PREFIX = "logger.http.authentication";
+ public static final String DEFAULT_NAME = "principal";
+ public static final int DEFAULT_ORDER = ServerFilterPhase.SECURITY.after();
+
+ private final String name;
+ private final List attributes;
+ private final String prefix;
+
+ public AuthenticationMdcFilter(
+ @Value("${" + PREFIX + ".name:principal}") Optional name,
+ @Value("${" + PREFIX + ".attributes:[]}") List attributes,
+ @Value("${" + PREFIX + ".prefix}") Optional prefix,
+ @Value("${" + PREFIX + ".order}") Optional order) {
+ super(order.orElse(DEFAULT_ORDER));
+ this.name = name.orElse(DEFAULT_NAME);
+ this.prefix = prefix.orElse(null);
+ this.attributes = attributes;
+ }
+
+ @Override
+ public Publisher> doFilter(HttpRequest> request, ServerFilterChain chain) {
+
+ // get authentication
+
+ var optional = request.getUserPrincipal(Authentication.class);
+ if (optional.isEmpty()) {
+ return chain.proceed(request);
+ }
+ var authentication = optional.get();
+ var authenticationAttributes = authentication.getAttributes();
+
+ // add mdc
+
+ var mdc = new HashMap();
+ MDC.put(prefix == null ? name : prefix + name, authentication.getName());
+ for (var header : attributes) {
+ var value = authenticationAttributes.get(header);
+ if (value != null) {
+ mdc.put(prefix == null ? header : prefix + header, String.valueOf(value));
+ }
+ }
+
+ return doFilter(request, chain, mdc);
+ }
+}
diff --git a/src/main/java/io/kokuwa/micronaut/logging/http/mdc/HttpHeadersMdcFilter.java b/src/main/java/io/kokuwa/micronaut/logging/http/mdc/HttpHeadersMdcFilter.java
new file mode 100644
index 0000000..efca100
--- /dev/null
+++ b/src/main/java/io/kokuwa/micronaut/logging/http/mdc/HttpHeadersMdcFilter.java
@@ -0,0 +1,58 @@
+package io.kokuwa.micronaut.logging.http.mdc;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.reactivestreams.Publisher;
+
+import io.kokuwa.micronaut.logging.http.AbstractMdcFilter;
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.context.annotation.Value;
+import io.micronaut.core.util.StringUtils;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.MutableHttpResponse;
+import io.micronaut.http.annotation.Filter;
+import io.micronaut.http.filter.ServerFilterChain;
+import io.micronaut.http.filter.ServerFilterPhase;
+import io.micronaut.runtime.context.scope.Refreshable;
+
+/**
+ * Filter to add http headers to MDC.
+ *
+ * @author Stephan Schnabel
+ */
+@Refreshable
+@Requires(property = HttpHeadersMdcFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
+@Requires(property = HttpHeadersMdcFilter.PREFIX + ".names")
+@Filter("${" + HttpHeadersMdcFilter.PREFIX + ".path:/**}")
+public class HttpHeadersMdcFilter extends AbstractMdcFilter {
+
+ public static final String PREFIX = "logger.http.headers";
+ public static final int DEFAULT_ORDER = ServerFilterPhase.FIRST.before();
+
+ private final Set headers;
+ private final String prefix;
+
+ public HttpHeadersMdcFilter(
+ @Value("${" + PREFIX + ".names}") List headers,
+ @Value("${" + PREFIX + ".prefix}") Optional prefix,
+ @Value("${" + PREFIX + ".order}") Optional order) {
+ super(order.orElse(DEFAULT_ORDER));
+ this.prefix = prefix.orElse(null);
+ this.headers = headers.stream().map(String::toLowerCase).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Publisher> doFilter(HttpRequest> request, ServerFilterChain chain) {
+ var mdc = new HashMap();
+ for (var header : headers) {
+ request.getHeaders()
+ .getFirst(header)
+ .ifPresent(value -> mdc.put(prefix == null ? header : prefix + header, String.valueOf(value)));
+ }
+ return doFilter(request, chain, mdc);
+ }
+}
diff --git a/src/main/java/io/kokuwa/micronaut/logging/request/PrincipalHttpFilter.java b/src/main/java/io/kokuwa/micronaut/logging/request/PrincipalHttpFilter.java
deleted file mode 100644
index 0e7a14d..0000000
--- a/src/main/java/io/kokuwa/micronaut/logging/request/PrincipalHttpFilter.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package io.kokuwa.micronaut.logging.request;
-
-import java.util.Optional;
-
-import org.reactivestreams.Publisher;
-import org.slf4j.MDC;
-
-import io.micronaut.context.annotation.Requires;
-import io.micronaut.context.annotation.Value;
-import io.micronaut.core.async.publisher.Publishers;
-import io.micronaut.core.util.StringUtils;
-import io.micronaut.http.HttpRequest;
-import io.micronaut.http.MutableHttpResponse;
-import io.micronaut.http.annotation.Filter;
-import io.micronaut.http.filter.HttpServerFilter;
-import io.micronaut.http.filter.ServerFilterChain;
-import io.micronaut.http.filter.ServerFilterPhase;
-import io.micronaut.runtime.server.EmbeddedServer;
-
-/**
- * Http request principal filter.
- *
- * @author Stephan Schnabel
- */
-@Requires(beans = EmbeddedServer.class)
-@Requires(property = PrincipalHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
-@Filter("${" + PrincipalHttpFilter.PREFIX + ".path:/**}")
-public class PrincipalHttpFilter implements HttpServerFilter {
-
- public static final String PREFIX = "logger.request.principal";
-
- public static final String DEFAULT_KEY = "principal";
- public static final int DEFAULT_ORDER = ServerFilterPhase.SECURITY.after();
-
- private final String key;
- private final int order;
-
- public PrincipalHttpFilter(
- @Value("${" + PREFIX + ".key:" + DEFAULT_KEY + "}") String key,
- @Value("${" + PREFIX + ".order}") Optional order) {
- this.key = key;
- this.order = order.orElse(DEFAULT_ORDER);
- }
-
- @Override
- public int getOrder() {
- return order;
- }
-
- @Override
- public Publisher> doFilter(HttpRequest> request, ServerFilterChain chain) {
- var princial = request.getUserPrincipal();
- if (princial.isPresent()) {
- MDC.put(key, princial.get().getName());
- return Publishers.map(chain.proceed(request), response -> {
- MDC.remove(key);
- return response;
- });
- } else {
- return chain.proceed(request);
- }
- }
-}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/AbstractTest.java b/src/test/java/io/kokuwa/micronaut/logging/AbstractTest.java
index 53960e4..366ba0c 100644
--- a/src/test/java/io/kokuwa/micronaut/logging/AbstractTest.java
+++ b/src/test/java/io/kokuwa/micronaut/logging/AbstractTest.java
@@ -2,7 +2,9 @@ package io.kokuwa.micronaut.logging;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.MethodOrderer.DisplayName;
+import org.junit.jupiter.api.ClassOrderer;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.TestClassOrder;
import org.junit.jupiter.api.TestMethodOrder;
import org.slf4j.MDC;
@@ -14,7 +16,8 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
* @author Stephan Schnabel
*/
@MicronautTest
-@TestMethodOrder(DisplayName.class)
+@TestClassOrder(ClassOrderer.DisplayName.class)
+@TestMethodOrder(MethodOrderer.DisplayName.class)
public abstract class AbstractTest {
@BeforeEach
diff --git a/src/test/java/io/kokuwa/micronaut/logging/http/AbstractFilterTest.java b/src/test/java/io/kokuwa/micronaut/logging/http/AbstractFilterTest.java
new file mode 100644
index 0000000..11d6676
--- /dev/null
+++ b/src/test/java/io/kokuwa/micronaut/logging/http/AbstractFilterTest.java
@@ -0,0 +1,133 @@
+package io.kokuwa.micronaut.logging.http;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.slf4j.MDC;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import ch.qos.logback.classic.Level;
+import io.kokuwa.micronaut.logging.AbstractTest;
+import io.micronaut.core.util.CollectionUtils;
+import io.micronaut.http.HttpHeaderValues;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpStatus;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.client.DefaultHttpClientConfiguration;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.filter.HttpServerFilter;
+import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.security.annotation.Secured;
+import io.micronaut.security.rules.SecurityRule;
+import io.micronaut.security.token.jwt.signature.SignatureGeneratorConfiguration;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Test for {@link HttpServerFilter}.
+ *
+ * @author Stephan Schnabel
+ */
+@MicronautTest(rebuildContext = true)
+public abstract class AbstractFilterTest extends AbstractTest {
+
+ private static boolean INIT = false;
+
+ @Inject
+ SignatureGeneratorConfiguration signature;
+ @Inject
+ EmbeddedServer embeddedServer;
+
+ @DisplayName("0 init")
+ @Test
+ @BeforeEach
+ void refresh() {
+ // https://github.com/micronaut-projects/micronaut-core/issues/5453#issuecomment-864594741
+ if (INIT) {
+ embeddedServer.refresh();
+ } else {
+ INIT = true;
+ }
+ }
+
+ // security
+
+ public String token(String subject) {
+ return token(subject, claims -> {});
+ }
+
+ @SneakyThrows
+ public String token(String subject, Consumer manipulator) {
+ var claims = new JWTClaimsSet.Builder().subject(subject);
+ manipulator.accept(claims);
+ return HttpHeaderValues.AUTHORIZATION_PREFIX_BEARER + " " + signature.sign(claims.build()).serialize();
+ }
+
+ // request
+
+ @SneakyThrows
+ public TestResponse get(Map headers) {
+
+ var request = HttpRequest.GET("/");
+ headers.forEach((name, value) -> request.header(name, value));
+ var configuration = new DefaultHttpClientConfiguration();
+ configuration.setLoggerName("io.kokuwa.TestClient");
+ var response = HttpClient
+ .create(embeddedServer.getURL(), configuration)
+ .toBlocking().exchange(request, TestResponse.class);
+ assertEquals(HttpStatus.OK, response.getStatus(), "status");
+ assertTrue(response.getBody().isPresent(), "body");
+ assertTrue(CollectionUtils.isEmpty(MDC.getCopyOfContextMap()), "mdc leaked: " + MDC.getCopyOfContextMap());
+
+ return response.body();
+ }
+
+ @Secured({ SecurityRule.IS_ANONYMOUS, SecurityRule.IS_AUTHENTICATED })
+ @Controller
+ @Slf4j
+ public static class TestController {
+
+ @Get("/")
+ TestResponse run() {
+
+ var level = Level.OFF;
+ if (log.isTraceEnabled()) {
+ level = Level.TRACE;
+ } else if (log.isDebugEnabled()) {
+ level = Level.DEBUG;
+ } else if (log.isInfoEnabled()) {
+ level = Level.INFO;
+ } else if (log.isWarnEnabled()) {
+ level = Level.WARN;
+ } else if (log.isErrorEnabled()) {
+ level = Level.ERROR;
+ }
+
+ var mdc = MDC.getCopyOfContextMap();
+ log.info("Found MDC: {}", mdc);
+
+ return new TestResponse(level.toString(), mdc == null ? Map.of() : mdc);
+ }
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class TestResponse {
+ private String level;
+ private Map context = Map.of();
+ }
+}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/http/level/LogLevelServerFilterTest.java b/src/test/java/io/kokuwa/micronaut/logging/http/level/LogLevelServerFilterTest.java
new file mode 100644
index 0000000..b25a6af
--- /dev/null
+++ b/src/test/java/io/kokuwa/micronaut/logging/http/level/LogLevelServerFilterTest.java
@@ -0,0 +1,80 @@
+package io.kokuwa.micronaut.logging.http.level;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import ch.qos.logback.classic.Level;
+import io.kokuwa.micronaut.logging.http.AbstractFilterTest;
+import io.micronaut.context.annotation.Property;
+
+/**
+ * Test for {@link LogLevelServerFilter}.
+ *
+ * @author Stephan Schnabel
+ */
+@DisplayName("http: set log level via http request")
+public class LogLevelServerFilterTest extends AbstractFilterTest {
+
+ @DisplayName("noop: disabled")
+ @Test
+ @Property(name = "logger.http.level.enabled", value = "false")
+ void noopDisabled() {
+ assertLevel(Level.INFO, "TRACE");
+ }
+
+ @DisplayName("noop: header missing")
+ @Test
+ void noopHeaderMissing() {
+ assertLevel(Level.INFO, null);
+ }
+
+ @DisplayName("noop: header invalid, use DEBUG as default from logback")
+ @Test
+ void noopHeaderInvalid() {
+ assertLevel(Level.DEBUG, "TRCE");
+ }
+
+ @DisplayName("level: trace (below default)")
+ @Test
+ void levelTrace() {
+ assertLevel(Level.TRACE, "TRACE");
+ }
+
+ @DisplayName("level: debug (below default)")
+ @Test
+ void levelDebug() {
+ assertLevel(Level.DEBUG, "DEBUG");
+ }
+
+ @DisplayName("level: info (is default)")
+ @Test
+ void levelInfo() {
+ assertLevel(Level.INFO, "INFO");
+ }
+
+ @DisplayName("level: warn (above default)")
+ @Test
+ void levelWarn() {
+ assertLevel(Level.INFO, "WARN");
+ }
+
+ @DisplayName("config: custom header name")
+ @Test
+ @Property(name = "logger.http.level.header", value = "FOO")
+ void configHeaderWarn() {
+ assertLevel(Level.TRACE, "FOO", "TRACE");
+ }
+
+ private void assertLevel(Level expectedLevel, String value) {
+ assertLevel(expectedLevel, LogLevelServerFilter.DEFAULT_HEADER, value);
+ }
+
+ private void assertLevel(Level expectedLevel, String name, String value) {
+ var headers = value == null ? Map.of() : Map.of(name, value);
+ assertEquals(expectedLevel.toString(), get(headers).getLevel());
+ }
+}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/http/mdc/AuthenticationMdcFilterTest.java b/src/test/java/io/kokuwa/micronaut/logging/http/mdc/AuthenticationMdcFilterTest.java
new file mode 100644
index 0000000..0492139
--- /dev/null
+++ b/src/test/java/io/kokuwa/micronaut/logging/http/mdc/AuthenticationMdcFilterTest.java
@@ -0,0 +1,73 @@
+package io.kokuwa.micronaut.logging.http.mdc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import io.kokuwa.micronaut.logging.http.AbstractFilterTest;
+import io.micronaut.context.annotation.Property;
+import io.micronaut.http.HttpHeaders;
+
+/**
+ * Test for {@link AuthenticationMdcFilter}.
+ *
+ * @author Stephan Schnabel
+ */
+@DisplayName("http: mdc from authentication")
+public class AuthenticationMdcFilterTest extends AbstractFilterTest {
+
+ @DisplayName("noop: disabled")
+ @Test
+ @Property(name = "logger.http.authentication.enabled", value = "false")
+ void noopDisabled() {
+ assertEquals(Map.of(), getContext(true));
+ }
+
+ @DisplayName("noop: token missing")
+ @Test
+ void noopTokenMissing() {
+ assertEquals(Map.of(), getContext(false));
+ }
+
+ @DisplayName("mdc: default config")
+ @Test
+ void mdcWithDefault() {
+ assertEquals(Map.of("principal", "mySubject"), getContext(true));
+ }
+
+ @DisplayName("mdc: with name")
+ @Test
+ @Property(name = "logger.http.authentication.name", value = "sub")
+ void mdcWithName() {
+ assertEquals(Map.of("sub", "mySubject"), getContext(true));
+ }
+
+ @DisplayName("mdc: with attribute keys")
+ @Test
+ @Property(name = "logger.http.authentication.attributes", value = "azp,aud")
+ void mdcWithAttributes() {
+ assertEquals(Map.of("principal", "mySubject", "aud", "[a, b]", "azp", "myAzp"), getContext(true));
+ }
+
+ @DisplayName("mdc: with prefix")
+ @Test
+ @Property(name = "logger.http.authentication.name", value = "sub")
+ @Property(name = "logger.http.authentication.attributes", value = "azp")
+ @Property(name = "logger.http.authentication.prefix", value = "auth.")
+ void mdcWithPrefix() {
+ assertEquals(Map.of("auth.sub", "mySubject", "auth.azp", "myAzp"), getContext(true));
+ }
+
+ private Map getContext(boolean token) {
+ return get(token
+ ? Map.of(HttpHeaders.AUTHORIZATION, token("mySubject", claims -> claims
+ .issuer("nope")
+ .claim("azp", "myAzp")
+ .audience(List.of("a", "b"))))
+ : Map.of()).getContext();
+ }
+}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/http/mdc/HttpHeadersMdcFilterTest.java b/src/test/java/io/kokuwa/micronaut/logging/http/mdc/HttpHeadersMdcFilterTest.java
new file mode 100644
index 0000000..f551bd9
--- /dev/null
+++ b/src/test/java/io/kokuwa/micronaut/logging/http/mdc/HttpHeadersMdcFilterTest.java
@@ -0,0 +1,60 @@
+package io.kokuwa.micronaut.logging.http.mdc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import io.kokuwa.micronaut.logging.http.AbstractFilterTest;
+import io.micronaut.context.annotation.Property;
+
+/**
+ * Test for {@link HttpHeadersMdcFilter}.
+ *
+ * @author Stephan Schnabel
+ */
+@DisplayName("http: mdc from headers")
+public class HttpHeadersMdcFilterTest extends AbstractFilterTest {
+
+ @DisplayName("noop: empty configuration")
+ @Test
+ void noopEmptyConfiguration() {
+ assertContext(Map.of(), Map.of("foo", "bar"));
+ }
+
+ @DisplayName("noop: disabled")
+ @Test
+ @Property(name = "logger.http.headers.enabled", value = "false")
+ @Property(name = "logger.http.headers.names", value = "foo")
+ void noopDisabled() {
+ assertContext(Map.of(), Map.of("foo", "bar"));
+ }
+
+ @DisplayName("mdc: mismatch")
+ @Test
+ @Property(name = "logger.http.headers.names", value = "foo")
+ void mdcMismatch() {
+ assertContext(Map.of(), Map.of("nope", "bar"));
+ }
+
+ @DisplayName("mdc: match without prefix")
+ @Test
+ @Property(name = "logger.http.headers.names", value = "foo")
+ void mdcMatchWithoutPrefix() {
+ assertContext(Map.of("foo", "bar"), Map.of("foo", "bar", "nope", "bar"));
+ }
+
+ @DisplayName("mdc: match with prefix")
+ @Test
+ @Property(name = "logger.http.headers.names", value = "foo")
+ @Property(name = "logger.http.headers.prefix", value = "header.")
+ void mdcMatchWithPrefix() {
+ assertContext(Map.of("header.foo", "bar"), Map.of("foo", "bar", "nope", "bar"));
+ }
+
+ private void assertContext(Map expectedMdcs, Map headers) {
+ assertEquals(expectedMdcs, get(headers).getContext());
+ }
+}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/mdc/MDCTurboFilterTest.java b/src/test/java/io/kokuwa/micronaut/logging/mdc/MDCTurboFilterTest.java
index 5fc80fa..186c2a3 100644
--- a/src/test/java/io/kokuwa/micronaut/logging/mdc/MDCTurboFilterTest.java
+++ b/src/test/java/io/kokuwa/micronaut/logging/mdc/MDCTurboFilterTest.java
@@ -17,7 +17,7 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
*
* @author Stephan Schnabel
*/
-@DisplayName("mdc")
+@DisplayName("mdc based log levels")
@MicronautTest(environments = "test-mdc")
public class MDCTurboFilterTest extends AbstractTest {
diff --git a/src/test/java/io/kokuwa/micronaut/logging/request/CompositeTest.java b/src/test/java/io/kokuwa/micronaut/logging/request/CompositeTest.java
deleted file mode 100644
index 2c69814..0000000
--- a/src/test/java/io/kokuwa/micronaut/logging/request/CompositeTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package io.kokuwa.micronaut.logging.request;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import ch.qos.logback.classic.Level;
-import io.kokuwa.micronaut.logging.AbstractTest;
-import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
-import jakarta.inject.Inject;
-
-/**
- * Test for MDC and request filter combined.
- *
- * @author Stephan Schnabel
- */
-@DisplayName("request-composite")
-@MicronautTest(environments = "test-composite")
-public class CompositeTest extends AbstractTest {
-
- @Inject
- TestClient client;
-
- @DisplayName("default level")
- @Test
- void defaultLogging() {
- client.assertLevel(Level.INFO, client.token("somebody"), null);
- }
-
- @DisplayName("level set by mdc")
- @Test
- void headerFromMdc() {
- client.assertLevel(Level.DEBUG, client.token("horst"), null);
- }
-
- @DisplayName("level set by header (overriding mdc)")
- @Test
- void headerFromHeader() {
- client.assertLevel(Level.TRACE, client.token("horst"), "TRACE");
- }
-}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/request/RequestHeaderTest.java b/src/test/java/io/kokuwa/micronaut/logging/request/RequestHeaderTest.java
deleted file mode 100644
index 476e976..0000000
--- a/src/test/java/io/kokuwa/micronaut/logging/request/RequestHeaderTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package io.kokuwa.micronaut.logging.request;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import ch.qos.logback.classic.Level;
-import io.kokuwa.micronaut.logging.AbstractTest;
-import jakarta.inject.Inject;
-
-/**
- * Test for {@link HeaderLoggingServerHttpFilter}.
- *
- * @author Stephan Schnabel
- */
-@DisplayName("request-header")
-public class RequestHeaderTest extends AbstractTest {
-
- @Inject
- TestClient client;
-
- @DisplayName("header missing")
- @Test
- void headerMissing() {
- client.assertLevel(Level.INFO, null, null);
- }
-
- @DisplayName("header invalid, use DEBUG as default from logback")
- @Test
- void headerInvalid() {
- client.assertLevel(Level.DEBUG, null, "TRCE");
- }
-
- @DisplayName("level trace (below default)")
- @Test
- void headerLevelTrace() {
- client.assertLevel(Level.TRACE, null, "TRACE");
- }
-
- @DisplayName("level debug (below default)")
- @Test
- void headerLevelDebug() {
- client.assertLevel(Level.DEBUG, null, "DEBUG");
- }
-
- @DisplayName("level info (is default)")
- @Test
- void headerLevelInfo() {
- client.assertLevel(Level.INFO, null, "INFO");
- }
-
- @DisplayName("level warn (above default)")
- @Test
- void headerLevelWarn() {
- client.assertLevel(Level.INFO, null, "WARN");
- }
-}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/request/RequestPrincipalTest.java b/src/test/java/io/kokuwa/micronaut/logging/request/RequestPrincipalTest.java
deleted file mode 100644
index 9a60d7c..0000000
--- a/src/test/java/io/kokuwa/micronaut/logging/request/RequestPrincipalTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package io.kokuwa.micronaut.logging.request;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import io.kokuwa.micronaut.logging.AbstractTest;
-import jakarta.inject.Inject;
-
-/**
- * Test for {@link PrincipalHttpFilter}.
- *
- * @author Stephan Schnabel
- */
-@DisplayName("request-principal")
-public class RequestPrincipalTest extends AbstractTest {
-
- @Inject
- TestClient client;
-
- @DisplayName("token missing")
- @Test
- void tokenMissing() {
- assertPrincipal(null, null);
- }
-
- @DisplayName("token invalid")
- @Test
- void tokenInvalid() {
- assertPrincipal(null, "meh");
- }
-
- @DisplayName("token valid")
- @Test
- void tokenValid() {
- assertPrincipal("meh", client.token("meh"));
- }
-
- private void assertPrincipal(String expectedPrincipal, String actualTokenValue) {
- assertEquals(expectedPrincipal, client.get(actualTokenValue, null).getPrincipal());
- }
-}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/request/TestClient.java b/src/test/java/io/kokuwa/micronaut/logging/request/TestClient.java
deleted file mode 100644
index edebb40..0000000
--- a/src/test/java/io/kokuwa/micronaut/logging/request/TestClient.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package io.kokuwa.micronaut.logging.request;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jwt.JWTClaimsSet;
-
-import ch.qos.logback.classic.Level;
-import io.kokuwa.micronaut.logging.request.TestController.TestResponse;
-import io.micronaut.http.HttpRequest;
-import io.micronaut.http.HttpStatus;
-import io.micronaut.http.client.HttpClient;
-import io.micronaut.http.client.annotation.Client;
-import io.micronaut.security.token.jwt.signature.SignatureGeneratorConfiguration;
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-
-/**
- * Contoller for testing {@link HeaderLoggingServerHttpFilter} and {@link PrincipalHttpFilter}.
- *
- * @author Stephan Schnabel
- */
-@Singleton
-public class TestClient {
-
- @Inject
- @Client("/")
- HttpClient client;
- @Inject
- SignatureGeneratorConfiguration signature;
-
- String token(String subject) {
- try {
- return signature.sign(new JWTClaimsSet.Builder().subject(subject).build()).serialize();
- } catch (JOSEException e) {
- fail("failed to create token");
- return null;
- }
- }
-
- TestResponse get(String token, String header) {
-
- var request = HttpRequest.GET("/");
- if (token != null) {
- request.bearerAuth(token);
- }
- if (header != null) {
- request.getHeaders().add(HeaderLoggingServerHttpFilter.DEFAULT_HEADER, header);
- }
-
- var response = client.toBlocking().exchange(request, TestResponse.class);
- assertEquals(HttpStatus.OK, response.getStatus(), "status");
- assertTrue(response.getBody().isPresent(), "body");
-
- return response.body();
- }
-
- void assertLevel(Level expectedLevel, String actualTokenValue, String actualHeaderValue) {
- assertEquals(expectedLevel.toString(), get(actualTokenValue, actualHeaderValue).getLevel());
- }
-}
diff --git a/src/test/java/io/kokuwa/micronaut/logging/request/TestController.java b/src/test/java/io/kokuwa/micronaut/logging/request/TestController.java
deleted file mode 100644
index d179f63..0000000
--- a/src/test/java/io/kokuwa/micronaut/logging/request/TestController.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package io.kokuwa.micronaut.logging.request;
-
-import org.slf4j.MDC;
-
-import ch.qos.logback.classic.Level;
-import io.micronaut.http.annotation.Controller;
-import io.micronaut.http.annotation.Get;
-import io.micronaut.security.annotation.Secured;
-import io.micronaut.security.rules.SecurityRule;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * Controller for testing {@link HeaderLoggingServerHttpFilter} and {@link PrincipalHttpFilter}.
- *
- * @author Stephan Schnabel
- */
-@Secured({ SecurityRule.IS_ANONYMOUS, SecurityRule.IS_AUTHENTICATED })
-@Controller
-@Slf4j
-public class TestController {
-
- @Get("/")
- TestResponse run() {
-
- var principal = MDC.get(PrincipalHttpFilter.DEFAULT_KEY);
- var level = Level.OFF;
- if (log.isTraceEnabled()) {
- level = Level.TRACE;
- } else if (log.isDebugEnabled()) {
- level = Level.DEBUG;
- } else if (log.isInfoEnabled()) {
- level = Level.INFO;
- } else if (log.isWarnEnabled()) {
- level = Level.WARN;
- } else if (log.isErrorEnabled()) {
- level = Level.ERROR;
- }
-
- log.info("Test log for MDC inclusion, expected: {}", principal);
-
- return new TestResponse(level.toString(), principal);
- }
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public static class TestResponse {
- private String level;
- private String principal;
- }
-}
diff --git a/src/test/resources/application-test-composite.yaml b/src/test/resources/application-test-composite.yaml
deleted file mode 100644
index d15a885..0000000
--- a/src/test/resources/application-test-composite.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-logger:
- mdc:
- principal:
- level: DEBUG
- loggers:
- - io.kokuwa
- values:
- - horst
diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml
index 71beb2c..867b23e 100644
--- a/src/test/resources/application-test.yaml
+++ b/src/test/resources/application-test.yaml
@@ -7,6 +7,3 @@ micronaut:
generator:
secret: pleaseChangeThisSecretForANewOne
jws-algorithm: HS256
- http:
- client:
- logger-name: io.kokuwa.Test