Skip to content

Commit 9706189

Browse files
committed
add: logging filter
1 parent 27839ea commit 9706189

File tree

3 files changed

+110
-3
lines changed

3 files changed

+110
-3
lines changed

build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plugins {
2-
kotlin("jvm") version "1.9.25"
2+
kotlin("jvm") version "2.1.0"
33
kotlin("plugin.spring") version "1.9.25"
44
id("org.springframework.boot") version "3.4.2"
55
id("io.spring.dependency-management") version "1.1.7"
@@ -29,6 +29,8 @@ repositories {
2929
extra["springCloudVersion"] = "2024.0.0"
3030

3131
dependencies {
32+
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
33+
implementation("org.springframework.boot:spring-boot-starter-actuator")
3234
implementation("org.springframework.cloud:spring-cloud-starter-bus-kafka")
3335
implementation("org.springframework.cloud:spring-cloud-starter-config")
3436
implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")

src/main/kotlin/gogo/gogobetting/global/config/SecurityConfig.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gogo.gogobetting.global.config
22

33
import gogo.gogobetting.global.filter.AuthenticationFilter
4+
import gogo.gogobetting.global.filter.LoggingFilter
45
import gogo.gogobetting.global.handler.CustomAccessDeniedHandler
56
import gogo.gogobetting.global.handler.CustomAuthenticationEntryPointHandler
67
import gogo.gogobetting.global.internal.user.stub.Authority
@@ -19,7 +20,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource
1920
class SecurityConfig(
2021
private val customAccessDeniedHandler: CustomAccessDeniedHandler,
2122
private val customAuthenticationEntryPointHandler: CustomAuthenticationEntryPointHandler,
22-
private val authenticationFilter: AuthenticationFilter
23+
private val authenticationFilter: AuthenticationFilter,
24+
private val loggingFilter: LoggingFilter
2325
) {
2426

2527
@Bean
@@ -39,11 +41,13 @@ class SecurityConfig(
3941
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
4042
}
4143

42-
http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter::class.java)
44+
http.addFilterBefore(loggingFilter, UsernamePasswordAuthenticationFilter::class.java)
45+
.addFilterBefore(authenticationFilter, LoggingFilter::class.java)
4346

4447
http.authorizeHttpRequests { httpRequests ->
4548
// health check
4649
httpRequests.requestMatchers(HttpMethod.GET, "/betting/health").permitAll()
50+
httpRequests.requestMatchers(HttpMethod.GET, "/actuator/**").permitAll()
4751

4852
// betting
4953
httpRequests.requestMatchers(HttpMethod.POST, "/betting/{match_id}").hasAnyRole(Authority.USER.name, Authority.STAFF.name)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package gogo.gogobetting.global.filter
2+
3+
import jakarta.servlet.FilterChain
4+
import jakarta.servlet.http.HttpServletRequest
5+
import jakarta.servlet.http.HttpServletResponse
6+
import org.slf4j.Logger
7+
import org.slf4j.LoggerFactory
8+
import org.springframework.stereotype.Component
9+
import org.springframework.util.AntPathMatcher
10+
import org.springframework.util.StringUtils
11+
import org.springframework.web.filter.OncePerRequestFilter
12+
import org.springframework.web.util.ContentCachingRequestWrapper
13+
import org.springframework.web.util.ContentCachingResponseWrapper
14+
import java.io.IOException
15+
import java.nio.charset.StandardCharsets
16+
import java.util.*
17+
18+
@Component
19+
class LoggingFilter : OncePerRequestFilter() {
20+
21+
private val log: Logger = LoggerFactory.getLogger(LoggingFilter::class.java)
22+
23+
private val NOT_LOGGING_URL: Array<String> = arrayOf(
24+
"/actuator/**"
25+
)
26+
27+
private val matcher = AntPathMatcher()
28+
29+
override fun doFilterInternal(
30+
request: HttpServletRequest,
31+
response: HttpServletResponse,
32+
filterChain: FilterChain
33+
) {
34+
35+
if (isNotLoggingURL(request.requestURI)) {
36+
try {
37+
filterChain.doFilter(request, response)
38+
} catch (e: Exception) {
39+
e.printStackTrace()
40+
}
41+
42+
return
43+
}
44+
45+
val requestWrapper = ContentCachingRequestWrapper(request)
46+
val responseWrapper = ContentCachingResponseWrapper(response)
47+
val logId = UUID.randomUUID()
48+
val startTime = System.currentTimeMillis()
49+
50+
try {
51+
requestLogging(requestWrapper, logId)
52+
filterChain.doFilter(requestWrapper, responseWrapper)
53+
} catch (e: Exception) {
54+
log.error("LoggingFilter의 FilterChain에서 예외가 발생했습니다.", e)
55+
} finally {
56+
responseLogging(responseWrapper, startTime, logId)
57+
try {
58+
responseWrapper.copyBodyToResponse()
59+
} catch (e: IOException) {
60+
log.error("LoggingFilter에서 response body를 출력하는 도중 예외가 발생했습니다.", e)
61+
}
62+
}
63+
}
64+
65+
private fun isNotLoggingURL(requestURI: String): Boolean {
66+
return Arrays.stream(NOT_LOGGING_URL)
67+
.anyMatch { pattern: String -> matcher.match(pattern, requestURI) }
68+
}
69+
70+
private fun requestLogging(request: ContentCachingRequestWrapper, logId: UUID) {
71+
log.info(
72+
"Log-ID: {}, IP: {}, URI: {}, Http-Method: {}, Params: {}, Content-Type: {}, User-Agent: {}, Request-Body: {}",
73+
logId,
74+
request.remoteAddr,
75+
request.requestURI,
76+
request.method,
77+
request.queryString,
78+
request.contentType,
79+
request.getHeader("User-Agent"),
80+
getRequestBody(request.contentAsByteArray)
81+
)
82+
}
83+
84+
private fun responseLogging(response: ContentCachingResponseWrapper, startTime: Long, logId: UUID) {
85+
val responseTime = System.currentTimeMillis() - startTime
86+
log.info(
87+
"Log-ID: {}, Status-Code: {}, Content-Type: {}, Response Time: {}ms, Response-Body: {}",
88+
logId,
89+
response.status,
90+
response.contentType,
91+
responseTime,
92+
String(response.contentAsByteArray, StandardCharsets.UTF_8)
93+
)
94+
}
95+
96+
private fun getRequestBody(byteArrayContent: ByteArray): String {
97+
val oneLineContent = String(byteArrayContent, StandardCharsets.UTF_8).replace(Regex("\\s"), "")
98+
return if (StringUtils.hasText(oneLineContent)) oneLineContent else "[empty]"
99+
}
100+
101+
}

0 commit comments

Comments
 (0)