25c - 日志管理与审计
本文是《AI Agent 实战手册》第 25 章第 3 节。 上一节:25b-监控与告警 | 下一节:25d-更新与回滚策略
概述
OpenClaw 作为 24/7 自主运行的 AI Agent 平台,日志管理和操作审计是生产运维的核心支柱。与传统 Web 应用不同,AI Agent 系统需要记录的不仅是基础设施日志(容器启动、HTTP 请求),还包括 Agent 特有的操作轨迹——每一次 LLM 调用、工具执行、决策推理和用户会话都需要可追溯的审计记录。2026 年,Grafana Loki 已成为轻量级日志聚合的首选方案(仅索引标签、存储成本低),而 ELK Stack 则在需要全文搜索和复杂分析的场景中保持优势。
本节提供从零搭建 OpenClaw 日志管理体系的完整指南,涵盖 Loki 栈部署、结构化日志设计、Agent 操作审计方案和合规日志策略。
1. 日志管理架构总览
日志体系分层
OpenClaw 的日志管理需要覆盖两大维度:基础设施日志和 AI Agent 审计日志。
┌─────────────────────────────────────────────────────────┐
│ 日志消费层 │
│ Grafana 日志面板 / Kibana / 告警规则 / 合规报告 │
├─────────────────────────────────────────────────────────┤
│ 日志存储层 │
│ Grafana Loki(标签索引) 或 Elasticsearch(全文索引) │
├─────────────────────────────────────────────────────────┤
│ 日志采集层 │
│ Promtail / Alloy 或 Logstash / Filebeat │
├─────────────────────────────────────────────────────────┤
│ 日志生成层 │
│ Docker 容器日志 应用结构化日志 Agent 审计日志 │
│ (stdout/stderr) (JSON 格式) (操作轨迹记录) │
├─────────────────────────────────────────────────────────┤
│ 日志来源 │
│ OpenClaw Gateway Agent Workers LLM API 调用 │
│ 工具执行器 用户会话 系统事件 │
└─────────────────────────────────────────────────────────┘工具推荐
| 工具 | 用途 | 价格 | 适用场景 |
|---|---|---|---|
| Grafana Loki | 轻量级日志聚合(仅索引标签) | 免费(开源)/ Cloud 免费层 50 GB/月 | 中小规模、已有 Grafana 栈 |
| Promtail | Loki 日志采集 Agent | 免费(开源) | Docker/K8s 日志采集 |
| Grafana Alloy | 统一遥测采集器(替代 Promtail) | 免费(开源) | 同时采集日志+指标+Trace |
| Elasticsearch | 全文搜索日志引擎 | 免费(开源)/ Cloud 起步 $95/月 | 复杂查询、安全取证、大规模分析 |
| Logstash | 日志处理管道 | 免费(开源) | 日志解析、转换、路由 |
| Kibana | Elasticsearch 可视化 | 免费(开源) | ELK 栈日志可视化 |
| Filebeat | 轻量级日志采集 | 免费(开源) | ELK 栈日志采集 |
| Fluentd / Fluent Bit | 通用日志采集与路由 | 免费(开源) | 多目标日志转发 |
| Vector | 高性能日志管道(Rust 编写) | 免费(开源) | 高吞吐日志处理 |
| SigNoz | 开源全栈可观测性 | 免费(开源)/ Cloud $199/月起 | 日志+指标+Trace 一体化 |
| Betterstack Logs | 托管日志平台 | 免费层 1 GB/月 / $25/月起 | 零运维日志管理 |
💡 推荐方案:对于 OpenClaw 单节点部署,Loki + Promtail + Grafana(PLG 栈)是最佳起点——资源占用低、与 25b 监控栈 无缝集成。仅当需要全文搜索或复杂日志分析时才考虑 ELK。
Loki vs ELK 对比
| 维度 | Grafana Loki | ELK Stack |
|---|---|---|
| 索引策略 | 仅索引标签(label),日志正文压缩存储 | 全文倒排索引,所有字段可搜索 |
| 存储成本 | 极低(压缩比高,无全文索引开销) | 较高(索引占用大量磁盘) |
| 内存需求 | 低(单节点 512 MB 起) | 高(Elasticsearch 建议 4 GB+) |
| 查询语言 | LogQL(类 PromQL 语法) | KQL / Lucene 查询语法 |
| 查询速度 | 标签过滤快,正文搜索需扫描 | 全文搜索极快(已建索引) |
| 学习曲线 | 低(熟悉 Prometheus 即可上手) | 中等(需理解 mapping、分片等概念) |
| 运维复杂度 | 低(单二进制文件,配置简单) | 高(需管理 ES 集群、分片、副本) |
| 可视化 | Grafana(与指标统一面板) | Kibana(独立 UI) |
| 适用规模 | 中小规模(TB 级) | 大规模(PB 级) |
| 最佳场景 | 运维日志、容器日志、Agent 操作日志 | 安全审计取证、合规全文搜索、业务分析 |
💡 选择建议:90% 的 OpenClaw 部署场景选 Loki 即可。如果你有合规要求需要对日志内容做全文搜索(如安全事件调查),可以 Loki + Elasticsearch 双写——Loki 做日常运维,ES 做合规审计。
2. Grafana Loki 栈部署
Docker Compose 配置
在 25b-监控与告警 的监控栈基础上,添加日志组件:
# docker-compose.logging.yml — 日志栈
# 与主服务和监控栈配合使用:
# docker compose -f docker-compose.yml \
# -f docker-compose.monitoring.yml \
# -f docker-compose.logging.yml up -d
version: '3.8'
services:
# ============================================
# Loki — 日志存储与查询引擎
# ============================================
loki:
image: grafana/loki:3.4.2
container_name: openclaw-loki
restart: unless-stopped
user: "10001:10001"
ports:
- "127.0.0.1:3100:3100"
volumes:
- ./logging/loki/loki-config.yml:/etc/loki/local-config.yaml:ro
- loki_data:/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- openclaw-net
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3100/ready || exit 1"]
interval: 15s
timeout: 5s
retries: 3
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# ============================================
# Promtail — 日志采集 Agent
# ============================================
promtail:
image: grafana/promtail:3.4.2
container_name: openclaw-promtail
restart: unless-stopped
volumes:
- ./logging/promtail/promtail-config.yml:/etc/promtail/config.yml:ro
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
command: -config.file=/etc/promtail/config.yml
networks:
- openclaw-net
depends_on:
loki:
condition: service_healthy
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
loki_data:Loki 配置文件
# logging/loki/loki-config.yml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: warn
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: "2024-01-01"
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
filesystem:
directory: /loki/chunks
limits_config:
retention_period: 30d # 日志保留 30 天
max_query_length: 721h # 最大查询范围 30 天
max_query_parallelism: 4
max_entries_limit_per_query: 10000
ingestion_rate_mb: 10 # 每租户摄入速率限制
ingestion_burst_size_mb: 20
per_stream_rate_limit: 5MB # 每流速率限制
per_stream_rate_limit_burst: 15MB
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true # 启用自动清理过期日志
retention_delete_delay: 2h
retention_delete_worker_count: 150
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
analytics:
reporting_enabled: falsePromtail 配置文件
# logging/promtail/promtail-config.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
tenant_id: openclaw
batchwait: 1s
batchsize: 1048576 # 1 MB
scrape_configs:
# ============================================
# Docker 容器日志采集
# ============================================
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
filters:
- name: label
values: ["logging=enabled"]
relabel_configs:
# 提取容器名作为标签
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: 'container'
# 提取 compose 服务名
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: 'service'
# 提取 compose 项目名
- source_labels: ['__meta_docker_container_label_com_docker_compose_project']
target_label: 'project'
# 提取日志流(stdout/stderr)
- source_labels: ['__meta_docker_container_log_stream']
target_label: 'stream'
pipeline_stages:
# 尝试解析 JSON 格式日志
- json:
expressions:
level: level
msg: msg
timestamp: timestamp
component: component
agent_id: agent_id
session_id: session_id
task_id: task_id
# 设置日志级别标签
- labels:
level:
component:
# 设置时间戳
- timestamp:
source: timestamp
format: RFC3339Nano
fallback_formats:
- "2006-01-02T15:04:05Z07:00"
- UnixMs
# 过滤健康检查噪音日志
- match:
selector: '{service="openclaw-gateway"}'
stages:
- regex:
expression: '.*GET /health.*'
- drop:
expression: '.*GET /health.*'
drop_counter_reason: health_check_noise
# ============================================
# OpenClaw 审计日志文件采集
# ============================================
- job_name: openclaw-audit
static_configs:
- targets:
- localhost
labels:
job: openclaw-audit
log_type: audit
__path__: /var/log/openclaw/audit/*.jsonl
pipeline_stages:
- json:
expressions:
level: level
event_type: event_type
agent_id: agent_id
user_id: user_id
action: action
timestamp: timestamp
- labels:
event_type:
agent_id:
action:
- timestamp:
source: timestamp
format: RFC3339Nano
# ============================================
# 系统日志采集
# ============================================
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: syslog
__path__: /var/log/syslog
pipeline_stages:
- regex:
expression: '^(?P<timestamp>\w+\s+\d+\s+\d+:\d+:\d+)\s+(?P<hostname>\S+)\s+(?P<app>\S+?)(\[(?P<pid>\d+)\])?:\s+(?P<message>.*)$'
- labels:
app:Grafana 数据源配置(添加 Loki)
在 25b-监控与告警 的 Grafana provisioning 基础上,添加 Loki 数据源:
# monitoring/grafana/provisioning/datasources/loki.yml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: false
editable: false
jsonData:
maxLines: 1000
derivedFields:
# 从日志中提取 trace_id,关联到 Tempo(如果部署了)
- datasourceUid: tempo
matcherRegex: '"trace_id":"(\w+)"'
name: TraceID
url: '$${__value.raw}'目录结构
openclaw-deploy/
├── docker-compose.yml # 主服务
├── docker-compose.monitoring.yml # 监控栈(25b)
├── docker-compose.logging.yml # 日志栈(本节)
├── logging/
│ ├── loki/
│ │ └── loki-config.yml # Loki 主配置
│ └── promtail/
│ └── promtail-config.yml # Promtail 采集配置
├── monitoring/
│ └── grafana/
│ └── provisioning/
│ └── datasources/
│ ├── prometheus.yml # Prometheus 数据源
│ └── loki.yml # Loki 数据源(新增)
└── .env启动日志栈
# 创建目录
mkdir -p logging/{loki,promtail}
# 为 Docker 容器添加日志采集标签
# 在 docker-compose.yml 中为需要采集日志的服务添加:
# labels:
# logging: "enabled"
# 启动全栈
docker compose -f docker-compose.yml \
-f docker-compose.monitoring.yml \
-f docker-compose.logging.yml up -d
# 验证 Loki 就绪
curl -s http://127.0.0.1:3100/ready
# 预期输出: ready
# 验证 Promtail 目标
curl -s http://127.0.0.1:9080/targets | head -20
# 在 Grafana 中验证(http://127.0.0.1:3000)
# 进入 Explore → 选择 Loki 数据源 → 输入 {service="openclaw-gateway"}3. 结构化日志设计
为什么需要结构化日志
传统的纯文本日志(如 [INFO] User logged in)在 AI Agent 场景下严重不足——你无法高效地按 Agent ID、会话 ID、工具名称等维度过滤和聚合。结构化日志(JSON 格式)让每条日志都成为可查询的数据记录。
OpenClaw 结构化日志规范
所有 OpenClaw 组件应输出统一格式的 JSON 日志:
{
"timestamp": "2026-01-15T08:30:45.123Z",
"level": "info",
"component": "agent-worker",
"msg": "Tool execution completed",
"agent_id": "agent-research-01",
"session_id": "sess_abc123def456",
"task_id": "task_789",
"trace_id": "trace_xyz",
"tool_name": "web_search",
"tool_input_hash": "sha256:a1b2c3...",
"tool_duration_ms": 1250,
"tool_status": "success",
"token_count": 0,
"cost_usd": 0.0,
"metadata": {
"search_query": "OpenClaw production deployment",
"results_count": 5
}
}日志字段规范
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
timestamp | string (ISO 8601) | ✅ | 事件发生时间,UTC 时区 |
level | string | ✅ | 日志级别:debug/info/warn/error/fatal |
component | string | ✅ | 组件名:gateway/agent-worker/scheduler/tool-executor |
msg | string | ✅ | 人类可读的事件描述 |
agent_id | string | ⚠️ | Agent 标识(Agent 相关日志必填) |
session_id | string | ⚠️ | 会话标识(会话相关日志必填) |
task_id | string | ⚠️ | 任务标识(任务相关日志必填) |
trace_id | string | 推荐 | 分布式追踪 ID,用于关联跨组件日志 |
tool_name | string | ⚠️ | 工具名称(工具调用日志必填) |
tool_duration_ms | number | ⚠️ | 工具执行耗时(毫秒) |
tool_status | string | ⚠️ | 工具执行结果:success/failure/timeout |
model_provider | string | ⚠️ | LLM 提供商(LLM 调用日志必填) |
model_name | string | ⚠️ | 模型名称 |
token_count | number | ⚠️ | Token 消耗量 |
cost_usd | number | ⚠️ | 估算成本(美元) |
error_code | string | ⚠️ | 错误码(错误日志必填) |
error_message | string | ⚠️ | 错误详情 |
metadata | object | 可选 | 额外上下文信息 |
日志级别使用规范
| 级别 | 使用场景 | 示例 |
|---|---|---|
| debug | 开发调试信息,生产环境通常关闭 | LLM prompt 完整内容、工具输入输出详情 |
| info | 正常业务事件 | Agent 启动、任务开始/完成、工具调用成功 |
| warn | 异常但可恢复的情况 | API 限流重试、工具超时重试、Token 接近预算 |
| error | 错误但不影响系统整体运行 | 单次工具调用失败、LLM API 返回错误 |
| fatal | 系统级严重错误,需要立即处理 | Gateway 启动失败、数据库连接断开 |
4. LogQL 日志查询实战
LogQL 基础语法
LogQL 是 Loki 的查询语言,语法灵感来自 PromQL。核心结构:
{标签选择器} |= "文本过滤" | json | line_format "格式化"常用查询示例
基础查询——按服务过滤
# 查看 OpenClaw Gateway 所有日志
{service="openclaw-gateway"}
# 查看所有错误日志
{service="openclaw-gateway"} |= "error"
# 查看特定 Agent 的日志
{service="openclaw-gateway"} | json | agent_id="agent-research-01"
# 排除健康检查日志
{service="openclaw-gateway"} != "/health" != "/ready"Agent 操作查询
# 查看特定会话的所有操作
{job="openclaw-audit"} | json | session_id="sess_abc123def456"
# 查看所有工具调用失败
{service="openclaw-gateway"} | json | tool_status="failure"
# 查看特定 Agent 的 LLM 调用
{service="openclaw-gateway"}
| json
| agent_id="agent-research-01"
| component="llm-client"
# 查看执行时间超过 30 秒的工具调用
{service="openclaw-gateway"}
| json
| tool_duration_ms > 30000聚合与统计查询
# 过去 1 小时各 Agent 的错误数量
sum by (agent_id) (
count_over_time(
{service="openclaw-gateway"} | json | level="error" [1h]
)
)
# 过去 24 小时各工具的调用次数
sum by (tool_name) (
count_over_time(
{job="openclaw-audit"} | json | event_type="tool_call" [24h]
)
)
# 每 5 分钟的日志量趋势
sum(rate({service="openclaw-gateway"}[5m])) by (level)
# 过去 1 小时 P95 工具执行延迟
quantile_over_time(0.95,
{service="openclaw-gateway"}
| json
| unwrap tool_duration_ms [1h]
) by (tool_name)
# 每小时 Token 消耗趋势
sum(sum_over_time(
{job="openclaw-audit"}
| json
| event_type="llm_call"
| unwrap token_count [1h]
)) by (model_name)告警规则查询
# 5 分钟内错误率超过阈值(用于 Grafana 告警)
sum(rate({service="openclaw-gateway"} | json | level="error" [5m]))
/
sum(rate({service="openclaw-gateway"} [5m]))
> 0.1
# 检测 Agent 死循环(同一 Agent 短时间内大量日志)
sum by (agent_id) (
count_over_time(
{service="openclaw-gateway"} | json [5m]
)
) > 1000提示词模板:生成 LogQL 查询
你是一个 Grafana Loki 专家。请根据以下需求生成 LogQL 查询。
日志格式:JSON 结构化日志
可用标签:service, level, component, agent_id, session_id
可用 JSON 字段:msg, tool_name, tool_status, tool_duration_ms,
model_provider, model_name, token_count, cost_usd, error_code
需求:[描述你想查询的内容]
请生成:
1. LogQL 查询语句
2. 查询说明(中文)
3. 如果是聚合查询,建议的 Grafana 面板类型(时序图/柱状图/表格/统计值)5. Agent 操作审计系统
审计日志 vs 运维日志
| 维度 | 运维日志 | 审计日志 |
|---|---|---|
| 目的 | 排障、性能分析 | 合规、安全追溯、责任归属 |
| 内容 | 系统事件、错误信息 | 谁在什么时间做了什么操作 |
| 保留期 | 7-30 天 | 90 天-7 年(取决于合规要求) |
| 可变性 | 可清理、可压缩 | 不可篡改、不可删除 |
| 访问控制 | 运维团队 | 审计员、合规官、安全团队 |
| 存储方式 | Loki/ES(可覆盖) | 独立存储、写入后不可修改 |
审计事件分类
OpenClaw 的审计日志需要覆盖以下事件类型:
| 事件类别 | 事件类型 | 说明 | 审计重要性 |
|---|---|---|---|
| 用户操作 | user.login | 用户登录 | 🔴 高 |
user.logout | 用户登出 | 🟡 中 | |
user.config_change | 用户修改配置 | 🔴 高 | |
user.agent_create | 创建新 Agent | 🔴 高 | |
user.agent_delete | 删除 Agent | 🔴 高 | |
| Agent 生命周期 | agent.start | Agent 启动 | 🟡 中 |
agent.stop | Agent 停止 | 🟡 中 | |
agent.error | Agent 运行错误 | 🔴 高 | |
agent.session_start | 会话开始 | 🟡 中 | |
agent.session_end | 会话结束 | 🟡 中 | |
| LLM 交互 | llm.request | LLM API 调用 | 🟡 中 |
llm.response | LLM 响应接收 | 🟡 中 | |
llm.error | LLM 调用失败 | 🔴 高 | |
llm.token_usage | Token 消耗记录 | 🟢 低 | |
| 工具调用 | tool.invoke | 工具调用发起 | 🔴 高 |
tool.result | 工具返回结果 | 🟡 中 | |
tool.error | 工具调用失败 | 🔴 高 | |
tool.permission_denied | 工具权限拒绝 | 🔴 高 | |
| 数据访问 | data.read | 读取外部数据 | 🟡 中 |
data.write | 写入外部数据 | 🔴 高 | |
data.delete | 删除外部数据 | 🔴 高 | |
| 系统事件 | system.startup | 系统启动 | 🟡 中 |
system.shutdown | 系统关闭 | 🟡 中 | |
system.config_reload | 配置热重载 | 🔴 高 | |
system.backup | 备份执行 | 🟡 中 |
审计日志 Schema 设计
{
"$schema": "openclaw-audit-log-v1",
"timestamp": "2026-01-15T08:30:45.123456Z",
"event_id": "evt_01JQXYZ123456789",
"event_type": "tool.invoke",
"event_category": "tool_call",
"severity": "info",
"actor": {
"type": "agent",
"id": "agent-research-01",
"name": "Research Assistant",
"session_id": "sess_abc123def456",
"parent_agent_id": null
},
"initiator": {
"type": "user",
"id": "user_john",
"ip": "192.168.1.100",
"user_agent": "Mozilla/5.0..."
},
"action": {
"type": "tool_invoke",
"tool_name": "web_search",
"tool_version": "1.2.0",
"input_summary": "Search: OpenClaw deployment best practices",
"input_hash": "sha256:a1b2c3d4e5f6...",
"output_summary": "5 results returned",
"output_hash": "sha256:f6e5d4c3b2a1...",
"duration_ms": 1250,
"status": "success"
},
"context": {
"task_id": "task_789",
"task_description": "Research production deployment",
"conversation_turn": 5,
"model_provider": "anthropic",
"model_name": "claude-sonnet-4-20250514",
"token_input": 1500,
"token_output": 800,
"estimated_cost_usd": 0.012
},
"resource": {
"type": "external_api",
"id": "google_search_api",
"permissions_used": ["web:read"]
},
"metadata": {
"openclaw_version": "0.5.2",
"node_id": "node-01",
"environment": "production"
}
}审计日志采集器实现
以下 Python 脚本展示如何为 OpenClaw 实现一个审计日志采集中间件:
#!/usr/bin/env python3
"""
openclaw_audit_logger.py — OpenClaw 审计日志中间件
将 Agent 操作事件写入 JSONL 审计日志文件,由 Promtail 采集到 Loki
"""
import json
import os
import time
import uuid
import hashlib
from datetime import datetime, timezone
from pathlib import Path
# 审计日志目录
AUDIT_LOG_DIR = Path(os.getenv("AUDIT_LOG_DIR", "/var/log/openclaw/audit"))
AUDIT_LOG_DIR.mkdir(parents=True, exist_ok=True)
# 当前日志文件(按天轮转)
def get_audit_log_path():
date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
return AUDIT_LOG_DIR / f"audit-{date_str}.jsonl"
def generate_event_id():
"""生成唯一事件 ID"""
return f"evt_{uuid.uuid4().hex[:20]}"
def hash_content(content: str) -> str:
"""对内容生成 SHA256 摘要(不存储原文,保护隐私)"""
return f"sha256:{hashlib.sha256(content.encode()).hexdigest()[:16]}"
def write_audit_event(event: dict):
"""写入审计事件到 JSONL 文件(追加模式,不可覆盖)"""
log_path = get_audit_log_path()
event["timestamp"] = datetime.now(timezone.utc).isoformat()
event["event_id"] = event.get("event_id", generate_event_id())
with open(log_path, "a") as f:
f.write(json.dumps(event, ensure_ascii=False) + "\n")
# ============================================
# 审计事件工厂函数
# ============================================
def audit_tool_call(agent_id, session_id, tool_name,
tool_input, tool_output, duration_ms,
status, user_id=None):
"""记录工具调用审计事件"""
write_audit_event({
"event_type": "tool.invoke",
"event_category": "tool_call",
"severity": "info" if status == "success" else "error",
"actor": {
"type": "agent",
"id": agent_id,
"session_id": session_id
},
"initiator": {
"type": "user",
"id": user_id or "system"
},
"action": {
"type": "tool_invoke",
"tool_name": tool_name,
"input_hash": hash_content(str(tool_input)),
"output_hash": hash_content(str(tool_output)),
"duration_ms": duration_ms,
"status": status
}
})
def audit_llm_call(agent_id, session_id, provider, model,
token_input, token_output, cost_usd,
duration_ms, status):
"""记录 LLM 调用审计事件"""
write_audit_event({
"event_type": "llm.request",
"event_category": "llm_interaction",
"severity": "info" if status == "success" else "error",
"actor": {
"type": "agent",
"id": agent_id,
"session_id": session_id
},
"context": {
"model_provider": provider,
"model_name": model,
"token_input": token_input,
"token_output": token_output,
"estimated_cost_usd": cost_usd,
"duration_ms": duration_ms
},
"action": {
"type": "llm_request",
"status": status
}
})
def audit_user_action(user_id, action_type, details, ip=None):
"""记录用户操作审计事件"""
write_audit_event({
"event_type": f"user.{action_type}",
"event_category": "user_action",
"severity": "info",
"actor": {
"type": "user",
"id": user_id,
"ip": ip
},
"action": {
"type": action_type,
"details": details
}
})
# ============================================
# 使用示例
# ============================================
if __name__ == "__main__":
# 记录工具调用
audit_tool_call(
agent_id="agent-research-01",
session_id="sess_abc123",
tool_name="web_search",
tool_input="OpenClaw deployment guide",
tool_output="5 results found",
duration_ms=1250,
status="success",
user_id="user_john"
)
# 记录 LLM 调用
audit_llm_call(
agent_id="agent-research-01",
session_id="sess_abc123",
provider="anthropic",
model="claude-sonnet-4-20250514",
token_input=1500,
token_output=800,
cost_usd=0.012,
duration_ms=3200,
status="success"
)
print(f"审计日志已写入: {get_audit_log_path()}")6. 日志保留与合规策略
保留策略设计
不同类型的日志需要不同的保留期限:
| 日志类型 | 保留期限 | 存储层 | 合规依据 |
|---|---|---|---|
| 调试日志 (debug) | 3 天 | 热存储(Loki 本地) | 仅用于排障 |
| 运维日志 (info/warn) | 30 天 | 热存储(Loki 本地) | 运维需求 |
| 错误日志 (error/fatal) | 90 天 | 热存储 → 冷存储 | 事后分析 |
| 审计日志 (audit) | 1-7 年 | 冷存储(S3/MinIO) | GDPR/SOC 2/EU AI Act |
| 安全事件日志 | 3-7 年 | 冷存储(不可变) | 安全合规 |
| LLM 交互摘要 | 90 天-1 年 | 冷存储 | EU AI Act 可解释性要求 |
Loki 保留策略配置
# 在 loki-config.yml 的 limits_config 中配置
limits_config:
retention_period: 30d # 默认保留 30 天
# 按标签设置不同保留期
# Loki 3.x 支持按流的保留策略
overrides:
# 审计日志保留 365 天
openclaw-audit:
retention_period: 365d
# 调试日志仅保留 3 天
debug:
retention_period: 72h审计日志长期归档
对于需要长期保留的审计日志,建议使用对象存储归档:
#!/bin/bash
# scripts/archive-audit-logs.sh
# 将超过 30 天的审计日志归档到 S3/MinIO
AUDIT_DIR="/var/log/openclaw/audit"
S3_BUCKET="s3://openclaw-audit-archive"
ARCHIVE_AFTER_DAYS=30
# 查找超过 30 天的审计日志文件
find "$AUDIT_DIR" -name "audit-*.jsonl" -mtime +$ARCHIVE_AFTER_DAYS | while read file; do
filename=$(basename "$file")
date_part=$(echo "$filename" | grep -oP '\d{4}-\d{2}-\d{2}')
# 压缩并上传到 S3
gzip -c "$file" > "/tmp/${filename}.gz"
# 上传到按年月组织的目录
year_month=$(echo "$date_part" | cut -d'-' -f1,2)
aws s3 cp "/tmp/${filename}.gz" \
"${S3_BUCKET}/${year_month}/${filename}.gz" \
--storage-class GLACIER_IR
# 验证上传成功后删除本地文件
if [ $? -eq 0 ]; then
rm "$file" "/tmp/${filename}.gz"
echo "[$(date)] Archived: $filename → ${S3_BUCKET}/${year_month}/"
fi
done配合 cron 定时执行:
# 每天凌晨 3 点执行归档
0 3 * * * /opt/openclaw/scripts/archive-audit-logs.sh >> /var/log/openclaw/archive.log 2>&1合规要求速查
| 合规框架 | 日志要求 | 保留期限 | 关键要点 |
|---|---|---|---|
| GDPR | 记录数据处理活动、访问日志 | 无固定要求,但需”合理期限” | PII 需脱敏或加密存储;数据主体有权要求删除 |
| SOC 2 | 完整审计轨迹、访问控制日志 | 至少 1 年 | 不可篡改;需定期审查 |
| HIPAA | 访问日志、修改日志 | 至少 6 年 | 医疗数据相关操作必须记录 |
| EU AI Act | AI 系统决策日志、可解释性记录 | 高风险系统至少 6 个月 | 2026 年 8 月开始执行;需记录 AI 决策依据 |
| PCI DSS | 系统活动日志 | 至少 1 年(3 个月在线可查) | 支付相关操作必须记录 |
提示词模板:生成合规日志策略
你是一个合规与安全专家。请为我的 AI Agent 平台生成日志合规策略。
平台信息:
- 平台类型:[OpenClaw / 自建 Agent 平台]
- 部署区域:[中国 / 欧盟 / 美国 / 全球]
- 适用合规框架:[GDPR / SOC 2 / HIPAA / EU AI Act / 等保 2.0]
- 数据类型:[是否处理 PII / 医疗数据 / 金融数据]
- 当前日志存储:[Loki / ELK / 云服务]
请生成:
1. 各类日志的保留策略表(类型、保留期、存储层、合规依据)
2. PII 脱敏规则(哪些字段需要脱敏、脱敏方法)
3. 审计日志不可篡改性保障方案
4. 日志访问控制策略(谁可以查看什么级别的日志)
5. 定期审查清单(频率、检查项)7. ELK Stack 部署参考(可选)
如果你的场景需要全文搜索能力(如安全事件调查、合规审计取证),可以部署 ELK Stack 作为补充。以下是轻量级 ELK 部署配置:
Docker Compose 配置
# docker-compose.elk.yml — ELK 栈(可选)
# 仅在需要全文搜索时部署
# docker compose -f docker-compose.yml \
# -f docker-compose.monitoring.yml \
# -f docker-compose.logging.yml \
# -f docker-compose.elk.yml up -d
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.4
container_name: openclaw-elasticsearch
restart: unless-stopped
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
- cluster.name=openclaw-logs
- bootstrap.memory_lock=true
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- es_data:/usr/share/elasticsearch/data
ports:
- "127.0.0.1:9200:9200"
networks:
- openclaw-net
healthcheck:
test: ["CMD-SHELL", "curl -s -u elastic:${ELASTIC_PASSWORD} http://localhost:9200/_cluster/health | grep -q '\"status\":\"green\\|yellow\"'"]
interval: 30s
timeout: 10s
retries: 5
kibana:
image: docker.elastic.co/kibana/kibana:8.17.4
container_name: openclaw-kibana
restart: unless-stopped
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
ports:
- "127.0.0.1:5601:5601"
networks:
- openclaw-net
depends_on:
elasticsearch:
condition: service_healthy
filebeat:
image: docker.elastic.co/beats/filebeat:8.17.4
container_name: openclaw-filebeat
restart: unless-stopped
user: root
volumes:
- ./elk/filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/log/openclaw:/var/log/openclaw:ro
- filebeat_data:/usr/share/filebeat/data
networks:
- openclaw-net
depends_on:
elasticsearch:
condition: service_healthy
volumes:
es_data:
filebeat_data:Filebeat 配置
# elk/filebeat/filebeat.yml
filebeat.inputs:
# 审计日志(JSON 格式)
- type: log
enabled: true
paths:
- /var/log/openclaw/audit/*.jsonl
json.keys_under_root: true
json.add_error_key: true
fields:
log_type: audit
fields_under_root: true
# Docker 容器日志
- type: container
paths:
- /var/lib/docker/containers/*/*.log
processors:
- add_docker_metadata:
host: "unix:///var/run/docker.sock"
output.elasticsearch:
hosts: ["elasticsearch:9200"]
username: "elastic"
password: "${ELASTIC_PASSWORD}"
indices:
- index: "openclaw-audit-%{+yyyy.MM}"
when.equals:
log_type: "audit"
- index: "openclaw-logs-%{+yyyy.MM.dd}"
# 索引生命周期管理
setup.ilm:
enabled: true
rollover_alias: "openclaw-logs"
pattern: "{now/d}-000001"
policy_name: "openclaw-log-policy"💡 资源提醒:ELK Stack 资源消耗较大——Elasticsearch 单节点建议至少 4 GB 内存。如果 VPS 资源有限(4 GB 以下),建议仅使用 Loki 栈,不部署 ELK。
实战案例:从零搭建 OpenClaw 日志审计体系
场景描述
你运营一个 OpenClaw 实例,有 3 个 Agent 24/7 运行(研究助手、邮件管理、DevOps 助手)。需要:
- 集中管理所有容器日志
- 记录每个 Agent 的工具调用和 LLM 交互
- 满足基本的审计合规要求(保留 90 天审计日志)
- 能快速排查 Agent 异常行为
步骤 1:准备目录结构
# 在 OpenClaw 部署目录下创建日志相关目录
cd /opt/openclaw
mkdir -p logging/{loki,promtail}
mkdir -p /var/log/openclaw/audit
# 设置审计日志目录权限(仅 root 和 openclaw 用户可写)
chown root:openclaw /var/log/openclaw/audit
chmod 770 /var/log/openclaw/audit步骤 2:配置 Loki
将前文的 loki-config.yml 写入 logging/loki/loki-config.yml,关键调整:
# 根据 VPS 资源调整
limits_config:
retention_period: 90d # 审计要求保留 90 天
ingestion_rate_mb: 5 # 3 个 Agent 足够
per_stream_rate_limit: 3MB步骤 3:配置 Promtail
将前文的 promtail-config.yml 写入 logging/promtail/promtail-config.yml。
步骤 4:为 OpenClaw 容器添加日志标签
在 docker-compose.yml 中为 OpenClaw 服务添加标签:
services:
openclaw-gateway:
image: openclaw/openclaw:latest
labels:
logging: "enabled"
# ... 其他配置
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
tag: "openclaw-gateway"步骤 5:启动日志栈
# 启动全栈
docker compose -f docker-compose.yml \
-f docker-compose.monitoring.yml \
-f docker-compose.logging.yml up -d
# 验证各组件
echo "=== Loki ===" && curl -s http://127.0.0.1:3100/ready
echo "=== Promtail targets ===" && curl -s http://127.0.0.1:9080/targets | python3 -m json.tool | head -30步骤 6:在 Grafana 中配置日志面板
- 打开 Grafana(http://127.0.0.1:3000)
- 进入 Explore → 选择 Loki 数据源
- 测试查询:
# 查看所有 OpenClaw 日志
{service="openclaw-gateway"} | json
# 查看审计日志
{job="openclaw-audit"} | json
# 查看特定 Agent 的错误
{service="openclaw-gateway"} | json | agent_id="agent-research-01" | level="error"- 创建日志仪表板,添加以下面板:
- 实时日志流:
{service="openclaw-gateway"} | json(Log 面板) - 每小时错误趋势:
sum(count_over_time({service="openclaw-gateway"} | json | level="error" [1h])) by (agent_id)(时序图) - 工具调用排行:
topk(10, sum by (tool_name) (count_over_time({job="openclaw-audit"} | json | event_type="tool.invoke" [24h])))(柱状图) - 今日 Token 消耗:
sum(sum_over_time({job="openclaw-audit"} | json | event_type="llm.request" | unwrap token_input [24h]))+ output(统计值)
- 实时日志流:
步骤 7:配置日志告警
在 Grafana 中创建基于日志的告警规则:
# 告警:5 分钟内错误日志超过 20 条
count_over_time({service="openclaw-gateway"} | json | level="error" [5m]) > 20
# 告警:检测到权限拒绝事件
count_over_time({job="openclaw-audit"} | json | event_type="tool.permission_denied" [10m]) > 0
# 告警:单个 Agent 日志量异常(可能死循环)
sum by (agent_id) (count_over_time({service="openclaw-gateway"} | json [5m])) > 500案例分析
关键决策点:
- 选择 Loki 而非 ELK:3 个 Agent 的日志量不大(预估每天 1-5 GB),Loki 的低资源消耗更适合单 VPS 部署
- 审计日志独立存储:审计日志写入独立的 JSONL 文件,与运维日志分离,便于长期归档和合规审查
- 日志标签设计:使用
agent_id、session_id、event_type作为 Loki 标签,实现高效过滤而不增加索引开销 - 保留策略分层:运维日志 30 天、审计日志 90 天、安全事件归档到 S3
避坑指南
❌ 常见错误
-
将 LLM 完整 prompt/response 写入日志
- 问题:LLM 的输入输出可能包含用户隐私数据(PII),直接记录违反 GDPR 等合规要求。同时,完整的 prompt 和 response 体积巨大,会迅速耗尽存储空间
- 正确做法:审计日志中仅记录 prompt/response 的 SHA256 摘要(hash),需要时通过 hash 反查原始数据。敏感字段使用脱敏处理(如邮箱
j***@example.com)
-
Loki 标签基数过高(High Cardinality)
- 问题:将
session_id、trace_id、user_id等高基数字段设为 Loki 标签,导致 Loki 创建大量日志流,性能急剧下降甚至 OOM - 正确做法:Loki 标签仅使用低基数字段(
service、level、component、agent_id)。高基数字段放在 JSON 日志正文中,通过| json | session_id="xxx"查询
- 问题:将
-
审计日志与运维日志混在一起
- 问题:审计日志有不同的保留要求(通常 1-7 年)和访问控制要求。混合存储导致无法独立管理生命周期,也无法满足”不可篡改”的合规要求
- 正确做法:审计日志写入独立的文件/索引,使用独立的保留策略和访问控制。考虑使用 WORM(Write Once Read Many)存储
-
没有配置日志轮转导致磁盘爆满
- 问题:Docker 默认的 json-file 日志驱动没有大小限制,长时间运行后容器日志文件可能增长到数十 GB
- 正确做法:在
docker-compose.yml中为每个服务配置日志驱动限制:max-size: "50m"和max-file: "5"。同时配置 Loki 的retention_period和compactor
-
ELK Stack 在小规模部署中过度使用
- 问题:Elasticsearch 单节点至少需要 4 GB 内存,在 2-4 GB 的 VPS 上部署 ELK 会严重影响 OpenClaw 本身的性能
- 正确做法:小规模部署(1-5 个 Agent)使用 Loki 栈。仅在有明确的全文搜索需求或合规要求时才引入 ELK
-
忽略日志时间戳标准化
- 问题:不同组件使用不同的时间格式(Unix 时间戳、本地时间、UTC),导致日志关联困难,排障时时间线混乱
- 正确做法:所有组件统一使用 ISO 8601 格式的 UTC 时间(
2026-01-15T08:30:45.123Z)。在 Promtail 配置中设置timestampstage 确保正确解析
✅ 最佳实践
- 日志分层存储:热数据(7 天)放本地 SSD,温数据(30 天)放本地 HDD,冷数据(90 天+)归档到 S3/MinIO 的 Glacier 层
- 结构化日志优先:所有应用日志输出 JSON 格式,避免正则解析的性能开销和维护成本
- 审计日志不可变:审计日志文件使用
chattr +a(仅追加)属性,或存储到 WORM 兼容的对象存储 - 定期审查日志策略:每季度审查一次日志保留策略、访问权限和存储成本
- 日志采样:对于高频低价值日志(如 debug 级别),在 Promtail 中配置采样率,减少存储压力
- 关联 ID 贯穿全链路:使用
trace_id将同一请求的日志、指标和追踪数据关联起来,实现端到端可观测性
相关资源与延伸阅读
- Grafana Loki 官方文档 — Loki 配置、LogQL 语法、部署指南的权威参考
- grafana/loki GitHub 仓库 — Loki 源码、Docker Compose 示例、Issue 讨论
- LogQL 查询示例集 — 官方 LogQL 查询模式和示例
- Elastic Stack 官方文档 — Elasticsearch、Logstash、Kibana、Filebeat 完整文档
- OpenTelemetry Logging 规范 — 开放遥测日志标准,结构化日志的行业规范
- Vector 日志管道 — Datadog 开源的高性能日志采集和路由工具(Rust 编写)
- EU AI Act 合规指南 — 欧盟 AI 法案官方资源,了解 AI 系统日志合规要求
- SigNoz 开源可观测性 — 开源的日志+指标+Trace 一体化平台,ELK/Loki 的替代方案
- Promtail Pipeline Stages 文档 — Promtail 日志处理管道的详细配置指南
- AI Agent 审计日志最佳实践 — LLM 交互审计日志的实现指南(2025)
参考来源
- Grafana Loki 官方文档 — 配置与部署 (持续更新,2025)
- Loki vs Elasticsearch — 日志系统选型指南 (2025-01)
- ELK to Loki 迁移指南 (2026-01)
- AI Agent 生产日志指南 (2026)
- LLM 交互审计日志实现 (2025-05)
- AI Agent 可观测性 7 大实践 (2025-08)
- AI 合规框架:SOC 2、ISO 27001、GDPR (2025-12)
- 审计日志 for AI & LLM 系统 (2025-06)
- Loki vs ELK for Kubernetes (2025-06)
- Grafana Loki — 云原生日志聚合 (2025-06)
📖 返回 总览与导航 | 上一节:25b-监控与告警 | 下一节:25d-更新与回滚策略