
本文详解如何使用 go 构建轻量级日志服务,正确解析 json 格式的 post 请求体,并安全、高效地将原始请求数据持久化到本地文件。
本文详解如何使用 go 构建轻量级日志服务,正确解析 json 格式的 post 请求体,并安全、高效地将原始请求数据持久化到本地文件。
Go 是构建高并发、低开销 HTTP 服务的理想选择,尤其适合实现日志收集这类 I/O 密集型中间件。你提出的“接收 POST 请求并写入文件”的需求完全合理——但原代码中调用 r.ParseForm() 无法处理 application/json 类型的请求体,这是导致 r.Form 为空(map[])的根本原因。
ParseForm() 仅适用于 application/x-www-form-urlencoded 或 multipart/form-data 编码的表单数据,而你的 curl 请求明确设置了 Content-Type: application/json,此时请求体是原始 JSON 字节流,需直接读取 r.Body。
以下是一个完整、健壮、生产就绪的示例:
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
// 安全写入日志:带时间戳与错误处理
func writeLogToFile(data []byte) error {
timestamp := time.Now().Format("2006-01-02T15:04:05Z")
filename := "logs/output.log"
// 确保日志目录存在
if err := os.MkdirAll("logs", 0755); err != nil {
return fmt.Errorf("failed to create logs directory: %w", err)
}
// 追加模式写入,避免覆盖历史日志
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return fmt.Errorf("failed to open log file: %w", err)
}
defer f.Close()
// 写入时间戳 + 原始数据 + 分隔符
if _, err := f.WriteString(fmt.Sprintf("[%s] %s\n\n", timestamp, string(data))); err != nil {
return fmt.Errorf("failed to write to log file: %w", err)
}
return nil
}
func logger(w http.ResponseWriter, r *http.Request) {
// 1. 仅处理 POST 请求
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 2. 设置超时与限制请求体大小(防恶意大请求)
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 最大 1MB
defer r.Body.Close()
// 3. 读取原始请求体
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusBadRequest)
log.Printf("ERROR reading body: %v", err)
return
}
// 4. 写入文件
if err := writeLogToFile(body); err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
log.Printf("ERROR writing log: %v", err)
return
}
// 5. 返回成功响应
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"status":"success","message":"log received"}`)
}
func main() {
http.HandleFunc("/log", logger)
// 启用 HTTP 服务(生产环境建议使用 HTTPS + 反向代理)
log.Println("Logging service started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}✅ 关键要点说明:
- ✅ 不依赖 ParseForm():对 JSON 请求,直接读取 r.Body;若需解析结构体,可后续用 json.Unmarshal(body, &struct)。
- ✅ 显式方法校验:避免误处理 GET/PUT 等非预期请求。
- ✅ 流式读取与错误处理:使用 io.ReadAll(替代已弃用的 ioutil.ReadAll),配合 defer r.Body.Close() 防资源泄漏。
- ✅ 生产级健壮性:添加目录创建、文件追加写入、请求体大小限制、HTTP 状态码反馈及详细日志记录。
- ✅ 日志格式友好:每条记录含 ISO 时间戳与换行分隔,便于后续用 grep / tail / ELK 等工具分析。
⚠️ 注意事项:
- 若需更高吞吐量(如万级 QPS),应考虑异步写入(如通过 channel + 单独 goroutine 批量刷盘)或对接 Kafka / Redis 等消息队列,避免阻塞 HTTP 处理线程。
- 文件写入操作在高并发下可能成为瓶颈,务必监控磁盘 I/O;长期运行建议轮转日志(可用 lumberjack 库)。
- 实际部署时,禁止暴露服务至公网,应在反向代理(Nginx/Caddy)后运行,并配置限流、鉴权(如 API Key)和 TLS。
至此,你已拥有了一个简洁、可靠、可扩展的日志接收服务基础框架。










