Add config options to add authentication attributes as mdc.
This commit is contained in:
parent
a350698f52
commit
7ad1ee0add
32 changed files with 964 additions and 541 deletions
|
@ -5,6 +5,3 @@ rules:
|
||||||
|
|
||||||
# no need for document start
|
# no need for document start
|
||||||
document-start: disable
|
document-start: disable
|
||||||
|
|
||||||
# line length is not important
|
|
||||||
line-length: disable
|
|
||||||
|
|
175
README.md
175
README.md
|
@ -1,176 +1,17 @@
|
||||||
# Micronaut Logging support
|
# Micronaut Logging support
|
||||||
|
|
||||||
This branch is for Micronaut 2.x, for 3.x see wip branch [3.x](../../tree/3.x).
|
|
||||||
|
|
||||||
## Features
|
## 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
|
* [build](docs/build.md)
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<configuration debug="true" scan="false">
|
|
||||||
<include resource="io/kokuwa/logback/base.xml" />
|
|
||||||
<logger name="io.micronaut.logging.PropertiesLoggingLevelsConfigurer" levels="WARN" />
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="${LOGBACK_APPENDER:-CONSOLE}" />
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Open Topics
|
## Open Topics
|
||||||
|
|
||||||
|
|
23
docs/build.md
Normal file
23
docs/build.md
Normal file
|
@ -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
|
||||||
|
```
|
35
docs/features/http_log_level.md
Normal file
35
docs/features/http_log_level.md
Normal file
|
@ -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}
|
||||||
|
```
|
29
docs/features/http_mdc_authentication.md
Normal file
29
docs/features/http_mdc_authentication.md
Normal file
|
@ -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
|
||||||
|
```
|
28
docs/features/http_mdc_headers.md
Normal file
28
docs/features/http_mdc_headers.md
Normal file
|
@ -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
|
||||||
|
```
|
16
docs/features/logback_appender.md
Normal file
16
docs/features/logback_appender.md
Normal file
|
@ -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`
|
18
docs/features/logback_default.md
Normal file
18
docs/features/logback_default.md
Normal file
|
@ -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
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration debug="true" scan="false">
|
||||||
|
|
||||||
|
<include resource="io/kokuwa/logback/base.xml" />
|
||||||
|
|
||||||
|
<logger name="io.micronaut.logging.PropertiesLoggingLevelsConfigurer" levels="WARN" />
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="${LOGBACK_APPENDER:-CONSOLE}" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
```
|
66
docs/features/logback_mdc_level.md
Normal file
66
docs/features/logback_mdc_level.md
Normal file
|
@ -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.<key>` | MDC key to use |
|
||||||
|
`logger.mdc.<key>.key` | MDC key override, see complex example below for usage | `<key>`
|
||||||
|
`logger.mdc.<key>.level` | log level to use | `TRACE`
|
||||||
|
`logger.mdc.<key>.loggers` | passlist of logger names, matches all loggers if empty | `[]`
|
||||||
|
`logger.mdc.<key>.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
|
||||||
|
```
|
9
pom.xml
9
pom.xml
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>io.kokuwa</groupId>
|
<groupId>io.kokuwa</groupId>
|
||||||
<artifactId>maven-parent</artifactId>
|
<artifactId>maven-parent</artifactId>
|
||||||
<version>0.5.4</version>
|
<version>0.5.5</version>
|
||||||
<relativePath />
|
<relativePath />
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
|
|
||||||
<version.ch.qos.logback.contrib>0.1.5</version.ch.qos.logback.contrib>
|
<version.ch.qos.logback.contrib>0.1.5</version.ch.qos.logback.contrib>
|
||||||
<version.io.micronaut>3.1.3</version.io.micronaut>
|
<version.io.micronaut>3.2.0</version.io.micronaut>
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
@ -92,6 +92,11 @@
|
||||||
<artifactId>micronaut-runtime</artifactId>
|
<artifactId>micronaut-runtime</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut.security</groupId>
|
||||||
|
<artifactId>micronaut-security</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.micronaut.test</groupId>
|
<groupId>io.micronaut.test</groupId>
|
||||||
<artifactId>micronaut-test-junit5</artifactId>
|
<artifactId>micronaut-test-junit5</artifactId>
|
||||||
|
|
169
pom.xml.versionsBackup
Normal file
169
pom.xml.versionsBackup
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>io.kokuwa</groupId>
|
||||||
|
<artifactId>maven-parent</artifactId>
|
||||||
|
<version>0.5.4</version>
|
||||||
|
<relativePath />
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>io.kokuwa.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-logging</artifactId>
|
||||||
|
<version>3.0.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>Logging support for Micronaut</name>
|
||||||
|
<description>Enhanced logging using MDC or request header.</description>
|
||||||
|
<url>https://github.com/kokuwaio/micronaut-logging</url>
|
||||||
|
<inceptionYear>2020</inceptionYear>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Apache License 2.0</name>
|
||||||
|
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>Stephan Schnabel</name>
|
||||||
|
<url>https://github.com/stephanschnabel</url>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<url>https://github.com/kokuwaio/micronaut-logging</url>
|
||||||
|
<connection>scm:git:https://github.com/kokuwaio/micronaut-logging.git</connection>
|
||||||
|
<developerConnection>scm:git:https://github.com/kokuwaio/micronaut-logging.git</developerConnection>
|
||||||
|
<tag>HEAD</tag>
|
||||||
|
</scm>
|
||||||
|
<issueManagement>
|
||||||
|
<system>github</system>
|
||||||
|
<url>https://github.com/kokuwaio/micronaut-logging/issues</url>
|
||||||
|
</issueManagement>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- ============================= Libaries ============================== -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
|
||||||
|
<version.ch.qos.logback.contrib>0.1.5</version.ch.qos.logback.contrib>
|
||||||
|
<version.io.micronaut>3.2.0</version.io.micronaut>
|
||||||
|
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- micronaut -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-bom</artifactId>
|
||||||
|
<version>${version.io.micronaut}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- logback -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback.contrib</groupId>
|
||||||
|
<artifactId>logback-json-classic</artifactId>
|
||||||
|
<version>${version.ch.qos.logback.contrib}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback.contrib</groupId>
|
||||||
|
<artifactId>logback-json-core</artifactId>
|
||||||
|
<version>${version.ch.qos.logback.contrib}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback.contrib</groupId>
|
||||||
|
<artifactId>logback-jackson</artifactId>
|
||||||
|
<version>${version.ch.qos.logback.contrib}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- micronaut -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-runtime</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut.security</groupId>
|
||||||
|
<artifactId>micronaut-security</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut.test</groupId>
|
||||||
|
<artifactId>micronaut-test-junit5</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-http-client</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-http-server-netty</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut.security</groupId>
|
||||||
|
<artifactId>micronaut-security-jwt</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- avoid compiler warning, see https://docs.micronaut.io/latest/guide/#_nullable_annotations -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.findbugs</groupId>
|
||||||
|
<artifactId>jsr305</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback.contrib</groupId>
|
||||||
|
<artifactId>logback-jackson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback.contrib</groupId>
|
||||||
|
<artifactId>logback-json-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
<!-- add compiler processors -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${version.org.projectlombok}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>io.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-inject-java</artifactId>
|
||||||
|
<version>${version.io.micronaut}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -1,5 +1,6 @@
|
||||||
package io.kokuwa.micronaut.logging.configurator;
|
package io.kokuwa.micronaut.logging.configurator;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
import ch.qos.logback.classic.spi.Configurator;
|
import ch.qos.logback.classic.spi.Configurator;
|
||||||
import ch.qos.logback.core.joran.spi.JoranException;
|
import ch.qos.logback.core.joran.spi.JoranException;
|
||||||
|
@ -29,5 +30,7 @@ public class DefaultConfigurator extends ContextAwareBase implements Configurato
|
||||||
} catch (JoranException e) {
|
} catch (JoranException e) {
|
||||||
addError("Failed to load logback.xml from io.kokuwa:micronaut-logging", e);
|
addError("Failed to load logback.xml from io.kokuwa:micronaut-logging", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loggerContext.getLogger("io.micronaut.logging.PropertiesLoggingLevelsConfigurer").setLevel(Level.WARN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<MutableHttpResponse<?>> doFilter(
|
||||||
|
HttpRequest<?> request,
|
||||||
|
ServerFilterChain chain,
|
||||||
|
Map<String, String> 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.kokuwa.micronaut.logging.request;
|
package io.kokuwa.micronaut.logging.http.level;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -13,30 +13,29 @@ import io.micronaut.http.annotation.Filter;
|
||||||
import io.micronaut.http.context.ServerRequestContext;
|
import io.micronaut.http.context.ServerRequestContext;
|
||||||
import io.micronaut.http.filter.ClientFilterChain;
|
import io.micronaut.http.filter.ClientFilterChain;
|
||||||
import io.micronaut.http.filter.HttpClientFilter;
|
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
|
* @author Stephan Schnabel
|
||||||
*/
|
*/
|
||||||
@Requires(beans = HeaderLoggingServerHttpFilter.class)
|
@Requires(beans = LogLevelServerFilter.class)
|
||||||
@Requires(property = HeaderLoggingClientHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
@Requires(property = LogLevelClientFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||||
@Filter("${" + HeaderLoggingClientHttpFilter.PREFIX + ".path:/**}")
|
@Filter("${" + LogLevelClientFilter.PREFIX + ".path:/**}")
|
||||||
public class HeaderLoggingClientHttpFilter implements HttpClientFilter {
|
public class LogLevelClientFilter implements HttpClientFilter {
|
||||||
|
|
||||||
public static final String PREFIX = "logger.request.propagation";
|
public static final String PREFIX = "logger.http.level.propagation";
|
||||||
public static final int DEFAULT_ORDER = ServerFilterPhase.TRACING.order();
|
public static final int DEFAULT_ORDER = HIGHEST_PRECEDENCE;
|
||||||
|
|
||||||
private final String serverHeader;
|
private final String serverHeader;
|
||||||
private final String propagationHeader;
|
private final String propagationHeader;
|
||||||
private final int order;
|
private final int order;
|
||||||
|
|
||||||
public HeaderLoggingClientHttpFilter(
|
public LogLevelClientFilter(
|
||||||
@Value("${" + HeaderLoggingServerHttpFilter.PREFIX + ".header}") Optional<String> serverHeader,
|
@Value("${" + LogLevelServerFilter.PREFIX + ".header}") Optional<String> serverHeader,
|
||||||
@Value("${" + PREFIX + ".header}") Optional<String> propagationHeader,
|
@Value("${" + PREFIX + ".header}") Optional<String> propagationHeader,
|
||||||
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||||
this.serverHeader = serverHeader.orElse(HeaderLoggingServerHttpFilter.DEFAULT_HEADER);
|
this.serverHeader = serverHeader.orElse(LogLevelServerFilter.DEFAULT_HEADER);
|
||||||
this.propagationHeader = propagationHeader.orElse(this.serverHeader);
|
this.propagationHeader = propagationHeader.orElse(this.serverHeader);
|
||||||
this.order = order.orElse(DEFAULT_ORDER);
|
this.order = order.orElse(DEFAULT_ORDER);
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.kokuwa.micronaut.logging.request;
|
package io.kokuwa.micronaut.logging.http.level;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import org.slf4j.MDC;
|
||||||
|
|
||||||
import ch.qos.logback.classic.turbo.TurboFilter;
|
import ch.qos.logback.classic.turbo.TurboFilter;
|
||||||
import io.kokuwa.micronaut.logging.LogbackUtil;
|
import io.kokuwa.micronaut.logging.LogbackUtil;
|
||||||
|
import io.kokuwa.micronaut.logging.http.AbstractMdcFilter;
|
||||||
import io.micronaut.context.annotation.Requires;
|
import io.micronaut.context.annotation.Requires;
|
||||||
import io.micronaut.context.annotation.Value;
|
import io.micronaut.context.annotation.Value;
|
||||||
import io.micronaut.core.async.publisher.Publishers;
|
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.HttpRequest;
|
||||||
import io.micronaut.http.MutableHttpResponse;
|
import io.micronaut.http.MutableHttpResponse;
|
||||||
import io.micronaut.http.annotation.Filter;
|
import io.micronaut.http.annotation.Filter;
|
||||||
import io.micronaut.http.filter.HttpServerFilter;
|
|
||||||
import io.micronaut.http.filter.ServerFilterChain;
|
import io.micronaut.http.filter.ServerFilterChain;
|
||||||
import io.micronaut.http.filter.ServerFilterPhase;
|
import io.micronaut.http.filter.ServerFilterPhase;
|
||||||
import io.micronaut.runtime.server.EmbeddedServer;
|
import io.micronaut.runtime.context.scope.Refreshable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http request logging filter.
|
* Http request logging filter.
|
||||||
*
|
*
|
||||||
* @author Stephan Schnabel
|
* @author Stephan Schnabel
|
||||||
*/
|
*/
|
||||||
@Requires(beans = EmbeddedServer.class)
|
@Refreshable
|
||||||
@Requires(property = HeaderLoggingServerHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
@Requires(property = LogLevelServerFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||||
@Filter("${" + HeaderLoggingServerHttpFilter.PREFIX + ".path:/**}")
|
@Filter("${" + LogLevelServerFilter.PREFIX + ".path:/**}")
|
||||||
public class HeaderLoggingServerHttpFilter implements HttpServerFilter {
|
public class LogLevelServerFilter extends AbstractMdcFilter {
|
||||||
|
|
||||||
public static final String PREFIX = "logger.request.filter";
|
|
||||||
public static final String MDC_FILTER = PREFIX;
|
|
||||||
public static final String MDC_KEY = "level";
|
|
||||||
|
|
||||||
|
public static final String PREFIX = "logger.http.level";
|
||||||
public static final String DEFAULT_HEADER = "x-log-level";
|
public static final String DEFAULT_HEADER = "x-log-level";
|
||||||
public static final int DEFAULT_ORDER = ServerFilterPhase.FIRST.before();
|
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 LogbackUtil logback;
|
||||||
private final String header;
|
private final String header;
|
||||||
private final int order;
|
|
||||||
|
|
||||||
public HeaderLoggingServerHttpFilter(
|
public LogLevelServerFilter(
|
||||||
LogbackUtil logback,
|
LogbackUtil logback,
|
||||||
@Value("${" + PREFIX + ".header}") Optional<String> header,
|
@Value("${" + PREFIX + ".header}") Optional<String> header,
|
||||||
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||||
|
super(order.orElse(DEFAULT_ORDER));
|
||||||
this.logback = logback;
|
this.logback = logback;
|
||||||
this.header = header.orElse(DEFAULT_HEADER);
|
this.header = header.orElse(DEFAULT_HEADER);
|
||||||
this.order = order.orElse(DEFAULT_ORDER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
void startTurbofilter() {
|
void startTurbofilter() {
|
||||||
logback.getTurboFilter(HeaderLoggingTurboFilter.class, MDC_FILTER, HeaderLoggingTurboFilter::new).start();
|
logback.getTurboFilter(LogLevelTurboFilter.class, MDC_FILTER, LogLevelTurboFilter::new).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
void stopTurbofilter() {
|
void stopTurbofilter() {
|
||||||
logback.getTurboFilter(HeaderLoggingTurboFilter.class, MDC_FILTER).ifPresent(TurboFilter::stop);
|
logback.getTurboFilter(LogLevelTurboFilter.class, MDC_FILTER).ifPresent(TurboFilter::stop);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return order;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
|
@ -1,4 +1,4 @@
|
||||||
package io.kokuwa.micronaut.logging.request;
|
package io.kokuwa.micronaut.logging.http.level;
|
||||||
|
|
||||||
import org.slf4j.MDC;
|
import org.slf4j.MDC;
|
||||||
import org.slf4j.Marker;
|
import org.slf4j.Marker;
|
||||||
|
@ -13,7 +13,7 @@ import ch.qos.logback.core.spi.FilterReply;
|
||||||
*
|
*
|
||||||
* @author Stephan Schnabel
|
* @author Stephan Schnabel
|
||||||
*/
|
*/
|
||||||
public class HeaderLoggingTurboFilter extends TurboFilter {
|
public class LogLevelTurboFilter extends TurboFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
|
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;
|
return FilterReply.NEUTRAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = MDC.get(HeaderLoggingServerHttpFilter.MDC_KEY);
|
var value = MDC.get(LogLevelServerFilter.MDC_KEY);
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return FilterReply.NEUTRAL;
|
return FilterReply.NEUTRAL;
|
||||||
}
|
}
|
|
@ -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<String> attributes;
|
||||||
|
private final String prefix;
|
||||||
|
|
||||||
|
public AuthenticationMdcFilter(
|
||||||
|
@Value("${" + PREFIX + ".name:principal}") Optional<String> name,
|
||||||
|
@Value("${" + PREFIX + ".attributes:[]}") List<String> attributes,
|
||||||
|
@Value("${" + PREFIX + ".prefix}") Optional<String> prefix,
|
||||||
|
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||||
|
super(order.orElse(DEFAULT_ORDER));
|
||||||
|
this.name = name.orElse(DEFAULT_NAME);
|
||||||
|
this.prefix = prefix.orElse(null);
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Publisher<MutableHttpResponse<?>> 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<String, String>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String> headers;
|
||||||
|
private final String prefix;
|
||||||
|
|
||||||
|
public HttpHeadersMdcFilter(
|
||||||
|
@Value("${" + PREFIX + ".names}") List<String> headers,
|
||||||
|
@Value("${" + PREFIX + ".prefix}") Optional<String> prefix,
|
||||||
|
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||||
|
super(order.orElse(DEFAULT_ORDER));
|
||||||
|
this.prefix = prefix.orElse(null);
|
||||||
|
this.headers = headers.stream().map(String::toLowerCase).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
|
||||||
|
var mdc = new HashMap<String, String>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Integer> order) {
|
|
||||||
this.key = key;
|
|
||||||
this.order = order.orElse(DEFAULT_ORDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Publisher<MutableHttpResponse<?>> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,9 @@ package io.kokuwa.micronaut.logging;
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
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.junit.jupiter.api.TestMethodOrder;
|
||||||
import org.slf4j.MDC;
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
@ -14,7 +16,8 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||||
* @author Stephan Schnabel
|
* @author Stephan Schnabel
|
||||||
*/
|
*/
|
||||||
@MicronautTest
|
@MicronautTest
|
||||||
@TestMethodOrder(DisplayName.class)
|
@TestClassOrder(ClassOrderer.DisplayName.class)
|
||||||
|
@TestMethodOrder(MethodOrderer.DisplayName.class)
|
||||||
public abstract class AbstractTest {
|
public abstract class AbstractTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
|
|
@ -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<JWTClaimsSet.Builder> 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<String, String> 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<String, String> context = Map.of();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.<String, String>of() : Map.of(name, value);
|
||||||
|
assertEquals(expectedLevel.toString(), get(headers).getLevel());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, String> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, String> expectedMdcs, Map<String, String> headers) {
|
||||||
|
assertEquals(expectedMdcs, get(headers).getContext());
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||||
*
|
*
|
||||||
* @author Stephan Schnabel
|
* @author Stephan Schnabel
|
||||||
*/
|
*/
|
||||||
@DisplayName("mdc")
|
@DisplayName("mdc based log levels")
|
||||||
@MicronautTest(environments = "test-mdc")
|
@MicronautTest(environments = "test-mdc")
|
||||||
public class MDCTurboFilterTest extends AbstractTest {
|
public class MDCTurboFilterTest extends AbstractTest {
|
||||||
|
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
logger:
|
|
||||||
mdc:
|
|
||||||
principal:
|
|
||||||
level: DEBUG
|
|
||||||
loggers:
|
|
||||||
- io.kokuwa
|
|
||||||
values:
|
|
||||||
- horst
|
|
|
@ -7,6 +7,3 @@ micronaut:
|
||||||
generator:
|
generator:
|
||||||
secret: pleaseChangeThisSecretForANewOne
|
secret: pleaseChangeThisSecretForANewOne
|
||||||
jws-algorithm: HS256
|
jws-algorithm: HS256
|
||||||
http:
|
|
||||||
client:
|
|
||||||
logger-name: io.kokuwa.Test
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue