在Go中实时读取更新的日志文件

huangapple go评论82阅读模式
英文:

Reading log files as they're updated in Go

问题

我正在尝试在Go中解析一些日志文件,但我不确定如何在不重复读取文件的情况下完成这个任务,同时还要检查文件是否有变化。

我希望能够读取到文件末尾,等待下一行被写入,然后再次读取到文件末尾,以此类推。这有点像tail -f的工作方式。

英文:

I'm trying to parse some log files as they're being written in Go but I'm not sure how I would accomplish this without rereading the file again and again while checking for changes.

I'd like to be able to read to EOF, wait until the next line is written and read to EOF again, etc. It feels a bit like how tail -f looks.

答案1

得分: 63

我已经编写了一个Go包--github.com/hpcloud/tail--来实现这个功能。

t, err := tail.TailFile("/var/log/nginx.log", tail.Config{Follow: true})
for line := range t.Lines {
    fmt.Println(line.Text)
}

...

引用kostix的回答:

> 在现实生活中,文件可能会被截断、替换或重命名(因为这就是像logrotate这样的工具的作用)。

如果文件被截断,它将自动重新打开。为了支持重新打开重命名的文件(由于logrotate等原因),您可以设置Config.ReOpen,如下所示:

t, err := tail.TailFile("/var/log/nginx.log", tail.Config{
    Follow: true,
    ReOpen: true})
for line := range t.Lines {
    fmt.Println(line.Text)
}

Config.ReOpen类似于tail -F(大写F):

 -F      -F选项意味着-f选项,但是tail还会检查被跟踪的文件是否已被重命名或轮换。当tail检测到正在读取的文件的文件名具有新的inode号码时,文件将被关闭并重新打开。如果从标准输入而不是文件中读取,则忽略-F选项。
英文:

I have written a Go package -- github.com/hpcloud/tail -- to do exactly this.

t, err := tail.TailFile("/var/log/nginx.log", tail.Config{Follow: true})
for line := range t.Lines {
    fmt.Println(line.Text)
}

...

Quoting kostix's answer:

> in real life files might be truncated, replaced or renamed (because that's what tools like logrotate are supposed to do).

If a file gets truncated, it will automatically be re-opened. To support re-opening renamed files (due to logrotate, etc.), you can set Config.ReOpen, viz.:

t, err := tail.TailFile("/var/log/nginx.log", tail.Config{
    Follow: true,
    ReOpen: true})
for line := range t.Lines {
    fmt.Println(line.Text)
}

Config.ReOpen is analogous to tail -F (capital F):

 -F      The -F option implies the -f option, but tail will also check to see if the file being followed has been
         renamed or rotated.  The file is closed and reopened when tail detects that the filename being read from
         has a new inode number.  The -F option is ignored if reading from standard input rather than a file.

答案2

得分: 9

你必须要么监视文件的变化(使用特定于操作系统的子系统来实现),要么定期轮询文件,以查看其修改时间(和大小)是否发生了变化。无论哪种情况,在读取另一块数据后,你都需要记住文件的偏移量,并在检测到变化后恢复它,然后再读取另一块数据。

但请注意,这在纸上看起来似乎很容易:在现实生活中,文件可能会被截断、替换或重命名(因为这就是像logrotate这样的工具应该做的事情)。

有关此问题的更多讨论,请参见此问题

英文:

You have to either watch the file for changes (using an OS-specific subsystem to accomplish this) or poll it periodically to see whether its modification time (and size) changed. In either case, after reading another chunk of data you remember the file offset and restore it before reading another chunk after detecting the change.

But note that this seems to be easy only on paper: in real life files might be truncated, replaced or renamed (because that's what tools like logrotate are supposed to do).

See this question for more discussion of this problem.

答案3

得分: 7

package main

import (
"bufio"
"fmt"
"io"
"os"
"time"
)

func tail(filename string, out io.Writer) {
f, err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
r := bufio.NewReader(f)
info, err := f.Stat()
if err != nil {
panic(err)
}
oldSize := info.Size()
for {
for line, prefix, err := r.ReadLine(); err != io.EOF; line, prefix, err = r.ReadLine() {
if prefix {
fmt.Fprint(out, string(line))
} else {
fmt.Fprintln(out, string(line))
}
}
pos, err := f.Seek(0, io.SeekCurrent)
if err != nil {
panic(err)
}
for {
time.Sleep(time.Second)
newinfo, err := f.Stat()
if err != nil {
panic(err)
}
newSize := newinfo.Size()
if newSize != oldSize {
if newSize < oldSize {
f.Seek(0, 0)
} else {
f.Seek(pos, io.SeekStart)
}
r = bufio.NewReader(f)
oldSize = newSize
break
}
}
}
}

func main() {
tail("x.txt", os.Stdout)
}

英文:

A simple example:

package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;os&quot;
	&quot;time&quot;
)

func tail(filename string, out io.Writer) {
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	r := bufio.NewReader(f)
	info, err := f.Stat()
	if err != nil {
		panic(err)
	}
	oldSize := info.Size()
	for {
		for line, prefix, err := r.ReadLine(); err != io.EOF; line, prefix, err = r.ReadLine() {
			if prefix {
				fmt.Fprint(out, string(line))
			} else {
				fmt.Fprintln(out, string(line))
			}
		}
		pos, err := f.Seek(0, io.SeekCurrent)
		if err != nil {
			panic(err)
		}
		for {
			time.Sleep(time.Second)
			newinfo, err := f.Stat()
			if err != nil {
				panic(err)
			}
			newSize := newinfo.Size()
			if newSize != oldSize {
				if newSize &lt; oldSize {
					f.Seek(0, 0)
				} else {
					f.Seek(pos, io.SeekStart)
				}
				r = bufio.NewReader(f)
				oldSize = newSize
				break
			}
		}
	}
}

func main() {
	tail(&quot;x.txt&quot;, os.Stdout)
}

答案4

得分: 4

我也对这个感兴趣,但是还没有时间来解决它。我想到的一个方法是让“tail”来完成大部分工作。这可能会使你的工具特定于平台,但这可能没关系。基本思路是使用“os/exec”包中的Cmd来跟踪文件。你可以fork一个进程,相当于“tail --retry --follow=name prog.log”,然后使用Cmd对象上的Stdout reader来监听它的Stdout。

抱歉,我知道这只是一个草图,但希望对你有帮助。

英文:

I'm also interested in doing this, but haven't (yet) had the time to tackle it. One approach that occurred to me is to let "tail" do the heavy lifting. It would likely make your tool platform-specific, but that may be ok. The basic idea would be to use Cmd from the "os/exec" package to follow the file. You could fork a process that was the equivalent of "tail --retry --follow=name prog.log", and then listen to it's Stdout using the Stdout reader on the the Cmd object.

Sorry I know it's just a sketch, but maybe it's helpful.

答案5

得分: 3

有很多方法可以做到这一点。在现代基于POSIX的操作系统中,可以使用inotify接口来实现。

可以使用这个包:https://github.com/fsnotify/fsnotify

示例代码:

watcher, err := fsnotify.NewWatcher()
if err != nil {
	log.Fatal(err)
}

done := make(chan bool)

err = watcher.Add(fileName)
if err != nil {
	log.Fatal(err)
}
for {
	select {
	case event := &lt;-watcher.Events:
		if event.Op&amp;fsnotify.Write == fsnotify.Write {
			log.Println(&quot;modified file:&quot;, event.Name)

		}
}

希望对你有帮助!

英文:

There are many ways to do this. In modern POSIX based Operating Systems, one can use the inotify interface to do this.

One can use this package: https://github.com/fsnotify/fsnotify

Sample code:

watcher, err := fsnotify.NewWatcher()
if err != nil {
	log.Fatal(err)
}

done := make(chan bool)

err = watcher.Add(fileName)
if err != nil {
	log.Fatal(err)
}
for {
	select {
	case event := &lt;-watcher.Events:
		if event.Op&amp;fsnotify.Write == fsnotify.Write {
			log.Println(&quot;modified file:&quot;, event.Name)

		}
}

Hope this helps!

huangapple
  • 本文由 发表于 2012年4月13日 13:35:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/10135738.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定