挖呀挖呀挖,站在巨人的头上挖坟,从头开始做一个日志采集工具 (一)

77 阅读3分钟

本想说从头开始做一个日志采集工具,emmm... 但是发现自己实现的主要功能都是无关紧要的,我就是一个技艺不精的缝衣匠,谈何而来的从头开始,哈哈哈 自我吐槽一番

使用的技术栈:

后端的开发语言: golang

前端框架: vue3

主要功能

  • 日志采集

    实现日志采集的功能,golang 中实现找到了 github.com/hpcloud/tail 这个库,我 fork了一下然后升级成了 go mod 模式, 库的地址 github.com/issueye/tail

    使用简单的几句代码就能够实现对日志文件的监听

cfg := tail.Config{
		ReOpen:    true,                                 // 重新打开
		Follow:    true,                                 // 是否跟随
		Location:  &tail.SeekInfo{Offset: 0, Whence: 2}, // 从文件的哪个地方开始读
		MustExist: false,                                // 文件不存在不报错
		Poll:      true,
	}

	tails, err := tail.TailFile("server.log", cfg)
	if err != nil {
		fmt.Println("tail file failed, err:", err)
		return
	}

	fmt.Println("开启监听:", a.Path)

	// 开始监听文件
	for {
		select {
		case line, ok := <-tails.Lines: // 读内容
			{
				//遍历chan,读取日志内容
				if !ok {
					fmt.Printf("tail file close reopen, filename:%s\n", tails.Filename)
					time.Sleep(time.Second)
					continue
				}

				fmt.Printf("content:%s\n", line.Text)
			}
		}
	}

  • 扩展性

    通过使用动态脚本语言来增加动态扩展性,使用库 github.com/issueye/lichee-js 使 golang 运行 javascript 脚本,库内部使用了 github.com/dop251/goja 这个库

src := `
console.log('你好')
`

c := licheejs.NewCore()
err := c.RunString("test", src)
if err != nil {
    panic(fmt.Errorf("运行代码失败,失败原因:%s", err.Error()))
}
  • 动态添加

    添加前端管理页面,动态的添加监听的路径日志,实现对多个日志的同时监听,使用 vue3 前端框架开发一个简单的管理页面

  • 本地保存记录

    使用 sqlite 存储数据,为了避免使用 gcc 所以使用的是用 golang实现的 sqliegithub.com/glebarez/sqlite

项目搭建

代码主要目录 internal

公共包目录 pkg

├─internal                  // 主要的内部代码
│  ├─agent                  // 对日志文件的处理
│  ├─controller             // 控制器
│  ├─config                 // 配置
│  ├─global                 // 内部的全局内容
│  ├─initialize             // 初始化
│  ├─middleware             // 中间件
│  ├─model                  // 数据模型
│  ├─router                 // 路由
│  └─service                // service
├─pkg
│  ├─db                     // 数据库
│  ├─logger                 // 日志
│  ├─middleware             // 中间件
│  ├─res                    // 返回
│  ├─utils                  // 工具方法
│  ├─validator              // 表单验证
│  └─ws                     // websocket

首先完成 main.go

主要是调用初始化单元的初始化方法进行初始化

func main() {
	initialize.Initialize()

	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
}

在包 initialize 中添加文件 initialize.go 并且添加方法 Initialize()

// 对项目初始化,并且启动服务
func Initialize() {
    // ...
}

程序所生成的文件都要存放在一个固定的文件夹下,我们规定生成的文件都放在 runtime 文件夹下,生成的数据库文件存放到 data 文件夹下,日志存放到 logs 文件夹下,静态文件存放当在 static 文件夹下

在包 initialize 添加文件 init_runtime.go 添加方法 isExistsCreatePath

func isExistsCreatePath(path, name string) string {
	p := filepath.Join(path, name)
	exists, err := utils.PathExists(p)
	if err != nil {
		panic(err.Error())
	}

	if !exists {
		panic(fmt.Errorf("创建【%s】文件夹失败", name))
	}

	return p
}

PathExists :判断文件夹是否存在

func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		// 创建文件夹
		err := os.MkdirAll(path, os.ModePerm)
		if err != nil {
			return false, err
		} else {
			return true, nil
		}
	}
	return false, err
}

添加方法 InitRuntime 用来初始化创建对应的文件夹

func InitRuntime() {
	// 检查本地是否存在runtime文件夹
	// 获取当前程序的路径
	path := utils.GetWorkDir()
	rtPath := isExistsCreatePath(path, "runtime")
	isExistsCreatePath(rtPath, "data")
	isExistsCreatePath(rtPath, "logs")
	staticPath := isExistsCreatePath(rtPath, "static")
}

GetWorkDir :获取程序当前运行目录

// 获取程序运行目录
func GetWorkDir() string {
	pwd, _ := os.Getwd()
	return pwd
}