Go 语言之自定义 zap 日志

这篇具有很好参考价值的文章主要介绍了Go 语言之自定义 zap 日志。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Go 语言之自定义 zap 日志

zap 日志:https://github.com/uber-go/zap

一、日志写入文件

  • zap.NewProductionzap.NewDevelopment 是预设配置好的。
  • zap.New 可自定义配置

zap.New源码

这是构造Logger最灵活的方式,但也是最冗长的方式。

对于典型的用例,高度固执己见的预设(NewProduction、NewDevelopment和NewExample)或Config结构体更方便。

// New constructs a new Logger from the provided zapcore.Core and Options. If
// the passed zapcore.Core is nil, it falls back to using a no-op
// implementation.
//
// This is the most flexible way to construct a Logger, but also the most
// verbose. For typical use cases, the highly-opinionated presets
// (NewProduction, NewDevelopment, and NewExample) or the Config struct are
// more convenient.
//
// For sample code, see the package-level AdvancedConfiguration example.
func New(core zapcore.Core, options ...Option) *Logger {
	if core == nil {
		return NewNop()
	}
	log := &Logger{
		core:        core,
		errorOutput: zapcore.Lock(os.Stderr),
		addStack:    zapcore.FatalLevel + 1,
		clock:       zapcore.DefaultClock,
	}
	return log.WithOptions(options...)
}

zapcore.Core 源码

// Core is a minimal, fast logger interface. It's designed for library authors
// to wrap in a more user-friendly API.
type Core interface {
	LevelEnabler

	// With adds structured context to the Core.
	With([]Field) Core
	// Check determines whether the supplied Entry should be logged (using the
	// embedded LevelEnabler and possibly some extra logic). If the entry
	// should be logged, the Core adds itself to the CheckedEntry and returns
	// the result.
	//
	// Callers must use Check before calling Write.
	Check(Entry, *CheckedEntry) *CheckedEntry
	// Write serializes the Entry and any Fields supplied at the log site and
	// writes them to their destination.
	//
	// If called, Write should always log the Entry and Fields; it should not
	// replicate the logic of Check.
	Write(Entry, []Field) error
	// Sync flushes buffered logs (if any).
	Sync() error
}

zapcore.AddSync(file) 源码解析

func AddSync(w io.Writer) WriteSyncer {
	switch w := w.(type) {
	case WriteSyncer:
		return w
	default:
		return writerWrapper{w}
	}
}

type writerWrapper struct {
	io.Writer
}

func (w writerWrapper) Sync() error {
	return nil
}

type WriteSyncer interface {
	io.Writer
	Sync() error
}

日志级别

// A Level is a logging priority. Higher levels are more important.
type Level int8

const (
	// DebugLevel logs are typically voluminous, and are usually disabled in
	// production.
	DebugLevel Level = iota - 1
	// InfoLevel is the default logging priority.
	InfoLevel
	// WarnLevel logs are more important than Info, but don't need individual
	// human review.
	WarnLevel
	// ErrorLevel logs are high-priority. If an application is running smoothly,
	// it shouldn't generate any error-level logs.
	ErrorLevel
	// DPanicLevel logs are particularly important errors. In development the
	// logger panics after writing the message.
	DPanicLevel
	// PanicLevel logs a message, then panics.
	PanicLevel
	// FatalLevel logs a message, then calls os.Exit(1).
	FatalLevel

	_minLevel = DebugLevel
	_maxLevel = FatalLevel

	// InvalidLevel is an invalid value for Level.
	//
	// Core implementations may panic if they see messages of this level.
	InvalidLevel = _maxLevel + 1
)

实操

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net/http"
	"os"
)

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
//与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
//	Info(...any)           Print-style logging
//	Infow(...any)          Structured logging (read as "info with")
//	Infof(string, ...any)  Printf-style logging
//	Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

func main() {
	// 初始化
	InitLogger()
	// Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
	// 在程序退出之前,把缓冲区里的日志刷到磁盘上
	defer logger.Sync()
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	// NewCore创建一个向WriteSyncer写入日志的Core。

	// A WriteSyncer is an io.Writer that can also flush any buffered data. Note
	// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.

	// LevelEnabler决定在记录消息时是否启用给定的日志级别。
	// Each concrete Level value implements a static LevelEnabler which returns
	// true for itself and all higher logging levels. For example WarnLevel.Enabled()
	// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
	// FatalLevel, but return false for InfoLevel and DebugLevel.
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	// New constructs a new Logger from the provided zapcore.Core and Options. If
	// the passed zapcore.Core is nil, it falls back to using a no-op
	// implementation.
	logger = zap.New(core)
	// Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
	// 因此一个应用程序同时使用Loggers和SugaredLoggers是合理的,在性能敏感代码的边界上在它们之间进行转换。
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	// NewJSONEncoder创建了一个快速、低分配的JSON编码器。编码器适当地转义所有字段键和值。
	// NewProductionEncoderConfig returns an opinionated EncoderConfig for
	// production environments.
	return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

func getLogWriter() zapcore.WriteSyncer {
	// Create创建或截断指定文件。如果文件已经存在,它将被截断。如果该文件不存在,则以模式0666(在umask之前)创建。
	// 如果成功,返回的File上的方法可以用于IO;关联的文件描述符模式为O_RDWR。如果有一个错误,它的类型将是PathError。
	file, _ := os.Create("./test.log")
	// AddSync converts an io.Writer to a WriteSyncer. It attempts to be
	// intelligent: if the concrete type of the io.Writer implements WriteSyncer,
	// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
	return zapcore.AddSync(file)
}

func simpleHttpGet(url string) {
	// Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
	//	301 (Moved Permanently)
	//	302 (Found)
	//	303 (See Other)
	//	307 (Temporary Redirect)
	//	308 (Permanent Redirect)
	// Get is a wrapper around DefaultClient.Get.
	// 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
	resp, err := http.Get(url)
	if err != nil {
		// Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Error(

		// 错误使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Error(
			"Error fetching url..",
			zap.String("url", url), // 字符串用给定的键和值构造一个字段。
			zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
	} else {
		// Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Info("Success..",

		// Info使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

test.log 文件

{"level":"error","ts":1686973863.114231,"msg":"Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get \"www.baidu.com\": unsupported protocol scheme \"\"}"}
{"level":"info","ts":1686973863.160213,"msg":"Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}"}

运行后终端无输出,日志写入到 test.log 文件中,是 JSON 格式的。

二、实现编码形式修改

NewJSONEncoder 修改为 NewConsoleEncoder

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net/http"
	"os"
)

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
//与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
//	Info(...any)           Print-style logging
//	Infow(...any)          Structured logging (read as "info with")
//	Infof(string, ...any)  Printf-style logging
//	Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

func main() {
	// 初始化
	InitLogger()
	// Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
	// 在程序退出之前,把缓冲区里的日志刷到磁盘上
	defer logger.Sync()
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	// NewCore创建一个向WriteSyncer写入日志的Core。

	// A WriteSyncer is an io.Writer that can also flush any buffered data. Note
	// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.

	// LevelEnabler决定在记录消息时是否启用给定的日志级别。
	// Each concrete Level value implements a static LevelEnabler which returns
	// true for itself and all higher logging levels. For example WarnLevel.Enabled()
	// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
	// FatalLevel, but return false for InfoLevel and DebugLevel.
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	// New constructs a new Logger from the provided zapcore.Core and Options. If
	// the passed zapcore.Core is nil, it falls back to using a no-op
	// implementation.
	logger = zap.New(core)
	// Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
	// 因此一个应用程序同时使用Loggers和SugaredLoggers是合理的,在性能敏感代码的边界上在它们之间进行转换。
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	// NewJSONEncoder创建了一个快速、低分配的JSON编码器。编码器适当地转义所有字段键和值。
	// NewProductionEncoderConfig returns an opinionated EncoderConfig for
	// production environments.
	//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	// NewConsoleEncoder创建一个编码器,其输出是为人类而不是机器设计的。
	// 它以纯文本格式序列化核心日志条目数据(消息、级别、时间戳等),并将结构化上下文保留为JSON。
	return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
}

func getLogWriter() zapcore.WriteSyncer {
	// Create创建或截断指定文件。如果文件已经存在,它将被截断。如果该文件不存在,则以模式0666(在umask之前)创建。
	// 如果成功,返回的File上的方法可以用于IO;关联的文件描述符模式为O_RDWR。如果有一个错误,它的类型将是PathError。
	file, _ := os.Create("./test.log")
	// AddSync converts an io.Writer to a WriteSyncer. It attempts to be
	// intelligent: if the concrete type of the io.Writer implements WriteSyncer,
	// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
	return zapcore.AddSync(file)
}

func simpleHttpGet(url string) {
	// Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
	//	301 (Moved Permanently)
	//	302 (Found)
	//	303 (See Other)
	//	307 (Temporary Redirect)
	//	308 (Permanent Redirect)
	// Get is a wrapper around DefaultClient.Get.
	// 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
	resp, err := http.Get(url)
	if err != nil {
		// Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Error(

		// 错误使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Error(
			"Error fetching url..",
			zap.String("url", url), // 字符串用给定的键和值构造一个字段。
			zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
	} else {
		// Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Info("Success..",

		// Info使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

test.log 文件

1.68697448701199e+09	error	Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
1.68697448705248e+09	info	Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}

思考:为什么 test.log 文件中日志不是追加?

答:因为使用的是 os.Create。 Create creates or truncates the named file. If the file already exists,

it is truncated. If the file does not exist, it is created with mode 0666
(before umask). 可以使用 os.OpenFile 实现追加。

三、使用 os.OpenFile 实现日志追加

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"log"
	"net/http"
	"os"
)

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
//与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
//	Info(...any)           Print-style logging
//	Infow(...any)          Structured logging (read as "info with")
//	Infof(string, ...any)  Printf-style logging
//	Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

func main() {
	// 初始化
	InitLogger()
	// Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
	// 在程序退出之前,把缓冲区里的日志刷到磁盘上
	defer logger.Sync()
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	// NewCore创建一个向WriteSyncer写入日志的Core。

	// A WriteSyncer is an io.Writer that can also flush any buffered data. Note
	// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.

	// LevelEnabler决定在记录消息时是否启用给定的日志级别。
	// Each concrete Level value implements a static LevelEnabler which returns
	// true for itself and all higher logging levels. For example WarnLevel.Enabled()
	// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
	// FatalLevel, but return false for InfoLevel and DebugLevel.
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	// New constructs a new Logger from the provided zapcore.Core and Options. If
	// the passed zapcore.Core is nil, it falls back to using a no-op
	// implementation.
	logger = zap.New(core)
	// Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
	// 因此一个应用程序同时使用Loggers和SugaredLoggers是合理的,在性能敏感代码的边界上在它们之间进行转换。
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	// NewJSONEncoder创建了一个快速、低分配的JSON编码器。编码器适当地转义所有字段键和值。
	// NewProductionEncoderConfig returns an opinionated EncoderConfig for
	// production environments.
	//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	// NewConsoleEncoder创建一个编码器,其输出是为人类而不是机器设计的。
	// 它以纯文本格式序列化核心日志条目数据(消息、级别、时间戳等),并将结构化上下文保留为JSON。
	return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
}

func getLogWriter() zapcore.WriteSyncer {
	// Create创建或截断指定文件。如果文件已经存在,它将被截断。如果该文件不存在,则以模式0666(在umask之前)创建。
	// 如果成功,返回的File上的方法可以用于IO;关联的文件描述符模式为O_RDWR。如果有一个错误,它的类型将是PathError。
	//file, _ := os.Create("./test.log")
	file, err := os.OpenFile("./test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalf("open log file failed with error: %v", err)
	}
	// AddSync converts an io.Writer to a WriteSyncer. It attempts to be
	// intelligent: if the concrete type of the io.Writer implements WriteSyncer,
	// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
	return zapcore.AddSync(file)
}

func simpleHttpGet(url string) {
	// Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
	//	301 (Moved Permanently)
	//	302 (Found)
	//	303 (See Other)
	//	307 (Temporary Redirect)
	//	308 (Permanent Redirect)
	// Get is a wrapper around DefaultClient.Get.
	// 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
	resp, err := http.Get(url)
	if err != nil {
		// Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Error(

		// 错误使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Error(
			"Error fetching url..",
			zap.String("url", url), // 字符串用给定的键和值构造一个字段。
			zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
	} else {
		// Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Info("Success..",

		// Info使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

test.log

1.68697448701199e+09	error	Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
1.68697448705248e+09	info	Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}
1.6869751152769642e+09	error	Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
1.686975115813772e+09	info	Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}

四、优化时间展示

  • 目前时间展示:1.686975115813772e+09

实操

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"log"
	"net/http"
	"os"
)

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
//与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
//	Info(...any)           Print-style logging
//	Infow(...any)          Structured logging (read as "info with")
//	Infof(string, ...any)  Printf-style logging
//	Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

func main() {
	// 初始化
	InitLogger()
	// Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
	// 在程序退出之前,把缓冲区里的日志刷到磁盘上
	defer logger.Sync()
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	// NewCore创建一个向WriteSyncer写入日志的Core。

	// A WriteSyncer is an io.Writer that can also flush any buffered data. Note
	// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.

	// LevelEnabler决定在记录消息时是否启用给定的日志级别。
	// Each concrete Level value implements a static LevelEnabler which returns
	// true for itself and all higher logging levels. For example WarnLevel.Enabled()
	// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
	// FatalLevel, but return false for InfoLevel and DebugLevel.
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	// New constructs a new Logger from the provided zapcore.Core and Options. If
	// the passed zapcore.Core is nil, it falls back to using a no-op
	// implementation.
	logger = zap.New(core)
	// Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
	// 因此一个应用程序同时使用Loggers和SugaredLoggers是合理的,在性能敏感代码的边界上在它们之间进行转换。
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	// NewJSONEncoder创建了一个快速、低分配的JSON编码器。编码器适当地转义所有字段键和值。
	// NewProductionEncoderConfig returns an opinionated EncoderConfig for
	// production environments.
	//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	// NewConsoleEncoder创建一个编码器,其输出是为人类而不是机器设计的。
	// 它以纯文本格式序列化核心日志条目数据(消息、级别、时间戳等),并将结构化上下文保留为JSON。
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter() zapcore.WriteSyncer {
	// Create创建或截断指定文件。如果文件已经存在,它将被截断。如果该文件不存在,则以模式0666(在umask之前)创建。
	// 如果成功,返回的File上的方法可以用于IO;关联的文件描述符模式为O_RDWR。如果有一个错误,它的类型将是PathError。
	//file, _ := os.Create("./test.log")
	file, err := os.OpenFile("./test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalf("open log file failed with error: %v", err)
	}
	// AddSync converts an io.Writer to a WriteSyncer. It attempts to be
	// intelligent: if the concrete type of the io.Writer implements WriteSyncer,
	// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
	return zapcore.AddSync(file)
}

func simpleHttpGet(url string) {
	// Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
	//	301 (Moved Permanently)
	//	302 (Found)
	//	303 (See Other)
	//	307 (Temporary Redirect)
	//	308 (Permanent Redirect)
	// Get is a wrapper around DefaultClient.Get.
	// 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
	resp, err := http.Get(url)
	if err != nil {
		// Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Error(

		// 错误使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Error(
			"Error fetching url..",
			zap.String("url", url), // 字符串用给定的键和值构造一个字段。
			zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
	} else {
		// Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Info("Success..",

		// Info使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

test.log

1.68697448701199e+09	error	Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
1.68697448705248e+09	info	Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}
1.6869751152769642e+09	error	Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
1.686975115813772e+09	info	Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}
2023-06-17T13:00:07.720+0800	error	Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
2023-06-17T13:00:07.766+0800	info	Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}

根据 ISO8601TimeEncoder 源码 可修改为自定义格式

func encodeTimeLayout(t time.Time, layout string, enc PrimitiveArrayEncoder) {
	type appendTimeEncoder interface {
		AppendTimeLayout(time.Time, string)
	}

	if enc, ok := enc.(appendTimeEncoder); ok {
		enc.AppendTimeLayout(t, layout)
		return
	}

	enc.AppendString(t.Format(layout))
}


// ISO8601TimeEncoder serializes a time.Time to an ISO8601-formatted string
// with millisecond precision.
//
// If enc supports AppendTimeLayout(t time.Time,layout string), it's used
// instead of appending a pre-formatted string value.
func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) {
	encodeTimeLayout(t, "2006-01-02T15:04:05.000Z0700", enc)
}

五、添加调用者信息(使用zap调用者的文件名、行号和函数名(或不使用)来注释每条消息)

logger = zap.New(core, zap.AddCaller())

AddCaller 源码

// AddCaller configures the Logger to annotate each message with the filename,
// line number, and function name of zap's caller. See also WithCaller.
func AddCaller() Option {
	return WithCaller(true)
}

// WithCaller configures the Logger to annotate each message with the filename,
// line number, and function name of zap's caller, or not, depending on the
// value of enabled. This is a generalized form of AddCaller.
func WithCaller(enabled bool) Option {
	return optionFunc(func(log *Logger) {
		log.addCaller = enabled
	})
}

实操

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"log"
	"net/http"
	"os"
)

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
//与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
//	Info(...any)           Print-style logging
//	Infow(...any)          Structured logging (read as "info with")
//	Infof(string, ...any)  Printf-style logging
//	Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

func main() {
	// 初始化
	InitLogger()
	// Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
	// 在程序退出之前,把缓冲区里的日志刷到磁盘上
	defer logger.Sync()
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("http://www.baidu.com")
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	// NewCore创建一个向WriteSyncer写入日志的Core。

	// A WriteSyncer is an io.Writer that can also flush any buffered data. Note
	// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.

	// LevelEnabler决定在记录消息时是否启用给定的日志级别。
	// Each concrete Level value implements a static LevelEnabler which returns
	// true for itself and all higher logging levels. For example WarnLevel.Enabled()
	// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
	// FatalLevel, but return false for InfoLevel and DebugLevel.
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	// New constructs a new Logger from the provided zapcore.Core and Options. If
	// the passed zapcore.Core is nil, it falls back to using a no-op
	// implementation.

	// AddCaller configures the Logger to annotate each message with the filename,
	// line number, and function name of zap's caller. See also WithCaller.
	logger = zap.New(core, zap.AddCaller())
	// Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
	// 因此一个应用程序同时使用Loggers和SugaredLoggers是合理的,在性能敏感代码的边界上在它们之间进行转换。
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	// NewJSONEncoder创建了一个快速、低分配的JSON编码器。编码器适当地转义所有字段键和值。
	// NewProductionEncoderConfig returns an opinionated EncoderConfig for
	// production environments.
	//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	// NewConsoleEncoder创建一个编码器,其输出是为人类而不是机器设计的。
	// 它以纯文本格式序列化核心日志条目数据(消息、级别、时间戳等),并将结构化上下文保留为JSON。
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter() zapcore.WriteSyncer {
	// Create创建或截断指定文件。如果文件已经存在,它将被截断。如果该文件不存在,则以模式0666(在umask之前)创建。
	// 如果成功,返回的File上的方法可以用于IO;关联的文件描述符模式为O_RDWR。如果有一个错误,它的类型将是PathError。
	//file, _ := os.Create("./test.log")
	file, err := os.OpenFile("./test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalf("open log file failed with error: %v", err)
	}
	// AddSync converts an io.Writer to a WriteSyncer. It attempts to be
	// intelligent: if the concrete type of the io.Writer implements WriteSyncer,
	// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
	return zapcore.AddSync(file)
}

func simpleHttpGet(url string) {
	// Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
	//	301 (Moved Permanently)
	//	302 (Found)
	//	303 (See Other)
	//	307 (Temporary Redirect)
	//	308 (Permanent Redirect)
	// Get is a wrapper around DefaultClient.Get.
	// 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
	resp, err := http.Get(url)
	if err != nil {
		// Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Error(

		// 错误使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Error(
			"Error fetching url..",
			zap.String("url", url), // 字符串用给定的键和值构造一个字段。
			zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
	} else {
		// Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Info("Success..",

		// Info使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

运行

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go

test.log

2023-06-17T13:31:22.417+0800	error	zap_demo/main.go:125	Error fetching url..{url 15 0 www.baidu.com <nil>} {error 26 0  Get "www.baidu.com": unsupported protocol scheme ""}
2023-06-17T13:31:22.462+0800	info	zap_demo/main.go:134	Success..{statusCode 15 0 200 OK <nil>} {url 15 0 http://www.baidu.com <nil>}

六、日志切割归档 zap 不支持,使用第三库 lumberjack

按日期切割参考以下链接:

file-rotatelogs:https://github.com/lestrrat-go/file-rotatelogs

日志切割归档

Lumberjack:https://github.com/natefinch/lumberjack

Lumberjack is a Go package for writing logs to rolling files.

lumberjack 使用

import "gopkg.in/natefinch/lumberjack.v2"

Lumberjack is intended to be one part of a logging infrastructure. It is not an all-in-one solution, but instead is a pluggable component at the bottom of the logging stack that simply controls the files to which logs are written.

Lumberjack plays well with any logging package that can write to an io.Writer, including the standard library's log package.

Lumberjack assumes that only one process is writing to the output files. Using the same lumberjack configuration from multiple processes on the same machine will result in improper behavior.

Example

To use lumberjack with the standard library's log package, just pass it into the SetOutput function when your application starts.

Code:

log.SetOutput(&lumberjack.Logger{
    Filename:   "/var/log/myapp/foo.log",
    MaxSize:    500, // megabytes
    MaxBackups: 3,
    MaxAge:     28, //days
    Compress:   true, // disabled by default
})

安装

go get gopkg.in/natefinch/lumberjack.v2

实操 测试

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"net/http"
)

// 定义一个全局 logger 实例
// Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
// Logger是为每一微秒和每一个分配都很重要的上下文设计的,
// 因此它的API有意倾向于性能和类型安全,而不是简便性。
// 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
var logger *zap.Logger

// SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
//与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
//   - methods named after the log level for log.Print-style logging
//   - methods ending in "w" for loosely-typed structured logging
//   - methods ending in "f" for log.Printf-style logging
//   - methods ending in "ln" for log.Println-style logging

// For example, the methods for InfoLevel are:
//
//	Info(...any)           Print-style logging
//	Infow(...any)          Structured logging (read as "info with")
//	Infof(string, ...any)  Printf-style logging
//	Infoln(...any)         Println-style logging
var sugarLogger *zap.SugaredLogger

func main() {
	// 初始化
	InitLogger()
	// Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
	// 在程序退出之前,把缓冲区里的日志刷到磁盘上
	defer logger.Sync()
	simpleHttpGet("www.baidu.com")
	simpleHttpGet("http://www.baidu.com")

	for i := 0; i < 10000; i++ {
		logger.Info("test lumberjack for log rotate....")
	}
}

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	// NewCore创建一个向WriteSyncer写入日志的Core。

	// A WriteSyncer is an io.Writer that can also flush any buffered data. Note
	// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.

	// LevelEnabler决定在记录消息时是否启用给定的日志级别。
	// Each concrete Level value implements a static LevelEnabler which returns
	// true for itself and all higher logging levels. For example WarnLevel.Enabled()
	// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
	// FatalLevel, but return false for InfoLevel and DebugLevel.
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	// New constructs a new Logger from the provided zapcore.Core and Options. If
	// the passed zapcore.Core is nil, it falls back to using a no-op
	// implementation.

	// AddCaller configures the Logger to annotate each message with the filename,
	// line number, and function name of zap's caller. See also WithCaller.
	logger = zap.New(core, zap.AddCaller())
	// Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
	// 因此一个应用程序同时使用Loggers和SugaredLoggers是合理的,在性能敏感代码的边界上在它们之间进行转换。
	sugarLogger = logger.Sugar()
}

func getEncoder() zapcore.Encoder {
	// NewJSONEncoder创建了一个快速、低分配的JSON编码器。编码器适当地转义所有字段键和值。
	// NewProductionEncoderConfig returns an opinionated EncoderConfig for
	// production environments.
	//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

	// NewConsoleEncoder创建一个编码器,其输出是为人类而不是机器设计的。
	// 它以纯文本格式序列化核心日志条目数据(消息、级别、时间戳等),并将结构化上下文保留为JSON。
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:        "ts",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		FunctionKey:    zapcore.OmitKey,
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.ShortCallerEncoder,
	}

	return zapcore.NewConsoleEncoder(encoderConfig)
}

//func getLogWriter() zapcore.WriteSyncer {
//	// Create创建或截断指定文件。如果文件已经存在,它将被截断。如果该文件不存在,则以模式0666(在umask之前)创建。
//	// 如果成功,返回的File上的方法可以用于IO;关联的文件描述符模式为O_RDWR。如果有一个错误,它的类型将是PathError。
//	//file, _ := os.Create("./test.log")
//	file, err := os.OpenFile("./test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
//	if err != nil {
//		log.Fatalf("open log file failed with error: %v", err)
//	}
//	// AddSync converts an io.Writer to a WriteSyncer. It attempts to be
//	// intelligent: if the concrete type of the io.Writer implements WriteSyncer,
//	// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
//	return zapcore.AddSync(file)
//}

func getLogWriter() zapcore.WriteSyncer {
	// Logger is an io.WriteCloser that writes to the specified filename.
	// 日志记录器在第一次写入时打开或创建日志文件。如果文件存在并且小于MaxSize兆字节,则lumberjack将打开并追加该文件。
	// 如果该文件存在并且其大小为>= MaxSize兆字节,
	// 则通过将当前时间放在文件扩展名(或者如果没有扩展名则放在文件名的末尾)的名称中的时间戳中来重命名该文件。
	// 然后使用原始文件名创建一个新的日志文件。
	// 每当写操作导致当前日志文件超过MaxSize兆字节时,将关闭当前文件,重新命名,并使用原始名称创建新的日志文件。
	// 因此,您给Logger的文件名始终是“当前”日志文件。
	// 如果MaxBackups和MaxAge均为0,则不会删除旧的日志文件。
	lumberJackLogger := &lumberjack.Logger{
		// Filename是要写入日志的文件。备份日志文件将保留在同一目录下
		Filename: "./test.log",
		// MaxSize是日志文件旋转之前的最大大小(以兆字节为单位)。默认为100兆字节。
		MaxSize: 1, // M
		// MaxBackups是要保留的旧日志文件的最大数量。默认是保留所有旧的日志文件(尽管MaxAge仍然可能导致它们被删除)。
		MaxBackups: 5, // 备份数量
		// MaxAge是根据文件名中编码的时间戳保留旧日志文件的最大天数。
		// 请注意,一天被定义为24小时,由于夏令时、闰秒等原因,可能与日历日不完全对应。默认情况下,不根据时间删除旧的日志文件。
		MaxAge: 30, // 备份天数
		// Compress决定是否应该使用gzip压缩旋转的日志文件。默认情况下不执行压缩。
		Compress: false, // 是否压缩
	}

	return zapcore.AddSync(lumberJackLogger)
}

func simpleHttpGet(url string) {
	// Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
	//	301 (Moved Permanently)
	//	302 (Found)
	//	303 (See Other)
	//	307 (Temporary Redirect)
	//	308 (Permanent Redirect)
	// Get is a wrapper around DefaultClient.Get.
	// 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
	resp, err := http.Get(url)
	if err != nil {
		// Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Error(

		// 错误使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Error(
			"Error fetching url..",
			zap.String("url", url), // 字符串用给定的键和值构造一个字段。
			zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
	} else {
		// Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
		//logger.Info("Success..",

		// Info使用fmt。以Sprint方式构造和记录消息。
		sugarLogger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

运行文章来源地址https://www.toymoban.com/news/detail-487508.html

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go                         

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ go run main.go

Code/go/zap_demo via 🐹 v1.20.3 via 🅒 base 
➜ 

到了这里,关于Go 语言之自定义 zap 日志的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang一日一库之 日志库 zap

    在开发过程中 会使用到日志库去记录错误的日志,尤其是golang中 有无穷无尽的error 如果不记录,当你的代码出错,就无从排错了。 zap 是开源的 Go 高性能日志库 主要有以下特点: 支持不同的日志级别 能够打印基本信息等但不支持日志的分割 但是可以使用 lumberjack 也是 za

    2023年04月12日
    浏览(50)
  • go语言日志收集系统

    完整项目的GitHub地址 1. 项目背景 a. 每个系统都有日志,当系统出现问题时,需要通过日志解决问题 b. 当系统机器比较少时,登陆到服务器上查看即可满足 c. 当系统机器规模巨大,登陆到机器上查看几乎不现实 2. 解决方案 a. 把机器上的日志实时收集,统一的存储到中心系统

    2023年04月08日
    浏览(40)
  • Gin框架原生方式切割日志,Go语言原生日志切割

    目录 摘要 痛点 正文 1.分析 io.Writer 接口 2.实现 io.Writer 接口 3.将它作为原生输出 4.将它作为 Gin 框架的输出 自定义一个日志输出,将go语言和gin框架的日志自动按天拆分。本文通过实现io.Writer接口的方式,替换原生和gin框架的默认Writer,并植入了自定义的逻辑。该示例只讲述

    2024年02月09日
    浏览(50)
  • GO语言日志切割 + 记录调用源

    日志记录对程序排查问题比较关键,记录下GO中日志选择,从以下出发点考虑: 日志文件能自动切割,以免过大 能记录从哪个文件哪行代码调用的,方便排查问题 配置简单明了 库文件使用人数较多,稳定 经过一段时间摸索,最终选择了 Logrus 和 lumberjack 两个库,使用人数都

    2024年02月15日
    浏览(38)
  • 【Go语言开发】将logrus日志送到elasticsearch构成elk体系

    这篇文章我们来讲讲怎么把logrus日志送到es。 使用的日志库是 github.com/sirupsen/logrus ,由于这个包中的日志对象是可以接入很多个hook的,所以我们可以使用hook来接入 elasticsearch 来操作 。 hook 就是钩子,当设置hook在某个点之后,hook会执行这个点之后异步进行。 比如让我们把

    2024年02月12日
    浏览(31)
  • SpringMVC之自定义注解

    目录 前言 一、自定义注解 1.Java注解简介 2. 注解的用处 3. 为什么要用注解 4.自定义注解的应用场景 5. 注解的分类 6.如何定义并使用自定义注解 7.自定义注解三种使用案例 案例一: 案例二: 案例三: 二、Aop自定义注解的应用   1 .自定义注解类 2.切面类 3.Controller层 随着We

    2024年02月07日
    浏览(51)
  • SpringBoot之自定义starter

    目录 一、什么是SpringBoot starter机制 二、为什么要自定义starter 三、什么时候需要创建自定义starter 四、自动加载核心注解说明 五、自定义starter的开发流程 案例一:为短信发送功能创建一个starter 案例二:AOP方式统一服务日志 SpringBoot中的starter是一种非常重要的机制(自动化配

    2024年02月01日
    浏览(42)
  • react之自定义hooks

    原文合集地址如下,有需要的朋友可以关注 本文地址 合集地址 任何相对独立、复用性强的逻辑,都可以 extract 为自定义 Hook,自定义 Hook 是一种复用 React 的状态逻辑的函数。 自定义 Hook 的主要特点是: 抽象组件间的状态逻辑,方便复用 让功能组件更纯粹,更易于维护 自定义

    2024年02月12日
    浏览(43)
  • Android 之自定义绘制一

    绘制的基本要素 onDraw(Canvas) 绘制方法 Canvas 绘制工具 Paint 调整风格 粗细等 坐标系: x y ,3D 会有z轴,x  左到右,y 上至下,与数学中y颠倒 尺寸单位: 布局中 dp ,sp ,代码中 px;dp 为了适配不同的尺寸 绘制的关键: draw(Canvas )......(关键类:Paint) Paint.ANTI_ALIAS_FLAG 抗锯齿 paint.setFlag 像

    2024年02月11日
    浏览(51)
  • Flutter之自定义路由切换动画

    在Flutter中,我们可以通过Navigator来实现路由管理,包括路由的跳转和返回等。默认情况下,Flutter提供了一些简单的路由切换动画,但是有时候我们需要自定义一些特殊的动画效果来提高用户体验。本文将介绍如何在Flutter中实现自定义的路由切换动画。 在Flutter中,路由切换

    2024年02月10日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包