主頁 > 後端開發 > Go 語言之自定義 zap 日志

Go 語言之自定義 zap 日志

2023-06-18 07:40:00 後端開發

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()
	}
}

運行

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

標籤:其他

上一篇:Spring Boot實作高質量的CRUD-5

下一篇:返回列表

標籤雲
其他(161182) Python(38240) JavaScript(25504) Java(18245) C(15237) 區塊鏈(8271) C#(7972) AI(7469) 爪哇(7425) MySQL(7256) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4601) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2436) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) HtmlCss(1968) 功能(1967) Web開發(1951) C++(1941) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Go 語言之自定義 zap 日志

    # Go 語言之自定義 zap 日志 [zap 日志](https://github.com/uber-go/zap):https://github.com/uber-go/zap ## 一、日志寫入檔案 - `zap.NewProduction`、`zap.NewDevelopment` 是預設配 ......

    uj5u.com 2023-06-18 07:40:00 more
  • Spring Boot實作高質量的CRUD-5

    (續前文) ## 9、Service實作類代碼示例 ? ? 以用戶管理模塊為例,展示Service實作類代碼。用戶管理的Service實作類為UserManServiceImpl。?UserManServiceImpl除了沒有deleteItems方法外,具備CRUD的其它常規方法。實際上?User ......

    uj5u.com 2023-06-18 07:39:53 more
  • ?任務編排工具在之家業務系統中應用探究

    本文主要介紹在之家廣告業務系統中運用任務編排治理工具的場景及其可以解決的問題,講解任務編排框架的核心要點,以及向大家演示一個任務編排框架的基本結構,讓大家對任務編排工具增強業務開發效率,提高研發質量有更深刻的理解。 ......

    uj5u.com 2023-06-18 07:39:37 more
  • Go 語言之 zap 日志庫簡單使用

    # Go 語言之 zap 日志庫簡單使用 ## 默認的 Go log log:https://pkg.go.dev/log ```go package main import ( "log" "os" ) func init() { log.SetPrefix("LOG: ") // 設定前綴 f, ......

    uj5u.com 2023-06-18 07:39:24 more
  • Scala面向物件

    # 類和物件 **組成結構** ? 建構式: 在創建物件的時候給屬性賦值 ? 成員變數: ? 成員方法(函式) ? 區域變數 ? 代碼塊 ## 構造器 每個類都有一個主構造器,這個構造器和類定義"交織"在一起類名后面的內容就是主構造器,如果引數串列為空的話,()可以省略 scala的類有且僅有一個 ......

    uj5u.com 2023-06-18 07:39:00 more
  • 基于回歸分析的波士頓房價分析

    #基于回歸分析的波士頓房價分析 專案實作步驟: 1.專案結構 2.處理資料 3.處理繪圖 4.對資料進行分析 5.結果展示 一.專案結構 ![image](https://img2023.cnblogs.com/blog/3047082/202306/3047082-2023061722315431 ......

    uj5u.com 2023-06-18 07:38:30 more
  • 通過模仿學會Python爬蟲(一):零基礎上手

    好家伙,爬蟲來了 爬蟲,這玩意,不會怎么辦, 誒,先抄一份作業回來 1.別人的爬蟲 Python爬蟲史上超詳細講解(零基礎入門,老年人都看的懂)_ChenBinBini的博客-CSDN博客 # -*- codeing = utf-8 -*- from bs4 import BeautifulSoup ......

    uj5u.com 2023-06-18 07:38:11 more
  • Python潮流周刊#7:我討厭用 asyncio

    你好,我是貓哥。這里記錄每周值得分享的 Python 及通用技術內容,部分為英文,已在小標題注明。(標題取自其中一則分享,不代表全部內容都是該主題,特此宣告。) 首發于我的博客:[https://pythoncat.top/posts/2023-06-17-weekly7](https://pyth ......

    uj5u.com 2023-06-18 07:32:25 more
  • Django學習筆記

    ## 1.常用命令 `創建專案:django-admin startproject 專案名` `創建APP(進入工程目錄):python manage.py startapp 網站名` `創建庫表(進入工程目錄):python manage.py makemigrations` `執行庫表建立(進入 ......

    uj5u.com 2023-06-18 07:32:06 more
  • 09. centos使用docker方式安裝mysql

    ## 一、創建宿主機物理路徑 新建/mydata/mysql/data、log和conf三個檔案夾 ```bash mkdir -p /mnt/mysql/log mkdir -p /mnt/mysql/data mkdir -p /mnt/mysql/config ``` 或者 ```bash m ......

    uj5u.com 2023-06-18 07:31:43 more