如何将日志写入文件

136

我正在尝试使用Go语言编写日志文件。

我尝试过几种方法,但都失败了。这是我尝试过的方法:

func TestLogging(t *testing.T) {
    if !FileExists("logfile") {
        CreateFile("logfile")
    }
    f, err := os.Open("logfile")
    if err != nil {
        t.Fatalf("error: %v", err)
    }

    // attempt #1
    log.SetOutput(io.MultiWriter(os.Stderr, f))
    log.Println("hello, logfile")

    // attempt #2
    log.SetOutput(io.Writer(f))
    log.Println("hello, logfile")

    // attempt #3
    log.SetOutput(f)
    log.Println("hello, logfile")
}

func FileExists(name string) bool {
    if _, err := os.Stat(name); err != nil {
       if os.IsNotExist(err) {
            return false
        }
    }
    return true
}

func CreateFile(name string) error {
    fo, err := os.Create(name)
    if err != nil {
        return err
    }
    defer func() {
        fo.Close()
    }()
    return nil
}

日志文件已创建,但从未打印或附加任何内容。为什么?

2
如果您在Linux中部署程序,只需将日志写入标准输出,然后将输出管道传输到文件中,例如:*./program 2>&1 | tee logs.txt*。在其他系统中可能有其他方法。 - nvcnvn
11个回答

219

os.Open() 在过去可能有不同的工作方式,但对我来说这种方式有效:


f, err := os.OpenFile("testlogfile", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
if err != nil {
    log.Fatalf("error opening file: %v", err)
}
defer f.Close()

log.SetOutput(f)
log.Println("This is a test log entry")

根据 Go 文档,os.Open() 无法用于 log.SetOutput,因为它打开文件 "只用于读取:"

func Open

func Open(name string) (file *File, err error) Open 打开指定的文件供读取。 如果成功,则可以使用返回的文件上的方法进行读取;相关联的文件描述符具有模式 O_RDONLY。如果发生错误,则错误类型将为 *PathError

编辑

defer f.Close() 移动到 if err != nil 检查之后


9
在检查 err 是否为 nil 之前,不要推迟(defer)Close操作! - Volker
关闭所有情况下实际上并不是有害的,如果我没记错的话。然而,并非对于所有类型都是如此。 - Dustin
2
@Dustin f 可能是 nil,这将导致 panic。因此,在延迟调用之前检查 err 是明智的做法。 - nemo
@AllisonA 能否解释一下为什么 Open 不能与 log.SetOutput 一起使用? - nemo
2
更安全的权限是0644或者0664,允许用户读/写,用户和组读/写,在这两种情况下都不允许其他人写入。 - Jonathan
显示剩余4条评论

55

我更喜欢12因素应用程序建议的简单性和灵活性来进行日志记录。要附加到日志文件,您可以使用shell重定向。Go中的默认记录器将写入stderr(2)。

./app 2>> logfile

参见: http://12factor.net/logs


1
当您想将事情变为守护进程,特别是使用start-tsop-daemon时,这不是一个好的实践。 - Shrey
3
"Systemd很容易处理日志记录,以及启动和停止功能。" - WarGasm
尽管这是否是一个好的实践,但这是我在Golang中一直在寻找的日志记录类型。感谢您分享这个! - addicted
在Windows下有类似的东西吗? - surfmuggle
像这样: $ cd /etc/systemd/system $ sudo vi app.service ExecStart=/bin/bash -c 'sudo go run main.go >> /home/ubuntu/go/src/html_menu_1/logfile' Ubuntu 18.04.3上对我不起作用。 - Ryosuke Hujisawa

45

我通常会在屏幕上打印日志并同时写入文件。希望这能帮到某些人。

f, err := os.OpenFile("/tmp/orders.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
    log.Fatalf("error opening file: %v", err)
}
defer f.Close()
wrt := io.MultiWriter(os.Stdout, f)
log.SetOutput(wrt)
log.Println(" Orders API Called")

12

这对我有效

  1. 创建了一个名为logger.go的包

package logger

import (
  "flag"
  "os"
  "log"
  "go/build"
)

var (
  Log      *log.Logger
)


func init() {
    // set location of log file
    var logpath = build.Default.GOPATH + "/src/chat/logger/info.log"

   flag.Parse()
   var file, err1 = os.Create(logpath)

   if err1 != nil {
      panic(err1)
   }
      Log = log.New(file, "", log.LstdFlags|log.Lshortfile)
      Log.Println("LogFile : " + logpath)
}
  • 在任何想要记录日志的地方导入包,例如 main.go

  • package main
    
    import (
       "logger"
    )
    
    const (
       VERSION = "0.13"
     )
    
    func main() {
    
        // time to use our logger, print version, processID and number of running process
        logger.Log.Printf("Server v%s pid=%d started with processes: %d", VERSION, os.Getpid(),runtime.GOMAXPROCS(runtime.NumCPU()))
    
    }
    

    1
    据我所知,这会使file保持打开状态。(在错误检查后缺少defer file.Close()语句) 这是一个问题吗? - Dean Coakley

    10
    如果您在Linux计算机上运行二进制文件,可以使用Shell脚本。将其覆盖到一个文件中。
    ./binaryapp > binaryapp.log
    

    将内容追加到文件中

    ./binaryapp >> binaryapp.log
    

    将stderr覆盖写入文件中

    ./binaryapp &> binaryapp.error.log
    

    将标准错误输出附加到文件中

    ./binaryapp &>> binalyapp.error.log
    

    使用shell脚本文件可以使其更加动态。


    很高兴知道,我们如何覆盖stderr以记录日志。 - impossible

    6
    在全局var中声明,这样所有进程都可以访问(如果需要)。
    package main
    
    import (
        "log"
        "os"
    )
    var (
        outfile, _ = os.Create("path/to/my.log") // update path for your needs
        l      = log.New(outfile, "", 0)
    )
    
    func main() {
        l.Println("hello, log!!!")
    }
    

    嘿 @CostaHuang,请留下详细的反馈。谢谢。 - openwonk
    @CostaHuang,我刚刚运行了我的代码片段,它可以正常工作。 - openwonk
    嗨@openwonk,我又测试了一遍,但在我的电脑上仍然无法运行。我的版本是go version go1.10.2 windows/amd64,你的呢? - Costa Huang
    @CostaHuang,我刚运行了与你一样的示例。该示例假设您已经设置好文件夹结构。有简单的方法可以检查这一点,但是我的目标是展示相对简单的写入日志文件的方法。将您的代码更改为outfile, _ = os.Create("my.log"),它将按预期工作。 - openwonk
    你的代码可行。我之前使用的是outfile, _ = os.Create("./path/to/my.log")。不知怎么的,我以为这段代码会创建path/to文件夹和my.log文件,但显然它没有起作用。我建议你修改你的答案为outfile, _ = os.Create("./my.log")。这样我们就清楚地知道它正在当前文件夹中创建一个日志。 - Costa Huang

    5

    在Allison和Deepak的答案基础上,我开始使用logrus并且非常喜欢它:

    var log = logrus.New()
    
    func init() {
    
        // log to console and file
        f, err := os.OpenFile("crawler.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
        if err != nil {
            log.Fatalf("error opening file: %v", err)
        }
        wrt := io.MultiWriter(os.Stdout, f)
    
        log.SetOutput(wrt)
    }
    

    我在主函数中有一个延迟执行 f.Close() 的语句。


    5

    Go语言中默认的日志记录器会将日志写入标准错误输出(stderr),可以使用以下方法将其重定向到文件:

    import ( 
        "syscall"
        "os" 
     )
    func main(){
      fErr, err = os.OpenFile("Errfile", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
      syscall.Dup2(int(fErr.Fd()), 1) /* -- stdout */
      syscall.Dup2(int(fErr.Fd()), 2) /* -- stderr */
    
    }
    

    1
    也许这会对你有所帮助(如果日志文件存在则使用它,如果不存在则创建它):
    package main
    
    import (
        "flag"
        "log"
        "os"
    )
    //Se declara la variable Log. Esta será usada para registrar los eventos.
    var (
        Log *log.Logger = Loggerx()
    )
    
    func Loggerx() *log.Logger {
        LOG_FILE_LOCATION := os.Getenv("LOG_FILE_LOCATION")
            //En el caso que la variable de entorno exista, el sistema usa la configuración del docker.
        if LOG_FILE_LOCATION == "" {
            LOG_FILE_LOCATION = "../logs/" + APP_NAME + ".log"
        } else {
            LOG_FILE_LOCATION = LOG_FILE_LOCATION + APP_NAME + ".log"
        }
        flag.Parse()
            //Si el archivo existe se rehusa, es decir, no elimina el archivo log y crea uno nuevo.
        if _, err := os.Stat(LOG_FILE_LOCATION); os.IsNotExist(err) {
            file, err1 := os.Create(LOG_FILE_LOCATION)
            if err1 != nil {
                panic(err1)
            }
                    //si no existe,se crea uno nuevo.
            return log.New(file, "", log.Ldate|log.Ltime|log.Lshortfile)
        } else {
                    //si existe se rehusa.
            file, err := os.OpenFile(LOG_FILE_LOCATION, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
            if err != nil {
                panic(err)
            }
            return log.New(file, "", log.Ldate|log.Ltime|log.Lshortfile)
        }
    }
    

    更多详情请查看:https://su9.co/9BAE74B


    0

    我正在将日志写入文件,这些文件是每天生成的(每天生成一个日志文件)。这种方法对我很有效:

    var (
        serverLogger *log.Logger
    )
    
    func init() {
        // set location of log file
        date := time.Now().Format("2006-01-02")
        var logpath = os.Getenv(constant.XDirectoryPath) + constant.LogFilePath + date + constant.LogFileExtension
        os.MkdirAll(os.Getenv(constant.XDirectoryPath)+constant.LogFilePath, os.ModePerm)
        flag.Parse()
        var file, err1 = os.OpenFile(logpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    
        if err1 != nil {
            panic(err1)
        }
        mw := io.MultiWriter(os.Stdout, file)
        serverLogger = log.New(mw, constant.Empty, log.LstdFlags)
        serverLogger.Println("LogFile : " + logpath)
    }
    
    // LogServer logs to server's log file
    func LogServer(logLevel enum.LogLevel, message string) {
        _, file, no, ok := runtime.Caller(1)
        logLineData := "logger_server.go"
        if ok {
            file = shortenFilePath(file)
            logLineData = fmt.Sprintf(file + constant.ColonWithSpace + strconv.Itoa(no) + constant.HyphenWithSpace)
        }
        serverLogger.Println(logLineData + logLevel.String() + constant.HyphenWithSpace + message)
    }
    
    // ShortenFilePath Shortens file path to a/b/c/d.go tp d.go
    func shortenFilePath(file string) string {
        short := file
        for i := len(file) - 1; i > 0; i-- {
            if file[i] == constant.ForwardSlash {
                short = file[i+1:]
                break
            }
        }
        file = short
        return file
    }
    

    “shortenFilePath()” 方法用于从文件的完整路径中获取文件名。而 “LogServer()” 方法则用于创建格式化的日志语句(包含:文件名、行号、日志级别、错误语句等)。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接