概述
- 基于前文,我们已经了解并搭建完成ELK的所有环境了,现在我们来结合应用程序来使用ELK
- 参考前文:https://active.blog.csdn.net/article/details/138898538
封装日志模块
- 在通用工具模块: gitee.com/go-micro-services/common 这个包是通用的工具包
- 新增 zap.go
package commonimport ("fmt""os""path/filepath""go.uber.org/zap""go.uber.org/zap/zapcore""gopkg.in/natefinch/lumberjack.v2" )// MyType 是我们想要提供的类型 type ZapLogger struct {logger *zap.SugaredLogger }// NewMyType 是一个构造函数,用于创建 MyType 的新实例 func NewZapLogger(filePath string, FilePerms int) *ZapLogger {log, _ := ZapLoggerInit(filePath, FilePerms)return &ZapLogger{logger: log} }func (zl *ZapLogger) Debug(args ...interface{}) {zl.logger.Debug(args) }func (zl *ZapLogger) Debugf(template string, args ...interface{}) {zl.logger.Debugf(template, args...) }func (zl *ZapLogger) Info(args ...interface{}) {zl.logger.Info(args...) }func (zl *ZapLogger) Infof(template string, args ...interface{}) {zl.logger.Infof(template, args...) }func (zl *ZapLogger) Warn(args ...interface{}) {zl.logger.Warn(args...) }func (zl *ZapLogger) Warnf(template string, args ...interface{}) {zl.logger.Warnf(template, args...) }func (zl *ZapLogger) Error(args ...interface{}) {zl.logger.Error(args...) }func (zl *ZapLogger) Errorf(template string, args ...interface{}) {zl.logger.Errorf(template, args...) }func (zl *ZapLogger) DPanic(args ...interface{}) {zl.logger.DPanic(args...) }func (zl *ZapLogger) DPanicf(template string, args ...interface{}) {zl.logger.DPanicf(template, args...) }func (zl *ZapLogger) Panic(args ...interface{}) {zl.logger.Panic(args...) }func (zl *ZapLogger) Panicf(template string, args ...interface{}) {zl.logger.Panicf(template, args...) }func (zl *ZapLogger) Fatal(args ...interface{}) {zl.logger.Fatal(args...) }func (zl *ZapLogger) Fatalf(template string, args ...interface{}) {zl.logger.Fatalf(template, args...) }// 这个供外部调用 func ZapLoggerInit(filePath string, FilePerms int) (*zap.SugaredLogger, error) {fileName, fileErr := createFileWithPerms(filePath, os.FileMode(FilePerms))if fileErr != nil {// 使用 %v 来打印 error 类型的变量fmt.Printf("Error: %v\n", fileErr)return nil, fileErr}fmt.Printf("日志文件路径: %s\n", fileName)syncWriter := zapcore.AddSync(&lumberjack.Logger{Filename: fileName, //文件名称MaxSize: 521, // MB// MaxAge: 0,MaxBackups: 0, //最大备份LocalTime: true,Compress: true, //是否启用压缩})// 编码encoder := zap.NewProductionEncoderConfig()// 时间格式encoder.EncodeTime = zapcore.ISO8601TimeEncodercore := zapcore.NewCore(// 编码器zapcore.NewJSONEncoder(encoder),syncWriter,zap.NewAtomicLevelAt(zap.DebugLevel))log := zap.New(core,zap.AddCaller(),zap.AddCallerSkip(1))return log.Sugar(), nil }// 创建一个多层目录下的文件,并设置权限, 如果文件已存在,则返回文件的路径;如果不存在,则创建并返回文件路径 func createFileWithPerms(filePath string, perms os.FileMode) (string, error) {// 检查文件或目录是否存在fileInfo, err := os.Lstat(filePath)if err == nil {// 文件或目录已存在if fileInfo.IsDir() {// 如果已存在的是目录,返回错误return "", fmt.Errorf("无法创建文件 '%s',因为该路径已存在且是一个目录", filePath)}// 如果已存在的是文件,则返回文件路径return filePath, nil}if !os.IsNotExist(err) {// 如果发生其他错误(如权限问题),则返回错误return "", fmt.Errorf("检查文件 '%s' 时发生错误: %v", filePath, err)}// 创建多级目录err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm) // 使用 os.ModePerm 允许所有权限,但文件权限会由下面的 os.Create 设置if err != nil {return "", fmt.Errorf("无法创建目录 '%s': %v", filepath.Dir(filePath), err)}// 创建文件file, err := os.Create(filePath)if err != nil {return "", fmt.Errorf("无法创建文件 '%s': %v", filePath, err)}// 关闭文件,因为我们只需要路径defer file.Close()// 设置文件权限err = file.Chmod(perms)if err != nil {return "", fmt.Errorf("无法设置文件 '%s' 的权限: %v", filePath, err)}// 返回新创建文件的路径return filePath, nil }
- 上面这个模块,封装了打印日志的各种方法,基于其定义可以看出是基于实例的
- 也就是说,不是静态的方法,而是可以 new 出很多实例的,这样可以让我们使用场景更加丰富
- 代码仓库:https://gitee.com/go-micro-services/common
网关和服务应用程序准备
1 )概述
- ELK简单来说,一般部署在我们的网关上,还是和之前一样,基于 gin 框架
- 如果部署在各个微服务中,那环节就比较麻烦,而且资源耗费较多
- 在我们的网关上来使用,还用之前的场景,基于网关上的某一个api来获取购物车中的数据
- 例如:
- /api/findAllTestElk1?user_id=1 基于这个路由,使用默认日志实例来输出, 正确日志
- /api/findAllTestElk1?user_id=x 同上,触发错误日志
- /api/findAllTestElk2?user_id=1 基于这个路由,自定义新的日志实例来输出
- /api/findAllTestElk2?user_id=x 同上,触发错误日志
- 基于以上,可以在不同模块实例化不同的日志文件
2 )utils包
-
utils/log.go
package utilsimport ("gitee.com/go-micro-services/common" )// 通用配置 var ZapLogger *common.ZapLogger// 日志默认设置 func initLog() {ZapLogger = common.NewZapLogger("logs/app.log", 0777) // 这里两个参数可以配置到 conf/app.ini 中 }
- 这里使用 common 工具包,来实例化一个默认的日志实例
-
utils/common.go
func init() {initLog() // 添加这个 }
- 可见,在 init 函数中添加
initLog
函数
- 可见,在 init 函数中添加
3 ) 定义路由
package routersimport ("gitee.com/go-micro-services/api/controllers/api""github.com/gin-gonic/gin"
)func RoutersInit(r *gin.Engine) {rr := r.Group("/api"){rr.GET("/findAll", api.ApiController{}.FindAll)rr.GET("/findAllTestElk1", api.Log1Controller{}.FindAllTestElk)rr.GET("/findAllTestElk2", api.Log2Controller{}.FindAllTestElk)}
}
- 可见这里定义了三个路由,我们主要关注后面两个
- 下面来看对应的控制器
4 )控制器
-
controllers/api/log1.go
package apiimport ("context""fmt""strconv""gitee.com/go-micro-services/api/utils"cart "gitee.com/go-micro-services/cart/proto/cart""github.com/gin-gonic/gin""github.com/prometheus/common/log" )type Log1Controller struct{}// 这个方法用于测试ELK func (con Log1Controller) FindAllTestElk(c *gin.Context) {log.Info("接受到 /api/findAllTestElk1 访问请求")// 1. 获取参数user_id_str := c.Query("user_id")userId, err := strconv.ParseInt(user_id_str, 10, 64)if err != nil {utils.ZapLogger.Error("参数异常")c.JSON(200, gin.H{"message": "参数异常","success": false,})return}fmt.Println(userId)// 2. rpc 远程调用:获取购物车所有商品cartClient := cart.NewCartService(utils.CartServices, utils.SrvClient)cartAll, err := cartClient.GetAll(context.TODO(), &cart.CartFindAll{UserId: userId})utils.ZapLogger.Info(cartAll)fmt.Println("-----")c.JSON(200, gin.H{"data": cartAll,"success": true,}) }
-
controllers/api/log2.go
package apiimport ("context""fmt""strconv""gitee.com/go-micro-services/api/utils"cart "gitee.com/go-micro-services/cart/proto/cart""gitee.com/go-micro-services/common""github.com/gin-gonic/gin""github.com/prometheus/common/log" )type Log2Controller struct{}// 通用配置 var ZapLogger *common.ZapLoggerfunc init() {ZapLogger = common.NewZapLogger("logs/app2.log", 0777) // 这里相关参数可以配置到 conf/app.ini 中 }// 这个方法用于测试ELK func (con Log2Controller) FindAllTestElk(c *gin.Context) {log.Info("接受到 /api/findAllTestElk2 访问请求")// 1. 获取参数user_id_str := c.Query("user_id")userId, err := strconv.ParseInt(user_id_str, 10, 64)if err != nil {ZapLogger.Error("参数异常")c.JSON(200, gin.H{"message": "参数异常","success": false,})return}fmt.Println(userId)// 2. rpc 远程调用:获取购物车所有商品cartClient := cart.NewCartService(utils.CartServices, utils.SrvClient)cartAll, err := cartClient.GetAll(context.TODO(), &cart.CartFindAll{UserId: userId})ZapLogger.Info(cartAll)fmt.Println("-----")c.JSON(200, gin.H{"data": cartAll,"success": true,}) }
-
上面两个控制器的内容,基本一致,我们的目的是用来测试不同的日志实例的测试
- 第一个控制器使用的是默认初始化时的日志对象
- 第二个控制器使用的是重新初始化后的日志实例,这样我们可以自定义不同的日志生成路径
-
代码仓库
- 网关: https://gitee.com/go-micro-services/api
- 购物车服务: https://gitee.com/go-micro-services/cart
5 )启动网关和购物车服务
- 可见,两个服务已经启动起来了
下载和运行 FileBeat 程序
- 访问:https://www.elastic.co/cn/downloads/past-releases/filebeat-7-9-3/
- 注意:这里的版本要和之前ELK环境搭建时的版本对应
- 选择合适的版本,在服务器要选择服务器版本,这里我选择Mac版本来测试
- 下载完成后,里面有很多配置好的文件,我们主要关注两个: 二进制文件
filebeat
和filebeat.yml
- 将这两个文件拷贝进入网关项目
- 编辑 filebeat.yml
# 输入 filebeat.inputs:- type: logenabled: truepaths:- ./logs/*.log #输出 output.logstash:hosts: ["localhost:5044"]
- 这里,可以配置不同的文件来区分各个部署环境,因为环境不同,参数也不同
- 也可以使用环境变量,部署时进行注入,我这里只是做了一个演示
- 启动命令 $
./filebeat -e -c ./filebeat.yml
- -e: 启动在终端输出采集信息
- -c: 指定yml启动配置文件
- 当然这些个启动命令,后期也可以在Dockerfile, DockerCompose 或 K8s中定义,不再赘述
登录并配置 Kibana 来查看日志
1 ) 概述
- 如果是本机搭建的,访问: http://localhost:5601
- 这里的 5601 就是之前搭建ELK时暴露出来的
- 基于前文配置的用户名和密码进行登录
2 ) 登录之后,点击右侧的 Discover
3 ) 创建和配置索引
4 )回到 Discover 查看日志
截止目前为止,所有ELK环境已经全部打通 ~