工具包目录结构:

.
├── 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,并实现热加载配置读取的日志路径的更多相关文章

  1. Laravel核心解读--ENV的加载和读取

    Laravel在启动时会加载项目中的.env文件.对于应用程序运行的环境来说,不同的环境有不同的配置通常是很有用的. 例如,你可能希望在本地使用测试的Mysql数据库而在上线后希望项目能够自动切换到生 ...

  2. 转载 Silverlight实用窍门系列:1.Silverlight读取外部XML加载配置---(使用WebClient读取XAP包同目录下的XML文件))

    转载:程兴亮文章,地址;http://www.cnblogs.com/chengxingliang/archive/2011/02/07/1949579.html 使用WebClient读取XAP包同 ...

  3. as3中xml文件的加载和读取

    ---恢复内容开始--- as代码如下: xml如下: 总结: 用URLReuqest对象加载xml的url 创建一个URLLoader对象,将1中的URLRequest指定给他 给URLLoader ...

  4. java 加载并读取Properties 文件

    1 .系统自带的application.properties  (以下代码仅供参考,不能粘贴复制) 假设application.properties文件有下面两个值: come.test.name = ...

  5. Silverlight实用窍门系列:1.Silverlight读取外部XML加载配置---(使用WebClient读取XAP包同目录下的XML文件))【附带实例源码】

    使用WebClient读取XAP包同目录下的XML文件 我们想要读取XAP包下面的XML文件,需要将此XML文件放在加载XAP包的网页的目录中去,然后使用URI方式读取此URL方式下的XML文件. 首 ...

  6. K8S学习笔记之使用Fluent-bit将容器标准输入和输出的日志发送到Kafka

    0x00 概述 K8S内部署微服务后,对应的日志方案是不落地方案,即微服务的日志不挂在到本地数据卷,所有的微服务日志都采用标准输入和输出的方式(stdin/stdout/stderr)存放到管道内,容 ...

  7. CentOS6.9安装Filebeat监控Nginx的访问日志发送到Kafka

    一.下载地址: 官方:https://www.elastic.co/cn/downloads/beats/filebeat 百度云盘:https://pan.baidu.com/s/1dvhqb0 二 ...

  8. 1. Spring基于xml加载和读取properties文件配置

    在src目录下,新建test.properties配置文件,内容如下 name=root password=123456 logArchiveCron=0/5 * * * * ? 一种是使用sprin ...

  9. Java读取Properties文件 Java加载配置Properties文件

    static{ Properties prop = new Properties(); prop.load(Thread.currentThread().getContextClassLoader() ...

  10. 可能是Asp.net Core On host、 docker、kubernetes(K8s) 配置读取的最佳实践

    写在前面 为了不违反广告法,我竭尽全力,不过"最佳实践"确是标题党无疑,如果硬要说的话 只能是个人最佳实践. 问题引出 ​ 可能很多新手都会遇到同样的问题:我要我的Asp.net ...

随机推荐

  1. 听说你想用免费的FOFA?

    非付费会员,fofa数据无限抓取版,配置普通用户cookie即可使用 FOFA的采集工具都陆续转变成了通过官方的API接口进行获取,例如狼组的fofa_viewer 蒽,好像已经没有我这种老年人的生存 ...

  2. 使用字节流丢读取中文的问题-Reader类

    使用字节流丢读取中文的问题 当使用字节流读取文本文件时,可能会有一个小问题.就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储.所以Java提供一些字符流类,以字符为 ...

  3. Unity之生成扫描二维码

    Unity之生成扫描二维码 Unity之生成扫描二维码 前言 开篇 Unity版本及使用插件 正题 前期准备 首先生成二维码 然后需要扫描二维码 该使用了 挂载脚本绑定按钮和输入框 运行内容 生成二维 ...

  4. Unity之语音识别

    Unity之语音识别 前言 开篇 Unity版本及使用插件 正题 写脚本 挂载到游戏场景中 结尾 唠家常 今日无推荐 前言 开篇 今儿心情好,哈哈哈哈哈 今天小黑给大家带来Unity的语音识别功能,超 ...

  5. 关于C#中async/await的用法

    一直对c#中async/await的用法模模糊糊,不太清晰,今天写了一下Demo彻底明确一下async/await的用法,以免因为对其不了解而对后期的业务产生影响(比如事务导致的锁表等等). 1. 首 ...

  6. 重启系统(等级考试4级 2021-03 T4)

    这道题如果没有一次重启系统的机会就相当于两个最长不下降子序列加在一起. 所以只需要改亿点点即可 把dp分为 dpleft 和 dpright 最长不下降子序列程序:最长上升子序列 II 时间复杂度(n ...

  7. WPF HandyOrg DataGrid 表格内容和标题居中显示

    表格内容居中 对于文本显示列DataGridTextColumn需要设定文本内容水平居中或者水平居右,而不是HandyControl中设定的样式默认显示为居左时,需要继承DataGridCellSty ...

  8. DBeaver连接clickhouse无法下载驱动的情况

    最近遇到dbeaver 连接clickhouse的时候提示下载驱动失败. 在网上找了些方法也不行,其中包括默认下载.配置阿里云的Maven. 最后在网上找到一个驱动包,自己手动添加即可.把下载地址分享 ...

  9. 张量局部保留投影TensorLPP

    Tensor locality preserving projection for hyperspectral image classification 复现的代码python:https://git ...

  10. ASP输出生成Word 、Excel、Txt文件的方法

    在ASP中生成Word文件.Excel文件和Txt文件,参考了微软的官方文档,自己简单弄了下,基本可以实现了,不足之处,望指导!下面言归正传. 1.用ASP生成Word文档,代码示例: 01 < ...