Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
423800d
[chore] gitignore에 logs 폴더 추가
KiSeungMin Aug 7, 2025
0208603
[feat] 예외, 에러 발생 시 로그로 기록하는 기능 구현
KiSeungMin Aug 7, 2025
6de88d8
[feat] 관리자 페이지 에러 조회 컨트롤러 구현
KiSeungMin Aug 7, 2025
ea676bd
[feat] 관리자 페이지 에러 로그 관리 view 구현
KiSeungMin Aug 7, 2025
0825853
[feat] 관리자 에러 로그 관리 페이지 디자인 개선
KiSeungMin Aug 7, 2025
705d1fb
[feat] 에러 로그를 디테일하게 개선
KiSeungMin Aug 7, 2025
b3c3b13
[chore] csv 파서 의존성 추가
seung-in-Yoo Aug 8, 2025
729ddd7
[feat] 서초구 무장애가게 csv 데이터 추가
seung-in-Yoo Aug 8, 2025
d5b3689
[feat] 웨이블존 장소,영업 시간 관련 레포지토리 구현
seung-in-Yoo Aug 8, 2025
43fa137
[feat] csv -> 엔티티 변환 유틸 구현
seung-in-Yoo Aug 8, 2025
c555aec
[feat] 영업시간 관련 파서 유틸 구현
seung-in-Yoo Aug 8, 2025
5185d03
[feat] 데이터 파싱을 위한 CommandLineRunner 구현
seung-in-Yoo Aug 8, 2025
f31f7c3
[fix] 파싱 관련 정적 팩토리 메서드 추가
seung-in-Yoo Aug 8, 2025
fb83f0a
[refactor] 영업시간 파서 보강 및 로직 추가
seung-in-Yoo Aug 8, 2025
795969f
[refactor] SeochoCsvImporter 파싱 리팩토링
seung-in-Yoo Aug 8, 2025
4921ea5
[refactor] 서초구 무장애 가게 데이터 경로 변경
seung-in-Yoo Aug 8, 2025
7818bed
[fix] 데이터 파싱 에러로 인한 openCsv 함수 추가
seung-in-Yoo Aug 8, 2025
13d6361
[refactor] 웨이블존 시설 정보 필드에 명시적으로 name 추가
seung-in-Yoo Aug 8, 2025
cb2d747
[refactor] ynToBool 메서드에서 null이나 빈 문자열 처리하도록 수정
seung-in-Yoo Aug 8, 2025
eae00af
[fix] 잘못 넣은 필드 name 수정
seung-in-Yoo Aug 8, 2025
4677085
[fix] 레포지토리에서 필요한 연관들을 미리 로딩하도록 수정
seung-in-Yoo Aug 8, 2025
9ba3215
[fix] 서비스 진입 전에 연관 데이터를 미리 가져오도록 로직 수정
seung-in-Yoo Aug 8, 2025
b881846
[fix] bag 두개 동시 fetch 에러로 인한 운영시간 미리 조회 제외
seung-in-Yoo Aug 8, 2025
970a93b
[fix] 웨이블존 서비스 로직 수정 및 리뷰 엔티티 DB 칼럼명과 일치시키기
seung-in-Yoo Aug 8, 2025
e521718
[fix] 리뷰 등록 관련 로직에 트랜잭션 추가
seung-in-Yoo Aug 8, 2025
0249284
[fix] 리뷰 엔티티 rating,like_count 중복 로직 수정
seung-in-Yoo Aug 8, 2025
55d14ab
[refactor] 웨이블존 리뷰 등록 관련 안전로직 추가
seung-in-Yoo Aug 8, 2025
c3bc5e5
[feat] ReviewRepository에 연관관계 즉시 로딩 설정 추가
seung-in-Yoo Aug 8, 2025
c0851d7
[fix] 트랜잭션 처리로 could not initialize proxy 문제 해결
seung-in-Yoo Aug 8, 2025
7d1c609
[fix] 유저 장소 관련 LAZY 연관관계 문제 해결
seung-in-Yoo Aug 8, 2025
38abd4c
[refactor] 코드리뷰 반영하여 리팩토링
seung-in-Yoo Aug 8, 2025
deeefd7
[refactor] total 집계 로그 수정
seung-in-Yoo Aug 8, 2025
70795d8
Merge pull request #120 from Wayble-Project/feature/seungin
seung-in-Yoo Aug 9, 2025
e949ab7
[feat] 에러 로그 출력 메시지 상세화 및 디자인 개선
KiSeungMin Aug 9, 2025
66ac095
[feat] 에러 조회 기간을 일주일로 설정
KiSeungMin Aug 9, 2025
827ff8f
[feat] Application Exception은 디코 알림 및 에러 로그에서 제외
KiSeungMin Aug 9, 2025
a083226
[chore] 배포 시에도 에러 로그가 사라지지 않도록 개선
KiSeungMin Aug 9, 2025
946b8e5
[feat] 서버 시간대를 서울 시간대로 배포하도록 수정
KiSeungMin Aug 9, 2025
fc54a75
[test] 기능 테스트를 위해 배포 스크립트 임시 수정
KiSeungMin Aug 9, 2025
d486867
[test] 에러 로그 테스트 완료, 배포 스크립트 원복
KiSeungMin Aug 9, 2025
68daedc
Merge branch 'develop' into feature/seungmin
KiSeungMin Aug 9, 2025
7d7f1c1
[chore] 웨이블존 csv 파일이름을 영문으로 변경
KiSeungMin Aug 9, 2025
47e8977
[test] 웨이블존 DB 삽입 테스트
KiSeungMin Aug 9, 2025
51c3640
[test] 웨이블존 DB 삽입 테스트
KiSeungMin Aug 9, 2025
6a514f8
[fix] csv importer 프로필을 develop으로 임시 설정
KiSeungMin Aug 9, 2025
73f15f1
[test] 서초구 웨이블존 삽입 배포 테스트 완료
KiSeungMin Aug 9, 2025
5fe9adf
[fix] 웨이블존 방문수 기반 조회 시, 방문 기록이 없으면 조회되지 않던 버그 수정
KiSeungMin Aug 9, 2025
8008993
[feat] 관리자 페이지 웨이블존 시설 정보 표현 디자인 개선
KiSeungMin Aug 9, 2025
90b1ca3
[test] 에러 로그 기능 배포 저장 테스트
KiSeungMin Aug 9, 2025
cac0f4c
[chore] workflow 원복
KiSeungMin Aug 9, 2025
e4f371c
[chore] gitignore에 logs 디렉토리만 추가
KiSeungMin Aug 9, 2025
8dbf31f
[chore] docker-compose 파일에도 서울 시간대로 설정
KiSeungMin Aug 9, 2025
15c1461
[feat] 에러 로그 조회 기능 구현, 웨이블존 csv data 삽입 및 각종 버그 수정
KiSeungMin Aug 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/cd-develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ on:
permissions:
contents: read


jobs:
# 1. 도커 이미지 빌드 및 푸시
build-docker-image:
Expand Down Expand Up @@ -145,7 +144,9 @@ jobs:
--name github-actions-demo \
--network wayble-network \
-p 8080:8080 \
-v /var/log/wayble:/app/logs \
-e "SPRING_PROFILES_ACTIVE=develop" \
-e "TZ=Asia/Seoul" \
${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo:latest

Comment on lines +147 to 151
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

CD 런 단계에 LOG_DIR 누락—logback 경로 불일치 가능성

docker-compose에는 LOG_DIR=/app/logs를 설정했지만, GitHub Actions의 컨테이너 실행 단계에는 LOG_DIR가 없습니다. logback이 ${LOG_DIR}를 참조한다면, 배포 경로에서 로그가 예상 위치로 기록되지 않을 수 있습니다.

다음과 같이 -e "LOG_DIR=/app/logs"를 추가해 주세요:

       sudo docker run -d \
         --name github-actions-demo \
         --network wayble-network \
         -p 8080:8080 \
-        -v /var/log/wayble:/app/logs \
+        -v /var/log/wayble:/app/logs \
         -e "SPRING_PROFILES_ACTIVE=develop" \
+        -e "LOG_DIR=/app/logs" \
         -e "TZ=Asia/Seoul" \
         ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo:latest

또한 호스트 디렉터리 생성/권한 보장을 위해 컨테이너 실행 전 단계에 다음을 추가하는 것을 권장합니다:

+      - name: Ensure host log directory exists
+        run: |
+          sudo mkdir -p /var/log/wayble
+          # 필요 시 권한 조정(비루트 컨테이너 사용자 사용 시)
+          sudo chown -R $USER:$USER /var/log/wayble || true

다음과 같이 docker-compose 설정에 LOG_DIR가 정의되어 있는지 확인하겠습니다:


🏁 Script executed:

#!/bin/bash
# docker-compose 파일 목록 확인
fd 'docker-compose.*\.yml'

# 리포지토리 전반에서 LOG_DIR 사용 여부 검색
rg -R "LOG_DIR" .

Length of output: 470


docker-compose.yml의 environment 섹션 및 LOG_DIR 설정 여부를 확인하기 위해 다음 스크립트를 실행하겠습니다:


🏁 Script executed:

#!/bin/bash
# environment 섹션이 있는 위치 찾기
grep -n "environment" docker-compose.yml

# 해당 위치부터 10줄 출력하여 상세 확인
grep -n "environment" docker-compose.yml | cut -d: -f1 | xargs -I{} sed -n '{},+10p' docker-compose.yml

# LOG_DIR 설정 여부 확인
rg "LOG_DIR" -n docker-compose.yml || echo "LOG_DIR 설정 없음"

Length of output: 851


CD 런 단계에 LOG_DIR 환경 변수 및 호스트 디렉터리 생성 추가 필요

docker-compose에서는 LOG_DIR=/app/logs를 설정하고 있지만, GitHub Actions의 docker run 단계에는 해당 환경 변수가 빠져 있어 logback이 ${LOG_DIR}를 참조할 경우 로그가 예상 위치에 쌓이지 않을 수 있습니다. 또한 호스트의 /var/log/wayble 디렉터리가 없거나 권한이 부적절할 경우 컨테이너가 로그를 쓸 수 없습니다. 아래와 같이 수정해 주세요.

  • 파일: .github/workflows/cd-develop.yml
  • 대상 위치: 147–151행 (docker run 스텝)
       sudo docker run -d \
         --name github-actions-demo \
         --network wayble-network \
         -p 8080:8080 \
-        -v /var/log/wayble:/app/logs \
-        -e "SPRING_PROFILES_ACTIVE=develop" \
-        -e "TZ=Asia/Seoul" \
+        -v /var/log/wayble:/app/logs \
+        -e "SPRING_PROFILES_ACTIVE=develop" \
+        -e "LOG_DIR=/app/logs" \
+        -e "TZ=Asia/Seoul" \
         ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo:latest
  • 호스트 로그 디렉터리 생성/권한 보장 스텝 추가
+      - name: Ensure host log directory exists
+        run: |
+          sudo mkdir -p /var/log/wayble
+          # 비루트 사용자로 컨테이너가 로그를 쓸 수 있도록 권한 조정
+          sudo chown -R $USER:$USER /var/log/wayble || true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-v /var/log/wayble:/app/logs \
-e "SPRING_PROFILES_ACTIVE=develop" \
-e "TZ=Asia/Seoul" \
${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo:latest
# Ensure host log directory exists before starting container
- name: Ensure host log directory exists
run: |
sudo mkdir -p /var/log/wayble
# 비루트 사용자로 컨테이너가 로그를 쓸 수 있도록 권한 조정
sudo chown -R $USER:$USER /var/log/wayble || true
# Deploy container
- name: Run Docker container
run: |
sudo docker run -d \
--name github-actions-demo \
--network wayble-network \
-p 8080:8080 \
-v /var/log/wayble:/app/logs \
-e "SPRING_PROFILES_ACTIVE=develop" \
-e "LOG_DIR=/app/logs" \
-e "TZ=Asia/Seoul" \
${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo:latest
🤖 Prompt for AI Agents
In .github/workflows/cd-develop.yml around lines 147 to 151, add the environment
variable LOG_DIR=/app/logs to the docker run command to match the docker-compose
setup and ensure logback writes logs to the correct directory. Additionally,
before running the container, insert a step to create the host directory
/var/log/wayble if it does not exist and set appropriate permissions so the
container can write logs without permission issues.

- name: Test application health
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ out/

### application.yml ###
application.yml

### Logs ###
logs/
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ dependencies {

// web client
implementation 'org.springframework.boot:spring-boot-starter-webflux'

// csv 파서
implementation 'org.apache.commons:commons-csv:1.10.0'
Comment on lines +67 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

CSV 파서 의존성 추가 적절함. 버전 최신/보안 상태 확인 권장

Apache Commons CSV 도입은 본 PR의 CSV 일괄 등록 기능과 일치합니다. 다만 1.10.0이 조직 표준 및 최신 보안 패치와 부합하는지 확인해 주세요. 필요 시 패치 버전으로 상향을 고려하세요.

다음 웹 질의를 통해 최신 버전 및 1.10.x 관련 CVE 여부를 확인해 주세요:


🌐 Web query:

What is the latest version of Apache Commons CSV, and are there any known CVEs for 1.10.x?

💡 Result:

The latest version of Apache Commons CSV is 1.14.0, released on March 15, 2025. (commons.apache.org)

Regarding known security vulnerabilities (CVEs) for the 1.10.x series, there are no reported issues. The Apache Commons CSV project maintains a security page listing all known vulnerabilities, and as of now, there are no reported vulnerabilities for version 1.10.x. (commons.apache.org)

For the most up-to-date information on security vulnerabilities, you can refer to the Apache Commons CSV security page. (commons.apache.org)

Citations:


CSV 파서 의존성 버전 검토 및 업그레이드 권장

현재 org.apache.commons:commons-csv:1.10.0에는 알려진 CVE가 없으나, 최신 버전인 1.14.0(2025-03-15 릴리스)가 배포되었습니다. 보안 패치 및 기능 개선을 위해 의존성을 1.14.0으로 상향 검토를 권장드립니다.

  • 파일: build.gradle
  • 라인: 67–68
🤖 Prompt for AI Agents
In build.gradle around lines 67 to 68, the dependency version for
'org.apache.commons:commons-csv' is set to 1.10.0. Update this version to the
latest stable release 1.14.0 to incorporate recent security patches and
improvements. Modify the implementation line to reflect the new version number.

}

tasks.named('test') {
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ services:
- SPRING_PROFILES_ACTIVE=develop
- ELASTICSEARCH_HOST=elasticsearch
- ELASTICSEARCH_PORT=9200
- TZ=Asia/Seoul
- JAVA_TOOL_OPTIONS=-Duser.timezone=Asia/Seoul
- LOG_DIR=/app/logs
volumes:
- ./logs:/app/logs # 로그 파일 영속성을 위한 볼륨 마운트
depends_on:
- elasticsearch
networks:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.wayble.server.admin.controller;

import com.wayble.server.admin.dto.log.ErrorLogDto;
import com.wayble.server.admin.service.LogService;
import com.wayble.server.admin.service.LogService.ErrorLogStats;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/admin/logs")
@RequiredArgsConstructor
public class AdminLogController {
Comment on lines +13 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

엔드포인트 보호 확인: 관리자 전용 접근 제어

컨트롤러가 /admin 하위이긴 하나, 보안 설정에서 실제로 관리자 권한만 접근 가능하도록 설정되어 있는지 확인 필요합니다. 명시적으로 @PreAuthorize를 권장합니다.

 @Controller
 @RequestMapping("/admin/logs")
 @RequiredArgsConstructor
+@org.springframework.security.access.prepost.PreAuthorize("hasRole('ADMIN')")
 public class AdminLogController {

보안 설정 점검 스크립트:


🏁 Script executed:

#!/bin/bash
# Admin 접근 제어 및 보안 설정 확인
rg -n --hidden -S "@EnableWebSecurity|SecurityFilterChain|WebSecurityConfigurerAdapter|@PreAuthorize|hasRole|ROLE_ADMIN" -A 3 -B 3

Length of output: 1832


🏁 Script executed:

#!/bin/bash
# SecurityConfig 내 인증 설정 확인: 관리자 경로 보호 여부
rg -nE "authorize(HttpSecurity|authorizeHttpRequests|authorizeRequests|antMatchers|requestMatchers)" -A 10 -B 3 src/main/java/com/wayble/server/common/config/SecurityConfig.java

Length of output: 308


🏁 Script executed:

#!/bin/bash
# 관리자 엔드포인트 접근 제어 설정 재확인
rg -nE 'antMatchers|requestMatchers|authorizeHttpRequests|authorizeRequests' -A 5 -B 2 src/main/java/com/wayble/server/common/config/SecurityConfig.java
rg -nE 'hasRole|hasAuthority|ROLE_ADMIN' -A 3 -B 3 src/main/java/com/wayble/server/common/config/SecurityConfig.java

Length of output: 447


🏁 Script executed:

#!/bin/bash
# SecurityConfig 파일에서 securityFilterChain 설정 부분 전체 확인
sed -n '1,200p' src/main/java/com/wayble/server/common/config/SecurityConfig.java

Length of output: 3543


관리자 엔드포인트 보안 설정 필요
현재 SecurityConfig에서 “/admin/**” 경로가 permitAll 목록에 포함되어 있어, 관리자 전용 접근 제어가 전혀 적용되지 않고 있습니다. 아래와 같이 수정해주세요.

• SecurityConfig.java

  • “/admin/**”를 permitAll 목록에서 제거
  • 관리자 전용 엔드포인트를 .requestMatchers("/admin/**").hasRole("ADMIN") 등으로 명시적 권한 설정
  • 권한 설정 순서상, 관리자 경로가 기타 공개 경로보다 먼저 선언되도록 배치

Diff 예시:

--- a/src/main/java/com/wayble/server/common/config/SecurityConfig.java
+++ b/src/main/java/com/wayble/server/common/config/SecurityConfig.java
@@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
-        .authorizeHttpRequests(auth -> auth
-                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
-                .requestMatchers(
-                        "/api/v1/users/signup",
-                        "/api/v1/users/login",
-                        "/api/v1/users/reissue",
-                        "/api/v1/users/logout",
-                        "/api/v1/auth/**",
-                        "/swagger-ui/**",
-                        "/v3/api-docs/**",
-                        "/",
-                        "/index",
-                        "/index.html",
-                        "/admin/**",            // ← 제거
-                        "/api/v1/files/**"
-                ).permitAll()
-                .anyRequest().authenticated()
-        )
+        .authorizeHttpRequests(auth -> auth
+                // CORS preflight
+                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
+                // 관리자 전용 경로 (인증·권한 필수)
+                .requestMatchers("/admin/**").hasRole("ADMIN")
+                // 공개 API
+                .requestMatchers(
+                        "/api/v1/users/signup",
+                        "/api/v1/users/login",
+                        "/api/v1/users/reissue",
+                        "/api/v1/users/logout",
+                        "/api/v1/auth/**",
+                        "/swagger-ui/**",
+                        "/v3/api-docs/**",
+                        "/",
+                        "/index",
+                        "/index.html",
+                        "/api/v1/files/**"
+                ).permitAll()
+                .anyRequest().authenticated()
+        )

• Controller 레벨 방어선 (선택 사항)

 @Controller
 @RequestMapping("/admin/logs")
 @RequiredArgsConstructor
+@org.springframework.security.access.prepost.PreAuthorize("hasRole('ADMIN')")
 public class AdminLogController {

위 수정 후, 관리자 기능 및 기타 엔드포인트에 대한 권한 정책이 의도대로 동작하는지 반드시 테스트 및 검증을 부탁드립니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/controller/AdminLogController.java
around lines 13 to 16, the admin endpoint security is not properly configured
because "/admin/**" is currently included in permitAll in SecurityConfig. To fix
this, remove "/admin/**" from the permitAll list in SecurityConfig.java, add
explicit role-based access control by configuring
.requestMatchers("/admin/**").hasRole("ADMIN"), and ensure this admin path
matcher is declared before other public path matchers to enforce correct
authorization order. After these changes, test to confirm admin and other
endpoints enforce the intended access controls.


private final LogService logService;

/**
* 에러 로그 관리 페이지
*/
@GetMapping("/error")
public String errorLogPage(
@RequestParam(defaultValue = "100") int limit,
Model model
) {
List<ErrorLogDto> errorLogs = logService.getRecentErrorLogs(limit);
ErrorLogStats stats = logService.getErrorLogStats();

model.addAttribute("errorLogs", errorLogs);
model.addAttribute("stats", stats);
model.addAttribute("limit", limit);

return "admin/log/error-logs";
Comment on lines +25 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

limit 파라미터 검증/클램핑 추가 권장

UI가 50/100/200/500만 제공하지만 API는 임의의 큰 값이 들어올 수 있습니다. 서비스/파일 I/O 부담을 줄이기 위해 상한/하한을 강제하세요.

 @GetMapping("/error")
 public String errorLogPage(
-        @RequestParam(defaultValue = "100") int limit,
+        @RequestParam(defaultValue = "100") int limit,
         Model model
 ) {
-    List<ErrorLogDto> errorLogs = logService.getRecentErrorLogs(limit);
+    int sanitized = sanitizeLimit(limit);
+    List<ErrorLogDto> errorLogs = logService.getRecentErrorLogs(sanitized);
-    ErrorLogStats stats = logService.getErrorLogStats();
+    ErrorLogStats stats = logService.getErrorLogStats();
     
     model.addAttribute("errorLogs", errorLogs);
     model.addAttribute("stats", stats);
-    model.addAttribute("limit", limit);
+    model.addAttribute("limit", sanitized);
     
     return "admin/log/error-logs";
 }
+
+private int sanitizeLimit(int limit) {
+    if (limit <= 0) return 100;
+    return Math.min(limit, 500);
+}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/controller/AdminLogController.java
around lines 25 to 35, the limit parameter from the request is not validated or
clamped, allowing potentially large values that could overload the service or
file I/O. Add validation logic to enforce a minimum and maximum limit value
(e.g., clamp limit to between 50 and 500) before passing it to the service
methods. This ensures the API respects the UI constraints and prevents excessive
resource usage.

}

/**
* 에러 로그 Ajax 조회
*/
@GetMapping("/error/data")
@ResponseBody
public List<ErrorLogDto> getErrorLogs(@RequestParam(defaultValue = "100") int limit) {
return logService.getRecentErrorLogs(limit);
}
Comment on lines +41 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

데이터 API에도 동일한 limit 검증 적용

프론트에서 limit를 바꿔 호출하므로 동일한 방어 로직이 필요합니다.

 @GetMapping("/error/data")
 @ResponseBody
 public List<ErrorLogDto> getErrorLogs(@RequestParam(defaultValue = "100") int limit) {
-    return logService.getRecentErrorLogs(limit);
+    return logService.getRecentErrorLogs(sanitizeLimit(limit));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@GetMapping("/error/data")
@ResponseBody
public List<ErrorLogDto> getErrorLogs(@RequestParam(defaultValue = "100") int limit) {
return logService.getRecentErrorLogs(limit);
}
@GetMapping("/error/data")
@ResponseBody
public List<ErrorLogDto> getErrorLogs(@RequestParam(defaultValue = "100") int limit) {
return logService.getRecentErrorLogs(sanitizeLimit(limit));
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/controller/AdminLogController.java
around lines 41 to 45, the getErrorLogs method accepts a limit parameter from
the request without validation. Add validation logic to ensure the limit
parameter is within acceptable bounds (e.g., a maximum allowed value) to prevent
misuse or excessive data requests, similar to other APIs. Implement defensive
checks before passing the limit to logService.getRecentErrorLogs.


/**
* 에러 로그 통계 Ajax 조회
*/
@GetMapping("/error/stats")
@ResponseBody
public ErrorLogStats getErrorLogStats() {
return logService.getErrorLogStats();
}
}
102 changes: 102 additions & 0 deletions src/main/java/com/wayble/server/admin/dto/log/ErrorLogDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.wayble.server.admin.dto.log;

import java.time.LocalDateTime;

public record ErrorLogDto(
LocalDateTime timestamp,
String level,
String logger,
String message,
String exception,
String method,
String path,
String stackTrace
) {
public static ErrorLogDto from(String logEntry) {
try {
// 멀티라인 로그 엔트리 파싱
if (!logEntry.contains("ERROR")) {
return null;
}

String[] lines = logEntry.split("\n");
String firstLine = lines[0];

// 타임스탬프 추출
int timestampEnd = firstLine.indexOf("]");
if (timestampEnd == -1) return null;

String timestampStr = firstLine.substring(1, timestampEnd);
LocalDateTime timestamp = LocalDateTime.parse(timestampStr.replace(" ", "T"));

// 레벨 추출
int levelStart = firstLine.indexOf("ERROR");
int levelEnd = levelStart + 5;
String level = "ERROR";

// 로거명 추출
int loggerStart = levelEnd + 1;
int loggerEnd = firstLine.indexOf(" - ");
if (loggerEnd == -1) return null;

String logger = firstLine.substring(loggerStart, loggerEnd).trim();

// 메시지 추출
String fullMessage = firstLine.substring(loggerEnd + 3);

// HTTP Method, Path, Location 추출
String method = "";
String path = "";
String location = "";
if (fullMessage.contains("Method:") && fullMessage.contains("Path:")) {
String[] parts = fullMessage.split(", ");
for (String part : parts) {
if (part.contains("Method:")) {
method = part.substring(part.indexOf("Method:") + 8).trim();
} else if (part.contains("Path:")) {
path = part.substring(part.indexOf("Path:") + 6).trim();
} else if (part.contains("Location:")) {
location = part.substring(part.indexOf("Location:") + 10).trim();
}
}
}

// 스택트레이스 추출 (예외 세부정보와 첫 번째 at 라인 찾기)
String stackTrace = "";
String exceptionDetail = "";

if (lines.length > 1) {
for (int i = 1; i < lines.length; i++) {
String line = lines[i].trim();

// 예외 세부 정보 라인 (org.springframework.web.bind.MethodArgumentNotValidException: ...)
if (line.contains("Exception:") && exceptionDetail.isEmpty()) {
// 예외 타입과 상세 설명 포함 (더 긴 길이 허용)
if (line.length() > 1000) {
exceptionDetail = line.substring(0, 1000) + "...";
} else {
exceptionDetail = line;
}
continue;
}

// 첫 번째 스택트레이스 라인 찾기
if (line.startsWith("at ")) {
stackTrace = line.substring(3); // "at " 제거
break;
}
}
}

// 예외 정보는 exceptionDetail이 있으면 그것을, 없으면 전체 메시지
String exception = !exceptionDetail.isEmpty() ? exceptionDetail : fullMessage;

return new ErrorLogDto(timestamp, level, logger, fullMessage, exception, method, path,
stackTrace.isEmpty() ? location : stackTrace);

} catch (Exception e) {
// 파싱 실패시 null 반환
return null;
}
}
Comment on lines +15 to +101
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

로그 파싱 로직이 복잡하고 취약할 수 있습니다.

현재 구현된 파싱 로직은 특정 로그 형식에 강하게 의존하고 있어서 로그 형식이 변경되면 쉽게 깨질 수 있습니다. 또한 예외 발생 시 null을 반환하는 방식보다는 Optional을 사용하거나 기본값을 가진 객체를 반환하는 것이 더 안전합니다.

다음과 같은 개선을 제안합니다:

-    public static ErrorLogDto from(String logEntry) {
+    public static Optional<ErrorLogDto> from(String logEntry) {
         try {
             // 파싱 로직...
-            return new ErrorLogDto(timestamp, level, logger, fullMessage, exception, method, path, 
-                    stackTrace.isEmpty() ? location : stackTrace);
+            return Optional.of(new ErrorLogDto(timestamp, level, logger, fullMessage, exception, method, path, 
+                    stackTrace.isEmpty() ? location : stackTrace));
         } catch (Exception e) {
-            return null;
+            return Optional.empty();
         }
     }

또한 매직 넘버와 하드코딩된 문자열들을 상수로 추출하는 것을 권장합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static ErrorLogDto from(String logEntry) {
try {
// 멀티라인 로그 엔트리 파싱
if (!logEntry.contains("ERROR")) {
return null;
}
String[] lines = logEntry.split("\n");
String firstLine = lines[0];
// 타임스탬프 추출
int timestampEnd = firstLine.indexOf("]");
if (timestampEnd == -1) return null;
String timestampStr = firstLine.substring(1, timestampEnd);
LocalDateTime timestamp = LocalDateTime.parse(timestampStr.replace(" ", "T"));
// 레벨 추출
int levelStart = firstLine.indexOf("ERROR");
int levelEnd = levelStart + 5;
String level = "ERROR";
// 로거명 추출
int loggerStart = levelEnd + 1;
int loggerEnd = firstLine.indexOf(" - ");
if (loggerEnd == -1) return null;
String logger = firstLine.substring(loggerStart, loggerEnd).trim();
// 메시지 추출
String fullMessage = firstLine.substring(loggerEnd + 3);
// HTTP Method, Path, Location 추출
String method = "";
String path = "";
String location = "";
if (fullMessage.contains("Method:") && fullMessage.contains("Path:")) {
String[] parts = fullMessage.split(", ");
for (String part : parts) {
if (part.contains("Method:")) {
method = part.substring(part.indexOf("Method:") + 8).trim();
} else if (part.contains("Path:")) {
path = part.substring(part.indexOf("Path:") + 6).trim();
} else if (part.contains("Location:")) {
location = part.substring(part.indexOf("Location:") + 10).trim();
}
}
}
// 스택트레이스 추출 (예외 세부정보와 첫 번째 at 라인 찾기)
String stackTrace = "";
String exceptionDetail = "";
if (lines.length > 1) {
for (int i = 1; i < lines.length; i++) {
String line = lines[i].trim();
// 예외 세부 정보 라인 (org.springframework.web.bind.MethodArgumentNotValidException: ...)
if (line.contains("Exception:") && exceptionDetail.isEmpty()) {
// 예외 타입과 상세 설명 포함 (더 긴 길이 허용)
if (line.length() > 1000) {
exceptionDetail = line.substring(0, 1000) + "...";
} else {
exceptionDetail = line;
}
continue;
}
// 첫 번째 스택트레이스 라인 찾기
if (line.startsWith("at ")) {
stackTrace = line.substring(3); // "at " 제거
break;
}
}
}
// 예외 정보는 exceptionDetail이 있으면 그것을, 없으면 전체 메시지
String exception = !exceptionDetail.isEmpty() ? exceptionDetail : fullMessage;
return new ErrorLogDto(timestamp, level, logger, fullMessage, exception, method, path,
stackTrace.isEmpty() ? location : stackTrace);
} catch (Exception e) {
// 파싱 실패시 null 반환
return null;
}
}
public static Optional<ErrorLogDto> from(String logEntry) {
try {
// 멀티라인 로그 엔트리 파싱
if (!logEntry.contains("ERROR")) {
return null;
}
String[] lines = logEntry.split("\n");
String firstLine = lines[0];
// 타임스탬프 추출
int timestampEnd = firstLine.indexOf("]");
if (timestampEnd == -1) return null;
String timestampStr = firstLine.substring(1, timestampEnd);
LocalDateTime timestamp = LocalDateTime.parse(timestampStr.replace(" ", "T"));
// 레벨 추출
int levelStart = firstLine.indexOf("ERROR");
int levelEnd = levelStart + 5;
String level = "ERROR";
// 로거명 추출
int loggerStart = levelEnd + 1;
int loggerEnd = firstLine.indexOf(" - ");
if (loggerEnd == -1) return null;
String logger = firstLine.substring(loggerStart, loggerEnd).trim();
// 메시지 추출
String fullMessage = firstLine.substring(loggerEnd + 3);
// HTTP Method, Path, Location 추출
String method = "";
String path = "";
String location = "";
if (fullMessage.contains("Method:") && fullMessage.contains("Path:")) {
String[] parts = fullMessage.split(", ");
for (String part : parts) {
if (part.contains("Method:")) {
method = part.substring(part.indexOf("Method:") + 8).trim();
} else if (part.contains("Path:")) {
path = part.substring(part.indexOf("Path:") + 6).trim();
} else if (part.contains("Location:")) {
location = part.substring(part.indexOf("Location:") + 10).trim();
}
}
}
// 스택트레이스 추출 (예외 세부정보와 첫 번째 at 라인 찾기)
String stackTrace = "";
String exceptionDetail = "";
if (lines.length > 1) {
for (int i = 1; i < lines.length; i++) {
String line = lines[i].trim();
// 예외 세부 정보 라인 (org.springframework.web.bind.MethodArgumentNotValidException: ...)
if (line.contains("Exception:") && exceptionDetail.isEmpty()) {
// 예외 타입과 상세 설명 포함 (더 긴 길이 허용)
if (line.length() > 1000) {
exceptionDetail = line.substring(0, 1000) + "...";
} else {
exceptionDetail = line;
}
continue;
}
// 첫 번째 스택트레이스 라인 찾기
if (line.startsWith("at ")) {
stackTrace = line.substring(3); // "at " 제거
break;
}
}
}
// 예외 정보는 exceptionDetail이 있으면 그것을, 없으면 전체 메시지
String exception = !exceptionDetail.isEmpty() ? exceptionDetail : fullMessage;
return Optional.of(new ErrorLogDto(
timestamp,
level,
logger,
fullMessage,
exception,
method,
path,
stackTrace.isEmpty() ? location : stackTrace
));
} catch (Exception e) {
// 파싱 실패시 Optional.empty() 반환
return Optional.empty();
}
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/dto/log/ErrorLogDto.java from lines 15
to 101, the current log parsing method is fragile due to hardcoded string
indices and direct string manipulation, which can break if the log format
changes. Refactor the parsing logic to be more robust by using regex patterns or
structured parsing to extract fields safely. Replace magic numbers and hardcoded
strings with named constants for clarity and maintainability. Also, change the
return type from nullable to Optional<ErrorLogDto> or return a default object to
avoid null returns on parsing failure, improving safety and usability.

}
Loading
Loading