用 net/http 搭建轻量日志接收端需三步:一设限(ReadTimeout、MaxBytesReader 控制请求大小与超时),二归一(Struct 定义标准 LogEntry,强制解析+字段默认值+RFC3339 时间统一),三稳转(context 超时+指数退避重试+本地磁盘暂存降级)。

如何用 net/http 搭建轻量日志接收端
Go 写日志收集 Agent,第一关不是转发,是稳稳接住日志。别一上来就搞 Kafka 或 gRPC,HTTP POST 是最常见、最易调试的入口。
常见错误是直接用 http.HandleFunc 读 r.Body 不设限,导致大日志(比如带堆栈的 JSON)卡死或 OOM。必须控制单条请求大小和超时。
- 用
http.Server显式设置ReadTimeout和ReadHeaderTimeout(建议 ≤5s) - 在 handler 里用
http.MaxBytesReader包裹r.Body,例如限制单次请求 ≤2MB:body := http.MaxBytesReader(w, r.Body, 2*1024*1024)
- 优先用
application/json解析,避免用io.ReadAll无脑读 —— 改用json.NewDecoder(body).Decode(&logEntry),边读边解析,内存友好 - 别忘了加
Content-Type: application/json校验,非 JSON 请求直接http.Error(w, "bad content type", http.StatusBadRequest)
日志结构怎么统一?别让 map[string]interface{} 搞乱后续处理
前端 SDK、Nginx、业务 Go 服务发来的日志字段五花八门,如果后端不做清洗,转发到 ES 或 Loki 就会字段错乱、检索失效。
关键不是“兼容所有格式”,而是定义最小必填字段集,并强制转换。用 struct 而不是 map 做中间载体,编译期就能拦住字段 typo。
立即学习“go语言免费学习笔记(深入)”;
- 定义标准结构体,例如:
type LogEntry struct { Timestamp time.Time `json:"timestamp"` Level string `json:"level"` Message string `json:"message"` Service string `json:"service"` Host string `json:"host,omitempty"` TraceID string `json:"trace_id,omitempty"` } - 对非标准输入(如 Nginx access log 的纯文本),写专用 parser,转成该 struct;不要试图用通用
json.Unmarshal硬套 - 字段缺失时给默认值(如
Level缺失设为"info"),而不是留空或 panic - 时间字段务必统一转为 RFC3339,避免下游解析失败 —— 即使原始日志是 Unix timestamp 或自定义格式,也要在接收端做归一化
转发失败怎么办?context.WithTimeout 和重试策略不能省
日志转发链路(HTTP → Kafka → ES)任意一环抖动,都会导致日志丢失。Agent 必须自带缓冲和退避重试,但又不能无限积压。
常见坑是用 goroutine 直接发 HTTP,没设超时、没管错误、没做背压,结果上游挂了,本地 goroutine 泛滥,OOM。
- 每次转发都用
context.WithTimeout(ctx, 3*time.Second)包裹请求上下文 - 失败时按指数退避重试(最多 3 次),间隔从 100ms 开始翻倍:
time.Sleep(time.Duration(math.Pow(2, float64(attempt))) * 100 * time.Millisecond) - 重试仍失败的日志,写入本地磁盘暂存(用
os.O_APPEND | os.O_CREATE),文件名带日期,避免单文件过大 - 磁盘写失败(如满盘)要降级:打 warning 日志 + drop 当前条目(宁可丢一条,不卡住整个 pipeline)
为什么不用 logrus 或 zerolog 做收集端?
因为它们是日志“输出”库,不是“接收”组件。拿 logrus.SetOutput(os.Stdout) 接 HTTP 请求,等于把网络输入硬塞进 stdout 管道 —— 无法做校验、限流、格式归一,还会污染进程标准输出,干扰运维排查。
真正需要的是:一个能独立监听端口、解析、过滤、转发、落盘的小型服务,它甚至不该依赖任何第三方日志库。标准库 net/http + encoding/json + os 就够用。
- 引入
logrus只会增加二进制体积和启动开销,对收集性能无益 - 如果想打 debug 日志,用
fmt.Fprintf(os.Stderr, "[debug] ...")即可,无需抽象层 - 唯一值得加的第三方是
go.uber.org/zap的Logger.Sugar()(仅用于 agent 自身运行日志),但它和日志收集逻辑完全隔离
最复杂的点其实是磁盘暂存的可靠性:fsync 是否调用、文件轮转时机、清理策略。这些细节不显眼,但线上跑一周后,往往就卡在这儿。


















