Go 語言之自定義 zap 日志
zap 日志:https://github.com/uber-go/zap
一、日志寫入檔案
zap.NewProduction、zap.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()
}
}
運行
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
?

本文來自博客園,作者:尋月隱君,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17487453.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555441.html
標籤:其他
下一篇:返回列表
