概述
- 基于前文,我们已经了解并搭建完成ELK的所有环境了,现在我们来结合应用程序来使用ELK
- 参考前文:https://active.blog.csdn.net/article/details/138898538
封装日志模块
- 在通用工具模块: gitee.com/go-micro-services/common 这个包是通用的工具包
- 新增 zap.go
package common import ( "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.ISO8601TimeEncoder core := 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 utils import ( "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 routers
import (
"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 api import ( "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 api import ( "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.ZapLogger func 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: log enabled: true paths: - ./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环境已经全部打通 ~