LogCollector01:LogAgent的设计

日志收集的agent开发

流程总结:

  1. ini.MapTo() 加载配置文件
  2. kafka.Init()
    2.1 sarama.NewSyncProducer()
    初始化kafka生产者client,并连接kafka
    2.2 msgChan = make(chan *sarama.ProducerMessage, chanSize)
    初始化存放日志数据的channel 将读日志和发送日志改为异步执行(channel)
    2.3 go sendMsg()
    后台goroutine 从msgChan中接收日志数据发送到kafka
  3. etcd.Init()
    3.1 clientv3.New()
    初始化一个连接etcd的client
  4. etcd.GetConf()
    从etcd中拉取要收集日志的配置项。(我要去哪个目录下取什么数据,发送到哪个topic下面)
    即去etcd中根据给定的key获取配置文件 配置文件是一个切片 切片中的每一项是日志文件的路径和topic
  5. go etcd.WatchConf()
    启动一个后台的goroutine 监听etcd中的日志收集项是否发生变化
  6. tailfile.Init()。
    之前通过GetConf()拿到配置文件,根据配置项读取日志。有一个配置项给一个tailobj。
    6.1 ttMgr = &tailTaskMgr{}
    创建一个全局的tailTask管理者。因为如果有配置项变更,需要去管理,如新建、关闭等。
    6.2 遍历配置项,newTailTask(conf.Path, conf.Topic)
    遍历传过来的配置 每有一个配置项就启动一个tailTask 启动一个后台的goroutine去执行日志收集
    6.3 go ttMgr.watch()
    启动后台的goroutine去等新的配置来 从一个无缓冲的channel中接收新配置。新配置来的话有三种情况:原来存在的,不管;原来没有,创建;原来有现在没有,停掉。

配置文件版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中读数据(main函数中的通道)
	go sendMsg()
	return
}

DATA := <-CHAN ,和向channel传入数据相反,在数据输送箭头的右侧的是channel,形象地展现了数据从‘隧道’流出到变量里。
go sendMsg()
在这里插入图片描述
利用select取channel,然后去除msg的值发送给kafka(SendMessage)

初始化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
	}
}

logfile --> TailObj --> log --> Client --> kafka
从日志logfile通过tailobj读取出一行,包装成kafka认识的一个msg对象,扔到通道中。
为什么要扔到通道?因为如果直接调用发送消息的函数,相当于编程for循环里取一行日志文件,包装成函数,然后再调用函数,相当于函数调用函数,是同步的过程,容易阻塞。

main函数:
在这里插入图片描述

小结

介绍etcd

类似于zookeeper, etcd\consul
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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还存在以下问题:

  1. 只能读取一个日志文件,不支持多个日志文件。
  2. 无法管理日志的topic

思路:

用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流程梳理

logagent流程

暂留的问题

如果logagent停了需要记录上一次的位置,参考filebeat。

不同服务器日志存放的位置可能不同,需要注意。它们的配置通过服务器ip来区分。
在这里插入图片描述
config.ini中的collect_key里放一个占位符,动态分配ip。

每台服务器上的logagent的收集项可能都不一致,我们需要让logagent去etcd中根据IP获取自己的配置

如何获取本机的IP

net.InterfaceAddrs() 用net库里的包

//方法一
func GetLocalIP() (ip string, err error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return
	}
	for _, addr := range addrs {
		ipAddr, ok := addr.(*net.IPNet) // 类型断言,addr是接口类型,如果是括号里的类型则返回true,否则false
		if !ok {
			continue
		}
		//三步过滤掉。
		if ipAddr.IP.IsLoopback() {
			continue
		}

		if !ipAddr.IP.IsGlobalUnicast() {
			continue
		}
		fmt.Println(ipAddr)
		return ipAddr.IP.String(), nil
	}
	return
}

//方法二
// Get preferred outbound ip of this machine
func GetOutboundIP() string {
	//udp拨号,但不会真建立请求。tcp也可
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)
	fmt.Println(localAddr.String())
	return localAddr.IP.String()
}

logagent中集成根据ip拉取配置

在这里插入图片描述

etcd中配置的key要注意使用IP

在这里插入图片描述

今日内容

gopsutil包

博客地址

influxDB时序数据库

安装

官网下载页面

Windows

下载链接:https://dl.influxdata.com/influxdb/releases/influxdb-1.7.7_windows_amd64.zip

Mac

下载链接:https://dl.influxdata.com/influxdb/releases/influxdb-1.7.7_darwin_amd64.tar.gz

或者:

brew update
brew install influxdb
基本命令

官方文档:https://docs.influxdata.com/influxdb/v1.7/introduction/getting-started

grafana

展示数据的工具,监控数据可视化

  • 搜索引擎找官网
下载

下载地址:https://grafana.com/grafana/download

安装

解压

conf/sample.ini复制一份然后重命名为conf/custom.ini

在解压目录下,执行

bin\grafana-server.exe 默认在本机的:3000端口启动

默认账号密码都是:admin

grafana安装插件

浏览插件仓库:https://grafana.com/grafana/plugins

选择自己要安装的插件,然后按照提示安装即可.

例如,要安装饼图插件:https://grafana.com/grafana/plugins/grafana-piechart-panel

在grafana目录下面,执行以下命令:

grafana-cli.exe plugins install grafana-piechart-panel

然后重启grafana即可.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值