工具包目录结构:

.
├── 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. 前端防错以及好用小tips指南总结

    @前端防錯以及好用小tips指南總結 1.一般情況下我們接收到的都是對象格式,某些情況下,需要接到後端傳過來的奇怪的字符串格式的JSON,需要解析成對象,但是有時候他們傳過來的格式有問題,會報錯 解決 ...

  2. 一看就会的 Anaconda 搭建 OpenCV for Python 环境(全平台通用)

    前言 在学习 OpenCV 的时候,需要搭建 OpenCV 的环境并安装一些库,本文就准备了 OpenCV for Python,换而言之就是 OpenCV 的 python 的 API 接口.它拥有 ...

  3. Stream流中的常用方法_skip-Stream流中的常用方法_concat

    Stream流中的常用方法_skip 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流∶ 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流.基本使用: Strea ...

  4. 【开源】libserial_parse_text:命令行解析的基础库

    借助五一假期,写了一个命令行解析的基础库,一般可用于串口命令解析.TCP命令解析等等. 具有以下几种特点: 不涉及到具体硬件, 纯软件协议,与具体硬件分离. 支持不定长命令行,逐个字符解码,可以支持不 ...

  5. STL关联式容器使用注意、概念总结

    引入 继上文 STL序列式容器使用注意.概念总结 继续总结关联式容器的概念以及一些使用事项. 关联式容器与容器适配器 基础容器 STL 中的关联式底层容器:RB tree, hash table,可以 ...

  6. 数据采集之刷cnblog评论

    python代码如下: import random import time import requests cookies = { '__gads': 'ID=3c504aa17c4a7048:T=1 ...

  7. Linux文件常用操作命令

    Linux文件常用操作命令 一.Linux文件和目录简单操作 1.1 查看文件 ls 查看当前目录下的文件 如: -a 显示所有文件及目录 (ls内定将文件名或目录名称开头为"." ...

  8. BAL数据集详解

    详细格式:https://grail.cs.washington.edu/projects/bal/ Bundle Adjustment in the Large Recent work in Str ...

  9. day12-SpringMVC文件上传

    SpringMVC文件上传 1.基本介绍 SpringMVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的.spring 用 Jacarta Com ...

  10. 硬件协议之i2c

    https://blog.csdn.net/ctyqy2015301200079/article/details/83830326  (此文章可能有误) 从目前来看,所有读写操作(包括ACK的读写)都 ...