必须使用封装的audit.readfile而非os.readfile或io.readall,以记录调用点(runtime.caller(2))、上下文(含audit.useridkey)、绝对路径、敏感路径标识、小写op_type、不记录内容、按环境输出日志、utc时间戳及固定结构化字段。

必须走封装的 audit.ReadFile,不能直接调 os.ReadFile 或 io.ReadAll —— 否则日志里根本看不到谁读了哪个文件、在哪个 handler 触发的。
audit.ReadFile 必须捕获调用点和上下文
原生 os.ReadFile 不带栈帧、不带 context.Context,打出来的日志永远只显示 “audit.ReadFile 第 42 行”,而不是业务代码里那行 data, _ := audit.ReadFile(ctx, "/etc/passwd")。
- 用
runtime.Caller(2)获取真实调用位置(跳过封装层 + 当前函数),填入call_site字段,格式为"service/user.go:105" -
ctx必须含audit.UserIDKey,没传就 warn 并设user_id: "unknown",绝不留空 - 路径参数强制要求绝对路径:
filepath.Abs(path)在入口转,避免"../config.yaml"这类相对路径绕过敏感路径校验 - 即使
os.IsNotExist(err)也要记日志:“尝试读取 /etc/shadow”,审计记录的是意图,不是结果
敏感路径和脱敏字段要提前识别
不是所有读操作都等价。读 /tmp/xxx.log 和读 /root/.ssh/id_rsa 的风险等级完全不同,日志字段必须能区分。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 对路径做前缀匹配:若
file_path以"/etc/"、"/root/.ssh/"、"$HOME/.aws/"开头,打标is_sensitive: true -
op_type统一用小写枚举值:"read",别用"READ"或"FileRead",方便后续 ES 聚合 - 禁止记录原始内容或哈希值 —— 审计日志只记“谁读了什么”,不记“读到了什么”。哪怕
audit.ReadFile返回了明文,日志里也绝不出现content_hash或first_10_bytes
日志输出目标必须和部署环境对齐
本地开发时写 os.Stdout 没问题,但上线后混用会丢日志、错时区、难归档。
- K8s 环境:输出到
os.Stderr,靠 fluentd sidecar 按 label 分流,audit标签单独采集 - 物理机/VM:用
syslog.New()直连 rsyslog,避免文件权限、磁盘满、rotate 失效等问题 - 时间戳必须是 UTC:
event_time: time.Now().UTC().Format(time.RFC3339)→"2026-06-17T07:31:22Z",不是"+08:00" - 结构化字段固定为:
op_type、file_path、user_id、call_site、is_sensitive、event_time、status("success"或"failed")
最常被忽略的是 call_site 的跳层数和 event_time 的时区 —— 一个导致查不到触发点,一个让 ELK 里时间全偏移 8 小时。这两个字段出错,整条日志就失去审计价值。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










