日志收集的agent开发
配置文件版logagent
ini配置文件解析
cfg , err := ini.Load("./conf/config.ini")
if err != nil {
logrus.Error("load config failed,err:%v", err)
return
}
kafkaAddr := cfg.Section("kafka").Key("address").String()
fmt.Println(kafkaAddr)
初始化kafka
// Init 是初始化全局的kafka Client
func Init(address []string, chanSize int64)(err error){
// 1. 生产者配置
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll // ACK
config.Producer.Partitioner = sarama.NewRandomPartitioner // 分区
config.Producer.Return.Successes = true // 确认
// 2. 连接kafka
client, err = sarama.NewSyncProducer(address, config)
if err != nil {
logrus.Error("kafka:producer closed, err:", err)
return
}
// 初始化MsgChan
MsgChan = make(chan *sarama.ProducerMessage, chanSize)
// 起一个后台的goroutine从msgchan中读数据
go sendMsg()
return
}
初始化tail
func Init(filename string)(err error){
config := tail.Config{
ReOpen: true,
Follow: true,
Location: &tail.SeekInfo{
Offset: 0, Whence: 2},
MustExist: false,
Poll: true,
}
// 打开文件开始读取数据
TailObj, err = tail.TailFile(filename, config)
if err != nil {
logrus.Error("tailfile: create tailObj for path:%s failed, err:%v\n", filename, err)
return
}
return
}
收集日志发送到kafka
func run ()(err error){
// logfile --> TailObj --> log --> Client --> kafka
for {
// 循环读数据
line, ok := <-tailfile.TailObj.Lines // chan tail.Line
if !ok {
logrus.Warn("tail file close reopen, filename:%s\n", tailfile.TailObj.Filename)
time.Sleep(time.Second) // 读取出错等一秒
continue
}
// 利用通道将同步的代码改为异步的
// 把读出来的一行日志包装秤kafka里面的msg类型
msg := &sarama.ProducerMessage{
}
msg.Topic = "web_log"
msg.Value = sarama.StringEncoder(line.Text)
// 丢到通道中
kafka.MsgChan <- msg
}
}
小结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aEesljA1-1614934947142)(assets/G$9ST@YGHTUJ}BP%%{%LEX.png)]
介绍etcd
类似于zookeeper, etcd\consul
详见etcd.PDF
课件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nGvksGO7-1614934947146)(assets/(I@0WM]RUH2@%R1JL3K)]E3.png)
etcd搭建
详见腾讯文档:https://docs.qq.com/doc/DTndrQXdXYUxUU09O?opendocxfrom=admin
Go操作etcd
注意 put是client/V3版本的命令!!!
如果使用etcdctl.exe
来操作etcd的话,记得要设置环境变量:
SET ETCDCTL_API=3
Mac&Linux:
export ETCDCTL_API=3
put和set
func main(){
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{
"127.0.0.1:2379"},
DialTimeout:time.Second*5,
})
if err != nil {
fmt.Printf("connect to etcd failed, err:%v", err)
return
}
defer cli.Close()
// put
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
_, err = cli.Put(ctx, "s4", "真好")
if err != nil {
fmt.Printf("put to etcd failed, err:%v", err)
return
}
cancel()
// get
ctx, cancel = context.WithTimeout(context.Background(), time.Second)
gr, err := cli.Get(ctx, "s4")
if err != nil {
fmt.Printf("get from etcd failed, err:%v", err)
return
}
for _, ev := range gr.Kvs{
fmt.Printf("key:%s value:%s\n", ev.Key, ev.Value)
}
cancel()
}
watch
监控etcd中key的变化(创建\更改\删除)
func main(){
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{
"127.0.0.1:2379"},
DialTimeout:time.Second*5,
})
if err != nil {
fmt.Printf("connect to etcd failed, err:%v", err)
return
}
defer cli.Close()
// watch
watchCh := cli.Watch(context.Background(), "s4")
for wresp := range watchCh{
for _, evt := range wresp.Events{
fmt.Printf("type:%s key:%s value:%s\n", evt.Type, evt.Kv.Key, evt.Kv.Value)
}
}
}
kafka消费
package main
import (
"fmt"
"github.com/Shopify/sarama"
"sync"
)
// kafka consumer(消费者)
func main(){
// 创建新的消费者
consumer, err:= sarama.NewConsumer([]string{
"127.0.0.1:9092"}, nil)
if err != nil {
fmt.Printf("fail to start consumer, err:%v\n", err)
return
}
// 拿到指定topic下面的所有分区列表
partitionList, err := consumer.Partitions("web_log") // 根据topic取到所有的分区
if err != nil {
fmt.Printf("fail to get list of partition:err%v\n", err)
return
}
fmt.Println(partitionList)
var wg sync.WaitGroup
for partition := range partitionList{
// 遍历所有的分区
// 针对每个分区创建一个对应的分区消费者
pc, err := consumer.ConsumePartition("web_log", int32(partition),sarama.OffsetNewest)
if err != nil {
fmt.Printf("failed to start consumer for partition %d,err:%v\n",
partition, err)
return
}
defer pc.AsyncClose()
// 异步从每个分区消费信息
wg.Add(1)
go func(sarama.PartitionConsumer){
for msg:=range pc.Messages(){
fmt.Printf("Partition:%d Offset:%d Key:%s Value:%s",
msg.Partition, msg.Offset, msg.Key, msg.Value)
}
}(pc)
}
wg.Wait()
}
总结
之前版本的logagent还存在以下问题:
- 只能读取一个日志文件,不支持多个日志文件
- 无法管理日志的topic
思路:
扫描二维码关注公众号,回复:
12677171 查看本文章

用etcd存储要收集的日志项,使用json格式数据:
[
{
"path": "d:\logs\s4.log",
"topic": "s4_log",
},
{
"path": "e:\logs\web.log",
"topic": "web_log",
},
]
lagagent使用etcd管理收集项
Init
func Init(address []string)(err error){
client, err = clientv3.New(clientv3.Config{
Endpoints: address,
DialTimeout:time.Second*5,
})
if err != nil {
fmt.Printf("connect to etcd failed, err:%v", err)
return
}
return
}
GetConf
// 拉取日志收集配置项的函数
func GetConf(key string)(collectEntryList []collectEntry, err error){
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
resp, err := client.Get(ctx, key)
if err != nil {
logrus.Errorf("get conf from etcd by key:%s failed ,err:%v",key, err)
return
}
if len(resp.Kvs) == 0 {
logrus.Warningf("get len:0 conf from etcd by key:%s",key)
return
}
ret := resp.Kvs[0]
// ret.Value // json格式字符串
fmt.Println(ret.Value)
err = json.Unmarshal(ret.Value, &collectEntryList)
if err != nil {
logrus.Errorf("json unmarshal failed, err:%v", err)
return
}
return
}
为每个单独的配置项启动tailTask
管理日志收集项
程序启动之后,拉去了最新的配置之后,就应该派一个小弟去监控etcd中 collect_log_conf
这个key的变化
暂留的问题
如果logagent停了需要记录上一次的位置,参考filebeat