From 04fceee835e4451afe27e3f24f73d0ba99d423b1 Mon Sep 17 00:00:00 2001 From: Muyao CHEN Date: Tue, 1 Oct 2024 23:36:22 +0200 Subject: [PATCH] feat: Implement logging module with zap --- README.md | 8 ++ go.mod | 4 +- go.sum | 10 ++- internal/pkg/log/log.go | 157 ++++++++++++++++++++++++++++++++++++ internal/pkg/log/options.go | 46 +++++++++++ 5 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 internal/pkg/log/log.go create mode 100644 internal/pkg/log/options.go diff --git a/README.md b/README.md index fd83657..3be65de 100644 --- a/README.md +++ b/README.md @@ -104,3 +104,11 @@ That is why we use `cobra`. So for this project, we will use the combination of `pflag`, `viper` and `cobra`. + +### 2024/10/02 + +#### Logging + +Use `zap` for logging system. Log will be output to stdout for dev purpose, +but it is also output to files. The log files can then be fetched to +`Elasticsearch` for analyzing. diff --git a/go.mod b/go.mod index 4e159e1..10147ca 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 go.uber.org/automaxprocs v1.6.0 + go.uber.org/zap v1.27.0 ) require ( @@ -41,8 +42,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect diff --git a/go.sum b/go.sum index ff551c4..1a7b3db 100644 --- a/go.sum +++ b/go.sum @@ -105,12 +105,14 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= diff --git a/internal/pkg/log/log.go b/internal/pkg/log/log.go new file mode 100644 index 0000000..e86248f --- /dev/null +++ b/internal/pkg/log/log.go @@ -0,0 +1,157 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package log + +import ( + "os" + "sync" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Logger interface { + FatalLog(msg string, keyValues ...interface{}) + PanicLog(msg string, keyValues ...interface{}) + ErrorLog(msg string, keyValues ...interface{}) + WarnLog(msg string, keyValues ...interface{}) + InfoLog(msg string, keyValues ...interface{}) + DebugLog(msg string, keyValues ...interface{}) + Sync() +} + +// zapLogger is an implementation of Logger interface +type zapLogger struct { + z *zap.Logger +} + +var ( + mu sync.Mutex + + // default global logger + std = NewLogger(NewOptions()) +) + +// Init initializes global logger with options +func Init(opts *Options) { + mu.Lock() + defer mu.Unlock() + + std = NewLogger(opts) +} + +func NewLogger(opts *Options) *zapLogger { + if opts == nil { + opts = NewOptions() + } + + var zapLevel zapcore.Level + // If unknown level, use info + if err := zapLevel.UnmarshalText([]byte(opts.Level)); err != nil { + zapLevel = zap.InfoLevel + } + + encoderCfg := zap.NewProductionEncoderConfig() + encoderCfg.TimeKey = "timestamp" + encoderCfg.MessageKey = "message" + encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder + + config := zap.Config{ + Level: zap.NewAtomicLevelAt(zapLevel), + Development: opts.Development, + DisableCaller: opts.DisableCaller, + DisableStacktrace: opts.DisableStacktrace, + Sampling: nil, + Encoding: opts.Format, + EncoderConfig: encoderCfg, + OutputPaths: opts.OutputPaths, + ErrorOutputPaths: []string{ + "stderr", + }, + InitialFields: map[string]interface{}{ + "pid": os.Getpid(), + }, + } + + z := zap.Must(config.Build(zap.AddStacktrace(zapcore.PanicLevel), zap.AddCallerSkip(1))) + + zap.RedirectStdLog(z) + + return &zapLogger{z: z} +} + +func (z *zapLogger) FatalLog(msg string, keyValues ...interface{}) { + z.z.Sugar().Fatalw(msg, keyValues...) +} + +func (z *zapLogger) PanicLog(msg string, keyValues ...interface{}) { + z.z.Sugar().Panicw(msg, keyValues...) +} + +func (z *zapLogger) ErrorLog(msg string, keyValues ...interface{}) { + z.z.Sugar().Errorw(msg, keyValues...) +} + +func (z *zapLogger) WarnLog(msg string, keyValues ...interface{}) { + z.z.Sugar().Warnw(msg, keyValues...) +} + +func (z *zapLogger) InfoLog(msg string, keyValues ...interface{}) { + z.z.Sugar().Infow(msg, keyValues...) +} + +func (z *zapLogger) DebugLog(msg string, keyValues ...interface{}) { + z.z.Sugar().Debugw(msg, keyValues...) +} + +func (z *zapLogger) Sync() { + _ = z.z.Sync() +} + +func FatalLog(msg string, keyValues ...interface{}) { + std.z.Sugar().Fatalw(msg, keyValues...) +} + +func PanicLog(msg string, keyValues ...interface{}) { + std.z.Sugar().Panicw(msg, keyValues...) +} + +func ErrorLog(msg string, keyValues ...interface{}) { + std.z.Sugar().Errorw(msg, keyValues...) +} + +func WarnLog(msg string, keyValues ...interface{}) { + std.z.Sugar().Warnw(msg, keyValues...) +} + +func InfoLog(msg string, keyValues ...interface{}) { + std.z.Sugar().Infow(msg, keyValues...) +} + +func DebugLog(msg string, keyValues ...interface{}) { + std.z.Sugar().Debugw(msg, keyValues...) +} + +func Sync() { + _ = std.z.Sync() +} diff --git a/internal/pkg/log/options.go b/internal/pkg/log/options.go new file mode 100644 index 0000000..184e02e --- /dev/null +++ b/internal/pkg/log/options.go @@ -0,0 +1,46 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package log + +// Options define the logger options +type Options struct { + Level string + Development bool + DisableCaller bool + DisableStacktrace bool + Format string + OutputPaths []string +} + +func NewOptions() *Options { + return &Options{ + Level: "debug", + Development: true, + DisableCaller: false, + DisableStacktrace: false, + Format: "console", + OutputPaths: []string{ + "stderr", + }, + } +}