我使用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()
也会关闭底层文件吗?
如果没有,除了将文件指针保存在某处之外,我还有什么选择呢。
答案 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的一部分)作为独立的日志轮换工具。