Bootstrap

golang 脚本实时监控错误日志

对于日志的监控分析, ELK 依然已经成为业界标准了。其中L代表Logstash, 用来采集日志。E代表Elasticsearch,用来存储检索分析。K是Kibana用来显示。这一套日志分析监控系统非常强大。

若我们的需求非常简单,例如,要实时监控MySQL,MongoDB等的日志文件, 实时监控 error log。 若出现了error log, 马上报警。

若是需求简单,没必要安装大块头的ELK 情况下, 我们也可以自己写代码来实现对日志的实时监控。

基本思路如下:

  • 主进程定时采集新增日志,比如每秒采集一次,因为采集的是新增日志,所以即使很大(例如,几GB或者几十GB)也不影响性能。当然,还要记录上次读取的位置。

  • 一个go routinue负责对新增日志的过滤,用来搜索过滤是否有error 日志

  • 一个go routinue负责报警,发邮件发钉钉发微信等

主线程核心代码如下:

这里使用了两个循环。 内层循环, 每次新增的日志信息, 内层循环每次读取一行,使用通道传输给日志过滤的go routinue。

外层循环,负责每秒钟读取一次新增的日志。seek, err := f.Seek(i, 0) 负责每次读取新增的日志,i是上次读取到的日志偏移量。 finfo.Size() < i 判断是负责检测日志是否切割了。 因为每天切割一次日志,生成一个新的日志文件。若当前日志的总大小小于记录的偏移量, 则认为这是一个新日志。

for {
        f, err := os.Open("D:\\project\\golang\\MyIncrLog\\1.log")
        finfo, err := f.Stat()
        //fmt.Println("file size:",finfo.Size()," offset:",i)
        if err != nil {
            fmt.Println(err)
        }
        if finfo.Size() < i {
            i = 0
        }
        if err != nil {
            fmt.Println(err)
        }
        seek, err := f.Seek(i, 0)
        if err != nil {
            fmt.Println(seek, err)
        }
        rd := bufio.NewReader(f)
        for {
            line, err := rd.ReadString('\n')
            if err != nil || io.EOF == err {
                if io.EOF != err {
                    fmt.Println(err)
                }
                break
            }
            in <- line
            n := len([]byte(line))
            i = i + int64(n)
            time.Sleep(time.Millisecond * 100)
        }
        time.Sleep(time.Second)
        f.Close()
    }

下面的代码是过滤error 日志的go routiune。这个函数返回两个通道,主进程写数据到in 通达以后,该go routinue马上启动去过滤该日志,并根据结果情况,确定是否输入到out 通道中。

func Match() (chan<- string, <-chan string) {
    in := make(chan string)
    out := make(chan string)
    go func() {
        for {
            select {
            case s:=<-in:
                if  strings.Contains(s, "[Error]") {
                    out <- s
                }
            }
        }
    }()
    return in, out
}

下面的代码是负责接收out 通道里面的值, 然后调用send函数去处理。send函数里面,我们可以根据我们的需要去报警, 发邮件发钉钉发微信等等都可以。

    go func(s send.Sender, out <-chan string) {
        for {
            err := s.Send(<-out)
            if err != nil {
                return
            }
        }
    }(send.Faker{}, out)
    
    func (f Faker) Send(msg string) error {
    fmt.Print(msg)
    // 发邮件,发微信,发钉钉
    return nil
}

运行效果如下。只需要把日志地址和过滤关键词做成配置文件, 就可以对我们所需要的日志进行监控, 结果可以按照我们的需要发邮件发微信发钉钉等。