英文:
How to elegantly configure a Logger with a configuration file while supporting log rotation
问题
问题描述
- 函数:
Test1()是一个在官方文档中推荐使用的日志轮转和切割库gopkg.in/natefinch/lumberjack.v2。 - 函数:
Test2()是一个使用 yaml 读取配置的日志记录器,基于官方文档中的基本配置。 
在执行主函数后,
在控制台输出中:
2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"}
在日志文件 foo.log 中输出:
{"level":"info","ts":1684111756.5545945,"msg":"logger construction succeeded:lumberjack.Logger"}
这两个日志明显是不同的。
我目前的要求:
- 
两者都支持使用配置文件
config_log_zap.yaml,以便所有配置都能生效,并让lumberjack完成日志轮转和切割工作。 - 
控制台和日志文件的输出应该是相同的,这样我就可以通过配置文件快速应用所需的内容。之所以需要控制台和日志文件都有输出,是因为我需要在开发过程中关注和记录过去的输出消息。
在控制台输出中:
2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"}在日志文件
foo.log中输出:2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"} - 
如何将
Test1()和Test2()合并为一个函数Test0(),以满足上述两个要求? 
请给我一些帮助,我已经研究了很长时间。
main.go
package main
import (
	"gopkg.in/yaml.v3"
	"os"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)
func Test1() {
	// lumberjack.Logger is already safe for concurrent use, so we don't need to
	// lock it.
	w := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./foo.log",
		MaxSize:    500, // megabytes
		MaxBackups: 3,
		MaxAge:     28, // days
	})
	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		w,
		zap.InfoLevel,
	)
	logger := zap.New(core)
	logger.Info("logger construction succeeded:lumberjack.Logger")
}
func Test2() {
	var cfg zap.Config
	yamlFile, _ := os.ReadFile("./config_log_zap.yaml")
	if err := yaml.Unmarshal(yamlFile, &cfg); err != nil {
		panic(err)
	}
	logger := zap.Must(cfg.Build())
	defer logger.Sync()
	logger.Info("logger construction succeeded:config from yaml")
}
func main() {
	Test1()
	Test2()
}
config_log_zap.yaml
# For the full description for the configuration, see
# https://github.com/uber-go/zap/blob/382e2511e51cda8afde24f9e6e741f934308edfa/config.go#L58-L94
level: 'debug'
development: true
disableCaller: true
disableStacktrace: false
sampling:
  initial: 100
  thereafter: 100
encoding: 'console'
encoderConfig:
  messageKey: 'msg'
  levelKey: 'level'
  timeKey: 'ts'
  nameKey: 'logger'
  callerKey: 'caller'
  functionKey: 'function'
  stacktraceKey: 'stacktrace'
  skipLineEnding: false
  lineEnding: "\n"
  levelEncoder: 'capital'
  timeEncoder: 'iso8601'
  durationEncoder: 'string'
  callerEncoder: 'full'
  nameEncoder: 'full'
  consoleSeparator: ' | '
outputPaths:
  - 'stdout'
  - './foo.log'
errorOutputPaths:
  - 'stderr'
  - './error_logs'
initialFields:
  app: 'jpz'
英文:
Problem Description
- Function: 
Test1()is a log rotation and cutting librarygopkg.in/natefinch/lumberjack.v2recommended in the official documentation. - Function: 
Test2()is a Logger that uses yaml to read configuration based on the basic configuration in the official documentation. 
After executing the main function,
in the console output:
2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"}
in the log file foo.log output:
{"level":"info","ts":1684111756.5545945,"msg":"logger construction succeeded:lumberjack.Logger"}
These two logs are definitely different.
My current requirements:
- 
Both support using the configuration file
config_log_zap.yamlso that all configurations can take effect, and letlumberjackcomplete the log rotation and splitting work. - 
The output of both the console and the log file should be the same, so that I can quickly apply the desired content through the configuration file. The reason why both the console and the log file are needed is because I need to pay attention to and record past output messages during development.
In the console output:
2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"}In the log file
foo.logoutput:2023-05-15T08:49:16.555+0800 | INFO | logger construction succeeded:config from yaml | {"app": "jpz"} - 
How should I merge
Test1()andTest2()into one functionTest0()to meet the above two requirements? 
Please give me some help, I've been studying it for a long time.
main.go
package main
import (
	"gopkg.in/yaml.v3"
	"os"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)
func Test1() {
	// lumberjack.Logger is already safe for concurrent use, so we don't need to
	// lock it.
	w := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "./foo.log",
		MaxSize:    500, // megabytes
		MaxBackups: 3,
		MaxAge:     28, // days
	})
	core := zapcore.NewCore(
		zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
		w,
		zap.InfoLevel,
	)
	logger := zap.New(core)
	logger.Info("logger construction succeeded:lumberjack.Logger")
}
func Test2() {
	var cfg zap.Config
	yamlFile, _ := os.ReadFile("./config_log_zap.yaml")
	if err := yaml.Unmarshal(yamlFile, &cfg); err != nil {
		panic(err)
	}
	logger := zap.Must(cfg.Build())
	defer logger.Sync()
	logger.Info("logger construction succeeded:config from yaml")
}
func main() {
	Test1()
	Test2()
}
config_log_zap.yaml
# For the full description for the configuration, see
# https://github.com/uber-go/zap/blob/382e2511e51cda8afde24f9e6e741f934308edfa/config.go#L58-L94
level: 'debug'
development: true
disableCaller: true
disableStacktrace: false
sampling:
  initial: 100
  thereafter: 100
encoding: 'console'
encoderConfig:
  messageKey: 'msg'
  levelKey: 'level'
  timeKey: 'ts'
  nameKey: 'logger'
  callerKey: 'caller'
  functionKey: 'function'
  stacktraceKey: 'stacktrace'
  skipLineEnding: false
  lineEnding: "\n"
  levelEncoder: 'capital'
  timeEncoder: 'iso8601'
  durationEncoder: 'string'
  callerEncoder: 'full'
  nameEncoder: 'full'
  consoleSeparator: ' | '
outputPaths:
  - 'stdout'
  - './foo.log'
errorOutputPaths:
  - 'stderr'
  - './error_logs'
initialFields:
  app: 'jpz'
答案1
得分: 0
使用zap.RegisterSink将lumberjack日志记录器注册为新的sink:
package main
import (
	"net/url"
	"os"
	"strconv"
	"strings"
	"gopkg.in/yaml.v3"
	"go.uber.org/zap"
	"gopkg.in/natefinch/lumberjack.v2"
)
type lumberjackSink struct {
	lumberjack.Logger
}
func (l *lumberjackSink) Sync() error {
	return nil
}
func parseNumber(s string, fallback int) int {
	v, err := strconv.Atoi(s)
	if err == nil {
		return v
	}
	return fallback
}
func Test0() {
	if err := zap.RegisterSink("lumberjack", func(u *url.URL) (zap.Sink, error) {
		// 从URL中读取参数:
		// lumberjack://localhost/foo.log?maxSize=500&maxBackups=3&maxAge=28
		filename := strings.TrimLeft(u.Path, "/")
		if filename == "" {
			filename = "foo.log"
		}
		q := u.Query()
		l := &lumberjackSink{
			Logger: lumberjack.Logger{
				Filename:   filename,
				MaxSize:    parseNumber(q.Get("maxSize"), 500),
				MaxBackups: parseNumber(q.Get("maxBackups"), 3),
				MaxAge:     parseNumber(q.Get("maxAge"), 28),
			},
		}
		return l, nil
	}); err != nil {
		panic(err)
	}
	var cfg zap.Config
	yamlFile, _ := os.ReadFile("./config_log_zap.yaml")
	if err := yaml.Unmarshal(yamlFile, &cfg); err != nil {
		panic(err)
	}
	logger := zap.Must(cfg.Build())
	defer logger.Sync()
	logger.Info("logger construction succeeded:config from yaml")
}
func main() {
	Test0()
}
并修改配置文件以设置outputPaths,如下所示:
outputPaths:
  - stdout
  - lumberjack://localhost/foo.log?maxSize=500&maxBackups=3&maxAge=28
英文:
Use zap.RegisterSink to register the lumberjack logger as a new sink:
package main
import (
	"net/url"
	"os"
	"strconv"
	"strings"
	"gopkg.in/yaml.v3"
	"go.uber.org/zap"
	"gopkg.in/natefinch/lumberjack.v2"
)
type lumberjackSink struct {
	lumberjack.Logger
}
func (l *lumberjackSink) Sync() error {
	return nil
}
func parseNumber(s string, fallback int) int {
	v, err := strconv.Atoi(s)
	if err == nil {
		return v
	}
	return fallback
}
func Test0() {
	if err := zap.RegisterSink("lumberjack", func(u *url.URL) (zap.Sink, error) {
		// Read parameters from URL:
		// lumberjack://localhost/foo.log?maxSize=500&maxBackups=3&maxAge=28
		filename := strings.TrimLeft(u.Path, "/")
		if filename == "" {
			filename = "foo.log"
		}
		q := u.Query()
		l := &lumberjackSink{
			Logger: lumberjack.Logger{
				Filename:   filename,
				MaxSize:    parseNumber(q.Get("maxSize"), 500),
				MaxBackups: parseNumber(q.Get("maxBackups"), 3),
				MaxAge:     parseNumber(q.Get("maxAge"), 28),
			},
		}
		return l, nil
	}); err != nil {
		panic(err)
	}
	var cfg zap.Config
	yamlFile, _ := os.ReadFile("./config_log_zap.yaml")
	if err := yaml.Unmarshal(yamlFile, &cfg); err != nil {
		panic(err)
	}
	logger := zap.Must(cfg.Build())
	defer logger.Sync()
	logger.Info("logger construction succeeded:config from yaml")
}
func main() {
	Test0()
}
And modify the config file to set outputPaths like this:
outputPaths:
  - stdout
  - lumberjack://localhost/foo.log?maxSize=500&maxBackups=3&maxAge=28
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论