LogAgent —— etcd+kafka+zookeeper+go实现实时读取日志发送到kafka,并实现热加载配置读取的日志路径
工具包目录结构:
.
├── conf
│ ├── logAgent.ini
│ └── logAgentConfig.go
├── etcd
│ └── etcd.go
├── kafka
│ └── kafka.go
├── main.go
└── taillog
├── taillog.go
└── taillog_mgr.go
logAgent.ini
1 [kafka]
2 address=127.0.0.1:9092
3 chan_max_size=100000
4
5 [etcd]
6 address=127.0.0.1:2379
7 timeout=5
8 collect_log_key=xxx
9
10 [taillog]
11 filename="./my.log"
logAgentConf.go
1 /**
2 * @Author: Mr.Cheng
3 * @Description:
4 * @File: logAgentConfig
5 * @Version: 1.0.0
6 * @Date: 2021/12/9 下午8:44
7 */
8
9 package logAgentConfig
10
11 type AppConf struct {
12 KafkaConf `ini:"kafka"`
13 EtcdConf `ini:"etcd"`
14 // TaillogConf `ini:"taillog"`
15 }
16
17 type KafkaConf struct {
18 Address string `ini:"address"`
19 Size int `ini:"chan_max_size"`
20 }
21
22 type EtcdConf struct {
23 Address string `ini:"address"`
24 Timeout int `ini:"timeout"`
25 Key string `ini:"collect_log_key"`
26 }
27
28 // ----- unused ↓️----
29
30 type TaillogConf struct {
31 FileName string `ini:"filename"`
32 }
etcd.go
1 /**
2 * @Author: Mr.Cheng
3 * @Description:
4 * @File: etcd
5 * @Version: 1.0.0
6 * @Date: 2021/12/9 下午9:12
7 */
8 package etcd
9
10 import (
11 "context"
12 "encoding/json"
13 "fmt"
14 "go.etcd.io/etcd/clientv3"
15 "time"
16 )
17
18 var (
19 client *clientv3.Client
20 )
21
22 type LogEntry struct {
23 Path string `json:"path"` // 日志存放的路径
24 Topic string `json:"topic"` // 日志要发往kafka的topic
25 }
26
27 func Init(address string, interval int) (err error) {
28 client, err = clientv3.New(clientv3.Config{
29 Endpoints: []string{address},
30 DialTimeout: time.Duration(interval) * time.Second,
31 })
32 if err != nil {
33 fmt.Printf("connect to etcd failed, err:%v\n", err)
34 return err
35 }
36 return
37 }
38
39 // 从Etcd中根据Key获取配置项
40 func GetConf(key string) (LogEntryConf []*LogEntry, err error) {
41 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
42 resp, err := client.Get(ctx, key)
43 cancel()
44 if err != nil {
45 fmt.Printf("get from etcd failed, err:%v\n", err)
46 return nil, err
47 }
48 for _, ev := range resp.Kvs {
49 //fmt.Printf("%s:%s\n", ev.Key, ev.Value)
50 err = json.Unmarshal(ev.Value, &LogEntryConf)
51 if err != nil {
52 fmt.Printf("unmarshal etcd value failed, err:%v\n", err)
53 return nil, err
54 }
55 }
56 return LogEntryConf, nil
57 }
58
59 // etcd watch
60 func WatchConf(key string, newConfChan chan<- []*LogEntry) {
61 ch := client.Watch(context.Background(), key)
62 for wresp := range ch {
63 for _, evt := range wresp.Events {
64 fmt.Printf("Type:%v key:%v value:%v\n", evt.Type, string(evt.Kv.Key), string(evt.Kv.Value))
65 var newConf []*LogEntry
66 // 如果是删除操作,json.Unmarshal会报错,需手动添加一个空的newConf
67 if evt.Type != clientv3.EventTypeDelete {
68 err := json.Unmarshal(evt.Kv.Value, &newConf)
69 if err != nil {
70 fmt.Printf("unmarshal new conf failed, err:%v\n", err)
71 continue
72 }
73 }
74 newConfChan <- newConf
75 }
76 }
77 }
kafka.go
1 /**
2 * @Author: Mr.Cheng
3 * @Description:往kafka写入日志
4 * @File: kafka
5 * @Version: 1.0.0
6 * @Date: 2021/12/9 下午2:19
7 */
8
9 package kafka
10
11 import (
12 "fmt"
13 "github.com/Shopify/sarama"
14 "time"
15 )
16
17 type logData struct {
18 Topic string
19 Data string
20 }
21
22 var (
23 client sarama.SyncProducer // 全局连接kafka的生产者
24 logDataChan chan *logData
25 )
26
27 // 初始化连接
28 func Init(address []string, size int) (err error) {
29 config := sarama.NewConfig()
30 config.Producer.RequiredAcks = sarama.WaitForAll // 发送模式(需leader和follow都确认)
31 config.Producer.Partitioner = sarama.NewRandomPartitioner // 选择分区的方式(轮询)
32 config.Producer.Return.Successes = true // 成功交付的消息将在success channel中返回
33
34 // 连接kafka
35 client, err = sarama.NewSyncProducer(address, config)
36 if err != nil {
37 fmt.Printf("client kafka failed, err:%v\n", err)
38 return err
39 }
40
41 // 初始化logDataChan
42 logDataChan = make(chan *logData, size)
43
44 // 从logDataChan中取数据发往kafaka
45 go sendToKafka()
46 return nil
47 }
48
49 func SendToChan(Topic, Data string) {
50 data := &logData{
51 Topic: Topic,
52 Data: Data,
53 }
54 select {
55 case logDataChan <- data:
56 default:
57 time.Sleep(time.Millisecond * 100)
58 }
59 }
60
61 func sendToKafka() {
62 // 循环从通道logDataChan取值并发送给kafka
63 for {
64 select {
65 case data := <-logDataChan:
66 msg := &sarama.ProducerMessage{}
67 msg.Topic = data.Topic
68 msg.Value = sarama.StringEncoder(data.Data)
69 pid, offset, err := client.SendMessage(msg)
70 if err != nil {
71 fmt.Printf("send msg failed, err:%v\n", err)
72 }
73 fmt.Printf("send msg success, pid:%v offect:%v\n", pid, offset)
74 default:
75 time.Sleep(time.Millisecond * 50)
76 }
77 }
78 }
taillog.go
1 /**
2 * @Author: Mr.Cheng
3 * @Description:收集日志模块
4 * @File: taillog
5 * @Version: 1.0.0
6 * @Date: 2021/12/8 下午9:54
7 */
8
9 package taillog
10
11 import (
12 "context"
13 "day21/02.log_agent/kafka"
14 "fmt"
15 "github.com/hpcloud/tail"
16 "time"
17 )
18
19 type TailTask struct {
20 Path string
21 Topic string
22 Instance *tail.Tail
23 // 为了停止任务,存下context
24 ctx context.Context
25 cancel context.CancelFunc
26 }
27
28 func NewTailTask(Path, Topic string) (tailtask *TailTask, err error) {
29 config := tail.Config{
30 Location: &tail.SeekInfo{Offset: 0, Whence: 2}, // 从文件那个地方开始读
31 ReOpen: true, // 重新打开
32 MustExist: false, // 文件不存在不报错
33 Poll: true,
34 Follow: true, // 是否跟随
35 }
36 ctx, cancel := context.WithCancel(context.Background())
37 tailObj, err := tail.TailFile(Path, config)
38 if err != nil {
39 fmt.Printf("tail file failed, err:%v\n", err)
40 return nil, err
41 }
42 tailtask = &TailTask{Path: Path, Topic: Topic, Instance: tailObj, ctx: ctx, cancel: cancel}
43 // 开启读取日志并发送给kafka
44 go tailtask.ReadFromTail()
45 return tailtask, nil
46 }
47
48 func (tailtask *TailTask) ReadFromTail() {
49 for {
50 select {
51 case <-tailtask.ctx.Done():
52 return
53 case line, ok := <-tailtask.Instance.Lines:
54 if !ok {
55 fmt.Printf("tail fail close reopen, filename:%s\n", tailtask.Path)
56 time.Sleep(time.Second)
57 continue
58 }
59 kafka.SendToChan(tailtask.Topic, line.Text)
60 default:
61 time.Sleep(time.Second)
62 }
63 }
64 }
taillog_mgr.go
1 /**
2 * @Author: Mr.Cheng
3 * @Description:
4 * @File: taillogMgr
5 * @Version: 1.0.0
6 * @Date: 2021/12/14 下午3:47
7 */
8
9 package taillog
10
11 import (
12 "day21/02.log_agent/etcd"
13 "fmt"
14 "time"
15 )
16
17 type TailMgr struct {
18 logEntry []*etcd.LogEntry
19 tskMap map[string]*TailTask
20 newConfChan chan []*etcd.LogEntry
21 }
22
23 var tskMgr *TailMgr
24
25 // 循环每一个日志收集项,创建tailObj,并发往kafka
26 func Init(logEntryConf []*etcd.LogEntry) {
27 tskMgr = &TailMgr{
28 logEntry: logEntryConf,
29 tskMap: make(map[string]*TailTask, 16),
30 newConfChan: make(chan []*etcd.LogEntry),
31 }
32
33 for _, LogEntry := range logEntryConf {
34 // fmt.Printf("Path:%v Topic:%v\n", LogEntry.Path, LogEntry.Topic)
35 tailtask, err := NewTailTask(LogEntry.Path, LogEntry.Topic)
36 if err != nil {
37 continue
38 }
39 // 在tskMap中存储一下,以便发生配置变更时做增删改操作
40 key := fmt.Sprintf("%s_%s", tailtask.Path, tailtask.Topic)
41 tskMgr.tskMap[key] = tailtask
42 }
43
44 go tskMgr.run()
45 }
46
47 // 监听newConfChan是否有数据,有数据则表示etcd配置有变化,需做相应的处理
48 func (t *TailMgr) run() {
49 for {
50 select {
51 case newConf := <- t.newConfChan:
52 fmt.Printf("配置发生变更,Conf:%v\n", newConf)
53 // 找出新增项
54 for _, logEntry := range newConf {
55 key := fmt.Sprintf("%s_%s", logEntry.Path, logEntry.Topic)
56 _, ok := t.tskMap[key]
57 if ok {
58 // 表示该配置项原先存在
59 continue
60 } else {
61 // 属于新增配置
62 fmt.Printf("新增项,path:%s topic:%s\n", logEntry.Path, logEntry.Topic)
63 tailtask, err := NewTailTask(logEntry.Path, logEntry.Topic)
64 if err != nil {
65 continue
66 }
67 // TailMgr的logEntry和tskMap增加对应项
68 t.logEntry = append(t.logEntry, logEntry)
69 t.tskMap[key] = tailtask
70 go tailtask.ReadFromTail()
71 }
72 }
73 // 找出删除项
74 for index, c1 := range t.logEntry {
75 isDelete := true
76 for _, c2 := range newConf {
77 if c1.Path == c2.Path && c1.Topic == c2.Topic {
78 isDelete = false
79 break
80 }
81 }
82 if isDelete{
83 // 表示属于删除项,从tskMap拿出tailtask对象,执行对象的cancel函数,并将该对象从tskMap中删除
84 fmt.Printf("删除项,path:%s topic:%s\n", c1.Path, c1.Topic)
85 key := fmt.Sprintf("%s_%s", c1.Path, c1.Topic)
86 t.tskMap[key].cancel()
87 // TailMgr的logEntry和tskMap删除对应项
88 delete(t.tskMap, key)
89 t.logEntry = append(t.logEntry[:index], t.logEntry[index + 1:]...)
90 }
91 }
92 default:
93 time.Sleep(time.Second)
94 }
95 }
96 }
97
98 // 向外暴露newConfChan
99 func NewConfChan() chan<- []*etcd.LogEntry{
100 return tskMgr.newConfChan
101 }
main.go
1 /**
2 * @Author: Mr.Cheng
3 * @Description:
4 * @File: main
5 * @Version: 1.0.0
6 * @Date: 2021/12/9 下午8:43
7 */
8
9 package main
10
11 import (
12 logAgentConfig "day21/02.log_agent/conf"
13 "day21/02.log_agent/etcd"
14 "day21/02.log_agent/kafka"
15 "day21/02.log_agent/taillog"
16 "fmt"
17 "gopkg.in/ini.v1"
18 "sync"
19 )
20
21 var (
22 cfg = new(logAgentConfig.AppConf)
23 wg sync.WaitGroup
24 )
25
26 func main() {
27 // 加载配置文件
28 err := ini.MapTo(cfg, "./conf/logAgent.ini")
29 if err != nil {
30 fmt.Printf("load ini failed, err:%v\n", err)
31 return
32 }
33
34 // 初始化kafka连接
35 err = kafka.Init([]string{cfg.KafkaConf.Address}, cfg.KafkaConf.Size)
36 if err != nil {
37 return
38 }
39 fmt.Println("init kafka success")
40
41 // 初始化etcd
42 err = etcd.Init(cfg.EtcdConf.Address, cfg.EtcdConf.Timeout)
43 if err != nil {
44 return
45 }
46 fmt.Println("init etcd success")
47
48 // 从etcd中获取日志收集项的配置信息
49 logEntryConf, err := etcd.GetConf(cfg.EtcdConf.Key)
50 if err != nil {
51 return
52 }
53 fmt.Printf("get conf from etcd success, conf:%v\n", logEntryConf)
54
55 // 收集日志发往kafka
56 // 循环每一个日志收集项,创建tailObj,并发往kafka
57 taillog.Init(logEntryConf)
58
59 // 监视etcd中配置的变动,如有变动,给新的配置信息给taillog
60 wg.Add(1)
61 go etcd.WatchConf(cfg.EtcdConf.Key, taillog.NewConfChan())
62 wg.Wait()
63 }
LogAgent —— etcd+kafka+zookeeper+go实现实时读取日志发送到kafka,并实现热加载配置读取的日志路径的更多相关文章
- Laravel核心解读--ENV的加载和读取
Laravel在启动时会加载项目中的.env文件.对于应用程序运行的环境来说,不同的环境有不同的配置通常是很有用的. 例如,你可能希望在本地使用测试的Mysql数据库而在上线后希望项目能够自动切换到生 ...
- 转载 Silverlight实用窍门系列:1.Silverlight读取外部XML加载配置---(使用WebClient读取XAP包同目录下的XML文件))
转载:程兴亮文章,地址;http://www.cnblogs.com/chengxingliang/archive/2011/02/07/1949579.html 使用WebClient读取XAP包同 ...
- as3中xml文件的加载和读取
---恢复内容开始--- as代码如下: xml如下: 总结: 用URLReuqest对象加载xml的url 创建一个URLLoader对象,将1中的URLRequest指定给他 给URLLoader ...
- java 加载并读取Properties 文件
1 .系统自带的application.properties (以下代码仅供参考,不能粘贴复制) 假设application.properties文件有下面两个值: come.test.name = ...
- Silverlight实用窍门系列:1.Silverlight读取外部XML加载配置---(使用WebClient读取XAP包同目录下的XML文件))【附带实例源码】
使用WebClient读取XAP包同目录下的XML文件 我们想要读取XAP包下面的XML文件,需要将此XML文件放在加载XAP包的网页的目录中去,然后使用URI方式读取此URL方式下的XML文件. 首 ...
- K8S学习笔记之使用Fluent-bit将容器标准输入和输出的日志发送到Kafka
0x00 概述 K8S内部署微服务后,对应的日志方案是不落地方案,即微服务的日志不挂在到本地数据卷,所有的微服务日志都采用标准输入和输出的方式(stdin/stdout/stderr)存放到管道内,容 ...
- CentOS6.9安装Filebeat监控Nginx的访问日志发送到Kafka
一.下载地址: 官方:https://www.elastic.co/cn/downloads/beats/filebeat 百度云盘:https://pan.baidu.com/s/1dvhqb0 二 ...
- 1. Spring基于xml加载和读取properties文件配置
在src目录下,新建test.properties配置文件,内容如下 name=root password=123456 logArchiveCron=0/5 * * * * ? 一种是使用sprin ...
- Java读取Properties文件 Java加载配置Properties文件
static{ Properties prop = new Properties(); prop.load(Thread.currentThread().getContextClassLoader() ...
- 可能是Asp.net Core On host、 docker、kubernetes(K8s) 配置读取的最佳实践
写在前面 为了不违反广告法,我竭尽全力,不过"最佳实践"确是标题党无疑,如果硬要说的话 只能是个人最佳实践. 问题引出 可能很多新手都会遇到同样的问题:我要我的Asp.net ...
随机推荐
- echart大坑总结~~echarts呈现的数据刷新不完全/echarts画图时tooltip不会更新
谨以此文记录在使用echarts中遇到的各种大坑或者小坑,陆续更新加入新坑 欢迎朋友们一起提坑,我们势必绕过它们,愿我们永不再入坑~ 坑一:echarts图表重新赋值数据,图表显示会存在一部分原来的数 ...
- elk引入redis
1.引入日志依赖 <dependency> <groupId>com.cwbase</groupId> <artifactId>logback-redi ...
- 【unity萌新第一步】Unity的Hello World(适合小白)
Unity萌新的第一步:使用unity写第一个Hello World IT界有个笑话:"我擅长用各种语言写Hello World".我讲这个笑话的目的是,指出:写一个HelloWo ...
- 【多线程与高并发】- synchronized锁的认知
synchronized锁的认知 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目录 s ...
- LAMP环境搭建——最详细的手工编译
环境:阿里云服务器ECS,Alibaba Cloud Linux 3.2104 LTS 64位 ,2核(vCPU) 2 GiB LAMP 是搭建Web应用时最常用的环境,LAMP 分别表示 Linux ...
- Vulhub 漏洞学习之:Django
Vulhub 漏洞学习之:Django 目录 Vulhub 漏洞学习之:Django 1 Django debug page XSS漏洞(CVE-2017-12794) 1.1 漏洞利用过程 2 Dj ...
- 超声能指导持续临床缓解的长病程RA患者减停TNF抑制剂吗
标签:类风湿关节炎; TNF拮抗剂; 超声缓解; TNFi减停 超声能指导持续临床缓解的长病程RA患者减停TNF抑制剂吗 电邮发布日期: 2016年2月25日 截至目前,针对类风湿关节炎(RA),尚未 ...
- CCRD_TOC_2008年第9和第10期
中信国健临床通讯 2008年第9.第10期合刊 目 录 类风湿关节炎 1. 关于RA临床缓解与关节持续破坏相分离现象的一种解释 Brown AK, et al. Arthritis Rheum. ...
- 脚本之美│VBS 入门交互实战
目录 什么是 VBS 第一个 VBS 脚本 msgbox 语法 中文乱码 弹窗交互功能 表白恶搞 什么是 VBS VBS 是一种 Windows 脚本语言,全称是 Microsoft Visual B ...
- C# 元组类型和元组文本
从 C# 7.0 开始,可以使用元组类型和元组文本轻松实现此目的. 元组类型定义元 组元素的数据类型. 元组文本提供返回的元组的实际值. 在下面的示例中, (string, string, strin ...