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 를 큐로 되돌려 다음 주기에 재시도.