gin.loggerwithwriter无法输出彩色是因为它仅按模板拼接字符串且不检测终端能力,需自定义colorwriter结合isatty判断终端并注入ansi码,再通过log.setoutput和中间件实现条件着色。

直接用 log.New 或 gin.Logger() 默认输出都是黑白的,要彩色必须接管日志写入逻辑,并在格式化时插入 ANSI 转义序列——但 Gin 的 gin.LoggerWithWriter 不会自动处理颜色,得自己拼接。
为什么 gin.LoggerWithWriter 输出不了彩色?
Gin 内置的 gin.LoggerWithWriter 本质是把结构化日志(状态码、路径、耗时等)按固定字符串模板拼好,再写入 io.Writer。它不识别 ANSI 转义符是否生效,也不做终端能力检测;如果你直接往 os.Stdout 写带 \x1b[32m 的字符串,在某些 IDE 终端或 CI 环境里会被原样打印成乱码,而非变色。
- 彩色只应在支持 ANSI 的真实终端中启用,CI/日志文件场景应自动降级为无色
-
gin.LoggerWithWriter的gin.LoggerConfig.Format只接受字符串模板,不能嵌入条件逻辑 - 所以必须绕过它,手写一个中间件,用
log.SetOutput+ 自定义io.Writer控制输出行为
实现带终端检测的彩色 io.Writer
核心是写一个能判断 os.Stdout 是否连着真终端的 io.Writer,并在写入前决定是否加颜色。Linux/macOS 用 isatty.IsTerminal(),Windows 用 isatty.IsCygwinTerminal(),推荐用 github.com/mattn/go-isatty。
import (
"io"
"os"
"runtime"
"github.com/mattn/go-isatty"
)
type ColorWriter struct {
writer io.Writer
}
func (w ColorWriter) Write(p []byte) (n int, err error) {
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
// 根据日志内容插入 ANSI 颜色码:200-299 绿,400-499 黄,500+ 红
if len(p) > 0 && p[0] == '[' {
if i := bytes.Index(p, []byte(" ")) + 1; i > 0 && i = 200 && code = 400 && code = 500 {
return io.WriteString(w.writer, "\x1b[31m"+string(p)+"\x1b[0m")
}
}
}
}
return w.writer.Write(p)
}
- 别直接改
os.Stdout,而是用log.SetOutput(ColorWriter{os.Stdout}) - 上面的匹配逻辑只是示意——实际建议用正则提取状态码,避免硬切片越界
- Windows PowerShell 默认支持 ANSI,但 cmd.exe 需要先调用
SetConsoleMode,go-isatty已处理该兼容性
在 Gin 中注册自定义日志中间件
不能用 gin.Logger(),得自己写 gin.HandlerFunc,手动记录请求信息并交由上述 log 实例输出。
import "log"
var logger = log.New(ColorWriter{os.Stdout}, "", 0)
func CustomLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
latency := time.Since(start)
status := c.Writer.Status()
clientIP := c.ClientIP()
method := c.Request.Method
path := c.Request.URL.Path
var statusColor, reset string
switch {
case status >= 200 && status = 400 && status = 500:
statusColor = "\x1b[31m"
default:
statusColor = "\x1b[37m"
}
reset = "\x1b[0m"
logger.Printf("%s[%s]%s %s%s%s %s%d%s %s%.4fs%s",
"\x1b[36m", method, reset,
"\x1b[34m", path, reset,
statusColor, status, reset,
"\x1b[35m", latency.Seconds(), reset)
}
}
- 注意:这里用
logger.Printf而非fmt.Printf,确保走的是你重写的ColorWriter - 如果项目同时写文件日志,需额外分支处理:对文件
io.Writer不加颜色,且避免 ANSI 字符污染日志文件 - 时间精度用
latency.Seconds()比latency.String()更易对齐,也方便后续做 P95 统计
最易被忽略的一点:很多开发者在容器环境(如 Docker)里测试时发现颜色失效,不是代码问题,而是 docker run 默认不分配伪终端(-t 缺失),导致 isatty 返回 false——这时候该让日志自动降级,而不是强行塞 ANSI 码。











