Kitex融合自制日志框架实现分片和ES日志存储 | 青训营

314 阅读3分钟

日志框架

Kitex 支持默认 logger 实现和注入自定义 logger 以及重定向默认 logger 输出。

可以用 klog.SetLogger 来替换掉默认的 logger 实现。

obs-opentelemetry 扩展下提供了基于 logruszap 的日志实现

采用logrus + go-file-rotatelogs + lfshook 实现日志分割(go的logger实例支持logrus接口)

logrus 支持日志分割,需要借助go-file-rotatelogs包来配合,go-file-rotatelogs 实现了 io.Writer 接口,并且提供了文件的切割功能,其实例可以作为 logrus 的目标输出,两者能无缝集成,这也是 file-rotatelogs 的设计初衷

Logger初始化

注意保证日志存放目录存在,最好使用绝对目录,相对目录目前实验不成功

package logger
​
import (
    "github.com/cloudwego/kitex/pkg/klog"
    "io"
    "path"
    "time"
​
    kitexlogrus "github.com/kitex-contrib/obs-opentelemetry/logging/logrus"
    rotatelogs "github.com/lestrrat/go-file-rotatelogs"
    "github.com/rifflock/lfshook"
    "github.com/sirupsen/logrus"
    "os"
)
​
func Initlog() {
    //os.MkdirAll("../../log/", 0755)
    logrus.SetReportCaller(true)
    // 设置日志输出控制台样式,自定义输出样式
    logrus.SetFormatter(&MyFormatter{})
    // 按按分钟分割,触发机制是有才会分割,然后根据当前是否存在该文进行选择写入还是新建
    logFileName := path.Join("/root/dockerfile/go/go/go_workspace/kitex-examples-main/bizdemo_copy/easy_note/log", "output") + ".%Y%m%d%H%M.log"
    // 配置日志分割
    logFileCut := LogFileCut(logFileName)
    writers := []io.Writer{
        logFileCut,
        os.Stdout}
​
    // 输出到控制台,方便定位到那个文件
    fileAndStdoutWriter := io.MultiWriter(writers...)
    klog.Info("init log end")
    //将logrus的唯一实例作为日志实例,继承了logrus并且开发了新方法
    klog.SetLogger(kitexlogrus.NewLogger())
    //设置日志级别
    klog.SetLevel(klog.LevelDebug)
    //设置控制台和日志文件双重输出
    klog.SetOutput(fileAndStdoutWriter)
​
}
​
// 配置日志切割
// LogFileCut 日志文件切割
func LogFileCut(fileName string) *rotatelogs.RotateLogs {
    logier, err := rotatelogs.New(
        // 切割后日志文件名称
        fileName,
        //rotatelogs.WithLinkName(Current.LogDir),   // 生成软链,指向最新日志文件
        rotatelogs.WithMaxAge(30*24*time.Hour),   // 文件最大保存时间
        rotatelogs.WithRotationTime(time.Minute), // 日志切割时间间隔
        //rotatelogs.WithRotationCount(3),
        //rotatelogs.WithRotationTime(time.Minute), // 日志切割时间间隔
    )
​
    if err != nil {
        panic(err)
    }
    lfHook := lfshook.NewHook(lfshook.WriterMap{
        logrus.InfoLevel:  logier,
        logrus.FatalLevel: logier,
        logrus.DebugLevel: logier,
        logrus.WarnLevel:  logier,
        logrus.ErrorLevel: logier,
        logrus.PanicLevel: logier,
    },
        // 设置分割日志样式
        &MyFormatter{})
    logrus.AddHook(lfHook)
    return logier
}
​

自定义日志打印形式

package logger
​
import (
    "bytes"
    "fmt"
    "github.com/sirupsen/logrus"
    "path/filepath"
)
​
type MyFormatter struct{}
​
func (m *MyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    var b *bytes.Buffer
    if entry.Buffer != nil {
        b = entry.Buffer
    } else {
        b = &bytes.Buffer{}
    }
​
    timestamp := entry.Time.Format("2006-01-02 15:04:05")
    var newLog string//HasCaller()为true才会有调用信息
    if entry.HasCaller() {
        fName := filepath.Base(entry.Caller.File)
        newLog = fmt.Sprintf("[xxx-app] [%s] [%s] [%s:%d] [msg=%s]\n",
            timestamp, entry.Level, fName, entry.Caller.Line, entry.Message)
    } else {
        newLog = fmt.Sprintf("[xxx-app] [%s] [%s] [msg=%s]\n", timestamp, entry.Level, entry.Message)
    }
​
    b.WriteString(newLog)
    return b.Bytes(), nil
}

自制日志收集模块

代码地址git@github.com:simon12138-code/log_collector.git

logagent

根据etcd指令收集对应文件的日志并且通过kafka传入消息队列中

.
├── conf
│   └── app.conf #存入初始话配置,etcd配置,本项目配置,kafka配置,监控日志配置等等
├── config
│   ├── config.go #配置热部署,tailconf中的修改内容并且刷新
│   └── config_notify.go 
├── config_log.go
├── data.go #kafka消息格式
├── etcd.go #监听etcd对应key的消息,是否有更新日志的指令
├── go.mod 
├── go.sum
├── ip.go #监听本地的所有IP帮助生成etcd的键
├── kafka.go #消息发送模块
├── limit.go #限制发送次数
├── logs
│   ├── logcollect.2023-07-29.001.log
│   └── logcollect.log #本服务的日志
├── main.go #主启动类
├── README.md
├── server.go #主要实现逻辑,监听etcd,读取配置文件

logtransfer

.
├── conf
│   └── app.conf #配置文件
├── config #热配置
│   ├── config.go 
│   └── config_notify.go
├── es.go #esclient
├── etcd.go #etcd监听器
├── go.mod
├── go.sum
├── ip.go #ip扫描
├── kafka.go #kafka消费者
├── logs
│   ├── transfer.2023-07-29.001.log
│   └── transfer.log
└── main.go

test_etcd

发出对应收集日志指令,格式如下

    type logConfig struct {
    Topic    string `json:"topic"`
    LogPath  string `json:"log_path"`
    Service  string `json:"service"`
    SendRate int    `json:"send_rate"`
}
​
    cfg := logConfig{Topic: "log", LogPath: "/root/dockerfile/go/go/go_workspace/kitex-examples-main/bizdemo_copy/easy_note/log/output.202307281459.log", Service: "user", SendRate: 10000}
    cfglist := []logConfig{cfg}
    jsonBytes, err := json.Marshal(cfglist)
​
    fmt.Println("connect succ")
    defer cli.Close()
    //设置1秒超时,访问etcd有超时控制
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    _, err = cli.Put(ctx, "/logagent/192.168.112.100/log_config", string(jsonBytes))

test_kafka

充当消费者检查对应的消息是否能够消费