给AI应用配安全日志,先定字段再落地采集与验证
AI安全日志配置要解决的是“出了事能查到什么”的问题,而不是简单打开一个日志开关。
对大多数自建AI应用来说,可行的最小方案是:在Nginx层记录请求元数据,在Python应用层记录模型调用和异常事件,统一字段格式后写入文件,再用日志轮转控制磁盘占用。
下面按这个顺序展开。
先定字段,再决定写到哪里
安全日志和普通业务日志的区别在于,它要能回答三个问题:谁在什么时候调用了哪个模型接口、请求内容是否异常、服务端返回了什么。
建议至少包含以下字段:
timestamp:ISO 8601 格式时间,方便跨服务对齐request_id:一次请求的唯一标识,用于串联Nginx日志和应用日志client_ip:调用方IP,经过代理时从X-Forwarded-For取第一个地址user_id或api_key_id:调用者身份,不要记录完整API Keymodel_name:被调用的模型名称prompt_length和completion_length:输入输出长度,用于发现异常大请求status_code:HTTP状态码或业务错误码latency_ms:处理耗时event_type:如request、auth_fail、rate_limit、model_error
字段确定后,写入位置就清楚了:Nginx能拿到IP、状态码、耗时和请求路径;
Python应用能拿到模型名称、token长度和业务异常。
两边用同一个 request_id 关联,后续排查才不用靠猜。
如果使用云厂商的API网关或模型服务,先确认其是否已提供调用日志和审计日志,能直接开启的优先开启,再补充应用层字段,避免重复建设。
在Nginx层记录请求元数据
假设AI推理服务通过Nginx反向代理暴露,在 http 块中定义日志格式,写入 /etc/nginx/nginx.conf 或站点配置的 server 块之前:
log_format ai_security escape=json
'{"time":"$time_iso8601",'
'"request_id":"$request_id",'
'"client_ip":"$remote_addr",'
'"method":"$request_method",'
'"path":"$request_uri",'
'"status":$status,'
'"body_bytes":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time",'
'"user_agent":"$http_user_agent"}';
然后在对应的 server 块中启用:
access_log /var/log/nginx/ai_access.log ai_security;
执行 nginx -t 检查语法,通过后 systemctl reload nginx 重载。
这里要注意:$request_id 是Nginx内置变量,但只有在配置中显式引用时才会生成并透传给后端,建议在 location 中加一行 proxy_set_header X-Request-Id $request_id;,让Python应用能拿到同一个ID。
Nginx日志不记录请求体,这是有意的设计。
请求体可能包含用户隐私或敏感提示词,是否记录、记录多少需要单独评估,不要默认全量落盘。
Python应用层记录模型调用事件
应用层推荐使用标准库 logging 配合JSON格式输出,避免自己拼接字符串。
下面是一个最小示例,写入 /opt/ai-app/app.py 同级目录的 security_logger.py:
import logging
import json
import time
logger = logging.getLogger("ai_security")
logger.setLevel(logging.INFO)
handler = logging.FileHandler("/var/log/ai-app/security.log")
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
def log_event(request_id, event_type, **kwargs):
record = {
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
"request_id": request_id,
"event_type": event_type,
}
record.update(kwargs)
logger.info(json.dumps(record, ensure_ascii=False))
在推理接口中调用:
log_event(
request_id=request_id,
event_type="request",
client_ip=client_ip,
model_name="qwen-local",
prompt_length=len(prompt),
completion_length=len(answer),
status_code=200,
latency_ms=int((time.time() - start) * 1000),
)
鉴权失败、触发限流、模型调用超时等分支也应各自调用一次 log_event,把 event_type 设为对应值。不要记录完整的prompt和response原文,除非业务明确要求且已做脱敏;
安全日志的目标是发现异常模式,不是保存聊天记录。
日志目录需要提前创建并设置权限:
sudo mkdir -p /var/log/ai-app
sudo chown www-data:www-data /var/log/ai-app
sudo chmod 750 /var/log/ai-app
运行服务的用户必须对目录有写权限,否则应用启动时不会报错,但日志会静默丢失。
用 ls -ld /var/log/ai-app 确认属主与运行用户一致。
配置日志轮转,避免磁盘被写满
安全日志增长快,必须配置轮转。
在 /etc/logrotate.d/ai-security 中写入:
/var/log/nginx/ai_access.log /var/log/ai-app/security.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
sharedscripts
postrotate
systemctl reload nginx > /dev/null 2>&1 || true
endscript
}
执行 logrotate -d /etc/logrotate.d/ai-security 做一次演练,确认没有语法错误和路径不匹配。
轮转生效后,security.log 会被压缩成 security.log.1.gz,检索历史日志时需要解压或用 zgrep。
验证日志链路是否真的通了
配置完成后不要只看文件存在,要实际发一次请求再检查。
- 发起测试请求:
curl -s -o /dev/null -w "%{http_code}\n" \
-H "Content-Type: application/json" \
-d '{"prompt":"test"}' \
http://127.0.0.1:8000/v1/chat
- 检查Nginx日志中是否出现对应记录:
tail -n 5 /var/log/nginx/ai_access.log
预期能看到包含 request_id、status、request_time 的JSON行。
- 检查应用日志:
tail -n 5 /var/log/ai-app/security.log
预期能看到 event_type 为 request 的记录,且 request_id 与Nginx日志中的值一致。
- 用同一个
request_id串联两边:
grep '你的request_id' /var/log/nginx/ai_access.log
grep '你的request_id' /var/log/ai-app/security.log
如果应用日志为空,优先检查运行用户是否有目录写权限、FileHandler 路径是否写错;
如果Nginx日志没有 request_id,检查 proxy_set_header 是否加在了正确的 location 中。
几个容易踩的坑
日志里出现完整API Key:鉴权失败时不要把请求头原样打进去,只记录 api_key_id 或Key的后四位。
只记录成功请求:安全事件往往发生在失败分支,401、429、5xx 必须记录,否则日志对排查没有价值。
时间格式不统一:Nginx用 $time_iso8601,Python用带时区的ISO格式,两边都保留时区信息,否则跨时区部署时对不上。
日志目录权限过宽:安全日志包含IP和调用行为,目录权限建议 750,文件权限 640,不要用 777 图省事。
日志配置完成后,下一步通常是基于这些字段设置异常规则,例如同一IP在短时间内的 auth_fail 次数、单次 prompt_length 超过阈值等。
先把字段和采集链路跑通,后续的检索和告警才有可靠的数据来源。