diff --git a/.github/workflows/deploy-java.yml b/.github/workflows/deploy-java.yml index 176666f2..973de4b0 100644 --- a/.github/workflows/deploy-java.yml +++ b/.github/workflows/deploy-java.yml @@ -20,8 +20,8 @@ jobs: - name: Copy docker compose files to EC2 uses: appleboy/scp-action@v0.1.7 with: - host: ${{ secrets.SERVER_HOST }}ZZ - username: ${{ secrets.SERVER_USER }} + host: ${{ secrets.SERVER_HOST }} + username: ubuntu key: ${{ secrets.SERVER_SSH_KEY }} source: "docker/production/docker-compose.yml" target: "~/app" diff --git a/apps/pre-processing-service/app/__init__.py b/apps/pre-processing-service/app/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/pre-processing-service/app/api/__init__.py b/apps/pre-processing-service/app/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/pre-processing-service/app/config/logging/RepositoryLoggerMiddleware.py b/apps/pre-processing-service/app/config/logging/RepositoryLoggerMiddleware.py new file mode 100644 index 00000000..703834a6 --- /dev/null +++ b/apps/pre-processing-service/app/config/logging/RepositoryLoggerMiddleware.py @@ -0,0 +1,117 @@ +import time +from typing import Dict, Any, List +from fastapi import Request +from loguru import logger +from contextvars import ContextVar + +trace_id_context: ContextVar[str] = ContextVar('trace_id', default="NO_TRACE_ID") + +class RepositoryLoggingDependency: + """ + 레포지토리 로깅을 위한 의존성 클래스 + :param repository_type: 레포지토리 유형 (예: "VECTOR_DB", "RDB", "REDIS") + :param track_params: 추적할 매개변수 이름 목록 + """ + + def __init__(self, repository_type: str, track_params: List[str] = None): + self.repository_type = repository_type + self.track_params = track_params or [] + + async def __call__(self, request: Request): + """ + 의존성 주입 시 호출되는 메서드 + :param request: FastAPI Request 객체 + :return: 레포지토리 유형과 추출된 매개변수 딕셔너리 + """ + trace_id = trace_id_context.get("NO_TRACE_ID") + start_time = time.time() + + # 파라미터 추출 + params = await self._extract_params(request) + param_str = "" + if params: + param_strs = [f"{k}={v}" for k, v in params.items()] + param_str = " " + " ".join(param_strs) + + logger.info(f"[{self.repository_type}_START] trace_id={trace_id}{param_str}") + + # 응답 시 사용할 정보를 request.state에 저장 + request.state.repository_type = self.repository_type + request.state.start_time = start_time + request.state.param_str = param_str + + return {"repository_type": self.repository_type, "params": params} + + async def _extract_params(self, request: Request) -> Dict[str, Any]: + """ + 요청에서 추적 파라미터 추출 + :param request: FastAPI Request 객체 + :return: 추출된 매개변수 딕셔너리 + """ + params = {} + + try: + # Query Parameters 추출 + for key, value in request.query_params.items(): + if key in self.track_params: + params[key] = value + + # JSON Body 추출 + try: + json_body = await request.json() + if json_body: + for key, value in json_body.items(): + if key in self.track_params: + if isinstance(value, str) and len(value) > 50: + params[f"{key}_length"] = len(value) + elif isinstance(value, list): + params[f"{key}_count"] = len(value) + else: + params[key] = value + except: + pass + except: + pass + + return params + + +# 레포지토리별 의존성 인스턴스 생성 +vector_db_dependency = RepositoryLoggingDependency("VECTOR_DB", ["query", "embeddings", "top_k", "collection", "filters"]) +rdb_dependency = RepositoryLoggingDependency("RDB", ["table", "where_clause", "limit", "data"]) +redis_dependency = RepositoryLoggingDependency("REDIS", ["key", "value", "ttl", "pattern"]) +elasticsearch_dependency = RepositoryLoggingDependency("ELASTICSEARCH", ["index", "query", "size", "document"]) + + +# 응답 로깅을 위한 의존성 +async def log_repository_response(request: Request): + """ + 레포지토리 응답 시 성공 로그 기록 + :param request: FastAPI Request 객체 + """ + if hasattr(request.state, 'repository_type'): + trace_id = trace_id_context.get("NO_TRACE_ID") + duration = time.time() - request.state.start_time + logger.info( + f"[{request.state.repository_type}_SUCCESS] trace_id={trace_id} execution_time={duration:.4f}s{request.state.param_str}") + return None + + +""" +라우터 예시 +@router.post("/search") +async def vector_search( + query: str, + top_k: int = 10, + request: Request = None, + _: None = Depends(vector_db_dependency), # 직접 의존성 주입 + __: None = Depends(log_repository_response) +): + +또는 라우터 레벨에서: +vector_router = APIRouter( + prefix="/vector", + tags=["vector"], + dependencies=[Depends(vector_db_dependency)] +) +""" \ No newline at end of file diff --git a/apps/pre-processing-service/app/config/logging/ServiceLoggerMiddleware.py b/apps/pre-processing-service/app/config/logging/ServiceLoggerMiddleware.py new file mode 100644 index 00000000..5e4816a1 --- /dev/null +++ b/apps/pre-processing-service/app/config/logging/ServiceLoggerMiddleware.py @@ -0,0 +1,109 @@ +import time +from typing import Dict, Any, List +from fastapi import Request +from loguru import logger +from contextvars import ContextVar + +trace_id_context: ContextVar[str] = ContextVar('trace_id', default="NO_TRACE_ID") + + +class ServiceLoggingDependency: + """ + 서비스 로깅을 위한 의존성 클래스 + :param service_type: 서비스 유형 (예: "CHUNKING", "PARSING", "EMBEDDING") + :param track_params: 추적할 매개변수 이름 목록 + """ + + def __init__(self, service_type: str, track_params: List[str] = None): + self.service_type = service_type + self.track_params = track_params or [] + + async def __call__(self, request: Request): + """ + 의존성 주입 시 호출되는 메서드 + :param request: FastAPI Request 객체 + :return: 서비스 유형과 추출된 매개변수 딕셔너리 + """ + trace_id = trace_id_context.get("NO_TRACE_ID") + start_time = time.time() + + # 파라미터 추출 + params = await self._extract_params(request) + param_str = "" + if params: + param_strs = [f"{k}={v}" for k, v in params.items()] + param_str = " " + " ".join(param_strs) + + logger.info(f"[{self.service_type}_START] trace_id={trace_id}{param_str}") + + # 응답 시 사용할 정보를 request.state에 저장 + request.state.service_type = self.service_type + request.state.start_time = start_time + request.state.param_str = param_str + + return {"service_type": self.service_type, "params": params} + + async def _extract_params(self, request: Request) -> Dict[str, Any]: + """ + 요청에서 추적 파라미터 추출 + :param request: FastAPI Request 객체 + :return: 추출된 매개변수 딕셔너리 + """ + params = {} + + try: + # Query Parameters 추출 + for key, value in request.query_params.items(): + if key in self.track_params: + params[key] = value + + # JSON Body 추출 + try: + json_body = await request.json() + if json_body: + for key, value in json_body.items(): + if key in self.track_params: + if isinstance(value, str) and len(value) > 50: + params[f"{key}_length"] = len(value) + elif isinstance(value, list): + params[f"{key}_count"] = len(value) + else: + params[key] = value + except: + pass + except: + pass + + return params + + +# 서비스별 의존성 인스턴스 생성 +chunking_dependency = ServiceLoggingDependency("CHUNKING", ["text", "chunk_size", "overlap"]) +parsing_dependency = ServiceLoggingDependency("PARSING", ["file_path", "file_type", "document"]) +embedding_dependency = ServiceLoggingDependency("EMBEDDING", ["chunks", "model_name", "batch_size"]) + +# 응답 로깅을 위한 의존성 +async def log_service_response(request: Request): + """ + 서비스 응답 시 성공 로그 기록 + :param request: FastAPI Request 객체 + """ + if hasattr(request.state, 'service_type'): + trace_id = trace_id_context.get("NO_TRACE_ID") + duration = time.time() - request.state.start_time + logger.info( + f"[{request.state.service_type}_SUCCESS] trace_id={trace_id} execution_time={duration:.4f}s{request.state.param_str}") + return None + +""" +라우터 예시 +@router.post("/chunk") +async def chunk_text( + text: str, + chunk_size: int = 100, + overlap: int = 20, + request: Request = None, + _: None = Depends(chunking_dependency), # 직접 의존성 주입 + __: None = Depends(log_service_response) +): +""" \ No newline at end of file diff --git a/apps/pre-processing-service/app/core/__init__.py b/apps/pre-processing-service/app/core/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/pre-processing-service/app/core/config.py b/apps/pre-processing-service/app/core/config.py index 134d0430..06b55aa2 100644 --- a/apps/pre-processing-service/app/core/config.py +++ b/apps/pre-processing-service/app/core/config.py @@ -33,16 +33,3 @@ class PrdSettings(BaseSettingsConfig): class Config: env_file = ['.env', 'prd.env'] - -def get_settings() -> BaseSettingsConfig: - """환경 변수에 따라 적절한 설정 객체를 반환하는 함수""" - mode = os.getenv("MODE", "dev") - if mode == "dev": - return DevSettings() - elif mode == "prd": - return PrdSettings() - else: - raise ValueError(f"Invalid MODE environment variable: {mode}") - - -settings = get_settings() \ No newline at end of file diff --git a/apps/pre-processing-service/app/db/__init__.py b/apps/pre-processing-service/app/db/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/pre-processing-service/app/services/__init__.py b/apps/pre-processing-service/app/services/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/pre-processing-service/app/services/preprocessing_service.py b/apps/pre-processing-service/app/services/preprocessing_service.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/user-service/Dockerfile b/apps/user-service/Dockerfile index e3fb24a4..b9ac7b3e 100644 --- a/apps/user-service/Dockerfile +++ b/apps/user-service/Dockerfile @@ -6,4 +6,4 @@ COPY build/libs/*.jar app.jar EXPOSE 8080 -CMD ["java", "-jar", "app.jar"] +CMD ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/config/WebConfig.java b/apps/user-service/src/main/java/com/gltkorea/icebang/config/WebConfig.java new file mode 100644 index 00000000..1ed10098 --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/config/WebConfig.java @@ -0,0 +1,26 @@ +package com.gltkorea.icebang.config; + +import java.time.Duration; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class WebConfig { + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder builder) { + // 1. SimpleClientHttpRequestFactory 객체를 직접 생성 + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + + // 2. 타임아웃 설정 (이 메서드들은 deprecated 아님) + requestFactory.setConnectTimeout(Duration.ofSeconds(5)); + requestFactory.setReadTimeout(Duration.ofSeconds(5)); + + // 3. 빌더에 직접 생성한 requestFactory를 설정 + return builder.requestFactory(() -> requestFactory).build(); + } +} diff --git a/apps/user-service/src/main/java/com/gltkorea/icebang/filter/LoggingFilter.java b/apps/user-service/src/main/java/com/gltkorea/icebang/filter/LoggingFilter.java new file mode 100644 index 00000000..e8dda321 --- /dev/null +++ b/apps/user-service/src/main/java/com/gltkorea/icebang/filter/LoggingFilter.java @@ -0,0 +1,43 @@ +package com.gltkorea.icebang.filter; + +import java.io.IOException; +import java.util.UUID; + +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class LoggingFilter extends OncePerRequestFilter { + + public static final String TRACE_ID_HEADER = "X-Request-ID"; + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // 다른 시스템에서 이미 전달한 Trace ID가 있는지 확인 + String traceId = request.getHeader(TRACE_ID_HEADER); + + // 없다면 새로 생성 (요청의 시작점) + if (traceId == null || traceId.isEmpty()) { + traceId = UUID.randomUUID().toString(); + } + + MDC.put("traceId", traceId.substring(0, 8)); + + // ⭐️ 요청 객체에 attribute로 traceId를 저장하여 컨트롤러 등에서 사용할 수 있게 함 + request.setAttribute("X-Request-ID", traceId); + + // 응답 헤더에 traceId를 넣어주면 클라이언트가 추적하기 용이 + response.setHeader(TRACE_ID_HEADER, traceId); + + filterChain.doFilter(request, response); + } +} diff --git a/apps/user-service/src/main/resources/log4j2-develop.yml b/apps/user-service/src/main/resources/log4j2-develop.yml index 4734a1ce..63f4c280 100644 --- a/apps/user-service/src/main/resources/log4j2-develop.yml +++ b/apps/user-service/src/main/resources/log4j2-develop.yml @@ -9,10 +9,10 @@ Configuration: value: "UTF-8" # 통일된 콘솔 패턴 - 모든 로그에 RequestId 포함 - name: "console-layout-pattern" - value: "%highlight{[%-5level]} [%X{id}] %d{MM-dd HH:mm:ss} [%t] %n %msg%n%n" + value: "%highlight{[%-5level]} [%X{traceId}] %d{MM-dd HH:mm:ss} [%t] %n %msg%n%n" # 파일용 상세 패턴 - RequestId 포함 - name: "file-layout-pattern" - value: "[%X{id}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + value: "[%X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" # 로그 파일 경로들 - name: "info-log" value: ${log-path}/user-service/info.log diff --git a/apps/user-service/src/test/resources/sql/create-schema.sql b/apps/user-service/src/test/resources/sql/create-schema.sql index 5980c3ab..115603f8 100644 --- a/apps/user-service/src/test/resources/sql/create-schema.sql +++ b/apps/user-service/src/test/resources/sql/create-schema.sql @@ -34,6 +34,7 @@ CREATE TABLE "USER" ( PRIMARY KEY ("user_id") ); + CREATE TABLE "GROUP_INFO" ( "group_info_id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "name" VARCHAR(255) NULL, @@ -95,4 +96,4 @@ CREATE TABLE "ROLE_PERMISSION" ( FOREIGN KEY ("role_id") REFERENCES "ROLE" ("role_id"), FOREIGN KEY ("permission_id") REFERENCES "PERMISSION" ("permission_id"), UNIQUE ("role_id", "permission_id") -); \ No newline at end of file +); diff --git a/docker/local/init-scripts/create-schema.sql b/docker/local/init-scripts/create-schema.sql index 5980c3ab..0e2467df 100644 --- a/docker/local/init-scripts/create-schema.sql +++ b/docker/local/init-scripts/create-schema.sql @@ -8,7 +8,6 @@ DROP TABLE IF EXISTS "GROUP_INFO"; DROP TABLE IF EXISTS "USER"; --- 사용자 정보 (외부 노출 가능성 높음 -> UUID) CREATE TABLE "USER" ( "user_id" VARCHAR(36) NOT NULL, "name" VARCHAR(100) NULL,