Go微服务: 日志系统ELK的应用

news2024/11/25 12:27:11

概述

  • 基于前文,我们已经了解并搭建完成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 函数

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版本来测试
  • 下载完成后,里面有很多配置好的文件,我们主要关注两个: 二进制文件 filebeatfilebeat.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环境已经全部打通 ~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1689304.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】——函数栈帧的创建与销毁

函数栈帧的创建与销毁 本文主要讲解了函数调用过程中其栈帧的创建与销毁,内容干货较多,希望大家认真品味。 使用C语言进行函数调用时,是否会有很多疑问: 1.局部变量是如何创建的? 2.局部变量在未初始化的情况下&#x…

物理服务器介绍

物理服务器介绍 概述分类按服务器应用分类按服务器结构分类塔式服务器机架式服务器刀片式服务器机架式服务器与刀片式服务器的对比按处理器个数分类按处理器架构分类 主板概述工作原理物理结构技术参数 CPU概述工作原理指令集相关技术技术参数主流产品 内存概述类型相关技术技术…

安卓分身大师4.6.0解锁会员安卓14可用机型伪装双开多开

需登录解锁会员功能,除了加速进入不能, 其他主要功能都是可以使用,由于验证较多一些功能需要特定操作使用,进行伪装时请不要直接伪装,先生成成功后再进行自定义伪装!链接:https://pan.baidu.com…

Transformer详解(3)-多头自注意力机制

attention multi-head attention pytorch代码实现 import math import torch from torch import nn import torch.nn.functional as Fclass MultiHeadAttention(nn.Module):def __init__(self, heads8, d_model128, droput0.1):super().__init__()self.d_model d_model # 12…

通过el-tree自定义渲染网页版工作目录,实现鼠标悬浮显示完整名称、用icon区分文件和文件夹等需求

目录 一、通过el-tree自定义渲染网页版工作目录 1.1、需求介绍 1.2、使用el-tree生成文档目录 1.2.1、官方基础用法 ①效果 ②代码: 1.2.2、自定义文档目录(实现鼠标悬浮显示完整名称、用icon区分文件和文件夹) ①效果(直接效…

JavaScript表达式和运算符

表达式 表达式一般由常量、变量、运算符、子表达式构成。最简单的表达式可以是一个简单的值。常量或变量。例:var a10 运算符 运算符一般用符号来表示,也有些使用关键字表示。运算符由3中类型 1.一元运算符:一个运算符能够结合一个操作数&…

YOLOv8_seg的训练、验证、预测及导出[实例分割实践篇]

实例分割数据集链接,还是和目标检测篇一样,从coco2017val数据集中挑出来person和surfboard两类:链接:百度网盘 请输入提取码 提取码:3xmm 1.实例分割数据划分及配置 1.1实例分割数据划分 从上面得到的数据还不能够直接训练,需要按照一定的比例划分训练集和验证集,并按…

如何远程实时查看监控管理员工电脑屏幕?远程查看多台员工电脑屏幕的方法

现代化企业管理中,远程监控员工电脑屏幕已经成为一种有效的手段,用于提升工作效率、确保信息安全以及维护工作纪律。 而这种事情,也仿佛已经很常见了。 那么一般都如何监控的呢? 一、选择合适的远程监控软件 1.域智盾 域智盾提…

2024 电工杯高校数学建模竞赛(B题)数学建模完整思路+完整代码全解全析

你是否在寻找数学建模比赛的突破点?数学建模进阶思路! 作为经验丰富的数学建模团队,我们将为你带来2024电工杯数学建模竞赛(B题)的全面解析。这个解决方案包不仅包括完整的代码实现,还有详尽的建模过程和解…

markdown 文件渲染工具推荐 obsidian publish

背景 Markdown 是一种轻量级的标记语言,最开始使用它是觉得码字非常方便,从一开始的 word 排版到 markdown ,还不太不习惯,用了 obsidian把一些文字发在网上后,才逐渐发现他的厉害之处。 让人更加专注于内容本身&…

机器学习云环境测试

等待创建完成后,点击 PyTorch 打开,创建一个全新的 notebook 在 Cell 中输入如下代码,并点击 Run 完成后点击 New Cell ,在 New Cell 中输入如下代码 输入完成后点击 Run ,运行 New Cell 。(每个 Cell 代…

Java面试八股之Thread类中的yeild方法有什么作用

Thread类中的yeild方法有什么作用 谦让机制:Thread.yield()方法主要用于实现线程间的礼让或谦让机制。当某个线程执行到yield()方法时,它会主动放弃当前已获得的CPU执行权,从运行状态(Running)转变为可运行状态&#…

朴素贝叶斯+SMSSpamCollections

1. 打开 Jupyter 后,在工作目录中,新建一个文件夹命名为 Test01 ,并且在文件夹中导入数据 集。在网页端界面点击 “upload” 按钮,在弹出的界面中选择要导入的数据集。然后数据集出现 在 jupyter 文件目录中,此时…

基于SSM的大学生兼职管理系统

基于SSM的大学生兼职管理系统的设计与实现~ 开发语言:Java数据库:MySQL技术:SpringSpringMVCMyBatis工具:IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 企业界面 前台学生界面 管理员界面 摘要 随着大学生兼职市场的日益繁…

VTK9.2.0+QT5.14.0绘制三维显示背景

背景 上一篇绘制点云的博文中,使用的vtkCameraOrientationWidget来绘制的坐标轴,最近又学习到两种新的坐标轴绘制形式。 vtkOrientationMarkerWidget vtkAxesActor 单独使用vtkAxesActor能够绘制出坐标轴,但是会随着鼠标操作旋转和平移时…

Java设计模式 _行为型模式_迭代器模式

一、迭代器模式 1、迭代器模式 迭代器模式(Iterator Pattern)是一种行为型设计模式,用于顺序访问集合对象的元素,不需要关心集合对象的底层表示。如:java中的Iterator接口就是这个工作原理。 2、实现思路 &#xff0…

linux 中 fd 申请和释放管理(两级 bitmap)

linux 中 fd 的几点理解_linux fd-CSDN博客 通过上边的文章,我们可以知道,在 linux 中,fd 有以下几点需要了解: (1)fd 表示进程打开的文件,是进程级别的资源,不是系统级别的资源 …

Rhinoceros v7.5 解锁版安装教程 (3D三维造型软件)

前言 Rhinoceros 中文名称犀牛是一款超强的三维建模工具,全称Rhinoceros,Rhino是美国Robert McNeel & Assoc开发的PC上强大的专业3D造型软件,它可以广泛地应用于三维动画制作、工业制造、科学研究以及机械设计等领域。它能轻易整合3DS M…

Nodejs及stfshow相关例题

Nodejs及stfshow相关例题 Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行环境。可以说nodejs是一个运行环境,或者说是一个 JS 语言解释器而不是某种库。 Node.js可以生成动态页面内容Node.js 可以在服务器上创建、打开、读取、写入、删除和关闭文件Node.js…

设计模式9——适配器模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用,主要是下面的UML图可以起到大作用,在你学习过一遍以后可能会遗忘,忘记了不要紧,只要看一眼UML图就能想起来了。同时也请大家多多指教。 适配器模式(Adapte…