Joyuno's Dev Blog

2026-09-11 (금) 개발일지

dev-blog

오늘의 트러블슈팅: LG-HYBCM 커스텀 UI의 보고서 저장 문제부터 Logpresso 교육 플랫폼 라이선스 활성화까지

오늘은 두 개의 다른 프로젝트에서 각각 다른 성격의 트러블슈팅을 했다. 하나는 lg-hybcm-custom-ui 의 점검자 의견 저장 문제, 다른 하나는 Logpresso 교육 플랫폼의 라이선스 연동과 SEC 미션 검증이었다. 두 이슈 모두 “저장은 되는데 불러오지 않는다”는 표면적 증상에서 시작했지만, 원인은 완전히 달랐다.


LG-HYBCM 점검자 의견 — 저장은 되는데 왜 불러오지 않나

증상

TB_SECURITY_REPORT 테이블에 점검자 의견을 저장하는 기능이 있었다. UI에서 mailComment 상태값을 t("defaultComment") 로 초기화하고, 저장 API를 통해 TB_SECURITY_REPORT 에 INSERT/UPDATE 하는 구조다. 문제는 저장은 성공했지만 화면을 다시 열면 항상 “특이사항 없음” 기본값으로 돌아온다는 점이었다. 대상자를 바꿔도, 해당 건을 예전에 점검해서 의견을 저장해 뒀어도, 텍스트박스는 늘 초기화됐다.

첫 가설 — 초기화 시점 문제

처음엔 단순히 useState 초기화 로직이 문제라고 생각했다. 화면을 열 때마다 t("defaultComment") 로 설정되니, 저장된 값을 읽어오기 전에 기본값이 덮어쓰는 것 아닐까? 하지만 useEffect로 useParams의 대상자 ID가 변경될 때마다 저장된 의견을 GET 요청으로 불러오는 로직을 확인해보니, 해당 요청은 정상적으로 200 응답을 받고 있었다.

전환점 — 서버 응답과 DB의 불일치

그제서야 GET 응답 본문을 직접 찍어봤다. 그랬더니 응답에는 정상적으로 저장된 의견이 담겨 있었지만, 응답 본문에서 필요한 필드를 꺼내 setMailComment() 로 상태를 업데이트하는 코드가 아예 없었다. useEffect 안에서 GET 응답을 받고도 response.data에서 comment 필드를 추출해 상태에 반영하는 로직이 누락된 상태였다.

// before — 저장된 의견을 무시하고 아무것도 하지 않음
useEffect(() => {
  if (targetId) {
    api.get(`/api/security/report/${targetId}/comment`).then(res => {
      // 응답을 받았지만 아무것도 안 함
    });
  }
}, [targetId]);

해결 — 응답 바인딩 추가

가장 단순한 해법이었다. response.data.comment가 존재할 때만 setMailComment() 를 호출하도록 처리했다. 기본값(defaultComment)과의 충돌을 피하기 위해 null/undefined 체크를 먼저 넣었다.

// after
useEffect(() => {
  if (targetId) {
    api.get(`/api/security/report/${targetId}/comment`).then(res => {
      if (res.data?.comment) {
        setMailComment(res.data.comment);
      }
    });
  }
}, [targetId]);

검증

저장 API를 호출한 뒤 다시 화면을 열었을 때, 저장된 의견이 그대로 표시됐다. 대상자를 바꿔가며 5건을 저장-재조회 테스트했고, 전부 정확히 불러와졌다. DB에는 TB_SECURITY_REPORT에 74건이 정확히 적재되어 있었고 — 이전에 테스트용으로 여러 번 중복 삽입한 이력을 정리하기 위해 DELETE /api/sonar/tables/{name} API를 처음 사용해봤는데, 이게 동작해서 테이블을 비우고 정확히 한 번만 적재할 수 있었다.

남은 과제: 보고서 생성 API가 아직 백엔드 마이그레이션 전이라 연동되지 않았다. 점검자 의견 저장은 MsgBus를 거쳐 Java에서 JDBC 트랜잭션으로 처리하는 구조로 추후 변경 예정이다.


Logpresso 교육 플랫폼 — 라이선스가 SEC 미션을 막고 있었다

증상

Logpresso 교육 플랫폼에서 SEC(보안) 미션을 실행하려고 하니, API Error: Connection refused 가 발생했다. 방화벽이나 프록시가 연결을 차단하고 있다는 메시지였다. 동시에 SEC 미션 페이지에는 “라이선스 미등록으로 인해 기능을 사용할 수 없음”이 표시됐다.

첫 가설 — 프록시/방화벽 문제

처음엔 .111 서버의 81번 포트와 .173 서버의 4318번 포트 등 낯선 포트가 눈에 띄어, 이 포트들이 방화벽에서 막혀 있나 확인했다. 하지만 curl로 직접 찔러보니 81번 포트에는 라이선스 관련 Logpresso 화면이 정상적으로 떠 있었다. 문제는 포트 접근이 아니라 라이선스 키 자체였다.

원인 — 라이선스 키 미등록

https://<INTERNAL_IP>:8443/ 관리 화면에서 라이선스 키를 입력하는 란을 발견했다. 본사 사이트에서 라이선스 코드를 가져와 등록했더니, 그제서야 실시간 탐지·배치 탐지·시그니처·행위 프로파일·티켓 기능이 전부 열렸다. R0 단계에서 “라이선스 미등록이라 못 함”으로 막혔던 게 풀린 것이다.

해결 — 라이선스 활성화 및 SEC 미션 검증

라이선스가 정상 활성화된 후, SEC-001(선수과목, 시뮬레이션)부터 SEC-003까지 실제 제품 서버에서 쿼리를 검증했다. 처음에 SEC-003의 stats count as fail_cnt 쿼리에서 as 키워드가 빠져 보였는데, 파일 자체는 정상이었다. 내 추출 스크립트 출력에서 표시 문제로 빠져 보인 것뿐이었다. 파일 그대로(as 포함)로 돌리니 통과했다.

미션 검증 결과 비고
SEC-001 통과 시뮬레이션형, 로컬 검증
SEC-002 통과 실습형, 랩 필요
SEC-003 통과 as fail_cnt 포함 정상

검증

세 미션의 쿼리 모두 실제 제품에서 검증 통과했다. 미션 파일 자체는 수정할 필요가 전혀 없었다. SEC-001이 아직 review 상태라 SEC-002를 pilot로 올리려면 선수과목 조건을 해결해야 하는데, 이건 배치 검증 담당자와 협의가 필요하다.

남은 과제: 남은 review 미션 26개가 시뮬레이션(로컬 검증 가능)인지 실습형(랩 필요)인지 분류하는 작업이 남았다. 또한 ProxyJump 설정이 있을 수도 없을 수도 있는 환경에 대응하는 deploy 스크립트를 작성해야 한다.


오늘의 한줄

가장 눈에 띄는 증상이 항상 진짜 원인은 아니다 — “저장이 안 된다”의 진짜 원인은 저장 자체가 아니라 불러오는 로직의 누락이었고, “API 연결 거부”의 진짜 원인은 방화벽이 아니라 라이선스 미등록이었다.