feat: initial commit
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
12
.idea/encodings.xml
generated
Normal file
12
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/lib-1/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/lib-1/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/spring-boot-common-utils/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/spring-boot-parent/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/spring-boot-parent/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/spring-boot-parent" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
29
lib-1/pom.xml
Normal file
29
lib-1/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?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>com.sligrofoodgroup.spring-boot-common</groupId>
|
||||||
|
<artifactId>common</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>lib-1</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webflux</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
33
pom.xml
Normal file
33
pom.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git:file://localhost/../sfg-sb-common-remote.git</connection>
|
||||||
|
<url>file://../sfg-sb-common-remote.git</url>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.sligrofoodgroup.spring-boot-common</groupId>
|
||||||
|
<artifactId>spring-boot-parent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>spring-boot-parent/pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>common</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<modules>
|
||||||
|
<module>spring-boot-parent</module>
|
||||||
|
<module>lib-1</module>
|
||||||
|
<module>spring-boot-common-utils</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
||||||
88
spring-boot-common-utils/pom.xml
Normal file
88
spring-boot-common-utils/pom.xml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?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>com.sligrofoodgroup.spring-boot-common</groupId>
|
||||||
|
<artifactId>common</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>spring-boot-common-utils</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains</groupId>
|
||||||
|
<artifactId>annotations</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
TODO: is it safe to mark these as optional?
|
||||||
|
Given these are used by @Component components (with a @Contitional)
|
||||||
|
-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-core</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.annotation</groupId>
|
||||||
|
<artifactId>jakarta.annotation-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>context-propagation</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webflux</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-web</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
import org.slf4j.event.Level;
|
||||||
|
import org.springframework.http.HttpRequest;
|
||||||
|
import org.springframework.http.HttpStatusCode;
|
||||||
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.util.StopWatch;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class AccessLogFilter {
|
||||||
|
public static final String MDC_PREFIX = "req.";
|
||||||
|
public static final String MDC_METHOD = MDC_PREFIX + "method";
|
||||||
|
public static final String MDC_URI = MDC_PREFIX + "uri";
|
||||||
|
public static final String MDC_REQUEST_ID = MDC_PREFIX + "requestId";
|
||||||
|
public static final String MDC_REAL_IP = MDC_PREFIX + "realIp";
|
||||||
|
public static final String MDC_STATUS_CODE = MDC_PREFIX + "statusCode";
|
||||||
|
public static final String MDC_DURATION_MS = MDC_PREFIX + "durationMs";
|
||||||
|
public static final String MDC_BASE_SITE_ID = MDC_PREFIX + "baseSiteId";
|
||||||
|
|
||||||
|
public static final String HTTP_HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
|
||||||
|
public static final String HTTP_HEADER_X_REAL_IP = "X-Real-IP";
|
||||||
|
public static final String HTTP_HEADER_X_REQUEST_ID = "X-Request-Id";
|
||||||
|
public static final String HTTP_HEADER_BASE_SITE_ID = "X-Base-Site-Id";
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Map<String, Object> getPropagationContext() {
|
||||||
|
return Map.of(MdcPropagator.MDC_TLA_NAME, MdcPropagator.getMdcContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InetAddress getRemoteAddress(@NotNull HttpRequest httpRequest) {
|
||||||
|
return getForwardedForAddress(httpRequest)
|
||||||
|
.or(() -> getRealIpAddress(httpRequest))
|
||||||
|
.or(() -> getFromRemoteAddress(httpRequest))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull Optional<InetAddress> getFromRemoteAddress(@NotNull HttpRequest httpRequest) {
|
||||||
|
try {
|
||||||
|
if (httpRequest instanceof final ServletServerHttpRequest servletServerHttpRequest) {
|
||||||
|
return Optional.ofNullable(servletServerHttpRequest.getRemoteAddress().getAddress());
|
||||||
|
}
|
||||||
|
} catch (final NoClassDefFoundError ncdfe) {
|
||||||
|
// This will happen when we do not have WebMVC classes available
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (httpRequest instanceof final ServerHttpRequest serverHttpRequest) {
|
||||||
|
return Optional.ofNullable(serverHttpRequest.getRemoteAddress())
|
||||||
|
.map(InetSocketAddress::getAddress);
|
||||||
|
}
|
||||||
|
} catch (final NoClassDefFoundError ncdfe) {
|
||||||
|
// This may happen if we do not have WebFlux classes available
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull Optional<InetAddress> getRealIpAddress(@NotNull HttpRequest httpRequest) {
|
||||||
|
return httpRequest.getHeaders()
|
||||||
|
.getOrEmpty(HTTP_HEADER_X_REAL_IP)
|
||||||
|
.stream()
|
||||||
|
.findFirst()
|
||||||
|
.map(host -> {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(host);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull Optional<InetAddress> getForwardedForAddress(@NotNull HttpRequest httpRequest) {
|
||||||
|
return httpRequest.getHeaders()
|
||||||
|
.getOrEmpty(HTTP_HEADER_X_FORWARDED_FOR)
|
||||||
|
.stream()
|
||||||
|
.flatMap(line -> Arrays.stream(line.split(",")).map(String::trim))
|
||||||
|
.findFirst() // todo filter untrusted
|
||||||
|
.map(host -> {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(host);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private String getBaseSiteId(@NotNull HttpRequest httpRequest) {
|
||||||
|
return Optional.ofNullable(httpRequest.getHeaders().getFirst(HTTP_HEADER_BASE_SITE_ID)).orElse("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillMdc(@NotNull final HttpRequest request) {
|
||||||
|
// todo, doesn't opentracing do this also? doesn't this conflict
|
||||||
|
String requestId = Optional.ofNullable(request.getHeaders().getFirst(HTTP_HEADER_X_REQUEST_ID))
|
||||||
|
.orElseGet(() -> UUID.randomUUID().toString());
|
||||||
|
MDC.put(MDC_METHOD, request.getMethod().name());
|
||||||
|
MDC.put(MDC_URI, request.getURI().toString());
|
||||||
|
MDC.put(MDC_REQUEST_ID, requestId);
|
||||||
|
MDC.put(MDC_REAL_IP, Optional.ofNullable(getRemoteAddress(request)).map(InetAddress::getHostAddress).orElse("unknown"));
|
||||||
|
MDC.put(MDC_BASE_SITE_ID, getBaseSiteId(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessLogContex requestStart(@NotNull final HttpRequest request) {
|
||||||
|
final AccessLogContex accessLogContex = new AccessLogContex(request);
|
||||||
|
fillMdc(request);
|
||||||
|
return accessLogContex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearMdc() {
|
||||||
|
MDC.remove(MDC_METHOD);
|
||||||
|
MDC.remove(MDC_URI);
|
||||||
|
MDC.remove(MDC_REQUEST_ID);
|
||||||
|
MDC.remove(MDC_REAL_IP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AccessLogContex {
|
||||||
|
@NotNull
|
||||||
|
private final HttpRequest request;
|
||||||
|
@NotNull
|
||||||
|
public final StopWatch stopWatch;
|
||||||
|
|
||||||
|
private AccessLogContex(@NotNull final HttpRequest request) {
|
||||||
|
this.request = request;
|
||||||
|
stopWatch = new StopWatch();
|
||||||
|
stopWatch.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestEnd(@Nullable final HttpStatusCode statusCode, @NotNull final Throwable throwable) {
|
||||||
|
stopWatch.stop();
|
||||||
|
log.makeLoggingEventBuilder(Level.WARN)
|
||||||
|
.addKeyValue(MDC_STATUS_CODE, Optional.ofNullable(statusCode).map(HttpStatusCode::value).map(Object::toString).orElse("n/a"))
|
||||||
|
.addKeyValue(MDC_DURATION_MS, stopWatch.getTotalTimeMillis())
|
||||||
|
.setCause(throwable)
|
||||||
|
.log("Request {} {} {}", request.getMethod(), request.getURI(), statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestEnd(@Nullable final HttpStatusCode statusCode) {
|
||||||
|
stopWatch.stop();
|
||||||
|
log.makeLoggingEventBuilder(Level.WARN)
|
||||||
|
.addKeyValue(MDC_STATUS_CODE, Optional.ofNullable(statusCode).map(HttpStatusCode::value).map(Object::toString).orElse("n/a"))
|
||||||
|
.addKeyValue(MDC_DURATION_MS, stopWatch.getTotalTimeMillis())
|
||||||
|
.log("Request {} {} {}", request.getMethod(), request.getURI(), statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Import({
|
||||||
|
ServletAccessLogFilter.class,
|
||||||
|
WebFluxAccessLogFilter.class,
|
||||||
|
MdcLoggingConfiguration.class,
|
||||||
|
MissingMdcLoggingConfiguration.class,
|
||||||
|
WebClientRequestIdForwarder.class,
|
||||||
|
})
|
||||||
|
public @interface EnableSfgSbLogging {
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
import io.micrometer.context.ContextRegistry;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass(ContextRegistry.class)
|
||||||
|
class MdcLoggingConfiguration {
|
||||||
|
@PostConstruct
|
||||||
|
public void registerMdcPropagation() {
|
||||||
|
log.info("Enabling MDC propagation");
|
||||||
|
ContextRegistry.getInstance().registerThreadLocalAccessor(new MdcPropagator());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
|
||||||
|
import io.micrometer.context.ThreadLocalAccessor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class MdcPropagator implements ThreadLocalAccessor<Map<String, String>> {
|
||||||
|
public static final String MDC_TLA_NAME = MdcPropagator.class.getName();
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static Map<String, String> getMdcContext() {
|
||||||
|
return Optional.ofNullable(MDC.getCopyOfContextMap())
|
||||||
|
.map(Map::entrySet)
|
||||||
|
.orElse(Collections.emptySet())
|
||||||
|
.stream()
|
||||||
|
.filter(e -> e.getKey().startsWith(AccessLogFilter.MDC_PREFIX))
|
||||||
|
.filter(e -> Objects.nonNull(e.getValue()))
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Object key() {
|
||||||
|
return MDC_TLA_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Map<String, String> getValue() {
|
||||||
|
return getMdcContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(@NotNull Map<String, String> value) {
|
||||||
|
value.forEach(MDC::put);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue() {
|
||||||
|
Optional.ofNullable(MDC.getCopyOfContextMap())
|
||||||
|
.map(Map::keySet)
|
||||||
|
.orElse(Collections.emptySet())
|
||||||
|
.stream()
|
||||||
|
.filter(key -> key.startsWith(AccessLogFilter.MDC_PREFIX))
|
||||||
|
.forEach(MDC::remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnMissingClass("io.micrometer.context.ContextRegistry")
|
||||||
|
class MissingMdcLoggingConfiguration {
|
||||||
|
@PostConstruct
|
||||||
|
public void registerMdcPropagation() {
|
||||||
|
log.warn("Missing Micrometer Context Propagator dependency, MDC is not propagated");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.http.HttpStatusCode;
|
||||||
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||||
|
class ServletAccessLogFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final AccessLogFilter accessLogFilterLogger = new AccessLogFilter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
AccessLogFilter.AccessLogContex accessLogContex = accessLogFilterLogger.requestStart(new ServletServerHttpRequest(request));
|
||||||
|
try {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
accessLogContex.requestEnd(HttpStatusCode.valueOf(response.getStatus()));
|
||||||
|
} catch (Exception throwable) {
|
||||||
|
accessLogContex.requestEnd(HttpStatusCode.valueOf(response.getStatus()), throwable);
|
||||||
|
throw throwable;
|
||||||
|
} finally {
|
||||||
|
accessLogFilterLogger.clearMdc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass(WebClient.class)
|
||||||
|
public class WebClientRequestIdForwarder {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WebClientCustomizer webClientCustomizer() {
|
||||||
|
return webClientBuilder ->
|
||||||
|
webClientBuilder.filter(((request, next) ->
|
||||||
|
Mono.deferContextual((contextView ->
|
||||||
|
next.exchange(ClientRequest
|
||||||
|
.from(request)
|
||||||
|
.headers(httpHeaders -> {
|
||||||
|
contextView.getOrEmpty(AccessLogFilter.MDC_REQUEST_ID)
|
||||||
|
.ifPresent(requestId -> httpHeaders.set(AccessLogFilter.HTTP_HEADER_X_REQUEST_ID, (String) requestId));
|
||||||
|
})
|
||||||
|
.build())))));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.sligrofoodgroup.sb.util.logging;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.WebFilter;
|
||||||
|
import org.springframework.web.server.WebFilterChain;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||||
|
class WebFluxAccessLogFilter implements WebFilter {
|
||||||
|
|
||||||
|
private final AccessLogFilter accessLogger = new AccessLogFilter();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Mono<Void> filter(@NotNull ServerWebExchange exchange, @NotNull WebFilterChain chain) {
|
||||||
|
final AccessLogFilter.AccessLogContex accessLogContex = this.accessLogger.requestStart(exchange.getRequest());
|
||||||
|
Mono<Void> filteredChain = chain.filter(exchange)
|
||||||
|
.doOnError(throwable -> accessLogContex.requestEnd(exchange.getResponse().getStatusCode(), throwable))
|
||||||
|
.doOnSuccess(v -> accessLogContex.requestEnd(exchange.getResponse().getStatusCode()))
|
||||||
|
.contextWrite(Context.of(this.accessLogger.getPropagationContext()));
|
||||||
|
this.accessLogger.clearMdc();
|
||||||
|
return filteredChain;
|
||||||
|
// NOTE: clearing the MDC isn't really possible in reactive context
|
||||||
|
// doFinally is invoked at the end of the mono chain in the thread the mono gets resolved.
|
||||||
|
// This makes doFinally unable to clear the MDC.
|
||||||
|
// Furthermore, this thread could already be handling a new request at this point in time.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
logging:
|
||||||
|
level:
|
||||||
|
nl:
|
||||||
|
sligro: INFO
|
||||||
|
com:
|
||||||
|
sligrofoodgroup: INFO
|
||||||
|
spring:
|
||||||
|
reactor:
|
||||||
|
context-propagation: auto
|
||||||
|
---
|
||||||
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: '!local'
|
||||||
|
logging:
|
||||||
|
structured:
|
||||||
|
format:
|
||||||
|
console: logstash
|
||||||
|
#---
|
||||||
|
#spring:
|
||||||
|
# config:
|
||||||
|
# activate:
|
||||||
|
# on-profile: local
|
||||||
48
spring-boot-parent/pom.xml
Normal file
48
spring-boot-parent/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?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>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.5.5</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.sligrofoodgroup.spring-boot-common</groupId>
|
||||||
|
<artifactId>spring-boot-parent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<sfg-sb-commons-version>${project.version}</sfg-sb-commons-version>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<!--Internal dependencies-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sligrofoodgroup</groupId>
|
||||||
|
<artifactId>lib-1</artifactId>
|
||||||
|
<version>${sfg-sb-commons-version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sligrofoodgroup.spring-boot-common</groupId>
|
||||||
|
<artifactId>spring-boot-common-utils</artifactId>
|
||||||
|
<version>${sfg-sb-commons-version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--Common dependencies-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains</groupId>
|
||||||
|
<artifactId>annotations</artifactId>
|
||||||
|
<version>24.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
</project>
|
||||||
Reference in New Issue
Block a user