工具包目录结构:

.
├── conf
│   ├── logAgent.ini
│   └── logAgentConfig.go
├── etcd
│   └── etcd.go
├── kafka
│   └── kafka.go
├── main.go
└── taillog
    ├── taillog.go
    └── taillog_mgr.go

logAgent.ini

  1. 1 [kafka]
  2. 2 address=127.0.0.1:9092
  3. 3 chan_max_size=100000
  4. 4
  5. 5 [etcd]
  6. 6 address=127.0.0.1:2379
  7. 7 timeout=5
  8. 8 collect_log_key=xxx
  9. 9
  10. 10 [taillog]
  11. 11 filename="./my.log"

logAgentConf.go

  1. 1 /**
  2. 2 * @Author: Mr.Cheng
  3. 3 * @Description:
  4. 4 * @File: logAgentConfig
  5. 5 * @Version: 1.0.0
  6. 6 * @Date: 2021/12/9 下午8:44
  7. 7 */
  8. 8
  9. 9 package logAgentConfig
  10. 10
  11. 11 type AppConf struct {
  12. 12 KafkaConf `ini:"kafka"`
  13. 13 EtcdConf `ini:"etcd"`
  14. 14 // TaillogConf `ini:"taillog"`
  15. 15 }
  16. 16
  17. 17 type KafkaConf struct {
  18. 18 Address string `ini:"address"`
  19. 19 Size int `ini:"chan_max_size"`
  20. 20 }
  21. 21
  22. 22 type EtcdConf struct {
  23. 23 Address string `ini:"address"`
  24. 24 Timeout int `ini:"timeout"`
  25. 25 Key string `ini:"collect_log_key"`
  26. 26 }
  27. 27
  28. 28 // ----- unused ↓️----
  29. 29
  30. 30 type TaillogConf struct {
  31. 31 FileName string `ini:"filename"`
  32. 32 }

etcd.go

  1. 1 /**
  2. 2 * @Author: Mr.Cheng
  3. 3 * @Description:
  4. 4 * @File: etcd
  5. 5 * @Version: 1.0.0
  6. 6 * @Date: 2021/12/9 下午9:12
  7. 7 */
  8. 8 package etcd
  9. 9
  10. 10 import (
  11. 11 "context"
  12. 12 "encoding/json"
  13. 13 "fmt"
  14. 14 "go.etcd.io/etcd/clientv3"
  15. 15 "time"
  16. 16 )
  17. 17
  18. 18 var (
  19. 19 client *clientv3.Client
  20. 20 )
  21. 21
  22. 22 type LogEntry struct {
  23. 23 Path string `json:"path"` // 日志存放的路径
  24. 24 Topic string `json:"topic"` // 日志要发往kafka的topic
  25. 25 }
  26. 26
  27. 27 func Init(address string, interval int) (err error) {
  28. 28 client, err = clientv3.New(clientv3.Config{
  29. 29 Endpoints: []string{address},
  30. 30 DialTimeout: time.Duration(interval) * time.Second,
  31. 31 })
  32. 32 if err != nil {
  33. 33 fmt.Printf("connect to etcd failed, err:%v\n", err)
  34. 34 return err
  35. 35 }
  36. 36 return
  37. 37 }
  38. 38
  39. 39 // 从Etcd中根据Key获取配置项
  40. 40 func GetConf(key string) (LogEntryConf []*LogEntry, err error) {
  41. 41 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
  42. 42 resp, err := client.Get(ctx, key)
  43. 43 cancel()
  44. 44 if err != nil {
  45. 45 fmt.Printf("get from etcd failed, err:%v\n", err)
  46. 46 return nil, err
  47. 47 }
  48. 48 for _, ev := range resp.Kvs {
  49. 49 //fmt.Printf("%s:%s\n", ev.Key, ev.Value)
  50. 50 err = json.Unmarshal(ev.Value, &LogEntryConf)
  51. 51 if err != nil {
  52. 52 fmt.Printf("unmarshal etcd value failed, err:%v\n", err)
  53. 53 return nil, err
  54. 54 }
  55. 55 }
  56. 56 return LogEntryConf, nil
  57. 57 }
  58. 58
  59. 59 // etcd watch
  60. 60 func WatchConf(key string, newConfChan chan<- []*LogEntry) {
  61. 61 ch := client.Watch(context.Background(), key)
  62. 62 for wresp := range ch {
  63. 63 for _, evt := range wresp.Events {
  64. 64 fmt.Printf("Type:%v key:%v value:%v\n", evt.Type, string(evt.Kv.Key), string(evt.Kv.Value))
  65. 65 var newConf []*LogEntry
  66. 66 // 如果是删除操作,json.Unmarshal会报错,需手动添加一个空的newConf
  67. 67 if evt.Type != clientv3.EventTypeDelete {
  68. 68 err := json.Unmarshal(evt.Kv.Value, &newConf)
  69. 69 if err != nil {
  70. 70 fmt.Printf("unmarshal new conf failed, err:%v\n", err)
  71. 71 continue
  72. 72 }
  73. 73 }
  74. 74 newConfChan <- newConf
  75. 75 }
  76. 76 }
  77. 77 }

kafka.go

  1. 1 /**
  2. 2 * @Author: Mr.Cheng
  3. 3 * @Description:往kafka写入日志
  4. 4 * @File: kafka
  5. 5 * @Version: 1.0.0
  6. 6 * @Date: 2021/12/9 下午2:19
  7. 7 */
  8. 8
  9. 9 package kafka
  10. 10
  11. 11 import (
  12. 12 "fmt"
  13. 13 "github.com/Shopify/sarama"
  14. 14 "time"
  15. 15 )
  16. 16
  17. 17 type logData struct {
  18. 18 Topic string
  19. 19 Data string
  20. 20 }
  21. 21
  22. 22 var (
  23. 23 client sarama.SyncProducer // 全局连接kafka的生产者
  24. 24 logDataChan chan *logData
  25. 25 )
  26. 26
  27. 27 // 初始化连接
  28. 28 func Init(address []string, size int) (err error) {
  29. 29 config := sarama.NewConfig()
  30. 30 config.Producer.RequiredAcks = sarama.WaitForAll // 发送模式(需leader和follow都确认)
  31. 31 config.Producer.Partitioner = sarama.NewRandomPartitioner // 选择分区的方式(轮询)
  32. 32 config.Producer.Return.Successes = true // 成功交付的消息将在success channel中返回
  33. 33
  34. 34 // 连接kafka
  35. 35 client, err = sarama.NewSyncProducer(address, config)
  36. 36 if err != nil {
  37. 37 fmt.Printf("client kafka failed, err:%v\n", err)
  38. 38 return err
  39. 39 }
  40. 40
  41. 41 // 初始化logDataChan
  42. 42 logDataChan = make(chan *logData, size)
  43. 43
  44. 44 // 从logDataChan中取数据发往kafaka
  45. 45 go sendToKafka()
  46. 46 return nil
  47. 47 }
  48. 48
  49. 49 func SendToChan(Topic, Data string) {
  50. 50 data := &logData{
  51. 51 Topic: Topic,
  52. 52 Data: Data,
  53. 53 }
  54. 54 select {
  55. 55 case logDataChan <- data:
  56. 56 default:
  57. 57 time.Sleep(time.Millisecond * 100)
  58. 58 }
  59. 59 }
  60. 60
  61. 61 func sendToKafka() {
  62. 62 // 循环从通道logDataChan取值并发送给kafka
  63. 63 for {
  64. 64 select {
  65. 65 case data := <-logDataChan:
  66. 66 msg := &sarama.ProducerMessage{}
  67. 67 msg.Topic = data.Topic
  68. 68 msg.Value = sarama.StringEncoder(data.Data)
  69. 69 pid, offset, err := client.SendMessage(msg)
  70. 70 if err != nil {
  71. 71 fmt.Printf("send msg failed, err:%v\n", err)
  72. 72 }
  73. 73 fmt.Printf("send msg success, pid:%v offect:%v\n", pid, offset)
  74. 74 default:
  75. 75 time.Sleep(time.Millisecond * 50)
  76. 76 }
  77. 77 }
  78. 78 }

taillog.go

  1. 1 /**
  2. 2 * @Author: Mr.Cheng
  3. 3 * @Description:收集日志模块
  4. 4 * @File: taillog
  5. 5 * @Version: 1.0.0
  6. 6 * @Date: 2021/12/8 下午9:54
  7. 7 */
  8. 8
  9. 9 package taillog
  10. 10
  11. 11 import (
  12. 12 "context"
  13. 13 "day21/02.log_agent/kafka"
  14. 14 "fmt"
  15. 15 "github.com/hpcloud/tail"
  16. 16 "time"
  17. 17 )
  18. 18
  19. 19 type TailTask struct {
  20. 20 Path string
  21. 21 Topic string
  22. 22 Instance *tail.Tail
  23. 23 // 为了停止任务,存下context
  24. 24 ctx context.Context
  25. 25 cancel context.CancelFunc
  26. 26 }
  27. 27
  28. 28 func NewTailTask(Path, Topic string) (tailtask *TailTask, err error) {
  29. 29 config := tail.Config{
  30. 30 Location: &tail.SeekInfo{Offset: 0, Whence: 2}, // 从文件那个地方开始读
  31. 31 ReOpen: true, // 重新打开
  32. 32 MustExist: false, // 文件不存在不报错
  33. 33 Poll: true,
  34. 34 Follow: true, // 是否跟随
  35. 35 }
  36. 36 ctx, cancel := context.WithCancel(context.Background())
  37. 37 tailObj, err := tail.TailFile(Path, config)
  38. 38 if err != nil {
  39. 39 fmt.Printf("tail file failed, err:%v\n", err)
  40. 40 return nil, err
  41. 41 }
  42. 42 tailtask = &TailTask{Path: Path, Topic: Topic, Instance: tailObj, ctx: ctx, cancel: cancel}
  43. 43 // 开启读取日志并发送给kafka
  44. 44 go tailtask.ReadFromTail()
  45. 45 return tailtask, nil
  46. 46 }
  47. 47
  48. 48 func (tailtask *TailTask) ReadFromTail() {
  49. 49 for {
  50. 50 select {
  51. 51 case <-tailtask.ctx.Done():
  52. 52 return
  53. 53 case line, ok := <-tailtask.Instance.Lines:
  54. 54 if !ok {
  55. 55 fmt.Printf("tail fail close reopen, filename:%s\n", tailtask.Path)
  56. 56 time.Sleep(time.Second)
  57. 57 continue
  58. 58 }
  59. 59 kafka.SendToChan(tailtask.Topic, line.Text)
  60. 60 default:
  61. 61 time.Sleep(time.Second)
  62. 62 }
  63. 63 }
  64. 64 }

taillog_mgr.go

  1. 1 /**
  2. 2 * @Author: Mr.Cheng
  3. 3 * @Description:
  4. 4 * @File: taillogMgr
  5. 5 * @Version: 1.0.0
  6. 6 * @Date: 2021/12/14 下午3:47
  7. 7 */
  8. 8
  9. 9 package taillog
  10. 10
  11. 11 import (
  12. 12 "day21/02.log_agent/etcd"
  13. 13 "fmt"
  14. 14 "time"
  15. 15 )
  16. 16
  17. 17 type TailMgr struct {
  18. 18 logEntry []*etcd.LogEntry
  19. 19 tskMap map[string]*TailTask
  20. 20 newConfChan chan []*etcd.LogEntry
  21. 21 }
  22. 22
  23. 23 var tskMgr *TailMgr
  24. 24
  25. 25 // 循环每一个日志收集项,创建tailObj,并发往kafka
  26. 26 func Init(logEntryConf []*etcd.LogEntry) {
  27. 27 tskMgr = &TailMgr{
  28. 28 logEntry: logEntryConf,
  29. 29 tskMap: make(map[string]*TailTask, 16),
  30. 30 newConfChan: make(chan []*etcd.LogEntry),
  31. 31 }
  32. 32
  33. 33 for _, LogEntry := range logEntryConf {
  34. 34 // fmt.Printf("Path:%v Topic:%v\n", LogEntry.Path, LogEntry.Topic)
  35. 35 tailtask, err := NewTailTask(LogEntry.Path, LogEntry.Topic)
  36. 36 if err != nil {
  37. 37 continue
  38. 38 }
  39. 39 // 在tskMap中存储一下,以便发生配置变更时做增删改操作
  40. 40 key := fmt.Sprintf("%s_%s", tailtask.Path, tailtask.Topic)
  41. 41 tskMgr.tskMap[key] = tailtask
  42. 42 }
  43. 43
  44. 44 go tskMgr.run()
  45. 45 }
  46. 46
  47. 47 // 监听newConfChan是否有数据,有数据则表示etcd配置有变化,需做相应的处理
  48. 48 func (t *TailMgr) run() {
  49. 49 for {
  50. 50 select {
  51. 51 case newConf := <- t.newConfChan:
  52. 52 fmt.Printf("配置发生变更,Conf:%v\n", newConf)
  53. 53 // 找出新增项
  54. 54 for _, logEntry := range newConf {
  55. 55 key := fmt.Sprintf("%s_%s", logEntry.Path, logEntry.Topic)
  56. 56 _, ok := t.tskMap[key]
  57. 57 if ok {
  58. 58 // 表示该配置项原先存在
  59. 59 continue
  60. 60 } else {
  61. 61 // 属于新增配置
  62. 62 fmt.Printf("新增项,path:%s topic:%s\n", logEntry.Path, logEntry.Topic)
  63. 63 tailtask, err := NewTailTask(logEntry.Path, logEntry.Topic)
  64. 64 if err != nil {
  65. 65 continue
  66. 66 }
  67. 67 // TailMgr的logEntry和tskMap增加对应项
  68. 68 t.logEntry = append(t.logEntry, logEntry)
  69. 69 t.tskMap[key] = tailtask
  70. 70 go tailtask.ReadFromTail()
  71. 71 }
  72. 72 }
  73. 73 // 找出删除项
  74. 74 for index, c1 := range t.logEntry {
  75. 75 isDelete := true
  76. 76 for _, c2 := range newConf {
  77. 77 if c1.Path == c2.Path && c1.Topic == c2.Topic {
  78. 78 isDelete = false
  79. 79 break
  80. 80 }
  81. 81 }
  82. 82 if isDelete{
  83. 83 // 表示属于删除项,从tskMap拿出tailtask对象,执行对象的cancel函数,并将该对象从tskMap中删除
  84. 84 fmt.Printf("删除项,path:%s topic:%s\n", c1.Path, c1.Topic)
  85. 85 key := fmt.Sprintf("%s_%s", c1.Path, c1.Topic)
  86. 86 t.tskMap[key].cancel()
  87. 87 // TailMgr的logEntry和tskMap删除对应项
  88. 88 delete(t.tskMap, key)
  89. 89 t.logEntry = append(t.logEntry[:index], t.logEntry[index + 1:]...)
  90. 90 }
  91. 91 }
  92. 92 default:
  93. 93 time.Sleep(time.Second)
  94. 94 }
  95. 95 }
  96. 96 }
  97. 97
  98. 98 // 向外暴露newConfChan
  99. 99 func NewConfChan() chan<- []*etcd.LogEntry{
  100. 100 return tskMgr.newConfChan
  101. 101 }

main.go

  1. 1 /**
  2. 2 * @Author: Mr.Cheng
  3. 3 * @Description:
  4. 4 * @File: main
  5. 5 * @Version: 1.0.0
  6. 6 * @Date: 2021/12/9 下午8:43
  7. 7 */
  8. 8
  9. 9 package main
  10. 10
  11. 11 import (
  12. 12 logAgentConfig "day21/02.log_agent/conf"
  13. 13 "day21/02.log_agent/etcd"
  14. 14 "day21/02.log_agent/kafka"
  15. 15 "day21/02.log_agent/taillog"
  16. 16 "fmt"
  17. 17 "gopkg.in/ini.v1"
  18. 18 "sync"
  19. 19 )
  20. 20
  21. 21 var (
  22. 22 cfg = new(logAgentConfig.AppConf)
  23. 23 wg sync.WaitGroup
  24. 24 )
  25. 25
  26. 26 func main() {
  27. 27 // 加载配置文件
  28. 28 err := ini.MapTo(cfg, "./conf/logAgent.ini")
  29. 29 if err != nil {
  30. 30 fmt.Printf("load ini failed, err:%v\n", err)
  31. 31 return
  32. 32 }
  33. 33
  34. 34 // 初始化kafka连接
  35. 35 err = kafka.Init([]string{cfg.KafkaConf.Address}, cfg.KafkaConf.Size)
  36. 36 if err != nil {
  37. 37 return
  38. 38 }
  39. 39 fmt.Println("init kafka success")
  40. 40
  41. 41 // 初始化etcd
  42. 42 err = etcd.Init(cfg.EtcdConf.Address, cfg.EtcdConf.Timeout)
  43. 43 if err != nil {
  44. 44 return
  45. 45 }
  46. 46 fmt.Println("init etcd success")
  47. 47
  48. 48 // 从etcd中获取日志收集项的配置信息
  49. 49 logEntryConf, err := etcd.GetConf(cfg.EtcdConf.Key)
  50. 50 if err != nil {
  51. 51 return
  52. 52 }
  53. 53 fmt.Printf("get conf from etcd success, conf:%v\n", logEntryConf)
  54. 54
  55. 55 // 收集日志发往kafka
  56. 56 // 循环每一个日志收集项,创建tailObj,并发往kafka
  57. 57 taillog.Init(logEntryConf)
  58. 58
  59. 59 // 监视etcd中配置的变动,如有变动,给新的配置信息给taillog
  60. 60 wg.Add(1)
  61. 61 go etcd.WatchConf(cfg.EtcdConf.Key, taillog.NewConfChan())
  62. 62 wg.Wait()
  63. 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. Java入门与进阶P-5.3+P-5.4

    数组的元素 有效的下标 最小的下标是0,最大的下标是数组的元素个数-1 可是编译器不会检查看你是否用了有效的下标 但是如果运行的时候出现了无效的下标,可能会导致程序终止 定义数组变量 元素个数必须是整 ...

  2. QtCharts模块勾画折线和曲线图

    QtCharts画线图主要三个部分组成 QLIneSeries或QSplineSeries用于保存联系的坐标位置数据,QChart用于管理图像显示,例如图例,坐标主题等,QChartView则用于显示 ...

  3. Hive删除分区名称中含有特殊字符

    先说方案:通过show partitions和hdfs url看到的都不是真正的分区名称,都是经过URI重新编码的,访问这些分区应该使用分区名称的原始字符串. 场景描述 当我们在SQL语句中使用变量时 ...

  4. springboot集成ElasticApm

    jvm参数方式: -javaagent:D:/codesoft/elastic-apm-agent-1.18.0.jar -Delastic.apm.service_name=my-applicati ...

  5. Cubase11/12 安装破解图文教程 【2022年12月29日亲测有效】

    Cubase11/12安装破解图文教程 下载安装包工具 Cubase官网:点击官网进行下载 Cubase11/12工具包:点击立即下载 Cubase12完成破解教程:点击立即查看 安装Cubase11 ...

  6. webrtc QOS笔记一 Neteq直方图算法浅读

    webrtc QOS笔记一 Neteq直方图算法浅读 目录 webrtc QOS笔记一 Neteq直方图算法浅读 Histogram Algorithm 获取目标延迟 遗忘因子曲线 想起博客园帐号了, ...

  7. WAF Bypass 介绍与实战

    前言 WAF是英文"Web Application Firewall"的缩写,中文意思是"Web应用防火墙",也称为"网站应用级入侵防御系统" ...

  8. 视觉SLAM:VIO的误差和误差雅可比矩阵

    1.两个相机之间的非线性优化 观测相机方程关于相机位姿与特征点的雅可比矩阵: 1.1 位姿: 1.2 3D特征点 fx,fy,fz为相机内参 X',Y',Z'为3D点在相机坐标系下的坐标 该误差是观测 ...

  9. Mockito单元测试 初试

    Mockito单元测试相对于Spring Boot 自带的好处理在于,单元测试不需要加载注入Spring Boot 启动项目. 1.需要注入的东西如下,@InjectMocks是注入需要测试的类,@S ...

  10. 郁金香5 分析游戏内npc 数据

    004D4BAE | CC | int3 | 004D4BAF | CC | int3 | 004D4BB0 | 55 | push ebp | 004D4BB1 | 8BEC | mov ebp,e ...