You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何修改日志控制函数,在默认丢弃日志时保留log.Fatalf输出?

实现仅放行log.Fatalf输出的解决方案

当然可以做到!你的需求是当debug模式关闭时,拦截普通的log.Println这类日志,但让log.Fatalf的输出正常记录下来。咱们可以通过自定义一个io.Writer来实现这个逻辑,核心思路就是在Writer里判断当前日志是不是来自log.Fatal系列函数,以此决定要不要输出内容。

具体实现思路

  1. 定义一个自定义的Writer结构体,它需要知道当前是否开启debug模式,以及要把日志写到哪个目标文件里。
  2. 实现io.Writer接口的Write方法:
    • 如果debug模式开启,所有日志直接写入目标文件;
    • 如果debug模式关闭,通过调用栈检查日志来源——如果是log.Fatallog.Fatalflog.Fatalln这类致命日志,就写入文件;普通日志直接丢弃。

完整代码示例

import (
    "log"
    "os"
    "runtime"
    "strings"
)

// 自定义条件Writer,控制日志是否输出
type conditionalWriter struct {
    debugMode bool   // 是否开启debug模式
    target    *os.File // 日志目标文件
}

// 实现io.Writer接口的Write方法
func (cw *conditionalWriter) Write(p []byte) (n int, err error) {
    // debug模式开启时,所有日志都写入目标文件
    if cw.debugMode {
        return cw.target.Write(p)
    }

    // 获取调用栈,判断日志是否来自Fatal系列函数
    // runtime.Caller(2) 跳过当前Write和log.Output的调用,拿到原始调用者
    pc, _, _, ok := runtime.Caller(2)
    if !ok {
        return len(p), nil // 拿不到调用栈时默认丢弃日志
    }

    // 获取调用函数的名称
    funcName := runtime.FuncForPC(pc).Name()
    // 判断是否是Fatal系列函数
    if strings.Contains(funcName, "Fatal") {
        return cw.target.Write(p)
    }

    // 普通日志,直接返回(相当于丢弃)
    return len(p), nil
}

func getdebugmode(d bool, env string) {
    // 先打开日志文件——不管debug模式,确保Fatal日志能正常写入
    logFilePath := "/app/monitoring/appdyn/logs/appdyn_server_monitoring_events_" + env + ".log"
    f, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
    if err != nil {
        log.Fatalf("无法打开日志文件: %v", err)
    }
    // 可以考虑在正常退出时关闭文件,比如defer f.Close(),但Fatal触发的退出不会执行defer

    // 创建自定义条件Writer
    cw := &conditionalWriter{
        debugMode: d,
        target:    f,
    }

    // 设置log包的全局输出为自定义Writer
    log.SetOutput(cw)
    log.SetFlags(log.LstdFlags | log.Lshortfile)
}

代码说明

  • debug模式开启时:所有日志(包括普通日志和致命日志)都会正常写入指定的日志文件,和你原来的逻辑一致。
  • debug模式关闭时:只有log.Fatalflog.Fatal这类致命日志会被写入文件,log.Printlnlog.Printf这类普通日志会被直接丢弃。
  • 调用栈检查逻辑:用runtime.Caller(2)跳过两层调用(当前的Write方法和log包内部的Output方法),拿到真正触发日志的函数名,通过判断函数名里是否包含"Fatal"来区分日志类型。

小提示

  • 如果你的代码里有自己封装的日志函数,可能需要调整runtime.Caller的参数(比如改成3),确保能拿到正确的调用者函数名。
  • 日志文件的关闭逻辑:如果程序是正常退出(不是通过Fatal),记得手动关闭文件;如果是Fatal触发的退出,defer语句不会执行,这部分可以根据你的实际场景调整。

内容的提问来源于stack exchange,提问作者user2821260

火山引擎 最新活动