-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle logging: log request + response+ MDC
- Loading branch information
1 parent
79eff5c
commit 7881823
Showing
7 changed files
with
289 additions
and
0 deletions.
There are no files selected for viewing
67 changes: 67 additions & 0 deletions
67
.../main/kotlin/com/vndevteam/kotlinwebspringboot3/application/logging/CustomResponseBody.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.vndevteam.kotlinwebspringboot3.application.logging | ||
|
||
import jakarta.servlet.http.HttpServletRequest | ||
import jakarta.servlet.http.HttpServletResponse | ||
import org.slf4j.Logger | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.beans.factory.annotation.Value | ||
import org.springframework.core.MethodParameter | ||
import org.springframework.http.MediaType | ||
import org.springframework.http.converter.HttpMessageConverter | ||
import org.springframework.http.server.ServerHttpRequest | ||
import org.springframework.http.server.ServerHttpResponse | ||
import org.springframework.http.server.ServletServerHttpRequest | ||
import org.springframework.http.server.ServletServerHttpResponse | ||
import org.springframework.web.bind.annotation.ControllerAdvice | ||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice | ||
|
||
|
||
@ControllerAdvice | ||
class CustomResponseBody : ResponseBodyAdvice<Any> { | ||
companion object { | ||
val log: Logger = LoggerFactory.getLogger(CustomResponseBody::class.java) | ||
} | ||
|
||
@Value("\${app.logging.enable-log-response}") | ||
val enableLogResponse: Boolean = false | ||
|
||
override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean { | ||
return true | ||
} | ||
|
||
override fun beforeBodyWrite( | ||
body: Any?, | ||
returnType: MethodParameter, | ||
selectedContentType: MediaType, | ||
selectedConverterType: Class<out HttpMessageConverter<*>>, | ||
request: ServerHttpRequest, | ||
response: ServerHttpResponse | ||
): Any? { | ||
if (enableLogResponse && request is ServletServerHttpRequest && response is ServletServerHttpResponse) { | ||
loggingResponse(request.servletRequest, response.servletResponse, body) | ||
} | ||
|
||
return body | ||
} | ||
|
||
fun loggingResponse(request: HttpServletRequest, response: HttpServletResponse, body: Any?) { | ||
val respMessage = StringBuilder() | ||
val headers = getHeaders(response) | ||
respMessage.append("RESPONSE ") | ||
respMessage.append(" method = [").append(request.method).append("]") | ||
if (headers.isNotEmpty()) { | ||
respMessage.append(" ResponseHeaders = [").append(headers).append("]") | ||
} | ||
respMessage.append(" responseBody = [").append(body).append("]") | ||
log.info("Log response: {}", respMessage) | ||
} | ||
|
||
private fun getHeaders(response: HttpServletResponse): Map<String, String> { | ||
val headers: MutableMap<String, String> = HashMap() | ||
val headerMap = response.headerNames | ||
for (str in headerMap) { | ||
headers[str] = response.getHeader(str) | ||
} | ||
return headers | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
...rc/main/kotlin/com/vndevteam/kotlinwebspringboot3/application/logging/MDCLoggingFilter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package com.vndevteam.kotlinwebspringboot3.application.logging | ||
|
||
import jakarta.servlet.FilterChain | ||
import jakarta.servlet.http.HttpServletRequest | ||
import jakarta.servlet.http.HttpServletResponse | ||
import org.jboss.logging.MDC | ||
import org.springframework.web.filter.OncePerRequestFilter | ||
import java.util.* | ||
|
||
class MDCLoggingFilter : OncePerRequestFilter { | ||
companion object { | ||
private const val X_FORWARDED_FOR = "X-Forwarded-For" | ||
} | ||
|
||
private var responseHeader: String? = null | ||
private var mdcTokenKey: String? = null | ||
private var mdcClientIpKey: String? = null | ||
private var requestHeader: String? = null | ||
|
||
constructor() { | ||
this.responseHeader = Slf4jMDCFilterConfig.DEFAULT_RESPONSE_TOKEN_HEADER | ||
this.mdcTokenKey = Slf4jMDCFilterConfig.DEFAULT_MDC_UUID_TOKEN_KEY | ||
this.mdcClientIpKey = Slf4jMDCFilterConfig.DEFAULT_MDC_CLIENT_IP_KEY | ||
this.requestHeader = null | ||
} | ||
|
||
constructor(responseHeader: String, mdcTokenKey: String, mdcClientIpKey: String, requestHeader: String?) { | ||
this.responseHeader = responseHeader | ||
this.mdcTokenKey = mdcTokenKey | ||
this.mdcClientIpKey = mdcClientIpKey | ||
this.requestHeader = requestHeader | ||
} | ||
|
||
override fun doFilterInternal( | ||
request: HttpServletRequest, | ||
response: HttpServletResponse, | ||
filterChain: FilterChain | ||
) { | ||
try { | ||
val clientId: String = getClientId(request) | ||
val clientIp: String = getClientIpAddress(request) | ||
MDC.put(mdcClientIpKey, clientIp) | ||
MDC.put(mdcTokenKey, clientId) | ||
|
||
if (!responseHeader.isNullOrBlank()) { | ||
response.addHeader(responseHeader, clientId) | ||
} | ||
|
||
filterChain.doFilter(request, response) | ||
} finally { | ||
MDC.remove(mdcTokenKey) | ||
MDC.remove(mdcClientIpKey) | ||
} | ||
} | ||
|
||
override fun isAsyncDispatch(request: HttpServletRequest): Boolean { | ||
return false | ||
} | ||
|
||
override fun shouldNotFilterErrorDispatch(): Boolean { | ||
return false | ||
} | ||
|
||
private fun getClientIpAddress(httpHeaders: HttpServletRequest): String { | ||
val xForwardedHeader = httpHeaders.getHeader(X_FORWARDED_FOR) | ||
if (xForwardedHeader.isNullOrBlank()) { | ||
return httpHeaders.remoteAddr | ||
} | ||
return xForwardedHeader.split(',')[0].trim() | ||
} | ||
|
||
private fun getClientId(request: HttpServletRequest): String { | ||
val clientId: String = | ||
if (!requestHeader.isNullOrBlank() && !request.getHeader(requestHeader).isNullOrBlank()) { | ||
request.getHeader(requestHeader) | ||
} else { | ||
UUID.randomUUID().toString() | ||
} | ||
return clientId | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
...ain/kotlin/com/vndevteam/kotlinwebspringboot3/application/logging/RequestLoggingFilter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.vndevteam.kotlinwebspringboot3.application.logging | ||
|
||
import jakarta.servlet.http.HttpServletRequest | ||
import org.springframework.beans.factory.annotation.Value | ||
import org.springframework.context.annotation.Configuration | ||
import org.springframework.web.filter.CommonsRequestLoggingFilter | ||
|
||
@Configuration | ||
class RequestLoggingFilter : CommonsRequestLoggingFilter() { | ||
companion object { | ||
private const val HEADER_NAME_API_KEY = "api-key" | ||
} | ||
|
||
@Value("\${app.logging.enable-log-request}") | ||
val enableLogRequest: Boolean = false | ||
|
||
init { | ||
isIncludeClientInfo = true | ||
isIncludeQueryString = true | ||
isIncludePayload = true | ||
maxPayloadLength = 10000 | ||
isIncludeHeaders = true | ||
super.setBeforeMessagePrefix("REQUEST DATA: ") | ||
// Hide sensitive header if need | ||
super.setHeaderPredicate { header -> header != HEADER_NAME_API_KEY } | ||
} | ||
|
||
override fun beforeRequest(request: HttpServletRequest, message: String) { | ||
if (enableLogRequest) { | ||
logger.info(message) | ||
} | ||
} | ||
|
||
override fun afterRequest(request: HttpServletRequest, message: String) { | ||
|
||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
...ain/kotlin/com/vndevteam/kotlinwebspringboot3/application/logging/Slf4jMDCFilterConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.vndevteam.kotlinwebspringboot3.application.logging | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
import org.springframework.boot.web.servlet.FilterRegistrationBean | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
|
||
|
||
@Configuration | ||
@ConfigurationProperties(prefix = "app.slf4jfilter") | ||
class Slf4jMDCFilterConfig { | ||
companion object { | ||
const val DEFAULT_RESPONSE_TOKEN_HEADER = "x-client-id" | ||
const val DEFAULT_MDC_UUID_TOKEN_KEY = "RequestFilter.UUID" | ||
const val DEFAULT_MDC_CLIENT_IP_KEY = "RequestFilter.ClientIP" | ||
} | ||
|
||
var responseHeader: String = DEFAULT_RESPONSE_TOKEN_HEADER | ||
var mdcTokenKey : String = DEFAULT_MDC_UUID_TOKEN_KEY | ||
var mdcClientIpKey: String = DEFAULT_MDC_CLIENT_IP_KEY | ||
var requestHeader: String? = null | ||
|
||
@Bean | ||
fun servletRegistrationBean(): FilterRegistrationBean<MDCLoggingFilter>? { | ||
val registrationBean: FilterRegistrationBean<MDCLoggingFilter> = FilterRegistrationBean<MDCLoggingFilter>() | ||
val log4jMDCFilterFilter = MDCLoggingFilter(responseHeader, mdcTokenKey, mdcClientIpKey, requestHeader) | ||
registrationBean.filter = log4jMDCFilterFilter | ||
registrationBean.order = 2 | ||
return registrationBean | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
...-web-spring-boot-3/src/main/kotlin/com/vndevteam/kotlinwebspringboot3/util/MapperUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.vndevteam.kotlinwebspringboot3.util | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.fasterxml.jackson.databind.SerializationFeature | ||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule | ||
|
||
class MapperUtils { | ||
companion object { | ||
fun objectMapperForKotlin(): ObjectMapper { | ||
val objectMapper = ObjectMapper() | ||
objectMapper.registerModule(JavaTimeModule()) | ||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) | ||
return objectMapper | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
kotlin-web-spring-boot-3/src/main/resources/logback-spring.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<configuration> | ||
|
||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> | ||
<pattern> | ||
%black(%d{ISO8601}) [%X{RequestFilter.UUID}] %highlight(%-5level) [%blue(%t)] %yellow(%C{2}:%L): %msg%n%throwable | ||
</pattern> | ||
</encoder> | ||
</appender> | ||
|
||
<root level="INFO"> | ||
<appender-ref ref="STDOUT"/> | ||
</root> | ||
|
||
<logger name="org.springframework.web" level="INFO" additivity="false"> | ||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
|
||
<logger name="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver" | ||
level="WARN" additivity="false"> | ||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
|
||
<logger name="org.hibernate" level="ERROR" additivity="false"> | ||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
|
||
<!-- service --> | ||
|
||
<logger name="com.vndevteam.kotlinwebspringboot3.application" level="DEBUG" additivity="false"> | ||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
|
||
<logger name="com.vndevteam.kotlinwebspringboot3.domain" level="DEBUG" additivity="false"> | ||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
|
||
<logger name="com.vndevteam.kotlinwebspringboot3.infrastructure" level="DEBUG" additivity="false"> | ||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
|
||
<logger name="com.vndevteam.kotlinwebspringboot3.util" level="DEBUG" additivity="false"> | ||
<appender-ref ref="STDOUT"/> | ||
</logger> | ||
|
||
</configuration> |