手冊首頁 / 系統設定 / 操作日誌

操作日誌(User Log)

功能群:系統管理|三層 RBAC 權限模型與角色權限基調先讀 功能群總覽

事實基準:2026-07-05 從 FE / BE code 掃出(BE 核心邏輯位於外部套件 jedi-api-log,主專案為 route + 全域 middleware 薄殼);API schema 逐條對照 marshmallow serializer 與 service 組裝碼

1. 功能描述

操作日誌頁是系統管理群「稽核與個人」分類下的唯讀查詢頁:檢視 public.api_logs 這張「每一次 API 請求」的稽核軌跡(誰、何時、打了哪個 URL、帶什麼 payload、回應耗時等)。本頁不寫入任何資料——api_logs 的寫入完全由全域 Flask middleware(common/middleware/app_mw.py)在每個請求的 before_request/after_request/teardown_request 自動完成,與本頁的使用者操作無關。頁面本身只有一張 DataTable + 全域搜尋 + 匯出,無左側樹、無 Tab、無新增/編輯/刪除

主要使用者:系統管理層使用者(頁面可見性由 RBAC 選單控管,見 §3)。

角色速覽

角色 一句話
系統管理層使用者 本頁唯一操作者——選單可見性由 RBAC 控管(見 _overview §2),BE 查詢端點僅檢查 JWT 登入;匯出端點連 JWT 都未檢查(見 §3)

⚠️ 本頁只查 public.api_logs,不查 public.system_logs:FE UserLogs.vue 唯一呼叫的資料來源是 POST /log/api-logs;同目錄下 UserLogService.jsfetch('/demo/data/logs.json'))是死碼,未被 UserLogs.vue 或任何其他檔案 import。system_logs 是另一條完全獨立的寫入管道(jedi-commonDBLogHandler,掛在 Python logging 層,於 WARNING 以上事件寫入 event_code),本頁沒有對應的查詢 UI,見 §9。

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 日誌清單(分頁/排序) 9 欄 DataTable,lazy 分頁(25/50/100)+ 多欄排序;per-user 查詢狀態持久化(useTableQuery scope) 主表格 UC-UL-01
2 全域關鍵字搜尋 單一搜尋框,同一關鍵字套用到 7 個欄位(user_name/level/request/source_ip/server_ip/method/url Header 搜尋框 UC-UL-01 / §12 坑 1
3 重置查詢 清空搜尋/排序/分頁回到第一頁 Header「重置查詢」按鈕 §10
4 Payload 密碼遮罩(顯示層) request 欄位可解析為 JSON 且含 password key,顯示前於 FE 再次遮罩為 *** 清單「Payload」欄 §10
5 匯出日誌(zip/xlsx) 匯出目前條件未套用的全量 api_logs 為 xlsx,包成 zip 下載 Header「匯出」按鈕 UC-UL-02 / §12 坑 1

UC(§2)展開 1(查詢/篩選/排序)、2(匯出)兩條核心操作路徑;其餘(重置查詢、密碼遮罩顯示)屬單點行為,不另展開。

2. Use Case

角色速覽

角色 一句話
系統管理層使用者 本頁唯一操作者——選單可見性由 RBAC 控管(見 _overview §2),BE 查詢端點僅檢查 JWT 登入;匯出端點連 JWT 都未檢查(見 §3)

流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-UL-01 查詢/篩選/排序日誌

項目 內容
角色 系統管理層使用者(頁面可見性由 RBAC 選單控管)
前置條件 已登入;user-log 選單可見
產出 / 後置條件 無資料異動(唯讀);DataTable / pager.total 更新
userlog_uc_query start enter 進入操作日誌頁 start->enter load onMounted 呼叫 getUserLogs() buildPayload() 組 {pager, sort, filters} enter->load keyword 輸入關鍵字 Enter/點搜尋圖示 enter->keyword sortclick 點欄位表頭排序 enter->sortclick reset 點「重置查詢」 enter->reset post POST /log/api-logs load->post jwt BE 檢查: 僅 @jwt_required()? (⚠️ 無角色/租戶檢查,見 §3) post->jwt deny 401 未登入 jwt->deny sortdef BE 補預設排序: 未帶 sort 時強制 created_at desc (避免 BaseRepository 預設 asc 把當日資料排到最後幾頁) jwt->sortdef query ApiLogRepoImpl(BaseRepositoryImpl) _get_pager_list_query: 字串欄位 ILIKE 後以 OR 連接、 其他型別 AND sortdef->query render 回填 DataTable + pager.total (無 tenant 過濾,見 §3/§12) query->render fanout FE 對 7 個欄位(user_name/level/ request/source_ip/server_ip/ method/url)帶同一關鍵字 keyword->fanout fanout->load 重新查詢 sortclick->load 重新查詢 resetstore queryStore.resetCurrent() 清空 filterTable/sort/pager reset->resetstore resetstore->load
UC-UL-01 流程:載入/搜尋/排序皆走同一 POST /log/api-logs;BE 補預設排序 created_at desc;查詢邏輯是 OR-across-fields ILIKE

UC-UL-02 匯出日誌

項目 內容
角色 名義上系統管理層使用者;實際上任何人(含未登入者)皆可呼叫(見下)
前置條件 無(端點未強制登入)
產出 / 後置條件 下載 zip 檔(內含 xlsx);無資料異動
userlog_uc_export start click 點「匯出」按鈕 start->click get GET /log/api-logs/export (axios responseType=blob) click->get noauth BE 檢查: @jwt_required() 已註解掉, 完全無登入檢查! get->noauth open ⚠️ 任何人(含未登入者) 皆可直接呼叫此 URL 匯出全部 api_logs (SEC-001,見 test_security.py + §3/§12 坑 1) noauth->open 是(現況) query get_all_by_fields(): ApiLogRepoImpl._gen_filters (AND-only,多數欄位用 ==, 非分頁頁面的 OR/ILIKE 邏輯,見 §12 坑 2) open->query build pandas DataFrame → ExcelUtil 清無效字元 → xlsx 寫入記憶體 → 壓成 zip(user_log_<timestamp>.zip) query->build resp 回傳 zip blob + Content-Disposition/X-Filename header build->resp save FE 用 Blob URL 觸發瀏覽器下載 resp->save
UC-UL-02 流程:匯出端點 @jwt_required() 已被註解掉,SEC-001 已知漏洞;匯出邏輯走另一條 AND-only 過濾且不吃目前畫面篩選條件

3. 權限矩陣

頁面可見性依 RBAC 三層模型基調,見 _overview §2。本頁端點層的實際檢查點:

操作 FE 判定 BE 強制 BE 檢查位置
選單 / 頁面可見性 RBAC 選單資料決定(route meta 無 capability 欄位,純靠 ui_routes/capabilities 回傳結果) 不適用(選單層問題,非本頁端點) jedi-auth web-menu(見 _overview §2.1
查詢清單 POST /log/api-logs 無額外前端判斷 ⚠️ @jwt_required(),無角色/租戶檢查;api_logs 表本身無 tenant_id/org_unit_id 欄位,任何租戶的登入使用者皆可查到全租戶的 API 呼叫紀錄 api/log/routes/api_log_route.py:24ApiLogsRoute.post
匯出 GET /log/api-logs/export 匯出按鈕對所有可見本頁的使用者皆顯示 🔴 完全無認證檢查——@jwt_required() 該行被註解掉(api/log/routes/api_log_route.py:51),任何人(含未登入、無 JWT)直接呼叫此 URL 即可下載全部 api_logs api/log/routes/api_log_route.py:48-51ExportApiLogsRoute.get

🔴 已知安全漏洞(SEC-001,已有 regression test 記錄,見 §12 坑 1)tests/test_security.py 明確記錄 ExportApiLogsRoute@jwt_required() 已被註解掉,並以 pytest.xfail 標記此為「預期會失敗」的已知 bug(test_SEC_001_no_jwt)。本頁 spec 僅如實記錄,不在此 session 修復(修正需另開 bug/issue session,走 docs/issues/ 流程)。

⚠️ 跨租戶可見性:即使是查詢端點(有 @jwt_required()),api_logs/system_logs 兩表皆無 tenant_id 欄位、亦無 RLS 隔離(session_scope() 注入的 app.allowed_tenant_paths 對這兩張表不生效,因為它們不受 RLS policy 涵蓋)。任何登入使用者查詢本頁,看到的是全系統、跨所有租戶的 API 呼叫紀錄,包含其他租戶使用者的操作內容與 request payload。這與 _overview §3 「RLS 做租戶隔離」的一般假設不同,是本頁特有的資料可見性坑。

4. 狀態機與前置條件

本頁狀態機、無輪次/階段概念(日誌是唯讀稽核紀錄,非生命週期資源)。無欄位層級前置條件——查詢與匯出皆無條件可用(除登入外,且匯出連登入都不需要,見 §3)。

條件 效果 出處
FE 未帶排序 BE 強制補 created_at desc(避免預設 asc 排序把當日資料排到最後幾頁) api/log/routes/api_log_route.py:36-39
搜尋關鍵字為空 buildPayload() 不帶 filters,等同查全部 useTableQuery.js:119-125UserLogs.vue 呼叫端)
匯出 不套用畫面上目前的搜尋/排序/分頁條件,永遠匯出全表(見 §12 坑 2) api/log/routes/api_log_route.py:53-54export_api_log_file() 無參數)

5. UI 設計

📸 實際畫面截圖待補(一律亮色模式拍攝):以 playwright 對 STG 擷取,預計補以下兩張到 assets/img/: ① 整頁(搜尋框 + DataTable 9 欄 + 匯出按鈕)userlog-overview.png ② Payload 欄 tooltip 特寫(顯示 password 遮罩效果)userlog-payload-tooltip.png

版面骨架(單欄:Header 搜尋/動作 + DataTable,無左側樹、無 Tab、無 Dialog):

Header:全域搜尋(同關鍵字套 7 欄 OR)/重置查詢/匯出按鈕 單欄 DataTable,唯讀,無新增/編輯/刪除、無左樹、無 Tab 開啟:UC-UL-01 查詢/篩選/排序日誌 日誌 DataTable:9 欄(時間/使用者/URL/method/IP/耗時…),lazy 分頁 + 多欄排序 POST /log/api-logs(分頁查詢,BE 補預設排序 created_at desc) Payload 欄 FE 二次遮罩 password;per-user 查詢狀態持久化 ⚠ 無 tenant_id 隔離:任何登入者可見全租戶紀錄 → UC-UL-01(點我開啟) 開啟:UC-UL-02 匯出日誌 匯出:下載 zip(內含 xlsx),永遠匯出全量 api_logs(不吃畫面篩選條件) GET /log/api-logs/export 🔴 SEC-001 已知漏洞:@jwt_required() 已被註解,無任何認證檢查 → UC-UL-02(點我開啟) 彩色=主要互動/明細區塊,整塊可點擊跳轉對應 UC|灰=本頁結構|虛線框=已知風險標註區塊 API 僅列代表性 2 條,完整規格見 §6;本頁唯讀,不寫入 api_logs(寫入由全域 middleware 負責)
操作日誌頁版面:Header 搜尋/重置/匯出、日誌 DataTable(可跳 UC-UL-01)、匯出(可跳 UC-UL-02,標註 SEC-001 已知漏洞)

狀態呈現:DataTable loading 遮罩載入中;匯出按鈕獨立 downloading loading 狀態,不阻塞整頁查詢;url 欄超長時 tooltip 顯示全文(無截斷樣式,欄寬用 max-w-*rem class 限制);user_agentrequest 欄以 text-overflow-ellipsis 截斷 + tooltip 顯示全文;無 socket,操作後皆重打 getUserLogs() 重新整理。查詢狀態(搜尋關鍵字/排序/分頁)以 useTableQuery 依 route scope + 使用者 uid 持久化於 queryStore,換使用者或重新登入會自動重新查詢(UserLogs.vue:51-55)。

6. API 規格

Envelope:成功 {"status": true, "data": [...], "meta": {...}}return_response 組裝,見 common/util/response_util.py:4-16);失敗為 4xx/5xx 標準錯誤 envelope。

6.1 總清單(本頁涉及的全部 endpoint)

分類 Method + Path 說明 完整規格
查詢 POST /log/api-logs 分頁查詢(本頁唯一資料來源,payload={pager, sort, filters} §6.2
匯出 GET /log/api-logs/export 匯出全量 api_logs 為 zip(內含 xlsx);🔴 無認證 §6.3

本頁僅 2 個 endpoint,皆非常規 CRUD,故 §6.2~6.3 展開完整規格(無 GAI-SD-02 常規 CRUD 段落可省略)。

6.2 查詢類

[POST] /log/api-logs

Request payloadApiLogPageQueryRequest):

欄位 型別 必填 說明
pager object {page, page_size, with_total}PagerSchemapage 預設 1、page_size 預設 25)
sort array [{field, order}];省略時 BE 強制補 [{field: created_at, order: desc}]
filters object ApiLogQueryEntity 子集:user_name/level/request/source_ip/server_ip/method/url(FE 全域搜尋對這 7 欄各自帶同一關鍵字)

Response data[](每項一筆 API 呼叫紀錄):

欄位 型別 說明
id int 內部序號(複合 PK 之一,另一是 act_time
act_time datetime 操作時間,YYYY-MM-DD HH:MM:SS(請求進入時的時間戳,非資料庫 created_at
user_uid string 命名易混淆:實際存的是使用者 login_name(非 UUID),由 after_request 回填
user_name string 使用者 nicknameafter_requestget_user_context() 回填),未登入請求為空字串
type string 日誌類型,固定為 "1"LogTypeCode.API_LOG),本頁未使用此欄位篩選
level string 日誌級別(INFO 等),寫入時固定 'INFO'update_api_log 不會改動此欄
source_ip string 來源 IP(request.remote_addr);敏感欄位,本文件不列任何實際值
server_ip string 伺服器 IP(request.host,含 port);敏感欄位,本文件不列任何實際值
url string 完整請求 URL(含 querystring)
method string HTTP method
params string Query string(不含路徑)
request string Request body 原文(before_request 寫入時已用 mark_password() 遮罩 password key)
user_agent string User-Agent header
created_at datetime DB 列建立時間(分區鍵之一)
message string 一般請求為 request path;發生例外時(teardown_request)覆寫為例外訊息字串
response string Response body 原文(after_request 回填,未做遮罩——回應內容若含敏感欄位不會被 mask,見 §12 坑 3)
duration float 請求耗時(秒),after_request 計算 now() - g.start_time

出處:api/log/serializers/api_log.py:20-40ApiLogResponse / ApiLogPageQueryResponse);寫入端 common/middleware/app_mw.py:34-137

6.3 匯出類

[GET] /log/api-logs/export

無 query 參數、無 body(不吃畫面上目前的搜尋/排序/分頁條件,永遠對全表匯出,見 §12 坑 2)。

Response:Content-Type: application/zipContent-Disposition: attachment,檔名 user_log_<YYYYMMDDHHMMSS>.zip(內含同名 .xlsx,無 header 列,欄位順序同 §6.2 Response,但實際輸出欄位由 ApiLogExportDTO 決定)。

出處:api/log/routes/api_log_route.py:48-68ExportApiLogsRoute.get)、jedi_api_log/app/service/api_log_service.py:53-76export_api_log_fileget_all_by_fields → pandas → ExcelUtil.clean_invalid_characters → zip)。

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/action-log/UserLogs.vue 唯一檢視檔(約 200 行;DataTable + 全域搜尋 + 匯出,無子元件)
src/config/router/index.js:236-243 route user-log,path /log/user-logmeta 無 capability 欄位,可見性由 RBAC 選單資料決定
src/config/api/api.js:90-91 API_LOGS/log/api-logs)/ EXPORT_API_LOGS/log/api-logs/export
src/service/BaseService.js 本頁查詢用 baseService.post(API.API_LOGS, payload);匯出改用裸 axios 直接打(因需 responseType: 'blob'BaseService 泛型不支援)
src/service/UserLogService.js ⚠️ 死碼fetch('/demo/data/logs.json') 假資料服務,未被 UserLogs.vue 或任何其他檔案 import,見 §1
src/composables/useTableQuery.js 共用分頁/排序/搜尋 state(per-scope + per-user 持久化);buildPayload() 是全域搜尋「同關鍵字套多欄」邏輯的來源
src/stores/queryStore.js useTableQuery 背後的 Pinia store(resetCurrent() 供「重置查詢」使用)
src/utils/userUtil.js useUserUtil()userInfo(供使用者變更時觸發重新查詢)
src/config/locales/i18n/{zh-tw,en}/pages.json 頁面 i18n,namespace lang.user_logs.*(第 226-239 行)

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
查詢清單 api/log/routes/api_log_route.py::ApiLogsRoute.post jedi_api_log.app.service.api_log_service.ApiLogService::get_devices_and_pager@transaction,方法名沿用自其他模組樣板、未改名) ApiLogDomainService.get_api_logs_and_pagerApiLogRepoImpl(繼承 BaseRepositoryImpl)→ _get_pager_list_query(OR-across-string-fields ILIKE,見 §12 坑 2)
匯出 api/log/routes/api_log_route.py::ExportApiLogsRoute.get ApiLogService::export_api_log_file@transaction ApiLogDomainService.get_all_by_fieldsApiLogRepoImpl.get_all_by_fields(走 BaseRepositoryImpl._gen_filters,AND-only、多數欄位 ==,見 §12 坑 2)→ pandas/openpyxl
寫入(全域,非本頁觸發) 無獨立 route;掛在 Flask app 生命週期鉤子 common/middleware/app_mw.py::init_app_interceptorbefore_request/after_request/teardown_request,三段式寫入) ApiLogService.add_api_log / get_api_log / update_api_logApiLogRepoImpl.add / .update(自訂 SQL,非 BaseRepositoryImpl 泛用版)

DDD 提醒:本模組是「主專案薄殼 + 外部套件扛全部 domain/infra」的典型範例——domain/log/infra/log/ 在主專案不存在,全數在 jedi-api-log(pip 套件名,對應原始碼倉庫 jedi-log~/Projects/Jedicogy/module/jedi-python-package/jedi-log/)。主專案僅有 route(HTTP 邊界)+ 全域 middleware(app_mw.py,寫入 api_logs 的唯一入口)。改日誌核心行為(欄位、過濾邏輯、匯出格式)要去 jedi-log 套件改,改動需遵守 CLAUDE.md「外部套件異動規範」。route 層無 DB 存取(守規範);但 common/middleware/app_mw.py全域基礎設施而非本頁專屬 app service,其 @transaction 語意由套件內 ApiLogService 方法自帶。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
public.api_logs 讀(本頁);(全域 middleware,非本頁操作觸發) 每次 HTTP API 請求的稽核軌跡:時間/使用者/URL/method/request-response/耗時;月分區(RANGE on act_time)+ BRIN 索引;retention 90 天 §9.3
public.system_logs 不讀不寫(本頁未查詢此表,見 §1) event_code 事件日誌:由 jedi-commonDBLogHandler(Python logging Handler,WARNING 以上等級)寫入,與本頁 API 呼叫軌跡是完全獨立的管道;月分區 + BRIN;retention 180 天 §9.3

api_logs vs system_logs 的本質區分api_logs 是「每一次 HTTP 請求」的軌跡(method/url/request/response/duration),由 Flask 生命週期鉤子(before_request/after_request/teardown_request)逐請求寫入;system_logs 是「系統事件」(event_code 語意化事件,如稽核進度、階段推進),由應用程式碼呼叫 logger.warning(...) 等經 DBLogHandler 落地。兩者欄位形狀不同(system_logsevent_code/path/func_name/line_no,少 source_ip/server_ip/request/response/duration),本頁只涵蓋前者

9.2 ER 圖(本頁讀寫範圍)

userlog_er api_logs public.api_logs (本頁讀;middleware 寫) PK (id, act_time) 月分區+BRIN user_uid(=login_name) / user_name(=nickname) source_ip / server_ip / url / method request(已mask) / response(未mask) / duration retention 90 天(maintain_log_partitions,排程未接) ⚠️ 無 tenant_id/不受 RLS system_logs public.system_logs (本頁不讀不寫,僅供對照) PK (id, act_time) 月分區+BRIN event_code / path / func_name / line_no 寫入者:jedi-common DBLogHandler(logging 層) retention 180 天(同一維護函式) middleware common/middleware/app_mw.py (before/after/teardown_request) 全域 Flask 鉤子,每個 API 請求皆觸發 middleware->api_logs 寫入 handler jedi-common DBLogHandler (logging.Handler) WARNING 以上等級事件觸發 handler->system_logs 寫入 maint public.maintain_log_partitions() 建未來分區+DROP 到期分區 (未接排程,見 §11) maint->api_logs 維護(未執行) maint->system_logs 維護(未執行)
操作日誌 ER:api_logs 是本頁唯讀查詢 + 全域 middleware 寫入的表;system_logs 為對照用(虛線)——本頁完全不涉及

9.3 核心表欄位(取自 DEV DB dump)

public.api_logs — API 日誌表(本頁資料來源)

DB 無表層 comment(db_schema.json 該表 comment 欄為 null;套件 ORM model 端有 comment「API日誌表:記錄API請求與回應的詳細資訊」但未同步灌回 DB)。已分區partitioned: trueRANGE(act_time),月分區 + DEFAULT 分區防溢出,見 scripts/sql/2026-06-03-log-tables-partitioning.sql)。複合主鍵 (id, act_time)(partition key 必入 PK,PostgreSQL 分區表限制)。

欄位 型別 說明
id integer 序號(複合 PK 之一,api_logs_id_seq
act_time timestamp(NOT NULL,複合 PK 之一) 操作時間,同時是分區鍵;有 BRIN 索引 ix_api_logs_act_time_brin
user_uid varchar(36)|null 命名為 uid 實存 login_name(見 §6.2)
user_name varchar(500)|null nickname
type varchar(10)(NOT NULL) 日誌類型碼(固定 "1" = API_LOG)
level varchar(10)(NOT NULL) 日誌級別
source_ip varchar(255)(NOT NULL) 來源 IP;本文件僅描述欄位存在,不列任何實際值
server_ip varchar(255)(NOT NULL) 伺服器 IP(含 port);本文件僅描述欄位存在,不列任何實際值
url varchar(500)(NOT NULL) 完整請求 URL
method varchar(10)(NOT NULL) HTTP method
params text|null Query string
request text|null Request body(寫入時已 mask password)
response text|null Response body(未 mask,見 §12 坑 3)
message text|null 一般為 request path;例外時為錯誤訊息
user_agent varchar(5000)|null User-Agent
duration numeric|null 請求耗時(秒),預設 0
created_at timestamp(NOT NULL) DB 列建立時間

tenant_id/org_unit_id 欄位、無 FK——本表刻意不綁租戶(見 §3 跨租戶可見性說明)。

public.system_logs — 系統事件日誌表(本頁不涉及,供對照)

同為已分區(月分區 + BRIN),複合 PK (id, act_time),retention 180 天。核心欄位:event_code(varchar(10),事件碼)、path/func_name/line_no(呼叫點座標,DBLogHandlerLogRecord 取得)、message(事件訊息);沒有 source_ip/server_ip/request/response/duration(因為它記錄的是應用程式事件而非 HTTP 請求)。

10. 頁面邏輯與資料對應

載入時序onMounted 呼叫 getUserLogs()buildPayload(){pager, sort, filters}filters 僅在搜尋框有值時才附帶)→ POST /log/api-logs → 回填 logs.valuepager.valueObject.assign(pager.value, meta))。使用者 uid 變化(如延遲取得或重新登入)觸發 watch 重新查詢(UserLogs.vue:51-55)。

關鍵欄位對應(API ↔︎ 畫面):

畫面元素 FE state API 欄位
DataTable 各欄 logs(array) data[] 逐項對應(act_time/level/user_name/url/method/source_ip/server_ip/user_agent/request
全域搜尋框 filterTable.global.value payload.filters.{user_name,level,request,source_ip,server_ip,method,url}(同一關鍵字套 7 欄,OR 連接,見 §12 坑 1)
Payload 欄顯示 markPassword(data.request) computed(v-dompurify-html 渲染) data.request(BE 側已遮罩一次,FE 端對可解析 JSON 的 password key 再遮罩一次,雙重防護)
分頁/排序 pager/sortuseTableQuery 提供) request payload.{pager,sort};response meta.{page,page_size,total}

儲存流程:本頁無任何寫入操作(唯讀查詢頁);使用者的操作(搜尋/排序/翻頁/重置)皆觸發同一 getUserLogs() 重新查詢,不做樂觀更新。

錯誤對應:BE error envelope → toast 顯示(若有 BaseService 統一攔截)。本頁無專屬 error codecommon/code/ 各檔搜尋日誌相關前綴皆無結果,jedi_api_log.common.enum.error_code 亦未見被主專案引用捕捉特定碼);查詢失敗多半是 401(未登入)或未捕捉例外的 500。

11. 背景行為與外部依賴

類型 內容
通知 無——本頁不觸發、也不消費任何通知
Socket 無 socket / 輪詢;操作後皆重打 getUserLogs()
全域寫入管線 common/middleware/app_mw.py 掛在 每一個 Flask 請求的 before_request(建立日誌列,失敗時 try/except 吞掉不影響業務 API)/after_request(回填 response/耗時/使用者資訊)/teardown_request(例外路徑回填 message);本頁完全是這條全域管線的唯讀消費端
分區維護 public.maintain_log_partitions() DB function(建未來 3 個月分區 + DROP 超過 retention 的月分區,api_logs=90 天/system_logs=180 天);⚠️ 目前無排程呼叫此函式scripts/sql/2026-06-03-log-tables-partitioning.sql 註解明載「排程未接(pg_cron 未裝)」,主專案程式碼亦未見任何 APScheduler/cron job 呼叫 maintain_log_partitions——需人工執行或後續補排程,否則分區只會累積不會清理,retention 政策實質未生效)
jedi-* 套件 jedi-api-log(原始碼倉庫名 jedi-log,日誌 domain/infra 核心)、jedi-commonBaseRepositoryImpl/transaction/DBLogHandler/分頁 spec)、jedi-authget_user_context()after_request 取當前使用者)
系統參數 無 feature flag

12. 邊界情況與已知坑

  1. 🔴 匯出端點無任何認證檢查(SEC-001,已有 regression test)GET /log/api-logs/export@jwt_required() 被註解在 api/log/routes/api_log_route.py:51,任何人(含未登入者)只要知道 URL 即可下載全部 api_logs(含其他使用者的 request/response 內容、IP 等)。tests/test_security.pytest_SEC_001_no_jwt 已用 pytest.xfail 記錄此已知漏洞;即使補上 @jwt_required()test_SEC_002_normal_user 也指出任何登入者(無角色檢查)仍可匯出全部日誌。本頁 spec 僅記錄不修,修正需另開 bug session
  2. 查詢與匯出走兩套不同的過濾邏輯,行為不對稱:畫面查詢(POST /log/api-logs)走 BaseRepositoryImpl._get_pager_list_query——字串欄位皆 ILIKE '%value%'跨欄位以 OR 連接(符合 FE「同關鍵字套 7 欄」的搜尋直覺);但匯出(GET /log/api-logs/export)走 ApiLogRepoImpl.get_all_by_fields_gen_filters(模組自訂、非 BaseRepositoryImpl 泛用版)——多數欄位是 == 精確比對、僅 user_name/level/request/source_ip/server_ip/method.like()(且 url 不在此清單內,若帶 url filter 會變成精確比對而非模糊)、且條件間是 AND。實務上匯出端點目前呼叫時未帶任何 filter 參數(§6.3 無 query/body),所以這套不對稱邏輯暫時不影響現況行為,但屬「兩套真相並存」的技術債,日後若要讓匯出吃畫面篩選條件,務必留意這條差異
  3. Response body 未做密碼遮罩before_request 寫入 request 欄位時有呼叫 mark_password() 遮罩 JSON body 內的 password key,但 after_request 回填 response 欄位時沒有同等遮罩處理(common/middleware/app_mw.py:93response.data.decode(...) 直接存)。若某支 API 的回應內容意外帶出密碼或 token 類敏感欄位,會原樣寫入 api_logs.response,且此表無 RLS、任何登入使用者皆可查詢(見坑 4),敏感度較高
  4. 跨租戶可見性api_logs/system_logs 皆無 tenant_id 欄位、不受 RLS 涵蓋,任何登入使用者查詢本頁即可看到全系統所有租戶的 API 呼叫紀錄與 request/response 內容,與其他頁面「RLS 自動做租戶隔離」的預設假設不同(見 _overview §3
  5. user_uid 欄位命名誤導ApiLogResponse.user_uid 實際存的是使用者 login_name 字串(after_requestcurr_user_info.login_name 回填),不是 UUID/不是 users.uid。維護時勿假設可拿此欄位去 join users.uid
  6. 分區 retention 政策未實際生效maintain_log_partitions() 函式已建立(90/180 天 DROP 邏輯完整),但沒有任何排程機制呼叫它(pg_cron 未裝、主專案亦無對應 APScheduler job),目前分區只會持續新建、不會自動清除到期分區——若未來磁碟空間或效能出現異常,先確認是否為此函式從未真正跑過
  7. UserLogService.js 是死碼src/service/UserLogService.js(讀 /demo/data/logs.json 假資料)未被 UserLogs.vue 或任何其他檔案引用,屬歷史殘留,日後清理死碼時可考慮移除(本頁 spec 僅記錄,不擅自刪除)
  8. level/type 欄位形同虛設level 寫入時固定 'INFO'update_api_log 不會改動),type 固定 "1"LogTypeCode.API_LOG)——雖然 DataTable 有「日誌等級」欄且可搜尋/排序,但目前系統實際上只會產生單一 level 值,欄位的區分能力未被實際使用
  9. 匯出檔名含時間戳但無條件摘要:匯出檔名 user_log_<YYYYMMDDHHMMSS>.zip 僅反映匯出時間,配合坑 2(匯出不吃畫面篩選),使用者若在特定條件下匯出、事後容易搞混是哪次查詢的結果(因為匯出其實永遠是全表,不是「當前篩選結果」)

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟(無 hot reload);改 jedi-api-logjedi-log)套件本身需依 poetry path dependency 流程(見 CLAUDE.md「外部套件異動規範」)
  • 測試帳號:dev 環境系統管理層角色帳號(密碼見 .env / 部署文件)
  • 導航路徑:登入 → 系統設定選單「操作日誌」(user-log,path /log/user-log
  • 前置資料:無強制前置資料——只要系統有任何 API 呼叫紀錄(幾乎必然存在,因為全域 middleware 對所有請求都寫)即可看到資料
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);本頁相關 feature 檔以 user-log / 操作日誌 / api-log 搜尋
  • 安全測試tests/test_security.pyTestExportApiLogsSecurityVulnerability)已涵蓋匯出端點的 SEC-001 已知漏洞回歸測試
  • 相關文件:DB / API 全量見 GAI-SD-02/03;分區與 retention 設計見 scripts/sql/2026-06-03-log-tables-partitioning.sql