From 55f79c943b1b1e2f6b656ef41e83d429179bf2a8 Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Thu, 28 May 2026 23:12:08 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20Actuator=20+=20Prometheus=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=B0=8F=20=EB=B3=B4=EC=95=88=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ .../api/common/config/SecurityConfig.java | 6 ++++++ src/main/resources/application.yaml | 16 ++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/build.gradle b/build.gradle index dc126273..4f6841c8 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,10 @@ dependencies { implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-database-postgresql' + // Actuator + Prometheus + implementation 'org.springframework.boot:spring-boot-starter-actuator' + runtimeOnly 'io.micrometer:micrometer-registry-prometheus' + } jar { enabled = false } diff --git a/src/main/java/com/semosan/api/common/config/SecurityConfig.java b/src/main/java/com/semosan/api/common/config/SecurityConfig.java index 4b4c90d4..5c45bac9 100644 --- a/src/main/java/com/semosan/api/common/config/SecurityConfig.java +++ b/src/main/java/com/semosan/api/common/config/SecurityConfig.java @@ -59,6 +59,11 @@ public class SecurityConfig { "/ws/tracking/**" }; + public static final String[] ACTUATOR_URIS = { + "/actuator/health", + "/actuator/prometheus" + }; + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http @@ -74,6 +79,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(OAUTH_URIS).permitAll() .requestMatchers(AUTH_URIS).permitAll() .requestMatchers(WEBSOCKET_URIS).permitAll() + .requestMatchers(ACTUATOR_URIS).permitAll() .requestMatchers(HttpMethod.GET, "/api/app-version").permitAll() .anyRequest().authenticated() ) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 86e8659d..9cea21c9 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -63,6 +63,22 @@ minio: demo: photo-filenames: ${DEMO_PHOTO_FILENAMES} +management: + endpoints: + web: + exposure: + include: health,prometheus + base-path: /actuator + endpoint: + health: + show-details: never + server: + port: 8080 + prometheus: + metrics: + export: + enabled: true + discord: alert: enabled: ${DISCORD_ALERT_ENABLED} From b764c44cabaa8a10f226756469b6ccf0c48dc12b Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Thu, 28 May 2026 23:13:08 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20JSON=20=EB=A1=9C=EA=B9=85=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80=20(logback-spring.xml)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ src/main/resources/logback-spring.xml | 28 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/main/resources/logback-spring.xml diff --git a/build.gradle b/build.gradle index 4f6841c8..87942b05 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' runtimeOnly 'io.micrometer:micrometer-registry-prometheus' + // JSON 로깅 (Loki 연동) + implementation 'net.logstash.logback:logstash-logback-encoder:8.0' + } jar { enabled = false } diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..ecd4cf86 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,28 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{sessionId}] - %msg%n + + + + + + + + + + + sessionId + userId + Asia/Seoul + + + + + + + + From d4d2d3f2a4fd693e427a8f49bb39d26c7a48d059 Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Thu, 28 May 2026 23:15:41 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20MDC=20sessionId/userId=20=EC=84=B8?= =?UTF-8?q?=ED=8C=85=20(HTTP,=20WebSocket,=20Redis=20Consumer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/semosan/api/common/jwt/JwtFilter.java | 6 ++- .../service/TrackingGpsPublisher.java | 47 +++++++++++-------- .../service/TrackingStreamConsumer.java | 6 +++ 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/semosan/api/common/jwt/JwtFilter.java b/src/main/java/com/semosan/api/common/jwt/JwtFilter.java index b1d6a62f..195a6113 100644 --- a/src/main/java/com/semosan/api/common/jwt/JwtFilter.java +++ b/src/main/java/com/semosan/api/common/jwt/JwtFilter.java @@ -11,6 +11,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; import org.springframework.http.MediaType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; @@ -68,6 +69,7 @@ protected void doFilterInternal( throw new GeneralException(ErrorStatus.JWT_BLACKLISTED); } Long userId = jwtService.getUserIdFromJwtToken(accessToken); + MDC.put("userId", String.valueOf(userId)); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userId, null, new ArrayList<>()); @@ -75,10 +77,10 @@ protected void doFilterInternal( } filterChain.doFilter(request, response); } catch (GeneralException e) { - // GeneralException을 그대로 ApiResponse 포맷으로 직렬화하여 응답 - // Spring Security가 가로채지 않도록 직접 response에 작성 log.warn("[*] JwtFilter GeneralException : {}", e.getMessage()); sendErrorResponse(response, e); + } finally { + MDC.clear(); } } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java index f6e7b2be..31ec8c1d 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java @@ -9,6 +9,7 @@ import com.semosan.api.domain.tracking.repository.TrackingSessionRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; import org.springframework.data.redis.connection.stream.RecordId; import org.springframework.data.redis.connection.stream.StringRecord; import org.springframework.data.redis.core.StringRedisTemplate; @@ -40,26 +41,32 @@ public class TrackingGpsPublisher { @Transactional(readOnly = true) public void publish(Long userId, Long sessionId, GpsPointMessage message) { - TrackingSession session = trackingSessionRepository.findById(sessionId) - .orElseThrow(() -> new GeneralException(ErrorStatus.TRACKING_SESSION_NOT_FOUND)); - if (!session.isOwnedBy(userId)) { - throw new GeneralException(ErrorStatus.TRACKING_SESSION_FORBIDDEN); - } - if (session.getStatus() != TrackingSessionStatus.IN_PROGRESS) { - log.debug("Dropping GPS point: sessionId={} status={}", sessionId, session.getStatus()); - return; - } + MDC.put("sessionId", String.valueOf(sessionId)); + MDC.put("userId", String.valueOf(userId)); + try { + TrackingSession session = trackingSessionRepository.findById(sessionId) + .orElseThrow(() -> new GeneralException(ErrorStatus.TRACKING_SESSION_NOT_FOUND)); + if (!session.isOwnedBy(userId)) { + throw new GeneralException(ErrorStatus.TRACKING_SESSION_FORBIDDEN); + } + if (session.getStatus() != TrackingSessionStatus.IN_PROGRESS) { + log.debug("Dropping GPS point: sessionId={} status={}", sessionId, session.getStatus()); + return; + } - Map body = Map.of( - FIELD_SESSION_ID, String.valueOf(sessionId), - FIELD_USER_ID, String.valueOf(userId), - FIELD_LAT, String.valueOf(message.lat()), - FIELD_LNG, String.valueOf(message.lng()), - FIELD_ALTITUDE, message.altitude() == null ? "" : String.valueOf(message.altitude()), - FIELD_RECORDED_AT, message.recordedAt().toString() - ); - StringRecord record = StringRecord.of(body).withStreamKey(trackingProperties.getStreamKey()); - RecordId id = redisTemplate.opsForStream().add(record); - log.trace("Published GPS point: sessionId={} streamId={}", sessionId, id); + Map body = Map.of( + FIELD_SESSION_ID, String.valueOf(sessionId), + FIELD_USER_ID, String.valueOf(userId), + FIELD_LAT, String.valueOf(message.lat()), + FIELD_LNG, String.valueOf(message.lng()), + FIELD_ALTITUDE, message.altitude() == null ? "" : String.valueOf(message.altitude()), + FIELD_RECORDED_AT, message.recordedAt().toString() + ); + StringRecord record = StringRecord.of(body).withStreamKey(trackingProperties.getStreamKey()); + RecordId id = redisTemplate.opsForStream().add(record); + log.trace("Published GPS point: sessionId={} streamId={}", sessionId, id); + } finally { + MDC.clear(); + } } } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java index 013cda98..85e1c8c8 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java @@ -3,6 +3,7 @@ import com.semosan.api.domain.tracking.event.TrackingSessionTerminatedEvent; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; import org.springframework.data.redis.connection.stream.MapRecord; import org.springframework.data.redis.stream.StreamListener; import org.springframework.scheduling.annotation.Scheduled; @@ -58,6 +59,9 @@ public void onMessage(MapRecord message) { try { Long sessionId = Long.parseLong(body.get(F_SESSION_ID)); Long userId = Long.parseLong(body.get(F_USER_ID)); + MDC.put("sessionId", String.valueOf(sessionId)); + MDC.put("userId", String.valueOf(userId)); + double lat = Double.parseDouble(body.get(F_LAT)); double lng = Double.parseDouble(body.get(F_LNG)); Double altitude = parseNullableDouble(body.get(F_ALTITUDE)); @@ -76,6 +80,8 @@ public void onMessage(MapRecord message) { } } catch (RuntimeException e) { log.warn("Failed to process GPS stream message: id={} body={}", message.getId(), body, e); + } finally { + MDC.clear(); } } From 39920102ec42d7da25963a887dc49423e848aaa9 Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Thu, 28 May 2026 23:17:53 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=ED=8A=B8=EB=9E=98=ED=82=B9=20?= =?UTF-8?q?=ED=95=B5=EC=8B=AC=20=EB=A1=9C=EA=B7=B8=20info=20=EB=A0=88?= =?UTF-8?q?=EB=B2=A8=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/tracking/service/TrackingGpsPublisher.java | 4 ++-- .../api/domain/tracking/service/TrackingSessionService.java | 6 ++++++ .../api/domain/tracking/service/TrackingStreamConsumer.java | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java index 31ec8c1d..c9375eda 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java @@ -50,7 +50,7 @@ public void publish(Long userId, Long sessionId, GpsPointMessage message) { throw new GeneralException(ErrorStatus.TRACKING_SESSION_FORBIDDEN); } if (session.getStatus() != TrackingSessionStatus.IN_PROGRESS) { - log.debug("Dropping GPS point: sessionId={} status={}", sessionId, session.getStatus()); + log.info("GPS point dropped: status={}", session.getStatus()); return; } @@ -64,7 +64,7 @@ public void publish(Long userId, Long sessionId, GpsPointMessage message) { ); StringRecord record = StringRecord.of(body).withStreamKey(trackingProperties.getStreamKey()); RecordId id = redisTemplate.opsForStream().add(record); - log.trace("Published GPS point: sessionId={} streamId={}", sessionId, id); + log.info("GPS point published: streamId={}", id); } finally { MDC.clear(); } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java index 39a2bc01..0a565425 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java @@ -68,6 +68,7 @@ public TrackingSessionResponse create(Long userId, CreateTrackingSessionRequest TrackingSession session = TrackingSession.create(user, mountain, course, request.isFreeRecording()); TrackingSession saved = saveSession(session); + log.info("Tracking session created: sessionId={} userId={} mountainId={}", saved.getId(), userId, request.mountainId()); // 세션 생성과 동시에 사진 마일스톤 거리 리스트(코스 4컷 / 자유 6컷)를 Redis 에 적재한다. // 이후 GPS Consumer 가 매 점마다 evaluate 호출 시 이 키를 읽어 마일스톤 도달을 판정하므로, @@ -91,6 +92,7 @@ public TrackingSessionResponse get(Long userId, Long sessionId) { public TrackingSessionResponse pause(Long userId, Long sessionId) { TrackingSession session = findOwnedSession(userId, sessionId); session.pause(); + log.info("Tracking session paused: sessionId={} userId={}", sessionId, userId); return TrackingSessionResponse.from(session); } @@ -98,6 +100,7 @@ public TrackingSessionResponse pause(Long userId, Long sessionId) { public TrackingSessionResponse resume(Long userId, Long sessionId) { TrackingSession session = findOwnedSession(userId, sessionId); session.resume(); + log.info("Tracking session resumed: sessionId={} userId={}", sessionId, userId); return TrackingSessionResponse.from(session); } @@ -131,6 +134,8 @@ public TrackingSessionResponse complete(Long userId, Long sessionId) { hikingMemberRepository.save(member); eventPublisher.publishEvent(new TrackingSessionTerminatedEvent(sessionId)); + log.info("Tracking session completed: sessionId={} userId={} distance={}m altitude={}m", + sessionId, userId, stats.distanceMeters(), stats.maxAltitudeMeters()); return TrackingSessionResponse.from(session, savedRecord.getId()); } @@ -140,6 +145,7 @@ public TrackingSessionResponse abandon(Long userId, Long sessionId) { TrackingSession session = findOwnedSession(userId, sessionId); session.abandon(); eventPublisher.publishEvent(new TrackingSessionTerminatedEvent(sessionId)); + log.info("Tracking session abandoned: sessionId={} userId={}", sessionId, userId); return TrackingSessionResponse.from(session); } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java index 85e1c8c8..3b4f65fa 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java @@ -112,7 +112,7 @@ private void flushSession(Long sessionId) { try { int saved = flushService.flush(sessionId, batch); if (saved > 0) { - log.debug("Flushed {} GPS points for session {}", saved, sessionId); + log.info("Flushed {} GPS points for session {}", saved, sessionId); } } catch (RuntimeException e) { // DB flush 실패 시 batch 를 큐로 되돌려 다음 주기에 재시도. @@ -145,7 +145,7 @@ public void onSessionTerminated(TrackingSessionTerminatedEvent event) { sessionId, chunk.size(), e); } } - log.debug("Cleaned up tracking buffer for terminated session {} ({} points)", + log.info("Cleaned up tracking buffer for terminated session {} ({} points)", sessionId, remaining.size()); } From 0bb5dd2526d8e40f51b9b4cb6d0cb9d8952e45bd Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Fri, 29 May 2026 23:40:49 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor:=20=ED=8A=B8=EB=9E=98=ED=82=B9=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/tracking/service/TrackingGpsPublisher.java | 4 ++-- .../tracking/service/TrackingSessionService.java | 10 +++++----- .../tracking/service/TrackingStreamConsumer.java | 5 ++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java index c9375eda..a170bba2 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java @@ -50,7 +50,7 @@ public void publish(Long userId, Long sessionId, GpsPointMessage message) { throw new GeneralException(ErrorStatus.TRACKING_SESSION_FORBIDDEN); } if (session.getStatus() != TrackingSessionStatus.IN_PROGRESS) { - log.info("GPS point dropped: status={}", session.getStatus()); + log.info("[GPS] 좌표 드롭 | status={}", session.getStatus()); return; } @@ -64,7 +64,7 @@ public void publish(Long userId, Long sessionId, GpsPointMessage message) { ); StringRecord record = StringRecord.of(body).withStreamKey(trackingProperties.getStreamKey()); RecordId id = redisTemplate.opsForStream().add(record); - log.info("GPS point published: streamId={}", id); + log.info("[GPS] 좌표 발행 | streamId={}", id); } finally { MDC.clear(); } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java index 0a565425..d088ed2e 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingSessionService.java @@ -68,7 +68,7 @@ public TrackingSessionResponse create(Long userId, CreateTrackingSessionRequest TrackingSession session = TrackingSession.create(user, mountain, course, request.isFreeRecording()); TrackingSession saved = saveSession(session); - log.info("Tracking session created: sessionId={} userId={} mountainId={}", saved.getId(), userId, request.mountainId()); + log.info("[SESSION] 세션 생성 | sessionId={} | userId={} | mountainId={}", saved.getId(), userId, request.mountainId()); // 세션 생성과 동시에 사진 마일스톤 거리 리스트(코스 4컷 / 자유 6컷)를 Redis 에 적재한다. // 이후 GPS Consumer 가 매 점마다 evaluate 호출 시 이 키를 읽어 마일스톤 도달을 판정하므로, @@ -92,7 +92,7 @@ public TrackingSessionResponse get(Long userId, Long sessionId) { public TrackingSessionResponse pause(Long userId, Long sessionId) { TrackingSession session = findOwnedSession(userId, sessionId); session.pause(); - log.info("Tracking session paused: sessionId={} userId={}", sessionId, userId); + log.info("[SESSION] 세션 일시정지 | sessionId={} | userId={}", sessionId, userId); return TrackingSessionResponse.from(session); } @@ -100,7 +100,7 @@ public TrackingSessionResponse pause(Long userId, Long sessionId) { public TrackingSessionResponse resume(Long userId, Long sessionId) { TrackingSession session = findOwnedSession(userId, sessionId); session.resume(); - log.info("Tracking session resumed: sessionId={} userId={}", sessionId, userId); + log.info("[SESSION] 세션 재개 | sessionId={} | userId={}", sessionId, userId); return TrackingSessionResponse.from(session); } @@ -134,7 +134,7 @@ public TrackingSessionResponse complete(Long userId, Long sessionId) { hikingMemberRepository.save(member); eventPublisher.publishEvent(new TrackingSessionTerminatedEvent(sessionId)); - log.info("Tracking session completed: sessionId={} userId={} distance={}m altitude={}m", + log.info("[SESSION] 세션 완료 | sessionId={} | userId={} | distance={}m | altitude={}m", sessionId, userId, stats.distanceMeters(), stats.maxAltitudeMeters()); return TrackingSessionResponse.from(session, savedRecord.getId()); @@ -145,7 +145,7 @@ public TrackingSessionResponse abandon(Long userId, Long sessionId) { TrackingSession session = findOwnedSession(userId, sessionId); session.abandon(); eventPublisher.publishEvent(new TrackingSessionTerminatedEvent(sessionId)); - log.info("Tracking session abandoned: sessionId={} userId={}", sessionId, userId); + log.info("[SESSION] 세션 포기 | sessionId={} | userId={}", sessionId, userId); return TrackingSessionResponse.from(session); } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java index 3b4f65fa..10447a16 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java @@ -112,7 +112,7 @@ private void flushSession(Long sessionId) { try { int saved = flushService.flush(sessionId, batch); if (saved > 0) { - log.info("Flushed {} GPS points for session {}", saved, sessionId); + log.info("[FLUSH] DB 저장 | {}건 | sessionId={}", saved, sessionId); } } catch (RuntimeException e) { // DB flush 실패 시 batch 를 큐로 되돌려 다음 주기에 재시도. @@ -145,8 +145,7 @@ public void onSessionTerminated(TrackingSessionTerminatedEvent event) { sessionId, chunk.size(), e); } } - log.info("Cleaned up tracking buffer for terminated session {} ({} points)", - sessionId, remaining.size()); + log.info("[FLUSH] 세션 종료 버퍼 정리 | {}건 | sessionId={}", remaining.size(), sessionId); } private static Double parseNullableDouble(String value) { From e3081835663db237af5455664b48e246baac86ef Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Sat, 30 May 2026 00:33:42 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20=ED=8A=B8=EB=9E=98=ED=82=B9=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=88=ED=84=B0=EB=A7=81=20=EB=B3=B4=EC=95=88=20?= =?UTF-8?q?=EC=B7=A8=EC=95=BD=EC=A0=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/semosan/api/common/config/SecurityConfig.java | 6 ------ src/main/java/com/semosan/api/common/jwt/JwtFilter.java | 2 +- .../api/domain/tracking/service/TrackingGpsPublisher.java | 3 ++- .../api/domain/tracking/service/TrackingStreamConsumer.java | 5 +++-- src/main/resources/application.yaml | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/semosan/api/common/config/SecurityConfig.java b/src/main/java/com/semosan/api/common/config/SecurityConfig.java index 5c45bac9..4b4c90d4 100644 --- a/src/main/java/com/semosan/api/common/config/SecurityConfig.java +++ b/src/main/java/com/semosan/api/common/config/SecurityConfig.java @@ -59,11 +59,6 @@ public class SecurityConfig { "/ws/tracking/**" }; - public static final String[] ACTUATOR_URIS = { - "/actuator/health", - "/actuator/prometheus" - }; - @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http @@ -79,7 +74,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(OAUTH_URIS).permitAll() .requestMatchers(AUTH_URIS).permitAll() .requestMatchers(WEBSOCKET_URIS).permitAll() - .requestMatchers(ACTUATOR_URIS).permitAll() .requestMatchers(HttpMethod.GET, "/api/app-version").permitAll() .anyRequest().authenticated() ) diff --git a/src/main/java/com/semosan/api/common/jwt/JwtFilter.java b/src/main/java/com/semosan/api/common/jwt/JwtFilter.java index 195a6113..ce85cf16 100644 --- a/src/main/java/com/semosan/api/common/jwt/JwtFilter.java +++ b/src/main/java/com/semosan/api/common/jwt/JwtFilter.java @@ -80,7 +80,7 @@ protected void doFilterInternal( log.warn("[*] JwtFilter GeneralException : {}", e.getMessage()); sendErrorResponse(response, e); } finally { - MDC.clear(); + MDC.remove("userId"); } } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java index a170bba2..115a3e11 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java @@ -66,7 +66,8 @@ public void publish(Long userId, Long sessionId, GpsPointMessage message) { RecordId id = redisTemplate.opsForStream().add(record); log.info("[GPS] 좌표 발행 | streamId={}", id); } finally { - MDC.clear(); + MDC.remove("sessionId"); + MDC.remove("userId"); } } } diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java index 10447a16..83c46025 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java @@ -79,9 +79,10 @@ public void onMessage(MapRecord message) { flushSession(sessionId); } } catch (RuntimeException e) { - log.warn("Failed to process GPS stream message: id={} body={}", message.getId(), body, e); + log.warn("[GPS] 메시지 처리 실패 | streamId={} | sessionId={}", message.getId(), body.get(F_SESSION_ID), e); } finally { - MDC.clear(); + MDC.remove("sessionId"); + MDC.remove("userId"); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 9cea21c9..378837ce 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -73,7 +73,7 @@ management: health: show-details: never server: - port: 8080 + port: 9090 prometheus: metrics: export: From b0986c074f8352aeec610c9890099fc831eb832d Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Sat, 30 May 2026 00:46:52 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=EA=B3=A0=EB=B9=88=EB=8F=84=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20DEBUG=20=EB=A0=88=EB=B2=A8=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20logback=20=ED=94=84=EB=A1=9C=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20fallback=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tracking/service/TrackingGpsPublisher.java | 2 +- .../tracking/service/TrackingStreamConsumer.java | 2 +- src/main/resources/logback-spring.xml | 13 +++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java index 115a3e11..f9d68a9b 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java @@ -64,7 +64,7 @@ public void publish(Long userId, Long sessionId, GpsPointMessage message) { ); StringRecord record = StringRecord.of(body).withStreamKey(trackingProperties.getStreamKey()); RecordId id = redisTemplate.opsForStream().add(record); - log.info("[GPS] 좌표 발행 | streamId={}", id); + log.debug("[GPS] 좌표 발행 | streamId={}", id); } finally { MDC.remove("sessionId"); MDC.remove("userId"); diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java index 83c46025..b7e669b9 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java @@ -113,7 +113,7 @@ private void flushSession(Long sessionId) { try { int saved = flushService.flush(sessionId, batch); if (saved > 0) { - log.info("[FLUSH] DB 저장 | {}건 | sessionId={}", saved, sessionId); + log.debug("[FLUSH] DB 저장 | {}건 | sessionId={}", saved, sessionId); } } catch (RuntimeException e) { // DB flush 실패 시 batch 를 큐로 되돌려 다음 주기에 재시도. diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index ecd4cf86..555daf2b 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,12 +1,13 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{sessionId}] - %msg%n - - + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{sessionId}] - %msg%n + + + + From c3080eb00c7c2b980604bcd837ccb711c5f89e43 Mon Sep 17 00:00:00 2001 From: Hoyeon Lee <89958157+howooyeon@users.noreply.github.com> Date: Sat, 30 May 2026 00:47:51 +0900 Subject: [PATCH 8/8] =?UTF-8?q?revert:=20GPS/FLUSH=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?INFO=20=EB=A0=88=EB=B2=A8=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/tracking/service/TrackingGpsPublisher.java | 2 +- .../api/domain/tracking/service/TrackingStreamConsumer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java index f9d68a9b..115a3e11 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingGpsPublisher.java @@ -64,7 +64,7 @@ public void publish(Long userId, Long sessionId, GpsPointMessage message) { ); StringRecord record = StringRecord.of(body).withStreamKey(trackingProperties.getStreamKey()); RecordId id = redisTemplate.opsForStream().add(record); - log.debug("[GPS] 좌표 발행 | streamId={}", id); + log.info("[GPS] 좌표 발행 | streamId={}", id); } finally { MDC.remove("sessionId"); MDC.remove("userId"); diff --git a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java index b7e669b9..83c46025 100644 --- a/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java +++ b/src/main/java/com/semosan/api/domain/tracking/service/TrackingStreamConsumer.java @@ -113,7 +113,7 @@ private void flushSession(Long sessionId) { try { int saved = flushService.flush(sessionId, batch); if (saved > 0) { - log.debug("[FLUSH] DB 저장 | {}건 | sessionId={}", saved, sessionId); + log.info("[FLUSH] DB 저장 | {}건 | sessionId={}", saved, sessionId); } } catch (RuntimeException e) { // DB flush 실패 시 batch 를 큐로 되돌려 다음 주기에 재시도.