必须绕过fiber默认日志中间件,通过自定义responsewriterwrapper获取状态码和响应大小,构造结构化日志字段并按指定顺序、时间格式(2006-01-02 15:04:05.000)同时输出到控制台和日期分割文件。

要在Fiber框架中精确控制每条日志的字段、顺序、时间格式和输出位置,必须绕过默认日志中间件的黑盒封装,直接构造结构化日志行并手动写入目标(控制台+文件)。
定义结构化日志字段与格式
先明确你要记录的核心字段:HTTP方法、请求路径、状态码、响应体大小、耗时毫秒、User-Agent、时间戳。这些字段不能靠拼接字符串硬编码,而要从c上下文和包装后的ResponseWriter中实时提取。
创建一个logEntry结构体,字段名与日志语义对齐,例如Method、Path、Status、Size、DurationMs、UserAgent、Time。
用time.Now().Format("2006-01-02 15:04:05.000")生成带毫秒精度的时间戳——这是Go唯一支持的固定格式,写错会返回空字符串。
包装ResponseWriter获取真实状态码和响应大小
原生c.Status()在中间件里不可靠,因为handler可能还没调用WriteHeader;必须用自定义responseWriterWrapper拦截WriteHeader和Write调用。
方法一:直接复用知识库中的responseWriterWrapper结构体,实现WriteHeader存状态码、Write累加字节数。
方法二:更轻量的做法是只重写WriteHeader,并在Write里判断是否已写头,未写则自动设为200——这能覆盖handler忘记显式设状态码的常见漏写场景。
【不包装ResponseWriter会导致Status恒为0、Size恒为0】
组装日志中间件函数
第一步:声明中间件函数签名func(c *fiber.Ctx) error。
第二步:记录起始时间start := time.Now()。
第三步:实例化responseWriterWrapper,用c.Context().SetUserValue或闭包变量暂存,确保后续能读取到statusCode和size。
第四步:调用c.Next()执行后续handler,此时responseWriterWrapper已被注入并开始拦截。
第五步:计算耗时durationMs := time.Since(start).Milliseconds(),强制转int64避免浮点数日志污染。
第六步:从c.Request().Header.UserAgent()取UA,注意它返回[]byte,需string()转换。
第七步:用fmt.Sprintf按预设字段顺序拼接日志行,例如"%s %s %s %d %d %s %.2fms",其中%.2fms对应durationMs——这里必须用float64(durationMs)转类型,否则格式化失败。
同时输出到控制台和日期分割的日志文件
控制台输出直接用fmt.Print(logLine),不要用fmt.Println——换行符已在logLine末尾手动添加。
文件输出路径按天生成:logs/2006-01-02.log,用os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)打开。
每次写入前检查file != nil且err == nil,否则跳过文件写入只保控制台——避免因磁盘满或权限问题导致整个请求panic。
写入完成后调用file.Close(),但不要用defer——defer在函数return后才执行,而中间件可能提前abort,导致文件句柄泄漏。











