Skip to content

Commit 4164827

Browse files
authored
feat: 로깅 체계 구축 JSON stdout, MDC 구현, tomcat access log 설정 추가
* feat: json로그 인코더 의존성 추가, yml dev프로필 과도한 로깅레벨 정리, 로그 파일 생성 설정 추가 * feat: logback-spring xml설정 추가, requestIdFilter try-catch 동작 수정 * feat: threadLocal 로깅 컨텍스트에 mdc로 userId추적 * refactor: securityConfig에서 필터순서 명시 * feat: tomcat access log yml설정 추가
1 parent ab70791 commit 4164827

9 files changed

Lines changed: 193 additions & 9 deletions

File tree

backend/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ src/main/resources/application-perf.yml
5959

6060
### querydsl ###
6161
src/main/generated/
62+
63+
### Logs ###
64+
logs/*.log

backend/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ dependencies {
7676
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
7777
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
7878

79+
// logstash logback encoder
80+
implementation("net.logstash.logback:logstash-logback-encoder:7.4")
81+
7982

8083
}
8184

backend/src/main/java/com/back/global/config/SecurityConfig.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.back.global.error.code.AuthErrorCode;
2020
import com.back.global.error.code.ErrorCode;
21+
import com.back.global.logging.RequestIdFilter;
2122
import com.back.global.properties.CorsProperties;
2223
import com.back.global.response.ApiResponse;
2324
import com.back.global.security.CustomAuthenticationFilter;
@@ -37,7 +38,12 @@ public class SecurityConfig {
3738
private final ObjectMapper objectMapper;
3839

3940
@Bean
40-
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
41+
public RequestIdFilter requestIdFilter() {
42+
return new RequestIdFilter();
43+
}
44+
45+
@Bean
46+
SecurityFilterChain filterChain(HttpSecurity http, RequestIdFilter requestIdFilter) throws Exception {
4147
http
4248

4349
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
@@ -77,7 +83,10 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
7783
})
7884
);
7985

80-
http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
86+
// MDC RequestId로깅용 필터 클래스 순서보장
87+
http.addFilterBefore(requestIdFilter, UsernamePasswordAuthenticationFilter.class);
88+
// MDC에서 requestId식별 후에 authenticationFilter적용
89+
http.addFilterAfter(authenticationFilter, RequestIdFilter.class);
8190

8291
return http.build();
8392
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.back.global.logging;
2+
3+
import org.slf4j.MDC;
4+
5+
public final class MdcContext {
6+
7+
private MdcContext() {
8+
}
9+
10+
public static void putUserId(Long userId) {
11+
if (userId != null) {
12+
MDC.put("userId", String.valueOf(userId));
13+
}
14+
}
15+
16+
public static void putEventId(Long eventId) {
17+
if (eventId != null) {
18+
MDC.put("eventId", String.valueOf(eventId));
19+
}
20+
}
21+
22+
public static void putSeatId(Long seatId) {
23+
if (seatId != null) {
24+
MDC.put("seatId", String.valueOf(seatId));
25+
}
26+
}
27+
28+
public static void removeUserId() {
29+
MDC.remove("userId");
30+
}
31+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.back.global.logging;
2+
3+
import java.io.IOException;
4+
import java.util.Optional;
5+
import java.util.UUID;
6+
7+
import org.slf4j.MDC;
8+
import org.springframework.web.filter.OncePerRequestFilter;
9+
10+
import jakarta.servlet.FilterChain;
11+
import jakarta.servlet.ServletException;
12+
import jakarta.servlet.http.HttpServletRequest;
13+
import jakarta.servlet.http.HttpServletResponse;
14+
15+
/**
16+
* @Component등록 없이 SecurityConfig에서 명시적으로 빈으로 등록 후 활용
17+
* Security FilterChain에서 확실하게 순서보장 목적
18+
*/
19+
public class RequestIdFilter extends OncePerRequestFilter {
20+
21+
private static final String HEADER = "X-Request-Id";
22+
23+
@Override
24+
protected void doFilterInternal(
25+
HttpServletRequest request,
26+
HttpServletResponse response,
27+
FilterChain filterChain
28+
) throws ServletException, IOException {
29+
30+
String requestId = Optional.ofNullable(request.getHeader(HEADER))
31+
.filter(id -> !id.isBlank())
32+
.orElse(UUID.randomUUID().toString());
33+
34+
MDC.put("requestId", requestId);
35+
response.setHeader(HEADER, requestId);
36+
37+
try {
38+
filterChain.doFilter(request, response);
39+
} finally {
40+
MDC.remove("requestId");
41+
}
42+
}
43+
}

backend/src/main/java/com/back/global/security/CustomAuthenticationFilter.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.back.global.error.code.AuthErrorCode;
2222
import com.back.global.error.exception.ErrorException;
2323
import com.back.global.http.CookieManager;
24+
import com.back.global.logging.MdcContext;
2425

2526
import jakarta.servlet.FilterChain;
2627
import jakarta.servlet.ServletException;
@@ -119,7 +120,14 @@ private void authenticate(
119120

120121
SecurityContextHolder.getContext().setAuthentication(authentication);
121122

122-
filterChain.doFilter(request, response);
123+
// 인증 성공 이후에 MDC 로그 컨텍스트에 userId추가, SecurityContext와 별개
124+
MdcContext.putUserId(userId);
125+
126+
try {
127+
filterChain.doFilter(request, response);
128+
} finally {
129+
MdcContext.removeUserId(); // 다음 스레드 재사용을 위해 반드시 제거
130+
}
123131
}
124132

125133
private String resolveAccessToken(HttpServletRequest request) {

backend/src/main/resources/application-dev.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ spring:
77
database-platform: org.hibernate.dialect.PostgreSQLDialect
88
hibernate:
99
ddl-auto: create-drop
10-
1110
security:
1211
password:
1312
bcrypt-strength: 4

backend/src/main/resources/application.yml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ spring:
1616
default_batch_fetch_size: 100
1717
hibernate:
1818
ddl-auto: update
19-
show-sql: true
19+
show-sql: false
2020
mvc:
2121
hiddenmethod:
2222
filter:
@@ -26,13 +26,25 @@ spring:
2626
host: localhost
2727
port: 6379
2828
password: ${REDIS_PASSWORD:}
29-
29+
server:
30+
tomcat:
31+
basedir: .
32+
accesslog:
33+
enabled: true
34+
directory: logs
35+
prefix: access
36+
suffix: .log
37+
file-date-format: .yyyy-MM-dd
38+
max-days: 14
39+
pattern: '%h %t "%r" %s %b %D %{X-Request-Id}o "%{User-Agent}i"'
3040
logging:
3141
level:
32-
org.hibernate.orm.jdbc.bind: TRACE
33-
org.hibernate.orm.jdbc.extract: TRACE
34-
org.springframework.transaction.interceptor: TRACE
42+
root: INFO
3543
com.back: DEBUG
44+
org.hibernate.orm.jdbc.bind: OFF
45+
org.hibernate.orm.jdbc.extract: OFF
46+
org.springframework.transaction.interceptor: INFO
47+
3648

3749
springdoc:
3850
swagger-ui:
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<configuration>
2+
3+
<!-- =========================
4+
공통 설정
5+
========================= -->
6+
<springProperty scope="context"
7+
name="APP_NAME"
8+
source="spring.application.name"
9+
defaultValue="waitfair"/>
10+
11+
<!-- =========================
12+
LOCAL / DEV
13+
- Spring Boot 기본 콘솔 로그 그대로
14+
- 파일로만 rolling
15+
========================= -->
16+
<springProfile name="local,dev">
17+
18+
<!-- Spring Boot 기본 console appender 그대로 사용 -->
19+
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
20+
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
21+
22+
<!-- 로컬 디버깅용 파일 로그 -->
23+
<appender name="FILE"
24+
class="ch.qos.logback.core.rolling.RollingFileAppender">
25+
<file>logs/dev.log</file>
26+
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
27+
<fileNamePattern>logs/dev.%d{yyyy-MM-dd}.log</fileNamePattern>
28+
<maxHistory>7</maxHistory>
29+
</rollingPolicy>
30+
<encoder>
31+
<pattern>
32+
%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n
33+
</pattern>
34+
</encoder>
35+
</appender>
36+
37+
<root level="INFO">
38+
<appender-ref ref="CONSOLE"/>
39+
<appender-ref ref="FILE"/>
40+
</root>
41+
</springProfile>
42+
43+
<!-- =========================
44+
PERF / PROD
45+
- JSON stdout only
46+
========================= -->
47+
<springProfile name="perf,prod">
48+
49+
<appender name="JSON_CONSOLE"
50+
class="ch.qos.logback.core.ConsoleAppender">
51+
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
52+
<!-- 고정 필드 -->
53+
<customFields>
54+
{"app":"${APP_NAME}"}
55+
</customFields>
56+
57+
<!-- MDC에서 가져올 필드 -->
58+
<includeMdcKeyName>requestId</includeMdcKeyName>
59+
<includeMdcKeyName>userId</includeMdcKeyName>
60+
<includeMdcKeyName>eventId</includeMdcKeyName>
61+
<includeMdcKeyName>seatId</includeMdcKeyName>
62+
<includeMdcKeyName>orderId</includeMdcKeyName>
63+
</encoder>
64+
</appender>
65+
66+
<!-- 불필요한 로그 줄이기 -->
67+
<logger name="org.hibernate.SQL" level="WARN"/>
68+
<logger name="org.hibernate.orm.jdbc.bind" level="WARN"/>
69+
<logger name="org.springframework.web" level="INFO"/>
70+
71+
<root level="INFO">
72+
<appender-ref ref="JSON_CONSOLE"/>
73+
</root>
74+
</springProfile>
75+
76+
</configuration>

0 commit comments

Comments
 (0)