44

我正在尝试解析一些日志文件,因为它们是用 Go 编写的,但我不确定如何在不检查更改时一次又一次地重新读取文件的情况下完成此操作。

我希望能够读取到 EOF,等到下一行被写入并再次读取到 EOF,等等。感觉有点像tail -f看起来。

4

5 回答 5

61

我已经编写了一个 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      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.
于 2013-03-10T23:08:09.250 回答
8

您必须观察文件的更改(使用特定于操作系统的子系统来完成此操作)或定期轮询它以查看其修改时间(和大小)是否已更改。无论哪种情况,在读取另一块数据后,您会记住文件偏移量并在检测到更改后读取另一块之前恢复它。

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

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

于 2012-04-13T07:56:30.733 回答
5

一个简单的例子:

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)
}
于 2018-03-01T13:21:45.527 回答
4

我也有兴趣这样做,但还没有(还)有时间解决它。我想到的一种方法是让“尾巴”完成繁重的工作。它可能会使您的工具特定于平台,但这可能没问题。基本思想是使用“os/exec”包中的Cmd来跟踪文件。您可以分叉一个相当于“tail --retry --follow=name prog.log”的进程,然后使用 Cmd 对象上的标准输出读取器来收听它的标准输出。

对不起,我知道这只是一个草图,但也许它会有所帮助。

于 2012-04-13T15:06:09.800 回答
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 := <-watcher.Events:
        if event.Op&fsnotify.Write == fsnotify.Write {
            log.Println("modified file:", event.Name)

        }
}

希望这可以帮助!

于 2017-01-20T09:15:47.357 回答