日志采集项目之logagent开发(一)

项目结构

  • 项目分为如下部分:

    • logagent

      • conf: 配置文件
      • kafka: kafka集成模块
      • tailf: 日志读取模块
      • main.go: 程序入口
    • xlog: 日志打印模块,参考https://blog.51cto.com/13812615/2490744
    • oconfig: 配置文件解析模块,参考:https://blog.51cto.com/13812615/2492150
logCollect/
├── logagent
│   ├── conf
│   │   └── config.ini
│   ├── kafka
│   │   └── kafka.go
│   ├── logs
│   │   └── logagent.log
│   ├── main.go
│   └── tailf
│       └── tail.go
├── oconfig
│   └── config.go
└── xlog
    ├── console.go
    ├── file.go
    ├── level.go
    ├── log.go
    ├── log_base.go
    └── tool.go

logagent代码:

  • config/config.ini
[kafka]
address=192.168.56.11:9092
queue_size=10000
[collect_log_conf]
log_filenames=/Users/wanghui/go/src/oldBoy/day11/my.log

[logs]
#level类型有debug,info,trace,warn,error,fatal
log_level=debug
filename=./logs/logagent.log
#log_type=file,console
log_type=console
module=logagent
  • main.go
package main

import (
    "fmt"
    "oldBoy/logagent/kafka"
    "oldBoy/logagent/tailf"
    "oldBoy/oconfig"
    "oldBoy/xlog"
    "strings"
)

var (
    appConfig AppConfig
)

// 配置文件结构体
type AppConfig struct {
    KafkaConf  KafkaConfig              `ini:"kafka"`
    CollectLogConf CollectLogConfig     `ini:"collect_log_conf"`
    LogConf    LogConfig                `ini:"logs"`
}

type KafkaConfig struct {
    Address string  `ini:"address"`
    QueueSize int    `ini:"queue_size"`
}

type CollectLogConfig struct {
    LogFilenames string `ini:"log_filenames"`
}

type LogConfig struct {
    LogLevel    string    `ini:"log_level"`
    Filename    string    `ini:"filename"`
    LogType     string    `ini:"log_type"`
    Module      string    `ini:"module"`
}

func initConfig(filename string) (err error)  {
    err = oconfig.UnMarshalFile(filename,&appConfig)
    if err != nil {
        return
    }
    //打印配置文件内容
    xlog.LogDebug("config:%#v",appConfig)
    return
}

func initLog() (err error)  {
    var logType int
    var level int
    //转换格式
    if appConfig.LogConf.LogType == "console" {
        logType = xlog.XLogTypeConsole
    }else {
        logType = xlog.XLogTypeFile
    }

    switch appConfig.LogConf.LogLevel {
    case "debug":
        level = xlog.XLogLevelDebug
    case "trace":
        level = xlog.XLogLevelTrace
    case "info":
        level = xlog.XLogLevelInfo
    case "warn":
        level = xlog.XLogLevelWarn
    case "error":
        level = xlog.XLogLevelError
    case "fatal":
        level = xlog.XLogLevelFatal
    default:
        level = xlog.XLogLevelDebug
    }
    //初始化日志库
    err = xlog.Init(logType,level,appConfig.LogConf.Filename,appConfig.LogConf.Module)
    return
}

func run() (err error) {
    //1. 从日志文件读取日志
    for {
        line,err := tailf.ReadLine()
        if err != nil {
            continue
        }
        xlog.LogDebug("line:%s",line.Text)
        //2. 发送日志数据到kafka
        msg := &kafka.Message{
            Line:  line.Text,
            Topic: "nginx_log",
        }
        err = kafka.SendLog(msg)
        if err != nil {
            xlog.LogWarn("send log to kafka faild,err:%v",err)
        }
        xlog.LogDebug("send to kafka succ")
    }
    return
}

func main() {
    //初始化配置
    err := initConfig("./conf/config.ini")
    if err != nil {
        panic(fmt.Sprintf("init config faild,err:%v",err))
    }
    //初始化日志库
    err = initLog()
    if err != nil{
        panic(fmt.Sprintf("init logs faild,err:%v",err))
    }
    xlog.LogDebug("init log success")
    //kafka初始化
    address := strings.Split(appConfig.KafkaConf.Address,",")
    err = kafka.Init(address,appConfig.KafkaConf.QueueSize)
    if err != nil {
        panic(fmt.Sprintf("init kafka faild,err:%v",err))
    }
    xlog.LogDebug("init kafka succ")
    //tail初始化
    err = tailf.Init(appConfig.CollectLogConf.LogFilenames)
    if err != nil {
        panic(fmt.Sprintf("init tail faild,err:%v",err))
    }
    xlog.LogDebug("init tailf succ")

    err = run()
    if err != nil {
        xlog.LogError("run faild,err:%v",err)
        return
    }
    xlog.LogDebug("run finished\n")
}
  • kafka/kafka.go
package kafka

import (
    "fmt"
    "github.com/Shopify/sarama"
    "oldBoy/xlog"     //这个取决于goPath的配置
)

var (
    client sarama.SyncProducer
    msgChan chan *Message
)

type Message struct {
    Line string
    Topic string
}

func Init(address []string,chanSize int) (err error){
    //初始化配置
    config := sarama.NewConfig()
    config.Producer.RequiredAcks = sarama.WaitForAll
    config.Producer.Partitioner = sarama.NewRandomPartitioner
    config.Producer.Return.Successes = true

    //连接配置
    client,err = sarama.NewSyncProducer(address,config)
    if err != nil {
        xlog.LogError("send message faild,error:%v",err)
        return
    }
    msgChan = make(chan *Message,chanSize)
    go SendKafka()
    return
}

func SendKafka()  {
    //从管道获取数据,并发送出去
    for msg := range msgChan {
        kafkaMsg := &sarama.ProducerMessage{}
        kafkaMsg.Topic = msg.Topic
        kafkaMsg.Value = sarama.StringEncoder(msg.Line)

        //发送数据
        pid,offset,err := client.SendMessage(kafkaMsg)
        if err != nil {
            xlog.LogError("send message faild,err:%v",err)
            continue   //持续发日志
        }
        xlog.LogDebug("pid:%v,offset:%v",pid,offset)
    }
}

func SendLog(msg *Message) (err error) {
    if len(msg.Line) == 0 {
        //过滤空行
        return
    }
    select {
    case msgChan <- msg:
    default:
        err = fmt.Errorf("chan is full")
    }
    return
}
  • tailf/tail.go
package tailf

import (
    "fmt"
    "github.com/hpcloud/tail"
    "oldBoy/xlog"
)

var (
    tailObj *tail.Tail
)

func Init(filename string) (err error) {
    tailObj,err = tail.TailFile(filename,tail.Config{
        ReOpen: true,
        Follow: true,
        Location: &tail.SeekInfo{Offset: 0, Whence: 2,},
        MustExist: true,
        Poll: true,
    })

    if err != nil {
        xlog.LogError("tail file error:%v",err)
        return
    }
    return
}

func ReadLine() (msg *tail.Line,err error) {
    var ok bool
    msg,ok = <- tailObj.Lines
    if !ok {
        err = fmt.Errorf("read line faild")
        return
    }
    return
}