Skip to content

feat: add chatbot response logging to server database - #90

Merged
Yu-JeSeung merged 2 commits into
mainfrom
feat/chatbot_feedback
Apr 22, 2026
Merged

feat: add chatbot response logging to server database#90
Yu-JeSeung merged 2 commits into
mainfrom
feat/chatbot_feedback

Conversation

@Yu-JeSeung

@Yu-JeSeung Yu-JeSeung commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

관련 이슈

Closes #89

🎯 배경

  • 챗봇 응답 품질 개선을 위한 모니터링이 필요했습니다. 데이터를 데이터베이스에 저장하여 챗봇 응답 품질을 개선에 사용합니다.

🔍 주요 내용

  • 챗봇 데이터 테이블 생성 chatbot_logs
  • 로깅 방식 구현 threading 비동기 처리
  • cache_key 개선

변경 요약

챗봇의 모든 응답을 PostgreSQL 데이터베이스에 자동으로 기록하여 사용자 질의, 응답 모드, 응답 내용, 캐시 여부, 응답 시간 등을 추적할 수 있도록 개선했습니다.

주요 변경점

  • _log_chatbot() 헬퍼 함수 추가: SSH 터널을 통한 원격 DB 접속 지원 및 chatbot_logs 테이블에 INSERT
  • /chatbot 엔드포인트에 time.monotonic()을 이용한 응답 레이턴시 측정 로직 추가
  • 캐시 히트 시점에 응답 내용과 함께 로깅 데몬 스레드 생성
  • 캐시 미스 시 _cache_and_return() 헬퍼 함수 내에서 로깅 처리
  • OSS 모드의 여러 반환 지점(스케줄 검색, 최종 응답 등)에 로깅 추가
  • 신규 의존성: psycopg2, sshtunnel 임포트

주의/리스크

  • 데몬 스레드로 실행되므로 DB 연결 실패 시 무시됨 (에러는 콘솔에만 출력)
  • 환경변수(DB_NAME, DB_USER, DB_PASSWORD, SSH_HOST 등)가 올바르게 설정되지 않으면 로깅이 작동하지 않음
  • 코드 여러 지점에서 동일한 로깅 로직이 반복되어 유지보수 복잡도 증가

다음 액션

  • chatbot_logs 테이블 스키마 생성 및 마이그레이션 실행 필요
  • 환경변수 설정 검증 (특히 DB 접속 정보 및 SSH 관련 설정)
  • 로깅 기능의 동작 확인 테스트 (DB에 데이터가 정상 저장되는지 검증)

@Yu-JeSeung Yu-JeSeung self-assigned this Apr 21, 2026
@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Yu-JeSeung has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 42 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 25 minutes and 42 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 784b19bf-e196-477c-bd0a-3a87e3eef13e

📥 Commits

Reviewing files that changed from the base of the PR and between f620233 and 5821c09.

📒 Files selected for processing (2)
  • LLM/OSS/Open_AI_OSS.py
  • app_oss_main.py
📝 Walkthrough

Walkthrough

챗봇 엔드포인트에 요청 레이턴시 측정 및 데이터베이스 로깅 기능을 추가했습니다. 새로운 _log_chatbot() 헬퍼 함수가 쿼리, 모드, 응답, URL, 캐시 여부, 지연 시간을 PostgreSQL 데이터베이스에 기록하며, SSH 터널링을 선택적으로 지원합니다.

Changes

Cohort / File(s) Summary
Chatbot Logging Feature
LLM/OSS/Open_AI_OSS.py
_log_chatbot() 헬퍼 함수 추가로 각 챗봇 상호작용(쿼리, 모드, 응답, URL, 캐시 여부, 지연 시간)을 PostgreSQL 데이터베이스에 기록. /chatbot 엔드포인트에서 time.monotonic()을 사용한 레이턴시 측정 및 데몬 스레드 생성으로 캐시 히트/미스 모두에서 로깅 수행. psycopg2, SSHTunnelForwarder 의존성 추가.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server as /chatbot<br/>Endpoint
    participant ThreadPool as Daemon<br/>Thread
    participant Cache as Cache<br/>Layer
    participant DB as PostgreSQL<br/>Database
    participant SSH as SSH<br/>Tunnel

    Client->>Server: 챗봇 쿼리 요청
    Note over Server: time.monotonic() 시작
    
    alt 캐시 히트
        Server->>Cache: 캐시 조회
        Cache-->>Server: 응답 반환
    else 캐시 미스
        Server->>Server: API 호출 및 처리
        Server-->>Server: 응답 생성
    end
    
    Note over Server: time.monotonic() 종료<br/>레이턴시 계산
    Server-->>Client: 응답 반환
    
    Server->>ThreadPool: _log_chatbot() 호출<br/>(데몬 스레드)
    ThreadPool->>SSH: SSH 터널 확인
    alt SSH_HOST 설정됨
        ThreadPool->>SSH: 터널 연결
        SSH->>DB: 로그 기록
    else SSH_HOST 미설정
        ThreadPool->>DB: 직접 연결으로<br/>로그 기록
    end
    
    Note over DB: chatbot_logs 테이블<br/>삽입
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항인 챗봇 응답 로깅 기능을 명확하게 설명하고 있습니다.
Description check ✅ Passed PR 설명이 필수 섹션(관련 이슈, 배경, 주요 내용)을 포함하고 있으며 각 항목이 구체적으로 작성되었습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/chatbot_feedback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Yu-JeSeung

Copy link
Copy Markdown
Contributor Author

@coderabbitai 해당 pr에 대한 리뷰 부탁합니다.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown

@Yu-JeSeung: 네, PR #90에 대한 리뷰를 시작하겠습니다! 챗봇 응답 로깅 기능 구현에 대해 검토해 드리겠습니다.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
LLM/OSS/Open_AI_OSS.py (1)

902-921: 반복되는 비동기 로깅 호출을 헬퍼로 추출하세요.

동일한 threading.Thread(target=_log_chatbot, args=(...), daemon=True).start() 블록이 이 핸들러 안에서 6번(902-908, 917-921, 994, 1004, 1015, 1030) 반복됩니다. 파라미터 순서가 미묘히 달라 실수로 인자를 뒤바꿔 넣을 여지가 큽니다. 시작 시각(_start)과 기본값들을 감싼 클로저 헬퍼로 묶는 편이 안전합니다.

♻️ 제안 리팩터
 async def chat(req: ChatReq, username: str = Depends(verify_jwt_token)):
     _start = time.monotonic()
+    def _fire_log(mode_: str, text_: str, url_: Optional[str], cache_hit_: bool) -> None:
+        latency_ = int((time.monotonic() - _start) * 1000)
+        threading.Thread(
+            target=_log_chatbot,
+            args=(user_text, mode_, text_, url_, cache_hit_, latency_),
+            daemon=True,
+        ).start()

이후 모든 호출부를 _fire_log(mode, resp.get("text",""), resp.get("url"), False) 같은 형태로 치환하면 인자 순서 실수와 중복이 사라집니다. 단, 앞서 제안드린 큐+워커 구조로 바꾸면 이 헬퍼 자체가 _LOG_Q.put_nowait(...) 한 줄로 더 단순해집니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@LLM/OSS/Open_AI_OSS.py` around lines 902 - 921, Extract the repeated
threading.Thread(... target=_log_chatbot ...) calls into a small helper/closure
(e.g. _fire_log) that captures _start and any default values so all call sites
(including where _cached is used and inside _cache_and_return) call
_fire_log(mode, text, url, cached_flag) instead of repeating the
threading.Thread invocation; update every occurrence around _log_chatbot,
_start, _cached, and _cache_and_return to call this helper to avoid
argument-order bugs and duplicated code, optionally making the helper push to a
queue if you later swap to a queue+worker model.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@LLM/OSS/Open_AI_OSS.py`:
- Around line 72-78: Replace the print-based error handling in the broad except
block with structured logging: add logger = logging.getLogger(__name__) at the
top of Open_AI_OSS.py, then in the except Exception as e: block call
logger.exception("Failed during chat operation", exc_info=True) (or
logger.error(..., exc_info=True)) so the full stacktrace is recorded; keep the
broad except to avoid crashing the flow but ensure the logging call itself
cannot raise (optionally wrap the logger call in a tiny try/except that swallows
logging failures) and leave the finally cleanup (conn.close(), tunnel.stop())
as-is.
- Around line 40-62: The code hardcodes SSH port 22 and remote DB port 5433 and
omits a configurable DB port for direct connections; update the
SSHTunnelForwarder and psycopg2.connect calls to read SSH port, remote DB port,
and DB port from environment variables instead of literals: use
os.getenv("SSH_PORT", "22") for the SSH port when constructing
SSHTunnelForwarder (instead of (ssh_host, 22)), use os.getenv("REMOTE_DB_PORT",
"5433") for the remote_bind_address port, and ensure the non-tunnel
psycopg2.connect includes port=int(os.getenv("DB_PORT", "5432")). Keep types
consistent (cast to int where needed) and preserve existing uses of
tunnel.local_bind_port for the tunneled psycopg2.connect.
- Around line 34-78: The _log_chatbot function opens an SSH tunnel and a new
psycopg2 connection per request which causes high latency, resource exhaustion,
and lost logs; refactor by initializing a single long-lived SSH tunnel and a
ThreadedConnectionPool at application startup (e.g., in a new
_init_logging_infra called on app startup), replace per-call
SSHTunnelForwarder.start()/stop() and psycopg2.connect() with
borrowing/returning connections from the pool, move asynchronous logging work
off the request path into a bounded ThreadPoolExecutor or queue+worker (avoid
daemon threads), and ensure you set a statement_timeout on connections before
executing the INSERT to avoid long-running statements; update _log_chatbot to
acquire a pooled connection, run the INSERT, and release the connection, and add
proper shutdown logic to close the tunnel, pool, and executor on app
termination.

---

Nitpick comments:
In `@LLM/OSS/Open_AI_OSS.py`:
- Around line 902-921: Extract the repeated threading.Thread(...
target=_log_chatbot ...) calls into a small helper/closure (e.g. _fire_log) that
captures _start and any default values so all call sites (including where
_cached is used and inside _cache_and_return) call _fire_log(mode, text, url,
cached_flag) instead of repeating the threading.Thread invocation; update every
occurrence around _log_chatbot, _start, _cached, and _cache_and_return to call
this helper to avoid argument-order bugs and duplicated code, optionally making
the helper push to a queue if you later swap to a queue+worker model.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4fa148e0-fd82-4207-99fc-dc98c12b8341

📥 Commits

Reviewing files that changed from the base of the PR and between 0594df7 and f620233.

📒 Files selected for processing (1)
  • LLM/OSS/Open_AI_OSS.py

Comment thread LLM/OSS/Open_AI_OSS.py Outdated
Comment thread LLM/OSS/Open_AI_OSS.py Outdated
Comment on lines +40 to +62
tunnel = SSHTunnelForwarder(
(ssh_host, 22),
ssh_username=os.getenv("SSH_USER"),
ssh_pkey=os.getenv("SSH_KEY_PATH"),
remote_bind_address=("localhost", 5433),
)
tunnel.start()
conn = psycopg2.connect(
host="localhost",
port=tunnel.local_bind_port,
dbname=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD"),
connect_timeout=3,
)
else:
conn = psycopg2.connect(
host="localhost",
dbname=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD"),
connect_timeout=3,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

하드코딩된 SSH 포트(22)·원격 DB 포트(5433)와 누락된 DB_PORT를 환경변수화하세요.

  • (ssh_host, 22)의 22, remote_bind_address=("localhost", 5433)의 5433이 코드에 박혀 있어 SSH를 비표준 포트로 운용하거나 원격 PostgreSQL 포트가 5432인 환경에서는 동작하지 않습니다.
  • else 브랜치(비터널)는 port를 지정하지 않아 항상 5432로 접속합니다. 터널 경로와 포트가 달라 동일 환경변수 세트로 로컬/운영 양쪽에서 쓰기 어렵습니다.
🔧 제안 수정
-        if ssh_host:
+        if ssh_host:
             tunnel = SSHTunnelForwarder(
-                (ssh_host, 22),
+                (ssh_host, int(os.getenv("SSH_PORT", "22"))),
                 ssh_username=os.getenv("SSH_USER"),
                 ssh_pkey=os.getenv("SSH_KEY_PATH"),
-                remote_bind_address=("localhost", 5433),
+                remote_bind_address=(
+                    os.getenv("REMOTE_DB_HOST", "localhost"),
+                    int(os.getenv("REMOTE_DB_PORT", "5432")),
+                ),
             )
             tunnel.start()
             conn = psycopg2.connect(
                 host="localhost",
                 port=tunnel.local_bind_port,
                 dbname=os.getenv("DB_NAME"),
                 user=os.getenv("DB_USER"),
                 password=os.getenv("DB_PASSWORD"),
                 connect_timeout=3,
             )
         else:
             conn = psycopg2.connect(
-                host="localhost",
+                host=os.getenv("DB_HOST", "localhost"),
+                port=int(os.getenv("DB_PORT", "5432")),
                 dbname=os.getenv("DB_NAME"),
                 user=os.getenv("DB_USER"),
                 password=os.getenv("DB_PASSWORD"),
                 connect_timeout=3,
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@LLM/OSS/Open_AI_OSS.py` around lines 40 - 62, The code hardcodes SSH port 22
and remote DB port 5433 and omits a configurable DB port for direct connections;
update the SSHTunnelForwarder and psycopg2.connect calls to read SSH port,
remote DB port, and DB port from environment variables instead of literals: use
os.getenv("SSH_PORT", "22") for the SSH port when constructing
SSHTunnelForwarder (instead of (ssh_host, 22)), use os.getenv("REMOTE_DB_PORT",
"5433") for the remote_bind_address port, and ensure the non-tunnel
psycopg2.connect includes port=int(os.getenv("DB_PORT", "5432")). Keep types
consistent (cast to int where needed) and preserve existing uses of
tunnel.local_bind_port for the tunneled psycopg2.connect.

Comment thread LLM/OSS/Open_AI_OSS.py Outdated
Comment on lines +72 to +78
except Exception as e:
print(f"[chatbot_log ERROR] {e}")
finally:
if conn:
conn.close()
if tunnel:
tunnel.stop()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

print 대신 logging을 사용하고 스택트레이스를 남겨주세요.

  • 운영 환경에서 print는 구조화 로깅/레벨 필터링이 안 되고 stdout 버퍼링 이슈도 있습니다. logging.getLogger(__name__).exception(...)로 예외 전문을 남기는 편이 디버깅에 훨씬 유리합니다.
  • Ruff BLE001(블라인드 except Exception)도 동시에 개선됩니다. 로깅 실패가 요청 흐름을 막으면 안 되므로 포괄 catch 자체는 유지하되, 로그에는 반드시 exc_info를 포함시키세요.
  • 참고로 daemon=True 스레드는 프로세스 종료 시 INSERT 중간에 잘려 로그가 유실될 수 있으니, 위의 워커/큐 전환 시에는 graceful shutdown(queue.join() 또는 sentinel)도 함께 고려하세요.
🔧 제안 수정
-    except Exception as e:
-        print(f"[chatbot_log ERROR] {e}")
+    except Exception:
+        logger.exception("chatbot_log failed")

파일 상단에 logger = logging.getLogger(__name__) 선언 필요.

🧰 Tools
🪛 Ruff (0.15.10)

[warning] 72-72: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@LLM/OSS/Open_AI_OSS.py` around lines 72 - 78, Replace the print-based error
handling in the broad except block with structured logging: add logger =
logging.getLogger(__name__) at the top of Open_AI_OSS.py, then in the except
Exception as e: block call logger.exception("Failed during chat operation",
exc_info=True) (or logger.error(..., exc_info=True)) so the full stacktrace is
recorded; keep the broad except to avoid crashing the flow but ensure the
logging call itself cannot raise (optionally wrap the logger call in a tiny
try/except that swallows logging failures) and leave the finally cleanup
(conn.close(), tunnel.stop()) as-is.

@Yu-JeSeung
Yu-JeSeung merged commit 241ca9c into main Apr 22, 2026
1 check passed
@Yu-JeSeung
Yu-JeSeung deleted the feat/chatbot_feedback branch April 22, 2026 04:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 챗봇 응답 피드백 구축

1 participant