go 微服务框架 kratos 日志库使用方法及原理探究

news2024/10/5 17:28:03

一、Kratos 日志设计理念

kratos 日志库相关的官方文档:日志 | Kratos

Kratos的日志库主要有如下特性:

  • Logger用于对接各种日志库或日志平台,可以用现成的或者自己实现
  • Helper是在您的项目代码中实际需要调用的,用于在业务代码里打日志
  • Filter用于对输出日志进行过滤或魔改(通常用于日志脱敏)
  • Valuer用于绑定一些全局的固定值或动态值(比如时间戳、traceID或者实例id之类的东西)到输出日志中

Logger统一了日志的接入方式,Helper接口统一的日志库的调用方式。

二、Kratos 使用标准内置日志库的方法

1、打印日志到控制台的步骤 

①导入kratos日志包

import "github.com/go-kratos/kratos/v2/log"

//若导入失败,则需先获取包
go get github.com/go-kratos/kratos/v2/log

②使用 kratos 内置标准输出创建日志对象 

logger := log.NewStdLogger(os.Stdout)

③用 Log 方法打印日志,需要传入日志级别 

logger.Log(log.LevelInfo, "msg", "logger.Log 打印的日志")

 ④为了简化写日志时需传入的参数,kratos 用 Helper 对 Logger 进行了包装,建议用新建 helper 对象写日志,方法如下:

//对 logger 进行包装,简化写日志时需传入的参数
h := log.NewHelper(logger)

//写入信息级别的日志
h.Info("使用 kratos 内置标准输出记录的日志")

//写入错误级别的日志
h.Errorf("用户名【%s】不存在", "张三")

 ⑤可通过 log.With() 方法绑定全局字段到 Vauler,用来打印全局信息到日志中

//通过 log.With 方法绑定全局字段到 Vauler,用来打印全局信息到日志中
logger = log.With(logger,
	"ts", log.DefaultTimestamp,
	"caller", log.DefaultCaller,
	"trace_id", tracing.TraceID(),
	"span_id", tracing.SpanID(),
)
h = log.NewHelper(logger)
h.Info("绑定了全局信息到日志中")

⑥用 log.NewFilter() 方法对日志输出进行过滤

// 日志过滤,显示特定级别的日志,或对敏感信息脱敏
h = log.NewHelper(
	log.NewFilter(logger,
		// 按等级过滤
		//log.FilterLevel(log.LevelError),

		// 按key遮蔽
		log.FilterKey("username"),

		// 按value遮蔽
		log.FilterValue("hello"),
	),
)
h.Warn("warn log")
h.Infow("password", "123456")
//日志中 kratos 会变为 ***
h.Infow("username", "kratos")
//日志中 hello 会变为 ***
h.Info("hello")

 效果演示:

 完整代码:

package main

import (
	"os"

	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/middleware/tracing"
	"github.com/go-kratos/kratos/v2/transport/http"
)

func main() {
	//使用 kratos 内置标准输出创建日志对象
	logger := log.NewStdLogger(os.Stdout)
	// 用 Log 方法打印日志,需要传入日志级别,不建议使用这种方式,而建议使用 helper 打印日志
	logger.Log(log.LevelInfo, "msg", "logger.Log 打印的日志")
	//对 logger 进行包装,简化写日志时需传入的参数
	h := log.NewHelper(logger)
	//写入信息级别的日志
	h.Info("使用 kratos 内置标准输出记录的日志")
	//写入错误级别的日志
	h.Errorf("用户名【%s】不存在", "张三")

	// 通过 log.With 方法绑定全局字段到 Vauler,用来打印全局信息到日志中
	logger = log.With(logger,
		"ts", log.DefaultTimestamp,
		"caller", log.DefaultCaller,
		"trace_id", tracing.TraceID(),
		"span_id", tracing.SpanID(),
	)
	h = log.NewHelper(logger)
	h.Info("绑定了全局信息到日志中")

	// 日志过滤,显示特定级别的日志,或对敏感信息脱敏
	h = log.NewHelper(
		log.NewFilter(logger,
			// 按等级过滤
			//log.FilterLevel(log.LevelError),

			// 按key遮蔽
			log.FilterKey("username"),

			// 按value遮蔽
			log.FilterValue("hello"),
		),
	)
	h.Warn("warn log")
	h.Infow("password", "123456")
	//日志中 kratos 会变为 ***
	h.Infow("username", "kratos")
	//日志中 hello 会变为 ***
	h.Info("hello")


	//创建 kratos http server 及 app
	httpSrv := http.NewServer(
		http.Address(":8080"),
	)

	app := kratos.New(
		kratos.Name("测试log"),
		kratos.Server(
			httpSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

2、将日志写入到本地文件的方法

 ① 通过 os.OpenFile 新建文件,获取到 os.File 对象

f, err := os.OpenFile("../test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
	return
}

② 通过 log.NewStdLogger(w io.Writer) 新建 logger 时,传入上述 os.File 对象 f

//输出到控制台,传入os.Stdout;输出到文件,则传文件指针
logger := log.NewStdLogger(f)

注意:os.File 实现了  io.Writer 接口的 Write(p []byte) (n int, err error) 方法,所以能将 f 作为输入参数传入 log.NewStdLogger() 函数

③用 log.NewHelper 包装 logger,并打印日志

h1 := log.NewHelper(logger1)
h1.Info("输出到日志文件中的日志信息")

效果演示:

代码实现:

package main

import (
	"os"

	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/transport/http"
)

func main() {
	
	// 通过 log.NewStdLogger(w io.Writer) 中的 io.Writer 设置日志输出方式
	// 将日志输出到 test.log 文件
	f, err := os.OpenFile("../test.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		return
	}
	logger1 := log.NewStdLogger(f)
	h1 := log.NewHelper(logger1)
	h1.Info("输出到日志文件中的日志信息")

	//创建 kratos http server 及 app
	httpSrv := http.NewServer(
		http.Address(":8080"),
	)

	app := kratos.New(
		kratos.Name("测试log"),
		kratos.Server(
			httpSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

三、 zap 日志库的使用方法

1、zap简介

Zap 是 uber 用 go 语言实现的超快、结构化、分级的日志记录库,性能极高,比其他结构化日志记录包快4-10倍。日志记录结构化,以JSON等格式输出。

有关详细介绍见:zap package - go.uber.org/zap - Go Packages

2、使用方法

①安装 zap 包

go get -u go.uber.org/zap

②导入 zap 包

import "go.uber.org/zap"

③用 NewProduction 等方法创建 zap 日志对象

// 创建 zap 日志对象
zaplogger, _ := zap.NewProduction()
defer logger.Sync()

 ④调用 Info、Error 等方法打印日志

//使用 zap 写日志
zaplogger.Info("failed to fetch URL",
	zap.String("url", "http://dddd.com"),
	zap.Int("attempt", 3),
	zap.Duration("backoff", time.Second),
)

效果演示:

 完整代码:

package main

import (
	"time"

	kzap "github.com/go-kratos/kratos/contrib/log/zap/v2"
	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/transport/http"
	"go.uber.org/zap"
)

func main() {
	// 创建 zap 日志对象,并打印日志
	zaplogger, _ := zap.NewProduction()
	defer zaplogger.Sync()
	//使用 zap 写日志
	zaplogger.Info("failed to fetch URL",
		zap.String("url", "http://dddd.com"),
		zap.Int("attempt", 3),
		zap.Duration("backoff", time.Second))


	httpSrv := http.NewServer(
		http.Address(":8080"),
	)

	app := kratos.New(
		kratos.Name("测试log"),
		kratos.Server(
			httpSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

⑤如果需要将日志输出到文件,则使用如下方法:

package main

import (
	"os"

	kzap "github.com/go-kratos/kratos/contrib/log/zap/v2"
	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/transport/http"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	// 配置Encoder
	encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	// 配置 WriteSyncer,将日志写入文件
	f, err := os.OpenFile("zaptest.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	writeSyncer := zapcore.AddSync(f)

	// 设置LogLevel
	level := zap.NewAtomicLevelAt(zapcore.InfoLevel)

	// 创建Core
	core := zapcore.NewCore(encoder, writeSyncer, level)

	// 构建Logger
	zaplogger := zap.New(core)
	logger := kzap.NewLogger(zaplogger)

	// 将 zap 日志对象适配到 kratos 的日志库,以便直接使用 kratos 的日志方法
	h := log.NewHelper(logger)
	h.Info("msg", "kratos适配的zap, 写入日志到文件")

	httpSrv := http.NewServer(
		http.Address(":8080"),
	)

	app := kratos.New(
		kratos.Name("测试log"),
		kratos.Server(
			httpSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

 效果演示:

四、Kratos 适配 zap 日志库的方法

kratos 在 contrib/log  实现好了一些插件,用于适配目前常用的日志库。

  • std 标准输出,Kratos内置
  • fluent 输出到fluentd
  • zap 适配了uber的zap日志库
  • aliyun 输出到阿里云日志

使用方法:

①安装 kratos 的 zap 适配包

go get github.com/go-kratos/kratos/contrib/log/zap/v2

②导入 zap 适配包

import (
	kzap "github.com/go-kratos/kratos/contrib/log/zap/v2"
)

 ③创建 zap 日志对象

// 创建 zap 日志对象,并打印日志
zaplogger, _ := zap.NewProduction()
defer zaplogger.Sync()

④调用函数 NewLogger(zlog *zap.Logger) 将创建的 zap 日志对象适配到 kratos 的日志操作对象

// 将 zap 日志对象适配到 kratos 的日志库,以便直接使用 kratos 的日志方法
logger := kzap.NewLogger(zaplogger)

 ⑤调用函数 log.NewHelper 将 logger 进行包装,简化日志写入操作

h := log.NewHelper(logger)
h.Info("kratos适配的zap日志库")

效果演示:

代码实现:

package main

import (
	kzap "github.com/go-kratos/kratos/contrib/log/zap/v2"
	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/go-kratos/kratos/v2/transport/http"
	"go.uber.org/zap"
)

func main() {
	// 创建 zap 日志对象,并打印日志
	zaplogger, _ := zap.NewProduction()
	defer zaplogger.Sync()

	// 将 zap 日志对象适配到 kratos 的日志库,以便直接使用 kratos 的日志方法
	logger := kzap.NewLogger(zaplogger)
	h := log.NewHelper(logger)
	h.Info("kratos适配的zap日志库")

	httpSrv := http.NewServer(
		http.Address(":8080"),
	)

	app := kratos.New(
		kratos.Name("测试log"),
		kratos.Server(
			httpSrv,
		),
	)

	if err := app.Run(); err != nil {
		log.Fatal(err)
	}
}

五、Kratos 适配第三方日志库的实现原理

kratos 底层日志接口 Logger,可用于快速适配各种日志库到框架中来,仅需要实现一个最简单的Log方法。

// Logger is a logger interface.
type Logger interface {
	Log(level Level, keyvals ...interface{}) error
}

 以适配 uber 的 zap 日志库为例,kratos 在 contrib/log/zap/zap.go 中定义了新的日志结构体 Logger,并实现了方法 Log(level log.Level, keyvals ...interface{}) error。其源码如下:

package zap

import (
	"fmt"

	"go.uber.org/zap"

	"github.com/go-kratos/kratos/v2/log"
)

var _ log.Logger = (*Logger)(nil)

type Logger struct {
	log    *zap.Logger
	msgKey string
}

type Option func(*Logger)

// WithMessageKey with message key.
func WithMessageKey(key string) Option {
	return func(l *Logger) {
		l.msgKey = key
	}
}

func NewLogger(zlog *zap.Logger) *Logger {
	return &Logger{
		log:    zlog,
		msgKey: log.DefaultMessageKey,
	}
}

func (l *Logger) Log(level log.Level, keyvals ...interface{}) error {
	var (
		msg    = ""
		keylen = len(keyvals)
	)
	if keylen == 0 || keylen%2 != 0 {
		l.log.Warn(fmt.Sprint("Keyvalues must appear in pairs: ", keyvals))
		return nil
	}

	data := make([]zap.Field, 0, (keylen/2)+1)
	for i := 0; i < keylen; i += 2 {
		if keyvals[i].(string) == l.msgKey {
			msg, _ = keyvals[i+1].(string)
			continue
		}
		data = append(data, zap.Any(fmt.Sprint(keyvals[i]), keyvals[i+1]))
	}

	switch level {
	case log.LevelDebug:
		l.log.Debug(msg, data...)
	case log.LevelInfo:
		l.log.Info(msg, data...)
	case log.LevelWarn:
		l.log.Warn(msg, data...)
	case log.LevelError:
		l.log.Error(msg, data...)
	case log.LevelFatal:
		l.log.Fatal(msg, data...)
	}
	return nil
}

func (l *Logger) Sync() error {
	return l.log.Sync()
}

func (l *Logger) Close() error {
	return l.Sync()
}

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

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

相关文章

学习图形推理

学习图形推理 1.位置规律1.1平移1.2翻转、旋转2.样式规律2.1加减异同2.2黑白运算3.属性规律3.1对称性3.2曲直性3.3开闭性4.数量规律4.1面4.2线数量4.3笔画数4.4点数量4.5素数量5.空间重构5.1相对面5.2相邻面-公共边5.3相邻面-公共点5.4相邻面-画边法题型 一组图:从左往右找规律…

SQL——SELECT相关的题目

目录 197、上升的温度 577、员工奖金 586、订单最多的客户 596、超过5名学生的课 610、判断三角形 620、有趣的电影 181、超过经理收入的员工 1179、重新格式化部门表 1280、学生参加各科测试的次数 1068、产品销售分析I 1075、项目员工I 1084、销售分析III 1327、列出指…

LLM-Llama在 MAC M1上体验Llama.cpp和通义千问Qwen 1.5-7B

Llama.cpp的主要目标是在各种硬件上&#xff08;本地和云端&#xff09;实现LLM推断&#xff0c;同时保持最小的设置和最先进的性能。 纯C/C实现&#xff0c;没有任何依赖关系Apple芯片是一级的支持对象 - 通过ARM NEON、Accelerate和Metal框架进行优化对x86架构的AVX、AVX2和…

后端之路第二站(正片)——SprintBoot之:分层解耦

很抽象&#xff0c;我自己也不好理解&#xff0c;仅作为一个前端转后端的个人理解 一、先解释一个案例&#xff0c;以这个案例来分析“三层架构” 这里我先解释一下黑马程序员里的这个案例&#xff0c;兄弟们看视频的可以跳过这节课&#xff1a;Day05-08. 请求响应-响应-案例_…

U-Mail邮件系统反垃圾解决方案,彻底解决垃圾邮件

随着互联网的普及和电子邮件的广泛应用&#xff0c;垃圾邮件已成为一种严重的网络污染。首先&#xff0c;垃圾邮件占用了大量的网络带宽&#xff0c;导致正常邮件的传输受阻&#xff0c;严重影响了用户的使用体验。其次&#xff0c;垃圾邮件中的恶意链接和欺诈信息可能导致用户…

day34 贪心算法 455.分发饼干 376. 摆动序列

贪心算法理论基础 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 贪心一般解题步骤&#xff08;贪心无套路&#xff09;&#xff1a; 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 455.分发饼干 …

go routing 之 gorilla/mux

1. 背景 继续学习 go 2. 关于 routing 的学习 上一篇 go 用的库是&#xff1a;net/http &#xff0c;这次我们使用官方的库 github.com/gorilla/mux 来实现 routing。 3. demo示例 package mainimport ("fmt""net/http""github.com/gorilla/mux&…

Python知识详解【1】~{正则表达式}

正则表达式是一种用于匹配字符串模式的文本工具&#xff0c;它由一系列普通字符和特殊字符组成&#xff0c;可以非常灵活地描述和处理字符串。以下是正则表达式的一些基本组成部分及其功能&#xff1a; 普通字符&#xff1a;大多数字母和数字在正则表达式中表示它们自己。例如…

深度学习之基于MTCNN+Facenet的人脸识别身份认证系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着信息技术的快速发展&#xff0c;身份认证技术在日常生活和工作中的重要性日益凸显。传统的…

初始MyBatis ,详细步骤运行第一个MyBatis程序,同时对应步骤MyBatis底层剖析

1. 初始MyBatis &#xff0c;详细步骤运行第一个MyBatis程序&#xff0c;同时对应步骤MyBatis底层剖析 文章目录 1. 初始MyBatis &#xff0c;详细步骤运行第一个MyBatis程序&#xff0c;同时对应步骤MyBatis底层剖析每博一文案2. 前沿知识2.1 框架&#xff08;framework&#…

Oracle递归查询笔记

目录 一、创建表结构和插入数据 二、查询所有子节点 三、查询所有父节点 四、查询指定节点的根节点 五、查询指定节点的递归路径 六、递归子类 七、递归父类 一、创建表结构和插入数据 CREATE TABLE "REGION" ( "ID" VARCHAR2(36) DEFAULT SYS_GUI…

jdk17安装教程详细(jdk17安装超详细图文)

2021年9月14日JDK17 发布&#xff0c;其中不仅包含很多新语言功能&#xff0c;而且与旧版 JDK 相比&#xff0c;性能提升也非常明显。与之前 LTS 版本的 JDK 8 和 JDK 11 相比&#xff0c;JDK17 的性能提升尤为明显&#xff0c;本文将教你如何安装 相比于JDK1.8&#xff0c;JD…

信号:MSK调制和GMSK调制

目录 一、MSK信号 1. MSK信号的第k个码元 2.MSK信号的频率间隔 3.MSK信号的相位连续性 3.1 相位路径 3.2初始相位ψk 4.MSK信号的产生 原理框图 5.MSK信号的频谱图 二、高斯最小频移键控(GMSK) 1.频率响应 2.GMSK调制产生方式 2.1 高斯滤波器法 2.2 正交调制器法…

《MySQL怎样运行的》—InnoDB数据页结构

在上一篇文章中我们讲了&#xff0c;InnoDB的数据页是InnoDB管理存储空间的基本单位&#xff0c;一个页的大小基本为16kb 那你有没有疑问&#xff0c;就是说这个InnoDB的数据页的结构是什么样的&#xff0c;还有他这些结构分别有那些功能~接下来我们一一讲解 数据页的总览结构…

内部类知识点

什么是内部类&#xff1f; 内部类何时出现&#xff1f;B类是A类的一部分&#xff0c;且B单独存在无意义 内部类分类 成员内部类&#xff1a; 当内部类被private修饰后&#xff0c;不能用方法2 调用外部类成员变量 内部类里面有隐藏的outer this来记录 静态内部类 创建对象&…

路由引入实验(华为)

思科设备参考&#xff1a;路由引入实验&#xff08;思科&#xff09; 技术简介 路由引入技术在网络通信中起着重要的作用&#xff0c;能够实现不同路由协议之间的路由传递&#xff0c;并在路由引入时部署路由控制&#xff0c;实现路径或策略的控制 实验目的 不同的路由协议之…

用Intellij实现web登录页面时,servlet已经配置好了,但是还是报404

今天看到一个404问题&#xff1a; 用Intellij实现web登录页面时&#xff0c;代码如下图所示。点击运行后会跳转到浏览器&#xff0c;但是输入/login时&#xff0c;浏览器显示404&#xff0c;且无法在控制面板上打印内容&#xff1b;输入/index时&#xff0c;也无法在浏览器上显…

html+css 驾考首页设计

效果 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>17sucai - A Pen by Mark Boots</title><!-- <link rel"stylesheet" href"./style.css"> --></head>…

【源码分享】简单的404 HTML页面示例,该页面在加载时会等待2秒钟,然后自动重定向到首页

展示效果 源码 html <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><title>404 页面未找到</title><meta http-equiv"refresh" content"2;url/"> <!-- 设置2秒后跳转到首…