关闭io.PipeWriter会关闭底层文件吗?

时间:2018-04-17 06:25:04

标签: go logging

我使用logrus进行日志记录,并拥有一些自定义格式记录器。每个都被初始化为写入不同的文件,如:

fp, _ := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755)
// error handling left out for brevity
log.Out = fp

稍后在应用程序中,我需要更改记录器正在写入的文件(对于日志轮换逻辑)。我想要实现的是在更改记录器的输出文件之前正确关闭当前文件。但是与文件句柄logrus最接近的是我提供的Writer()方法返回io.PipeWriter指针。那么在PipeWriter上调用Close()也会关闭底层文件吗? 如果没有,除了将文件指针保存在某处之外,我还有什么选择呢。

2 个答案:

答案 0 :(得分:0)

结束io.PipeWriter不会影响其背后的实际Writer。密切执行链: PipeWriter.Close() - > PipeWriter.CloseWithError(err error) - > pipe.CloseWrite(err error) 它并没有影响潜在的io.Writer

要关闭实际作者,您只需关闭导出字段Logger.Out即可。

答案 1 :(得分:0)

对于记录,twelve-factor 告诉我们应用程序不应该关注日志轮换。是否以及如何最好地处理日志取决于应用程序的部署方式。例如,Systemd有自己的日志记录系统。在(Docker)容器中部署时写入文件很烦人。旋转文件在开发过程中很烦人。

现在,管道没有"底层文件"。有一个读者端和一个写作者端,就是这样。来自the docs for PipeWriter

  

关闭作家;从管道的读取一半开始的后续读取将不返回字节和EOF。

那么关闭编写器时会发生什么取决于Logrus如何在Reader端处理EOF。由于Logger.Out是io.Writer,因此Logrus无法在您的文件上调用Close。

你最好的选择是包装* os.File,也许是这样:

package main

import "os"

type RotatingFile struct {
        *os.File
        rotate chan struct{}
}

func NewRotatingFile(f *os.File) RotatingFile {
        return RotatingFile{
                File:   f,
                rotate: make(chan struct{}, 1),
        }
}

func (r RotatingFile) Rotate() {
        r.rotate <- struct{}{}
}

func (r RotatingFile) doRotate() error {
        // file rotation logic here
        return nil
}

func (r RotatingFile) Write(b []byte) (int, error) {
        select {
        case <-r.rotate:
                if err := r.doRotate(); err != nil {
                        return 0, err
                }
        default:
        }

        return r.File.Write(b)
}

以强大的方式实现日志文件轮换非常棘手。例如,在创建新文件之前关闭旧文件不是一个好主意。如果日志目录权限更改怎么办?如果你用完了inode怎么办?如果您无法创建新的日志文件,则可能需要继续写入当前文件。你是否可以分开线,或者你只想在换行后旋转?你想旋转空文件吗?如果有人删除了第N-1个文件,您如何可靠地删除旧日志?你会注意到第N个文件还是停止查看N-2nd?

我能给你的最好建议是将日志轮换留给专业人士。我喜欢svlogd(runit的一部分)作为独立的日志轮换工具。