【Claude】日志审计与合规追踪配置 — 已解决

📅 2026/7/1 17:06:14
【Claude】日志审计与合规追踪配置 — 已解决
【Claude】日志审计与合规追踪配置 — 已解决适用版本Claude Code v1.0.x 及以上受影响场景企业审计、合规追踪、操作日志、安全监控、数据泄露防护阅读时长约 25 分钟目录问题现象原理深挖Claude Code 日志体系根因分析审计缺失的五大根因多方案解决从日志到合规验证回归审计配置验证避坑最佳实践附录审计配置速查表1. 问题现象1.1 典型问题表现问题一无法追踪 Claude Code 的操作历史# Claude Code 修改了文件但不知道什么时候改了什么 git diff # src/config.py 被修改了但不知道是 Claude Code 还是人工修改 # 没有操作日志问题二企业审计要求记录所有 AI 操作合规要求: - 记录所有 Claude Code 的文件读写操作 - 记录所有命令执行 - 记录所有 API 调用的 Token 消耗 - 日志保留 90 天 - 支持审计查询问题三无法检测敏感数据泄露# Claude Code 可能读取了 .env 文件或密钥 # 但没有日志记录哪些敏感文件被访问 # 无法在事后检测数据泄露问题四成本无法按项目/用户追踪# 多个项目使用同一个 API Key # 月底账单 $500但不知道哪个项目花了多少 # 缺少按项目/会话的成本追踪问题五CI 中的操作无审计# CI 环境中使用 Claude Code claude -p --dangerously-skip-permissions 修复 bug # 没有记录 Claude 做了什么操作 # 如果 Claude 修改了不该改的文件无法追踪2. 原理深挖Claude Code 日志体系2.1 日志层级┌─────────────────────────────────────────────────────┐ │ Claude Code 日志层级 │ ├─────────────────────────────────────────────────────┤ │ │ │ Layer 1: 会话日志 (内置) │ │ ├── ~/.claude/projects/project/sessions/ │ │ ├── JSONL 格式记录每轮对话 │ │ ├── 包含: 消息内容、工具调用、Token 消耗 │ │ └── 自动生成无需配置 │ │ │ │ Layer 2: Verbose 日志 (--verbose) │ │ ├── 实时输出到 stderr │ │ ├── 包含: API 请求/响应、工具调用详情 │ │ └── 用于调试 │ │ │ │ Layer 3: Hook 日志 (自定义) │ │ ├── PreToolUse / PostToolUse hooks │ │ ├── 记录每次工具调用的详细信息 │ │ └── 需要手动配置 │ │ │ │ Layer 4: API 使用日志 (Anthropic Console) │ │ ├── console.anthropic.com │ │ ├── 记录 API 调用次数、Token 消耗、成本 │ │ └── 按组织/API Key 维度统计 │ │ │ │ Layer 5: 系统级日志 (OS) │ │ ├── shell history (~/.zsh_history) │ │ ├── 文件系统审计 (auditd/FSEvents) │ │ └── 需要操作系统级配置 │ │ │ └─────────────────────────────────────────────────────┘2.2 会话日志格式// ~/.claude/projects/project-hash/sessions/session-id.jsonl {type:user,message:修复 auth.py 的 bug,timestamp:2025-01-15T10:00:00Z} {type:assistant,message:我来检查 auth.py...,timestamp:2025-01-15T10:00:02Z,model:claude-sonnet-4-20250514,usage:{input:1500,output:200}} {type:tool_use,tool:Read,input:{file_path:src/auth.py},timestamp:2025-01-15T10:00:03Z} {type:tool_result,tool:Read,output:file content,timestamp:2025-01-15T10:00:03Z} {type:tool_use,tool:Edit,input:{file_path:src/auth.py,old_str:...,new_str:...},timestamp:2025-01-15T10:00:10Z} {type:tool_result,tool:Edit,output:成功,timestamp:2025-01-15T10:00:10Z} {type:assistant,message:已修复 bug,timestamp:2025-01-15T10:00:15Z,usage:{input:2000,output:150}}2.3 Hook 审计机制Claude Code Hook 审计流程: Claude 要执行操作 (如 Edit) ↓ PreToolUse Hook 被触发 → 记录: 时间、工具名、输入参数 → 可以阻止操作 (返回 deny) ↓ 操作执行 ↓ PostToolUse Hook 被触发 → 记录: 执行结果、耗时、状态 ↓ 审计日志写入 配置位置: .claude/settings.json → hooks2.4 合规审计需求矩阵合规标准审计要求Claude Code 对应SOC 2操作日志、访问控制Hook 日志 权限配置GDPR数据访问记录、删除权文件读取日志HIPAAPHI 访问审计敏感文件访问 HookISO 27001安全事件记录安全操作日志企业内部成本追踪、操作审计Token 日志 Hook3. 根因分析审计缺失的五大根因3.1 根因一未配置 HookClaude Code 默认不记录操作日志需要通过 Hook 配置审计日志。3.2 根因二会话日志不集中会话日志分散在多个项目目录中难以集中查询和分析。3.3 根因三缺少成本追踪没有按项目、用户、会话维度追踪 Token 消耗和成本。3.4 根因四敏感文件访问无告警Claude Code 读取.env、密钥等文件时没有实时告警机制。3.5 根因五日志保留策略缺失会话日志和 Hook 日志没有自动清理或归档策略可能无限增长。4. 多方案解决从日志到合规4.1 方案一Hook 审计系统// .claude/settings.json — 审计 Hook 配置 { hooks: { PreToolUse: [ { matcher: *, hooks: [ { type: command, command: python3 .claude/hooks/audit-pre-tool.py } ] } ], PostToolUse: [ { matcher: *, hooks: [ { type: command, command: python3 .claude/hooks/audit-post-tool.py } ] } ], Stop: [ { matcher: *, hooks: [ { type: command, command: python3 .claude/hooks/audit-session-end.py } ] } ] } }#!/usr/bin/env python3 # .claude/hooks/audit-pre-tool.py — 工具调用前审计 import json import sys import os from datetime import datetime from pathlib import Path # 审计日志目录 AUDIT_DIR Path(os.environ.get(CLAUDE_AUDIT_DIR, .claude/audit)) AUDIT_DIR.mkdir(parentsTrue, exist_okTrue) # 敏感文件模式 SENSITIVE_PATTERNS [ .env, id_rsa, id_ed25519, credentials, secret, token, apikey, api_key, password, private_key, .pem, .key ] def is_sensitive(filepath): 检查是否是敏感文件 filepath_lower str(filepath).lower() for pattern in SENSITIVE_PATTERNS: if pattern in filepath_lower: return True return False def log_audit(event_type, tool_name, input_data, alertFalse): 写入审计日志 log_entry { timestamp: datetime.utcnow().isoformat() Z, event: event_type, tool: tool_name, input: input_data, project: os.getcwd(), session: os.environ.get(CLAUDE_SESSION_ID, unknown), user: os.environ.get(USER, unknown), alert: alert } # 按日期分文件 date_str datetime.utcnow().strftime(%Y-%m-%d) log_file AUDIT_DIR / faudit-{date_str}.jsonl with open(log_file, a) as f: f.write(json.dumps(log_entry, ensure_asciiFalse) \n) # 敏感操作实时告警 if alert: alert_file AUDIT_DIR / alerts.jsonl with open(alert_file, a) as f: f.write(json.dumps(log_entry, ensure_asciiFalse) \n) print(f⚠ AUDIT ALERT: 敏感文件访问 {input_data}, filesys.stderr) # 读取 Hook 输入 try: hook_input json.load(sys.stdin) tool_name hook_input.get(tool_name, unknown) tool_input hook_input.get(tool_input, {}) # 检查敏感文件 alert False if tool_name in [Read, Write, Edit]: filepath tool_input.get(file_path, ) if is_sensitive(filepath): alert True # Bash 命令审计 if tool_name Bash: command tool_input.get(command, ) # 检查危险命令 dangerous [rm -rf, sudo, curl.*|.*sh, wget.*|.*sh] import re for pattern in dangerous: if re.search(pattern, command): alert True break log_audit(pre_tool_use, tool_name, tool_input, alert) except Exception as e: # 审计日志不应阻止操作 print(fAudit error: {e}, filesys.stderr) # 不阻止操作 (exit 0) sys.exit(0)#!/usr/bin/env python3 # .claude/hooks/audit-post-tool.py — 工具调用后审计 import json import sys import os from datetime import datetime from pathlib import Path AUDIT_DIR Path(os.environ.get(CLAUDE_AUDIT_DIR, .claude/audit)) AUDIT_DIR.mkdir(parentsTrue, exist_okTrue) def log_audit(event_type, tool_name, input_data, output_data, durationNone): 写入工具调用后审计日志 log_entry { timestamp: datetime.utcnow().isoformat() Z, event: event_type, tool: tool_name, input: input_data, output_summary: str(output_data)[:500] if output_data else None, duration_ms: duration, project: os.getcwd(), session: os.environ.get(CLAUDE_SESSION_ID, unknown), status: success } date_str datetime.utcnow().strftime(%Y-%m-%d) log_file AUDIT_DIR / faudit-{date_str}.jsonl with open(log_file, a) as f: f.write(json.dumps(log_entry, ensure_asciiFalse) \n) try: hook_input json.load(sys.stdin) tool_name hook_input.get(tool_name, unknown) tool_input hook_input.get(tool_input, {}) tool_output hook_input.get(tool_output, {}) log_audit(post_tool_use, tool_name, tool_input, tool_output) except Exception as e: print(fAudit error: {e}, filesys.stderr) sys.exit(0)4.2 方案二成本追踪系统#!/usr/bin/env python3 # .claude/hooks/cost-tracker.py — Token 成本追踪 import json import sys import os from datetime import datetime from pathlib import Path COST_LOG_DIR Path(.claude/audit/costs) COST_LOG_DIR.mkdir(parentsTrue, exist_okTrue) # 模型定价 (每百万 Token) PRICING { claude-opus-4-20250514: {input: 15.0, output: 75.0, cache_read: 1.5, cache_write: 18.75}, claude-sonnet-4-20250514: {input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75}, claude-haiku-4-20250422: {input: 0.25, output: 1.25, cache_read: 0.025, cache_write: 0.3125}, } def calculate_cost(model, usage): 计算 API 调用成本 pricing PRICING.get(model, PRICING[claude-sonnet-4-20250514]) input_tokens usage.get(input_tokens, 0) output_tokens usage.get(output_tokens, 0) cache_read usage.get(cache_read_input_tokens, 0) cache_write usage.get(cache_creation_input_tokens, 0) cost ( input_tokens * pricing[input] / 1_000_000 output_tokens * pricing[output] / 1_000_000 cache_read * pricing[cache_read] / 1_000_000 cache_write * pricing[cache_write] / 1_000_000 ) return round(cost, 6) def log_cost(model, usage): 记录成本 cost calculate_cost(model, usage) entry { timestamp: datetime.utcnow().isoformat() Z, model: model, input_tokens: usage.get(input_tokens, 0), output_tokens: usage.get(output_tokens, 0), cache_read_tokens: usage.get(cache_read_input_tokens, 0), cache_write_tokens: usage.get(cache_creation_input_tokens, 0), cost_usd: cost, project: os.path.basename(os.getcwd()), ![配图](https://i-blog.csdnimg.cn/img_convert/3b093a3e2ba4b771c9fbeccb12bcbfca.png) session: os.environ.get(CLAUDE_SESSION_ID, unknown) } date_str datetime.utcnow().strftime(%Y-%m-%d) log_file COST_LOG_DIR / fcost-{date_str}.jsonl with open(log_file, a) as f: f.write(json.dumps(entry) \n) # Hook 入口 try: hook_input json.load(sys.stdin) # 从 Hook 输入中提取使用信息 # PostToolUse 或 Stop hook 中可能包含 usage if usage in hook_input: model hook_input.get(model, claude-sonnet-4-20250514) log_cost(model, hook_input[usage]) except: pass sys.exit(0)4.3 方案三审计日志分析工具#!/usr/bin/env python3 审计日志分析工具 查询和分析 Claude Code 操作日志 import json import os import sys from pathlib import Path from datetime import datetime, timedelta from collections import defaultdict AUDIT_DIR Path(.claude/audit) def load_audit_logs(days7): 加载最近 N 天的审计日志 logs [] for i in range(days): date (datetime.utcnow() - timedelta(daysi)).strftime(%Y-%m-%d) log_file AUDIT_DIR / faudit-{date}.jsonl if log_file.exists(): with open(log_file) as f: for line in f: try: logs.append(json.loads(line)) except json.JSONDecodeError: continue return logs def analyze_operations(logs): 分析操作统计 tool_counts defaultdict(int) file_access defaultdict(int) alerts [] for log in logs: tool log.get(tool, unknown) tool_counts[tool] 1 if tool in [Read, Write, Edit]: filepath log.get(input, {}).get(file_path, unknown) file_access[filepath] 1 if log.get(alert): alerts.append(log) return tool_counts, file_access, alerts def generate_report(days7): 生成审计报告 logs load_audit_logs(days) if not logs: print(无审计日志) return tool_counts, file_access, alerts analyze_operations(logs) print(f Claude Code 审计报告 ({days} 天) ) print(f日志条目: {len(logs)}) print(f时间范围: {logs[0][timestamp][:10]} ~ {logs[-1][timestamp][:10]}) print(f\n--- 工具调用统计 ---) for tool, count in sorted(tool_counts.items(), keylambda x: -x[1]): print(f {tool}: {count} 次) print(f\n--- 文件访问 Top 10 ---) for filepath, count in sorted(file_access.items(), keylambda x: -x[1])[:10]: print(f {count}x {filepath}) print(f\n--- 敏感操作告警 ({len(alerts)}) ---) for alert in alerts[-10:]: # 最近 10 条 print(f [{alert[timestamp]}] {alert[tool]}: {alert.get(input, {})}) # 成本分析 cost_logs load_cost_logs(days) if cost_logs: total_cost sum(l.get(cost_usd, 0) for l in cost_logs) total_input sum(l.get(input_tokens, 0) for l in cost_logs) total_output sum(l.get(output_tokens, 0) for l in cost_logs) print(f\n--- 成本统计 ---) print(f 总成本: ${total_cost:.4f}) print(f 输入 Token: {total_input:,}) print(f 输出 Token: {total_output:,}) print(f API 调用: {len(cost_logs)} 次) def load_cost_logs(days7): 加载成本日志 logs [] cost_dir AUDIT_DIR / costs for i in range(days): date (datetime.utcnow() - timedelta(daysi)).strftime(%Y-%m-%d) log_file cost_dir / fcost-{date}.jsonl if log_file.exists(): with open(log_file) as f: for line in f: try: logs.append(json.loads(line)) except: continue return logs def query_sensitive_access(): 查询敏感文件访问记录 logs load_audit_logs(30) sensitive [l for l in logs if l.get(alert)] print(f\n 敏感文件访问记录 (30 天) ) print(f总计: {len(sensitive)} 次) for log in sensitive: timestamp log.get(timestamp, ) tool log.get(tool, ) user log.get(user, ) input_data log.get(input, {}) if tool in [Read, Write, Edit]: filepath input_data.get(file_path, ) print(f [{timestamp}] {user} {tool} {filepath}) elif tool Bash: command input_data.get(command, ) print(f [{timestamp}] {user} Bash: {command[:100]}) # 使用 if __name__ __main__: if len(sys.argv) 1 and sys.argv[1] sensitive: query_sensitive_access() else: generate_report(daysint(sys.argv[1]) if len(sys.argv) 1 and sys.argv[1].isdigit() else 7)4.4 方案四日志集中化#!/usr/bin/env python3 日志集中化将分散的会话日志和审计日志汇总 import json import os import shutil from pathlib import Path from datetime import datetime class LogCentralizer: 日志集中管理器 def __init__(self, central_dirNone): self.central_dir Path(central_dir or os.environ.get( CLAUDE_LOG_CENTER, os.path.expanduser(~/.claude/audit-central) )) self.central_dir.mkdir(parentsTrue, exist_okTrue) def collect_session_logs(self): 收集所有项目的会话日志 projects_dir Path.home() / .claude / projects if not projects_dir.exists(): return collected 0 for project_dir in projects_dir.iterdir(): if not project_dir.is_dir(): continue project_name project_dir.name sessions_dir project_dir / sessions if not sessions_dir.exists(): continue # 目标目录 dest_dir self.central_dir / sessions / project_name dest_dir.mkdir(parentsTrue, exist_okTrue) # 复制会话日志 for session_file in sessions_dir.glob(*.jsonl): dest_file dest_dir / session_file.name # 不覆盖已收集的 if not dest_file.exists(): shutil.copy2(session_file, dest_file) collected 1 print(f✓ 收集了 {collected} 个会话日志) def collect_audit_logs(self): 收集项目级审计日志 # 遍历所有项目目录 for audit_dir in Path(.).glob(*/.claude/audit): project_name audit_dir.parent.parent.name dest_dir self.central_dir / audit / project_name dest_dir.mkdir(parentsTrue, exist_okTrue) for log_file in audit_dir.glob(*.jsonl): dest_file dest_dir / log_file.name if not dest_file.exists(): shutil.copy2(log_file, dest_file) def cleanup_old_logs(self, retention_days90): 清理过期日志 cutoff datetime.utcnow().timestamp() - (retention_days * 86400) removed 0 for log_file in self.central_dir.rglob(*.jsonl): if log_file.stat().st_mtime cutoff: log_file.unlink() removed 1 print(f✓ 清理了 {removed} 个过期日志 ({retention_days} 天)) # 使用 centralizer LogCentralizer() centralizer.collect_session_logs() centralizer.collect_audit_logs() centralizer.cleanup_old_logs(retention_days90)4.5 方案五实时告警系统#!/usr/bin/env python3 # .claude/hooks/alert-system.py — 实时安全告警 import json import sys import os import smtplib from email.mime.text import MIMEText from datetime import datetime # 告警规则 ALERT_RULES { # 敏感文件读取 sensitive_read: { patterns: [.env, id_rsa, credentials, secret, apikey], severity: HIGH, message: 敏感文件被读取 }, # 危险命令 dangerous_command: { patterns: [rm -rf, sudo , chmod 777, curl.*|.*sh], severity: CRITICAL, message: 执行危险命令 }, # 大量文件操作 mass_operation: { threshold: 50, # 单次会话操作超过 50 次 severity: MEDIUM, message: 大量文件操作 }, # 网络请求 network_access: { patterns: [curl, wget, WebFetch], severity: MEDIUM, message: 网络访问 } } def send_alert(severity, message, details): 发送告警 timestamp datetime.utcnow().isoformat() # 控制台输出 print(f[{severity}] {timestamp} - {message}, filesys.stderr) print(f 详情: {json.dumps(details, ensure_asciiFalse)[:200]}, filesys.stderr) # 写入告警文件 alert_file Path(.claude/audit/alerts-realtime.jsonl) alert_file.parent.mkdir(parentsTrue, exist_okTrue) with open(alert_file, a) as f: f.write(json.dumps({ timestamp: timestamp, severity: severity, message: message, details: details }, ensure_asciiFalse) \n) # 高严重度发邮件 (可选) if severity CRITICAL and os.environ.get(ALERT_EMAIL): try: send_email(severity, message, details) except: pass # 告警不应阻止操作 def send_email(severity, message, details): 发送邮件告警 smtp_host os.environ.get(SMTP_HOST, localhost) smtp_port int(os.environ.get(SMTP_PORT, 25)) from_addr os.environ.get(ALERT_FROM, claude-auditcompany.com) to_addr os.environ.get(ALERT_EMAIL) subject f[Claude Code {severity}] {message} body f 时间: {datetime.utcnow().isoformat()} 严重度: {severity} 消息: {message} 详情: {json.dumps(details, ensure_asciiFalse, indent2)} 项目: {os.getcwd()} 用户: {os.environ.get(USER, unknown)} msg MIMEText(body) msg[Subject] subject msg[From] from_addr msg[To] to_addr with smtplib.SMTP(smtp_host, smtp_port) as server: server.sendmail(from_addr, [to_addr], msg.as_string()) # Hook 入口 try: hook_input json.load(sys.stdin) tool_name hook_input.get(tool_name, ) tool_input hook_input.get(tool_input, {}) # 检查敏感文件 if tool_name in [Read, Write, Edit]: filepath str(tool_input.get(file_path, )).lower() for pattern in ALERT_RULES[sensitive_read][patterns]: if pattern in filepath: send_alert( ALERT_RULES[sensitive_read][severity], ALERT_RULES[sensitive_read][message], {tool: tool_name, file: tool_input.get(file_path)} ) break # 检查危险命令 if tool_name Bash: command tool_input.get(command, ) import re for pattern in ALERT_RULES[dangerous_command][patterns]: if re.search(pattern, command): send_alert( ALERT_RULES[dangerous_command][severity], ALERT_RULES[dangerous_command][message], {command: command} ) break # 检查网络访问 for pattern in ALERT_RULES[network_access][patterns]: if pattern in command: send_alert( ALERT_RULES[network_access][severity], ALERT_RULES[network_access][message], {command: command} ) break except Exception as e: print(fAlert error: {e}, filesys.stderr) sys.exit(0)4.6 方案六日志保留策略#!/bin/bash # log-retention.sh — 日志保留和归档策略 # 配置 RETENTION_DAYS${CLAUDE_LOG_RETENTION:-90} ARCHIVE_DIR${CLAUDE_LOG_ARCHIVE:-.claude/audit/archive} AUDIT_DIR.claude/audit echo 日志保留策略 echo 保留: ${RETENTION_DAYS} 天 echo 归档目录: ${ARCHIVE_DIR} # 创建归档目录 mkdir -p $ARCHIVE_DIR # 归档超过保留期的日志 CURRENT_DATE$(date %Y%m%d) CUTOFF_DATE$(date -v-${RETENTION_DAYS}d %Y-%m-%d 2/dev/null || date -d -${RETENTION_DAYS} days %Y-%m-%d) echo 归档 ${CUTOFF_DATE} 之前的日志... ARCHIVED0 for log_file in $AUDIT_DIR/audit-*.jsonl; do [ -f $log_file ] || continue # 从文件名提取日期 FILE_DATE$(basename $log_file | sed s/audit-\(.*\)\.jsonl/\1/) if [[ $FILE_DATE $CUTOFF_DATE ]]; then # 压缩并移到归档 gzip $log_file mv ${log_file}.gz $ARCHIVE_DIR/ ARCHIVED$((ARCHIVED 1)) fi done echo ✓ 归档了 ${ARCHIVED} 个日志文件 # 清理超过 1 年的归档 echo echo 清理超过 1 年的归档... YEAR_AGO$(date -v-365d %Y-%m-%d 2/dev/null || date -d -365 days %Y-%m-%d) PURGED0 for archive_file in $ARCHIVE_DIR/*.gz; do [ -f $archive_file ] || continue FILE_DATE$(basename $archive_file | sed s/audit-\(.*\)\.jsonl\.gz/\1/) if [[ $FILE_DATE $YEAR_AGO ]]; then rm $archive_file PURGED$((PURGED 1)) fi done echo ✓ 清理了 ${PURGED} 个过期归档 # 日志大小报告 echo echo 日志大小 du -sh $AUDIT_DIR 2/dev/null du -sh $ARCHIVE_DIR 2/dev/null echo echo 文件数量: find $AUDIT_DIR -name *.jsonl 2/dev/null | wc -l | xargs echo 活跃日志: find $ARCHIVE_DIR -name *.gz 2/dev/null | wc -l | xargs echo 归档日志:5. 验证回归审计配置验证5.1 审计验证脚本#!/bin/bash # verify-audit.sh — 验证审计配置 echo 审计配置验证 # 1. 检查 Hook 配置 echo Hook 配置: if [ -f .claude/settings.json ]; then python3 -c import json with open(.claude/settings.json) as f: data json.load(f) hooks data.get(hooks, {}) for event, hook_list in hooks.items(): for h in hook_list: for hook in h.get(hooks, []): print(f ✓ {event}: {hook.get(\command\, \\)}) if not hooks: print( ✗ 未配置审计 Hook) 2/dev/null fi # 2. 检查审计日志目录 echo echo 审计日志: if [ -d .claude/audit ]; then LOG_COUNT$(find .claude/audit -name *.jsonl | wc -l) LOG_SIZE$(du -sh .claude/audit 2/dev/null | cut -f1) echo ✓ 目录存在: $LOG_COUNT 个日志, $LOG_SIZE else echo ✗ 审计目录不存在 fi # 3. 检查告警文件 echo echo 告警记录: if [ -f .claude/audit/alerts.jsonl ]; then ALERT_COUNT$(wc -l .claude/audit/alerts.jsonl) echo ✓ $ALERT_COUNT 条告警 else echo - 无告警记录 fi # 4. 检查成本日志 echo echo 成本日志: if [ -d .claude/audit/costs ]; then COST_FILES$(find .claude/audit/costs -name *.jsonl | wc -l) echo ✓ $COST_FILES 个成本日志文件 else echo ✗ 无成本日志 fi echo echo 验证完成 5.2 验证清单#验证项预期方法1Hook 配置PreToolUse/PostToolUsesettings.json 检查2审计日志生成有日志文件执行操作后检查3敏感文件告警触发告警读取 .env 测试4成本追踪有成本记录检查 costs/ 目录5日志格式合法 JSONLjson.load 验证6日志保留自动归档retention 脚本7集中化日志汇总centralizer 工具8查询功能可查询audit-analyzer6. 避坑最佳实践6.1 审计配置原则原则 1: Hook 审计 — 用 PreToolUse/PostToolUse 记录所有操作 原则 2: 敏感检测 — 自动检测 .env/密钥/危险命令 原则 3: 成本追踪 — 按项目/会话记录 Token 消耗 原则 4: 集中管理 — 日志汇总到统一目录 原则 5: 保留策略 — 90 天活跃 1 年归档 原则 6: 实时告警 — 高危操作即时通知 原则 7: 不阻塞 — 审计日志不应阻止操作 原则 8: 定期审查 — 定期分析审计报告6.2 常见陷阱#陷阱后果解决1无 Hook无操作日志配置审计 Hook2日志不集中难以查询用 centralizer3无成本追踪不知花费cost-tracker Hook4无敏感检测数据泄露敏感文件模式匹配5日志无限增长磁盘满保留策略6审计阻塞操作Claude 卡住Hook exit(0)7无告警不知风险实时告警系统8CI 无审计操作不可追CI 中也配 Hook7. 附录审计配置速查表7.1 Hook 事件事件触发时机用途PreToolUse工具调用前记录操作、安全检查PostToolUse工具调用后记录结果、成本统计Stop会话结束会话总结、成本汇总Notification通知事件实时告警7.2 审计日志字段字段说明示例timestamp时间戳2025-01-15T10:00:00Zevent事件类型pre_tool_usetool工具名Read/Write/Edit/Bashinput输入参数{file_path: src/app.py}project项目路径/home/user/myprojectsession会话 IDabc-123-defuser用户zhuboalert是否告警true/false7.3 保留策略推荐日志类型活跃保留归档保留格式操作日志90 天1 年JSONL成本日志90 天2 年JSONL告警日志90 天2 年JSONL会话日志30 天6 月JSONL结语日志审计与合规追踪是企业级 Claude Code 使用的必备配置。通过 Hook 审计系统、成本追踪、敏感文件检测、实时告警、日志集中化和保留策略可以满足 SOC 2、GDPR、企业内部等合规审计要求。核心要点回顾Hook 审计用 PreToolUse/PostToolUse Hook 记录所有工具调用敏感检测自动检测.env、密钥、危险命令的访问成本追踪按项目、会话、模型维度记录 Token 消耗和成本集中管理用 LogCentralizer 汇总分散的日志实时告警高危操作即时告警邮件/控制台保留策略90 天活跃 1 年归档 自动清理不阻塞审计 Hook 始终 exit(0)不影响 Claude 操作定期审查用审计分析工具生成定期报告