openfeign normalized logger

This commit is contained in:
bvn13 2022-07-25 14:40:36 +03:00
parent c07d962847
commit 11feba2cb3
4 changed files with 500 additions and 2 deletions

2
.gitignore vendored
View File

@ -24,3 +24,5 @@
hs_err_pid* hs_err_pid*
replay_pid* replay_pid*
.idea
.idea/**

149
README.md
View File

@ -1,3 +1,148 @@
# OpenFeign-NormalizedLogger # OpenFeign Normalized Logger
Normalized Logger for OpenFeign ![](https://img.shields.io/maven-central/v/me.bvn13.openfeign.logger/feign-normalized-logger)
Standard OpenFeign logger provides the only approach to log communications -
it logs every header in separated log entries, the body goes into another log entry.
It is very inconvenient to deal with such logs in production especially in multithreaded systems.
This 'Normalized Logger' is intended to combine all log entries related to one request-reply
communication into one log entry.
## Old bad Logger
All parts are separeted from each other:
1) Request:
1) request headers - every header is put into separated entry
2) requst body - at separated entry
2) Response:
1) response headers - separately
2) response body - at separated entry as well
```
2022-07-25 14:12:43.572 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] ---> POST https://example.com/api/v1/login HTTP/1.1
2022-07-25 14:12:43.573 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] Content-Length: 23
2022-07-25 14:12:43.573 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] Content-Type: application/json
2022-07-25 14:12:43.574 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4464.5 Safari/537.36
2022-07-25 14:12:43.575 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login]
2022-07-25 14:12:43.576 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] {"login":"123456789"}
2022-07-25 14:12:43.576 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] ---> END HTTP (21-byte body)
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] <--- UNKNOWN 200 (324ms)
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] cache-control: no-cache
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] cf-cache-status: DYNAMIC
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] cf-ray: 730476518ea441ce-AMS
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] content-type: application/json
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] date: Mon, 25 Jul 2022 11:12:43 GMT
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] feature-policy: accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment *; usb 'none'
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] referrer-policy: strict-origin-when-cross-origin
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] server: cloudflare
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] set-cookie: ACCESS_TOKEN=eyJh9uygCUMA659bAZ54SHpSNy_KFXQ; Max-Age=1800; Domain=.example.com; Path=/; Secure; SameSite=None
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] strict-transport-security: max-age=31536000
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] x-content-type-options: nosniff
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] x-frame-options: sameorigin
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] x-xss-protection: 1; mode=block
2022-07-25 14:12:43.901 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login]
2022-07-25 14:12:43.902 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] {"status":{"code":"OK","message":"OK"},"body":{"id":"20826"}}
2022-07-25 14:12:43.902 DEBUG 1032530 --- [Executor] feign.Logger : [AuthApi#login] <--- END HTTP (61-byte body)
```
## New Normalized Logger
The whole communication (request and response parts) is combined into one log entry.
```
2022-07-25 14:16:06.217 INFO 1057053 --- [Executor] com.pf.profee.api.NormalizedFeignLogger : normalized feign request {AuthApi#login(LoginRequestDto)=[AuthApi#login] }: [
---> POST https://example.com/api/v1/login HTTP/1.1
Content-Length: 23
Content-Type: application/json
user-agent: bvn13 | Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4464.5 Safari/537.36
{"phone":"123456789"}
---> END HTTP (21-byte body)
] has response [
<--- UNKNOWN 200 (411ms)
cache-control: no-cache
cf-cache-status: DYNAMIC
cf-ray: 73047b41ffd2fa4c-AMS
content-type: application/json
date: Mon, 25 Jul 2022 11:16:06 GMT
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
feature-policy: accelerometer 'none'; camera 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; payment *; usb 'none'
referrer-policy: strict-origin-when-cross-origin
server: cloudflare
set-cookie: QA_JWT_ACCESS_TOKEN=eyJhboft6rzD6Be16dXY5lgQNCzOZNFe4ra_NDIdmXlXi19hlvaQ; Max-Age=1800; Domain=.example.com; Path=/; Secure; SameSite=None
strict-transport-security: max-age=31536000
x-content-type-options: nosniff
x-frame-options: sameorigin
x-xss-protection: 1; mode=block
{"status":{"code":"OK","message":"OK"},"body":{"id":"20826"}}
<--- END HTTP (61-byte body)
]
```
# How to use
In order to user Normalized Logger into the application they must the following.
## 0) Check the latest version
at [Maven Central Repo](https://repo1.maven.org/maven2/me/bvn13/openfeign/logger)
## 1) Add dependency
for Maven
```xml
<dependency>
<groupId>me.bvn13.openfeign.logger</groupId>
<artifactId>feign-normalized-logger</artifactId>
<version>0.1.0</version>
</dependency>
```
for Gradle
```groovy
implementation 'me.bvn13.openfeign.logger:feign-normalized-logger:0.1.0'
```
## 2) Create Feign configuration and enable logger
```java
import feign.Logger;
public class MyFeignConfig {
@Bean
public Logger logger() {
return new NormalizedFeignLogger();
}
}
```
### 3) Use this configuration into `@FeignClient` objects
```java
@FeignClient(name = "auth", configuration = MyFeignConfig.class)
public interface AuthApi {
/*...methods...*/
}
```
### 4) Adjust DEBUG level for Normalized Logger
for Slf4J + Logback
```yaml
logging:
level:
me.bvn13.openfeign.logger.NormalizedFeignLogger: DEBUG
```

229
pom.xml Normal file
View File

@ -0,0 +1,229 @@
<?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>
<groupId>me.bvn13.openfeign.logger</groupId>
<artifactId>feign-normalized-logger</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>OpenFeign Normalized Logger</name>
<description>Normalized Logger for OpenFeign</description>
<url>https://github.com/bvn13/OpenFeign-NormalizedLogger</url>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<properties>
<!-- Java -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- Encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- OpenFeign -->
<feign.version>11.9.1</feign.version>
<slf4j.version>1.7.9</slf4j.version>
<!-- Publishing -->
<nexus.url>https://s01.oss.sonatype.org</nexus.url>
<maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
<nexus-staging-maven-plugin.version>1.6.13</nexus-staging-maven-plugin.version>
<maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>
<gitflow-maven-plugin.version>1.18.0</gitflow-maven-plugin.version>
</properties>
<distributionManagement>
<repository>
<id>internal.repo</id>
<name>Temporary Staging Repository</name>
<url>file://${project.build.directory}/mvn-repo</url>
</repository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${feign.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>feign-normalized-logger</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<plugin>
<groupId>com.amashchenko.maven.plugin</groupId>
<artifactId>gitflow-maven-plugin</artifactId>
<version>${gitflow-maven-plugin.version}</version>
<configuration>
<pushRemote>false</pushRemote>
<gitFlowConfig>
<developmentBranch>develop</developmentBranch>
</gitFlowConfig>
<incrementVersionAtFinish>true</incrementVersionAtFinish>
<versionDigitToIncrement>2</versionDigitToIncrement>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<developers>
<developer>
<id>bvn13</id>
<name>Vyacheslav Boyko</name>
<email>dev@bvn13.me</email>
<roles>
<role>Developer</role>
</roles>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/bvn13/OpenFeign-NormalizedLogger.git</connection>
<developerConnection>scm:git:ssh://git@github.com:bvn13/OpenFeign-NormalizedLogger.git</developerConnection>
<tag>HEAD</tag>
<url>https://github.com/bvn13/OpenFeign-NormalizedLogger.git</url>
</scm>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>${maven-deploy-plugin.version}</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>${nexus-staging-maven-plugin.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>default-deploy</id>
<phase>deploy</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
</executions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>${nexus.url}</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>${maven-gpg-plugin.version}</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>${nexus.url}/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>${nexus.url}/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<pluginRepositories>
<pluginRepository>
<id>jcenter</id>
<name>JCenter</name>
<url>https://jcenter.bintray.com/</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,122 @@
package me.bvn13.openfeign.logger.normalized;
import feign.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* OpenFeign Logger
* combines request and response part into single log entry:
* <br></br>
* <pre>
* {@code
*
* normalized feign request (HERE-IS-CLASS-AND-METHOD): [
*
* ] has response [
*
* ]
* }
* </pre>
*/
public class NormalizedFeignLogger extends feign.Logger {
private static final Logger log = LoggerFactory.getLogger(NormalizedFeignLogger.class);
private final ThreadLocal<Map<String, String>> methodName;
private final ThreadLocal<Map<String, List<String>>> logsRequest;
private final ThreadLocal<Map<String, List<String>>> logsResponse;
private final ThreadLocal<Map<String, Boolean>> isResponse;
public NormalizedFeignLogger() {
methodName = new ThreadLocal<>();
isResponse = new ThreadLocal<>();
logsRequest = new ThreadLocal<>();
logsResponse = new ThreadLocal<>();
}
@Override
protected void logRequest(String configKey, Level logLevel, Request request) {
init();
super.logRequest(configKey, logLevel, request);
}
@Override
protected void log(String configKey, String format, Object... args) {
if (format.startsWith("--->") && !format.startsWith("---> END")) {
// the very beginning
clean(configKey);
}
if (!isResponse.get().getOrDefault(configKey, false)) {
log(logsRequest, configKey, format, args);
} else {
log(logsResponse, configKey, format, args);
if (format.startsWith("<--- END")) {
showLogs(configKey);
}
}
if (format.startsWith("---> END")) {
isResponse.get().put(configKey, true);
}
}
private void init() {
if (isResponse.get() == null) {
isResponse.set(new ConcurrentHashMap<>());
}
if (methodName.get() == null) {
methodName.set(new ConcurrentHashMap<>());
}
if (logsRequest.get() == null) {
logsRequest.set(new ConcurrentHashMap<>());
}
if (logsResponse.get() == null) {
logsResponse.set(new ConcurrentHashMap<>());
}
}
private void clean(String configKey) {
isResponse.get().put(configKey, false);
methodName.get().put(configKey, methodTag(configKey));
logsRequest.get().put(configKey, new LinkedList<>());
logsResponse.get().put(configKey, new LinkedList<>());
}
private void log(ThreadLocal<Map<String, List<String>>> container, String configKey, String format, Object... args) {
extractList(container, configKey)
.add(String.format(format, args));
}
private void showLogs(String configKey) {
log.debug("normalized feign request " + methodName.get() + ": [\n" +
collectionToDelimitedString(logsRequest.get().getOrDefault(configKey, Collections.emptyList()), "\n") +
"\n] has response [\n" +
collectionToDelimitedString(logsResponse.get().getOrDefault(configKey, Collections.emptyList()), "\n") +
"\n]");
}
private List<String> extractList(ThreadLocal<Map<String, List<String>>> container, String configKey) {
return container.get().get(configKey);
}
private String collectionToDelimitedString(Collection<String> collection, String delimeter) {
final StringBuilder sb = new StringBuilder();
final Iterator<String> iter = collection.iterator();
int i = 0;
while (iter.hasNext()) {
if (i++ > 0) {
sb.append(delimeter);
}
sb.append(iter.next());
}
return sb.toString();
}
}