commit 1e5870c065e270f00ae11395867660d037dd9ca0 Author: daniel8642 <859926750@qq.com> Date: Thu Nov 17 17:21:12 2022 +0800 1.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e326a6 --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +# pkg/util/log + +本包基于`github.com/tkestack/tke/pkg/util/log`开发 + +`pkg/util/log` 是一个生产可用的日志包,基于 `zap` 包封装。除了实现 `Go` 日志包的基本功能外,还实现了很多高级功能,`pkg/util/log`具有如下特性: + +- 支持日志级别:`Debug`、`Info`、`Warn`、`Error`、`Panic`、`Fatal`。 +- 支持自定义配置。 +- 支持文件名和行号。 +- 支持输出掉标准输出和文件,可以同时输出到多个地方。 +- 支持 `JSON` 和 `Text` 两种日志格式。 +- 支持颜色输出。 +- 高性能。 +- 支持结构化日志记录。 +- **兼容标准库 `log` 包**。 +- **支持Context(业务定制)** + +## 使用方法 + + +### 一个简单的示例 + +创建一个 `example2.go` 文件,内容如下: + +```go +package main + +func main() { + defer log.Flush() + + // Debug、Info(with field)、Warnf、Errorw使用 + log.Debug("This is a debug message") + log.Info("This is a info message", log.Int32("int_key", 10)) + log.Warnf("This is a formatted %s message", "warn") +} +``` + +执行代码: + +```bash +$ go run example2.go +2020-12-05 07:56:37.154 info example/example2.go:12 This is a info message {"int_key": 10} +2020-12-05 07:56:37.154 warn example/example2.go:13 This is a formatted warn message +``` + +上述代码使用 `pkg/util/log` 包默认的全局 `logger`,分别在 `Debug` 、`Info` 和 `Warn` 级别打印了一条日志。 + +### 初始化日志包 + +可以使用 `Init` 来初始化一个日志包,如下: + +```go +// logger配置 +opts := &log.Options{ + Level: "debug", + Format: "console", + EnableColor: true, + EnableCaller: true, + OutputPaths: []string{"test.log", "stdout"}, + ErrorOutputPaths: []string{"error.log"}, +} +// 初始化全局logger +log.Init(opts) +``` + +Format 支持 `console` 和 `json` 2 种格式: +- console:输出为 text 格式。例如:`2020-12-05 08:12:02.324 DEBUG example/example.go:43 This is a debug message` +- json:输出为 json 格式,例如:`{"level":"debug","time":"2020-12-05 08:12:54.113","caller":"example/example.go:43","msg":"This is a debug message"}` + +OutputPaths,可以设置日志输出: +- stdout:输出到标准输出。 +- stderr:输出到标准错误输出。 +- /var/log/test.log:输出到文件。 + +支持同时输出到多个输出。 + +EnableColor 为 `true` 开启颜色输出,为 `false` 关闭颜色输出。 + +### 结构化日志输出 + +`pkg/util/log` 也支持结构化日志打印,例如: + +```go +log.Info("This is a info message", log.Int32("int_key", 10)) +log.Infow("Message printed with Errorw", "X-Request-ID", "fbf54504-64da-4088-9b86-67824a7fb508") +``` +对应的输出结果为: + +``` +2020-12-05 08:16:18.749 INFO example/example.go:44 This is a info message {"int_key": 10} +2020-12-05 08:16:18.749 ERROR example/example.go:46 Message printed with Errorw {"X-Request-ID": "fbf54504-64da-4088-9b86-67824a7fb508"} +``` + +log.Info 这类函数需要指定具体的类型,以最大化的 提高日志的性能。log.Infow 这类函数,不用指定具体的类型,底层使用了反射,性能会差些。建议用在低频调用的函数中。 + +## 支持V level + +创建 `v_level.go`,内容如下: + +```go +package main + +func main() { + defer log.Flush() + + log.V(0).Info("This is a V level message") + log.V(0).Infow("This is a V level message with fields", "X-Request-ID", "7a7b9f24-4cae-4b2a-9464-69088b45b904") +} +``` + +执行如上代码: + +```bash +$ go run v_level.go +2020-12-05 08:20:37.763 info example/v_level.go:10 This is a V level message +2020-12-05 08:20:37.763 info example/v_level.go:11 This is a V level message with fields {"X-Request-ID": "7a7b9f24-4cae-4b2a-9464-69088b45b904"} +``` + +## 完整的示例 + +一个完整的示例请参考[example.go](./example/example.go)。 diff --git a/context.go b/context.go new file mode 100644 index 0000000..9ab6d96 --- /dev/null +++ b/context.go @@ -0,0 +1,33 @@ +package log + +import ( + "context" +) + +type key int + +const ( + logContextKey key = iota +) + +// WithContext 把命令行输出logger保存到context中.可以使用FromContext(ctx)获取绑定的logger +func WithContext(ctx context.Context) context.Context { + return std.WithContext(ctx) +} + +// WithContext 把指定的logger保存到context中.可以使用FromContext(ctx)获取绑定的logger +func (l *zapLogger) WithContext(ctx context.Context) context.Context { + return context.WithValue(ctx, logContextKey, l) +} + +// FromContext 从ctx中获取logger +func FromContext(ctx context.Context) Logger { + if ctx != nil { + logger := ctx.Value(logContextKey) + if logger != nil { + return logger.(Logger) + } + } + + return WithName("Unknown-Context") +} diff --git a/cronlog/log.go b/cronlog/log.go new file mode 100644 index 0000000..c25c7f7 --- /dev/null +++ b/cronlog/log.go @@ -0,0 +1,27 @@ +package cronlog + +import ( + "fmt" + "go.uber.org/zap" +) + +type logger struct { + zapLogger *zap.SugaredLogger +} + +// NewLogger 创建一个兼容 `github.com/robfig/cron.Logger` 的logger +func NewLogger(zapLogger *zap.SugaredLogger) logger { + return logger{zapLogger: zapLogger} +} + +func (l logger) Info(msg string, args ...interface{}) { + l.zapLogger.Infow(msg, args...) +} + +func (l logger) Error(err error, msg string, args ...interface{}) { + l.zapLogger.Errorw(fmt.Sprintf(msg, args...), "error", err.Error()) +} + +func (l logger) Flush() { + _ = l.zapLogger.Sync() +} diff --git a/distribution/logger.go b/distribution/logger.go new file mode 100644 index 0000000..0c6de9b --- /dev/null +++ b/distribution/logger.go @@ -0,0 +1,165 @@ +// Package distribution 实现了兼容 logrus/std log/prometheus 的logger +package distribution + +import ( + logruslogger "cynking.com/pkg/log/logrus" + "fmt" + "github.com/sirupsen/logrus" + "go.uber.org/zap" +) + +// Logger is a logger which compatible to logrus/std log/prometheus. +// it implements Print() Println() Printf() Dbug() Debugln() Debugf() Info() Infoln() Infof() Warn() Warnln() Warnf() +// Error() Errorln() Errorf() Fatal() Fataln() Fatalf() Panic() Panicln() Panicf() With() WithField() WithFields(). +type Logger struct { + logger *zap.Logger + logrusLogger *logrus.Logger +} + +// NewLogger create the field logger object by giving zap logger. +func NewLogger(logger *zap.Logger) *Logger { + return &Logger{ + logger: logger, + logrusLogger: logruslogger.NewLogger(logger), + } +} + +// Print logs a message at level Print on the compatibleLogger. +func (l *Logger) Print(args ...interface{}) { + l.logger.Info(fmt.Sprint(args...)) +} + +// Println logs a message at level Print on the compatibleLogger. +func (l *Logger) Println(args ...interface{}) { + l.logger.Info(fmt.Sprint(args...)) +} + +// Printf logs a message at level Print on the compatibleLogger. +func (l *Logger) Printf(format string, args ...interface{}) { + l.logger.Info(fmt.Sprintf(format, args...)) +} + +// Trace logs a message at level Trace on the compatibleLogger. +func (l *Logger) Trace(args ...interface{}) { + l.logger.Debug(fmt.Sprint(args...)) +} + +// Traceln logs a message at level Trace on the compatibleLogger. +func (l *Logger) Traceln(args ...interface{}) { + l.logger.Debug(fmt.Sprint(args...)) +} + +// Tracef logs a message at level Trace on the compatibleLogger. +func (l *Logger) Tracef(format string, args ...interface{}) { + l.logger.Debug(fmt.Sprintf(format, args...)) +} + +// Debug logs a message at level Debug on the compatibleLogger. +func (l *Logger) Debug(args ...interface{}) { + l.logger.Debug(fmt.Sprint(args...)) +} + +// Debugln logs a message at level Debug on the compatibleLogger. +func (l *Logger) Debugln(args ...interface{}) { + l.logger.Debug(fmt.Sprint(args...)) +} + +// Debugf logs a message at level Debug on the compatibleLogger. +func (l *Logger) Debugf(format string, args ...interface{}) { + l.logger.Debug(fmt.Sprintf(format, args...)) +} + +// Info logs a message at level Info on the compatibleLogger. +func (l *Logger) Info(args ...interface{}) { + l.logger.Info(fmt.Sprint(args...)) +} + +// Infoln logs a message at level Info on the compatibleLogger. +func (l *Logger) Infoln(args ...interface{}) { + l.logger.Info(fmt.Sprint(args...)) +} + +// Infof logs a message at level Info on the compatibleLogger. +func (l *Logger) Infof(format string, args ...interface{}) { + l.logger.Info(fmt.Sprintf(format, args...)) +} + +// Warn logs a message at level Warn on the compatibleLogger. +func (l *Logger) Warn(args ...interface{}) { + l.logger.Warn(fmt.Sprint(args...)) +} + +// Warnln logs a message at level Warn on the compatibleLogger. +func (l *Logger) Warnln(args ...interface{}) { + l.logger.Warn(fmt.Sprint(args...)) +} + +// Warnf logs a message at level Warn on the compatibleLogger. +func (l *Logger) Warnf(format string, args ...interface{}) { + l.logger.Warn(fmt.Sprintf(format, args...)) +} + +// Warning logs a message at level Warn on the compatibleLogger. +func (l *Logger) Warning(args ...interface{}) { + l.logger.Warn(fmt.Sprint(args...)) +} + +// Warningln logs a message at level Warning on the compatibleLogger. +func (l *Logger) Warningln(args ...interface{}) { + l.logger.Warn(fmt.Sprint(args...)) +} + +// Warningf logs a message at level Warning on the compatibleLogger. +func (l *Logger) Warningf(format string, args ...interface{}) { + l.logger.Warn(fmt.Sprintf(format, args...)) +} + +// Error logs a message at level Error on the compatibleLogger. +func (l *Logger) Error(args ...interface{}) { + l.logger.Error(fmt.Sprint(args...)) +} + +// Errorln logs a message at level Error on the compatibleLogger. +func (l *Logger) Errorln(args ...interface{}) { + l.logger.Error(fmt.Sprint(args...)) +} + +// Errorf logs a message at level Error on the compatibleLogger. +func (l *Logger) Errorf(format string, args ...interface{}) { + l.logger.Error(fmt.Sprintf(format, args...)) +} + +// Fatal logs a message at level Fatal on the compatibleLogger. +func (l *Logger) Fatal(args ...interface{}) { + l.logger.Fatal(fmt.Sprint(args...)) +} + +// Fatalln logs a message at level Fatal on the compatibleLogger. +func (l *Logger) Fatalln(args ...interface{}) { + l.logger.Fatal(fmt.Sprint(args...)) +} + +// Fatalf logs a message at level Fatal on the compatibleLogger. +func (l *Logger) Fatalf(format string, args ...interface{}) { + l.logger.Fatal(fmt.Sprintf(format, args...)) +} + +// Panic logs a message at level Painc on the compatibleLogger. +func (l *Logger) Panic(args ...interface{}) { + l.logger.Panic(fmt.Sprint(args...)) +} + +// Panicln logs a message at level Painc on the compatibleLogger. +func (l *Logger) Panicln(args ...interface{}) { + l.logger.Panic(fmt.Sprint(args...)) +} + +// Panicf logs a message at level Painc on the compatibleLogger. +func (l *Logger) Panicf(format string, args ...interface{}) { + l.logger.Panic(fmt.Sprintf(format, args...)) +} + +// WithError return a logger with an error field. +func (l *Logger) WithError(err error) *logrus.Entry { + return logrus.NewEntry(l.logrusLogger).WithError(err) +} diff --git a/encoder.go b/encoder.go new file mode 100644 index 0000000..76474c8 --- /dev/null +++ b/encoder.go @@ -0,0 +1,18 @@ +package log + +import ( + "go.uber.org/zap/zapcore" + "time" +) + +func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format("2006-01-02 15:04:05.000")) +} + +func autoDurationEncoder(d time.Duration, enc zapcore.PrimitiveArrayEncoder) { + //enc.AppendFloat64(float64(d) / float64(time.Millisecond)) + if d > time.Minute { + d = d.Truncate(time.Second) + } + enc.AppendString(d.String()) +} diff --git a/example/context/main.go b/example/context/main.go new file mode 100644 index 0000000..2e71dc7 --- /dev/null +++ b/example/context/main.go @@ -0,0 +1,59 @@ +// Copyright 2020 Lingfei Kong . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package main + +import ( + "context" + "flag" + + "cynking.com/pkg/log" +) + +var ( + h bool + + level int + format string +) + +func main() { + flag.BoolVar(&h, "h", false, "Print this help.") + flag.IntVar(&level, "l", 0, "Log level.") + flag.StringVar(&format, "f", "console", "log output format.") + + flag.Parse() + + if h { + flag.Usage() + + return + } + + // logger配置 + opts := &log.Options{ + Level: "debug", + Format: "console", + EnableColor: true, + DisableCaller: true, + OutputPaths: []string{"test.log", "stdout"}, + ErrorOutputPaths: []string{"error.log"}, + } + // 初始化全局logger + log.Init(opts) + defer log.Flush() + + // WithValues使用 + lv := log.WithValues("X-Request-ID", "7a7b9f24-4cae-4b2a-9464-69088b45b904") + + // Context使用 + lv.Infof("Start to call pirntString function") + ctx := lv.WithContext(context.Background()) + pirntString(ctx, "World") +} + +func pirntString(ctx context.Context, str string) { + lc := log.FromContext(ctx) + lc.Infof("Hello %s", str) +} diff --git a/example/example.go b/example/example.go new file mode 100644 index 0000000..dd686e2 --- /dev/null +++ b/example/example.go @@ -0,0 +1,70 @@ +// Copyright 2020 Lingfei Kong . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package main + +import ( + "context" + "flag" + + "cynking.com/pkg/log" +) + +var ( + h bool + + level int + format string +) + +func main() { + flag.BoolVar(&h, "h", false, "Print this help.") + flag.IntVar(&level, "l", 0, "Log level.") + flag.StringVar(&format, "f", "console", "log output format.") + + flag.Parse() + + if h { + flag.Usage() + + return + } + + // logger配置 + opts := &log.Options{ + Level: "debug", + Format: "console", + EnableColor: true, // if you need output to local path, with EnableColor must be false. + DisableCaller: true, + OutputPaths: []string{"test.log", "stdout"}, + ErrorOutputPaths: []string{"error.log"}, + } + // 初始化全局logger + log.Init(opts) + defer log.Flush() + + // Debug、Info(with field)、Warnf、Errorw使用 + log.Debug("This is a debug message") + log.Info("This is a info message", log.Int32("int_key", 10)) + log.Warnf("This is a formatted %s message", "warn") + log.Errorw("Message printed with Errorw", "X-Request-ID", "fbf54504-64da-4088-9b86-67824a7fb508") + + // WithValues使用 + lv := log.WithValues("X-Request-ID", "7a7b9f24-4cae-4b2a-9464-69088b45b904") + lv.Infow("Info message printed with [WithValues] logger") + lv.Infow("Debug message printed with [WithValues] logger") + + // Context使用 + ctx := lv.WithContext(context.Background()) + lc := log.FromContext(ctx) + lc.Info("Message printed with [WithContext] logger") + + ln := lv.WithName("test") + ln.Info("Message printed with [WithName] logger") + + // V level使用 + log.V(log.InfoLevel).Info("This is a V level message") + log.V(log.ErrorLevel). + Infow("This is a V level message with fields", "X-Request-ID", "7a7b9f24-4cae-4b2a-9464-69088b45b904") +} diff --git a/example/simple/simple.go b/example/simple/simple.go new file mode 100644 index 0000000..b532eb5 --- /dev/null +++ b/example/simple/simple.go @@ -0,0 +1,11 @@ +// Copyright 2020 Lingfei Kong . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package main + +import "cynking.com/pkg/log" + +func main() { + log.Infof("this is a test log, message: %s", "good") +} diff --git a/example/vlevel/v_level.go b/example/vlevel/v_level.go new file mode 100644 index 0000000..d0cf08e --- /dev/null +++ b/example/vlevel/v_level.go @@ -0,0 +1,16 @@ +// Copyright 2020 Lingfei Kong . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package main + +import ( + "cynking.com/pkg/log" +) + +func main() { + defer log.Flush() + + log.V(0).Info("This is a V level message") + log.V(0).Infow("This is a V level message with fields", "X-Request-ID", "7a7b9f24-4cae-4b2a-9464-69088b45b904") +} diff --git a/gin/logMiddleware.go b/gin/logMiddleware.go new file mode 100644 index 0000000..49e5bbb --- /dev/null +++ b/gin/logMiddleware.go @@ -0,0 +1,47 @@ +package ginLog + +import ( + "cynking.com/pkg/log" + "github.com/gin-gonic/gin" + "time" +) + +func NamedLogger(msg string) gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + path := c.Request.URL.Path + raw := c.Request.URL.RawQuery + + // Process request + c.Next() + + param := gin.LogFormatterParams{ + Request: c.Request, + Keys: c.Keys, + } + + // Stop timer + param.TimeStamp = time.Now() + param.Latency = param.TimeStamp.Sub(start) + + param.ClientIP = c.ClientIP() + param.Method = c.Request.Method + param.StatusCode = c.Writer.Status() + param.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String() + param.BodySize = c.Writer.Size() + + if raw != "" { + path = path + "?" + raw + } + param.Path = path + // todo:请求处理过长时间提升日志等级 debug(正常),info(慢速),warn(可能浏览器已判定超时)处理 + log.Debug(msg, + log.Int("Status", param.StatusCode), + log.Duration("Latency", param.Latency), + log.String("Ip", param.ClientIP), + log.String("Method", param.Method), + log.String("Path", param.Path), + log.String("ErrorMessage", param.ErrorMessage)) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cefd26b --- /dev/null +++ b/go.mod @@ -0,0 +1,38 @@ +module cynking.com/pkg/log + +go 1.18 + +require ( + github.com/gin-gonic/gin v1.8.1 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.1 + go.uber.org/zap v1.23.0 + k8s.io/klog v1.0.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.10.0 // indirect + github.com/goccy/go-json v0.9.7 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..79c8f0c --- /dev/null +++ b/go.sum @@ -0,0 +1,108 @@ +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= diff --git a/klog/logger.go b/klog/logger.go new file mode 100644 index 0000000..7cf1426 --- /dev/null +++ b/klog/logger.go @@ -0,0 +1,61 @@ +// Package klog init klog logger. klog is used by kubernetes, this can compatible the kubernetes packages. +package klog + +import ( + "flag" + "go.uber.org/zap" + "k8s.io/klog" +) + +// InitLogger init klog by zap logger. +func InitLogger(zapLogger *zap.Logger) { + fs := flag.NewFlagSet("klog", flag.ExitOnError) + klog.InitFlags(fs) + defer klog.Flush() + klog.SetOutputBySeverity("INFO", &infoLogger{logger: zapLogger}) + klog.SetOutputBySeverity("WARNING", &warnLogger{logger: zapLogger}) + klog.SetOutputBySeverity("FATAL", &fatalLogger{logger: zapLogger}) + klog.SetOutputBySeverity("ERROR", &errorLogger{logger: zapLogger}) + _ = fs.Set("skip_headers", "true") + _ = fs.Set("logtostderr", "false") +} + +type infoLogger struct { + logger *zap.Logger +} + +func (l *infoLogger) Write(p []byte) (n int, err error) { + l.logger.Info(string(p[:len(p)-1])) + + return len(p), nil +} + +type warnLogger struct { + logger *zap.Logger +} + +func (l *warnLogger) Write(p []byte) (n int, err error) { + l.logger.Warn(string(p[:len(p)-1])) + + return len(p), nil +} + +type fatalLogger struct { + logger *zap.Logger +} + +func (l *fatalLogger) Write(p []byte) (n int, err error) { + l.logger.Fatal(string(p[:len(p)-1])) + + return len(p), nil +} + +type errorLogger struct { + logger *zap.Logger +} + +func (l *errorLogger) Write(p []byte) (n int, err error) { + l.logger.Error(string(p[:len(p)-1])) + + return len(p), nil +} diff --git a/log.go b/log.go new file mode 100644 index 0000000..58eb8e0 --- /dev/null +++ b/log.go @@ -0,0 +1,553 @@ +package log + +import ( + "context" + "fmt" + "log" + "sync" + + "cynking.com/pkg/log/klog" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// InfoLogger represents the ability to log non-error messages, at a particular verbosity. +type InfoLogger interface { + // Info logs a non-error message with the given key/value pairs as context. + // + // The msg argument should be used to add some constant description to + // the log line. The key/value pairs can then be used to add additional + // variable information. The key/value pairs should alternate string + // keys and arbitrary values. + Info(msg string, fields ...Field) + Infof(format string, v ...interface{}) + Infow(msg string, keysAndValues ...interface{}) + + // Enabled tests whether this InfoLogger is enabled. For example, + // commandline flags might be used to set the logging verbosity and disable + // some info logs. + Enabled() bool +} + +// Logger represents the ability to log messages, both errors and not. +type Logger interface { + // All Loggers implement InfoLogger. Calling InfoLogger methods directly on + // a Logger value is equivalent to calling them on a V(0) InfoLogger. For + // example, logger.Info() produces the same result as logger.V(0).Info. + InfoLogger + Debug(msg string, fields ...Field) + Debugf(format string, v ...interface{}) + Debugw(msg string, keysAndValues ...interface{}) + Warn(msg string, fields ...Field) + Warnf(format string, v ...interface{}) + Warnw(msg string, keysAndValues ...interface{}) + Error(msg string, fields ...Field) + Errorf(format string, v ...interface{}) + Errorw(msg string, keysAndValues ...interface{}) + Panic(msg string, fields ...Field) + Panicf(format string, v ...interface{}) + Panicw(msg string, keysAndValues ...interface{}) + Fatal(msg string, fields ...Field) + Fatalf(format string, v ...interface{}) + Fatalw(msg string, keysAndValues ...interface{}) + + // V returns an InfoLogger value for a specific verbosity level. A higher + // verbosity level means a log message is less important. It's illegal to + // pass a log level less than zero. + V(level Level) InfoLogger + Write(p []byte) (n int, err error) + + // WithValues adds some key-value pairs of context to a logger. + // See Info for documentation on how key/value pairs work. + WithValues(keysAndValues ...interface{}) Logger + + // WithName adds a new element to the logger's name. + // Successive calls with WithName continue to append + // suffixes to the logger's name. It's strongly recommended + // that name segments contain only letters, digits, and hyphens + // (see the package documentation for more information). + WithName(name string) Logger + + // WithContext returns a copy of context in which the log value is set. + WithContext(ctx context.Context) context.Context + + // Flush calls the underlying Core's Sync method, flushing any buffered + // log entries. Applications should take care to call Sync before exiting. + Flush() +} + +var _ Logger = &zapLogger{} + +// noopInfoLogger is a logr.InfoLogger that's always disabled, and does nothing. +type noopInfoLogger struct{} + +func (l *noopInfoLogger) Enabled() bool { return false } +func (l *noopInfoLogger) Info(_ string, _ ...Field) {} +func (l *noopInfoLogger) Infof(_ string, _ ...interface{}) {} +func (l *noopInfoLogger) Infow(_ string, _ ...interface{}) {} + +var disabledInfoLogger = &noopInfoLogger{} + +// NB: right now, we always use the equivalent of sugared logging. +// This is necessary, since logr doesn't define non-suggared types, +// and using zap-specific non-suggared types would make uses tied +// directly to Zap. + +// infoLogger is a logr.InfoLogger that uses Zap to log at a particular +// level. The level has already been converted to a Zap level, which +// is to say that `logrLevel = -1*zapLevel`. +type infoLogger struct { + level zapcore.Level + log *zap.Logger +} + +func (l *infoLogger) Enabled() bool { return true } +func (l *infoLogger) Info(msg string, fields ...Field) { + if checkedEntry := l.log.Check(l.level, msg); checkedEntry != nil { + checkedEntry.Write(fields...) + } +} + +func (l *infoLogger) Infof(format string, args ...interface{}) { + if checkedEntry := l.log.Check(l.level, fmt.Sprintf(format, args...)); checkedEntry != nil { + checkedEntry.Write() + } +} + +func (l *infoLogger) Infow(msg string, keysAndValues ...interface{}) { + if checkedEntry := l.log.Check(l.level, msg); checkedEntry != nil { + checkedEntry.Write(handleFields(l.log, keysAndValues)...) + } +} + +// zapLogger is a logr.Logger that uses Zap to log. +type zapLogger struct { + // NB: this looks very similar to zap.SugaredLogger, but + // deals with our desire to have multiple verbosity levels. + zapLogger *zap.Logger + infoLogger +} + +// handleFields converts a bunch of arbitrary key-value pairs into Zap fields. It takes +// additional pre-converted Zap fields, for use with automatically attached fields, like +// `error`. +func handleFields(l *zap.Logger, args []interface{}, additional ...zap.Field) []zap.Field { + // a slightly modified version of zap.SugaredLogger.sweetenFields + if len(args) == 0 { + // fast-return if we have no suggared fields. + return additional + } + + // unlike Zap, we can be pretty sure users aren't passing structured + // fields (since logr has no concept of that), so guess that we need a + // little less space. + fields := make([]zap.Field, 0, len(args)/2+len(additional)) + for i := 0; i < len(args); { + // check just in case for strongly-typed Zap fields, which is illegal (since + // it breaks implementation agnosticism), so we can give a better error message. + if _, ok := args[i].(zap.Field); ok { + l.DPanic("strongly-typed Zap Field passed to logr", zap.Any("zap field", args[i])) + + break + } + + // make sure this isn't a mismatched key + if i == len(args)-1 { + l.DPanic("odd number of arguments passed as key-value pairs for logging", zap.Any("ignored key", args[i])) + + break + } + + // process a key-value pair, + // ensuring that the key is a string + key, val := args[i], args[i+1] + keyStr, isString := key.(string) + if !isString { + // if the key isn't a string, DPanic and stop logging + l.DPanic( + "non-string key argument passed to logging, ignoring all later arguments", + zap.Any("invalid key", key), + ) + + break + } + + fields = append(fields, zap.Any(keyStr, val)) + i += 2 + } + + return append(fields, additional...) +} + +var ( + std = New(NewOptions()) + mu sync.Mutex +) + +// Init initializes logger with specified options. +func Init(opts *Options) { + mu.Lock() + defer mu.Unlock() + std = New(opts) +} + +// New create logger by opts which can custmoized by command arguments. +func New(opts *Options) *zapLogger { + if opts == nil { + opts = NewOptions() + } + + var zapLevel zapcore.Level + if err := zapLevel.UnmarshalText([]byte(opts.Level)); err != nil { + zapLevel = zapcore.InfoLevel + } + encodeLevel := zapcore.CapitalLevelEncoder + // when output to local path, with color is forbidden + if opts.Format == ConsoleFormat && opts.EnableColor { + encodeLevel = zapcore.CapitalColorLevelEncoder + } + + encoderConfig := zapcore.EncoderConfig{ + MessageKey: "message", + LevelKey: "level", + TimeKey: "timestamp", + NameKey: "logger", + CallerKey: "caller", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: encodeLevel, + EncodeTime: timeEncoder, + EncodeDuration: autoDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } + + loggerConfig := &zap.Config{ + Level: zap.NewAtomicLevelAt(zapLevel), + Development: opts.Development, + DisableCaller: opts.DisableCaller, + DisableStacktrace: opts.DisableStacktrace, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: opts.Format, + EncoderConfig: encoderConfig, + OutputPaths: opts.OutputPaths, + ErrorOutputPaths: opts.ErrorOutputPaths, + } + + var err error + l, err := loggerConfig.Build(zap.AddStacktrace(zapcore.PanicLevel), zap.AddCallerSkip(1)) + if err != nil { + panic(err) + } + logger := &zapLogger{ + zapLogger: l.Named(opts.Name), + infoLogger: infoLogger{ + log: l, + level: zap.InfoLevel, + }, + } + klog.InitLogger(l) + zap.RedirectStdLog(l) + + return logger +} + +// SugaredLogger returns global sugared logger. +func SugaredLogger() *zap.SugaredLogger { + return std.zapLogger.Sugar() +} + +// StdErrLogger returns logger of standard library which writes to supplied zap +// logger at error level. +func StdErrLogger() *log.Logger { + if std == nil { + return nil + } + if l, err := zap.NewStdLogAt(std.zapLogger, zapcore.ErrorLevel); err == nil { + return l + } + + return nil +} + +// StdInfoLogger returns logger of standard library which writes to supplied zap +// logger at info level. +func StdInfoLogger() *log.Logger { + if std == nil { + return nil + } + if l, err := zap.NewStdLogAt(std.zapLogger, zapcore.InfoLevel); err == nil { + return l + } + + return nil +} + +// V return a leveled InfoLogger. +func V(level Level) InfoLogger { return std.V(level) } + +func (l *zapLogger) V(level Level) InfoLogger { + if l.zapLogger.Core().Enabled(level) { + return &infoLogger{ + level: level, + log: l.zapLogger, + } + } + + return disabledInfoLogger +} + +func (l *zapLogger) Write(p []byte) (n int, err error) { + l.zapLogger.Info(string(p)) + + return len(p), nil +} + +// WithValues creates a child logger and adds adds Zap fields to it. +func WithValues(keysAndValues ...interface{}) Logger { return std.WithValues(keysAndValues...) } + +func (l *zapLogger) WithValues(keysAndValues ...interface{}) Logger { + newLogger := l.zapLogger.With(handleFields(l.zapLogger, keysAndValues)...) + + return NewLogger(newLogger) +} + +// WithName adds a new path segment to the logger's name. Segments are joined by +// periods. By default, Loggers are unnamed. +func WithName(s string) Logger { return std.WithName(s) } + +func (l *zapLogger) WithName(name string) Logger { + newLogger := l.zapLogger.Named(name) + + return NewLogger(newLogger) +} + +// Flush calls the underlying Core's Sync method, flushing any buffered +// log entries. Applications should take care to call Sync before exiting. +func Flush() { std.Flush() } + +func (l *zapLogger) Flush() { + _ = l.zapLogger.Sync() +} + +// NewLogger creates a new logr.Logger using the given Zap Logger to log. +func NewLogger(l *zap.Logger) Logger { + return &zapLogger{ + zapLogger: l, + infoLogger: infoLogger{ + log: l, + level: zap.InfoLevel, + }, + } +} + +// ZapLogger used for other log wrapper such as klog. +func ZapLogger() *zap.Logger { + return std.zapLogger +} + +// CheckIntLevel used for other log wrapper such as klog which return if logging a +// message at the specified level is enabled. +func CheckIntLevel(level int32) bool { + var lvl zapcore.Level + if level < 5 { + lvl = zapcore.InfoLevel + } else { + lvl = zapcore.DebugLevel + } + checkEntry := std.zapLogger.Check(lvl, "") + + return checkEntry != nil +} + +// Debug method output debug level log. +func Debug(msg string, fields ...Field) { + std.zapLogger.Debug(msg, fields...) +} + +func (l *zapLogger) Debug(msg string, fields ...Field) { + l.zapLogger.Debug(msg, fields...) +} + +// Debugf method output debug level log. +func Debugf(format string, v ...interface{}) { + std.zapLogger.Sugar().Debugf(format, v...) +} + +func (l *zapLogger) Debugf(format string, v ...interface{}) { + l.zapLogger.Sugar().Debugf(format, v...) +} + +// Debugw method output debug level log. +func Debugw(msg string, keysAndValues ...interface{}) { + std.zapLogger.Sugar().Debugw(msg, keysAndValues...) +} + +func (l *zapLogger) Debugw(msg string, keysAndValues ...interface{}) { + l.zapLogger.Sugar().Debugw(msg, keysAndValues...) +} + +// Info method output info level log. +func Info(msg string, fields ...Field) { + std.zapLogger.Info(msg, fields...) +} + +func (l *zapLogger) Info(msg string, fields ...Field) { + l.zapLogger.Info(msg, fields...) +} + +// Infof method output info level log. +func Infof(format string, v ...interface{}) { + std.zapLogger.Sugar().Infof(format, v...) +} + +func (l *zapLogger) Infof(format string, v ...interface{}) { + l.zapLogger.Sugar().Infof(format, v...) +} + +// Infow method output info level log. +func Infow(msg string, keysAndValues ...interface{}) { + std.zapLogger.Sugar().Infow(msg, keysAndValues...) +} + +func (l *zapLogger) Infow(msg string, keysAndValues ...interface{}) { + l.zapLogger.Sugar().Infow(msg, keysAndValues...) +} + +// Warn method output warning level log. +func Warn(msg string, fields ...Field) { + std.zapLogger.Warn(msg, fields...) +} + +func (l *zapLogger) Warn(msg string, fields ...Field) { + l.zapLogger.Warn(msg, fields...) +} + +// Warnf method output warning level log. +func Warnf(format string, v ...interface{}) { + std.zapLogger.Sugar().Warnf(format, v...) +} + +func (l *zapLogger) Warnf(format string, v ...interface{}) { + l.zapLogger.Sugar().Warnf(format, v...) +} + +// Warnw method output warning level log. +func Warnw(msg string, keysAndValues ...interface{}) { + std.zapLogger.Sugar().Warnw(msg, keysAndValues...) +} + +func (l *zapLogger) Warnw(msg string, keysAndValues ...interface{}) { + l.zapLogger.Sugar().Warnw(msg, keysAndValues...) +} + +// Error method output error level log. +func Error(msg string, fields ...Field) { + std.zapLogger.Error(msg, fields...) +} + +func (l *zapLogger) Error(msg string, fields ...Field) { + l.zapLogger.Error(msg, fields...) +} + +// Errorf method output error level log. +func Errorf(format string, v ...interface{}) { + std.zapLogger.Sugar().Errorf(format, v...) +} + +func (l *zapLogger) Errorf(format string, v ...interface{}) { + l.zapLogger.Sugar().Errorf(format, v...) +} + +// Errorw method output error level log. +func Errorw(msg string, keysAndValues ...interface{}) { + std.zapLogger.Sugar().Errorw(msg, keysAndValues...) +} + +func (l *zapLogger) Errorw(msg string, keysAndValues ...interface{}) { + l.zapLogger.Sugar().Errorw(msg, keysAndValues...) +} + +// Panic method output panic level log and shutdown application. +func Panic(msg string, fields ...Field) { + std.zapLogger.Panic(msg, fields...) +} + +func (l *zapLogger) Panic(msg string, fields ...Field) { + l.zapLogger.Panic(msg, fields...) +} + +// Panicf method output panic level log and shutdown application. +func Panicf(format string, v ...interface{}) { + std.zapLogger.Sugar().Panicf(format, v...) +} + +func (l *zapLogger) Panicf(format string, v ...interface{}) { + l.zapLogger.Sugar().Panicf(format, v...) +} + +// Panicw method output panic level log. +func Panicw(msg string, keysAndValues ...interface{}) { + std.zapLogger.Sugar().Panicw(msg, keysAndValues...) +} + +func (l *zapLogger) Panicw(msg string, keysAndValues ...interface{}) { + l.zapLogger.Sugar().Panicw(msg, keysAndValues...) +} + +// Fatal method output fatal level log. +func Fatal(msg string, fields ...Field) { + std.zapLogger.Fatal(msg, fields...) +} + +func (l *zapLogger) Fatal(msg string, fields ...Field) { + l.zapLogger.Fatal(msg, fields...) +} + +// Fatalf method output fatal level log. +func Fatalf(format string, v ...interface{}) { + std.zapLogger.Sugar().Fatalf(format, v...) +} + +func (l *zapLogger) Fatalf(format string, v ...interface{}) { + l.zapLogger.Sugar().Fatalf(format, v...) +} + +// Fatalw method output Fatalw level log. +func Fatalw(msg string, keysAndValues ...interface{}) { + std.zapLogger.Sugar().Fatalw(msg, keysAndValues...) +} + +func (l *zapLogger) Fatalw(msg string, keysAndValues ...interface{}) { + l.zapLogger.Sugar().Fatalw(msg, keysAndValues...) +} + +// L method output with specified context value. +func L(ctx context.Context) *zapLogger { + return std.L(ctx) +} + +func (l *zapLogger) L(ctx context.Context) *zapLogger { + lg := l.clone() + + if requestID := ctx.Value(KeyRequestID); requestID != nil { + lg.zapLogger = lg.zapLogger.With(zap.Any(KeyRequestID, requestID)) + } + if username := ctx.Value(KeyUsername); username != nil { + lg.zapLogger = lg.zapLogger.With(zap.Any(KeyUsername, username)) + } + if watcherName := ctx.Value(KeyWatcherName); watcherName != nil { + lg.zapLogger = lg.zapLogger.With(zap.Any(KeyWatcherName, watcherName)) + } + + return lg +} + +//nolint:predeclared +func (l *zapLogger) clone() *zapLogger { + clone := *l + + return &clone +} diff --git a/log_test.go b/log_test.go new file mode 100644 index 0000000..78a4dc1 --- /dev/null +++ b/log_test.go @@ -0,0 +1,42 @@ +package log_test + +import ( + "cynking.com/pkg/log" + "github.com/spf13/pflag" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_WithName(t *testing.T) { + defer log.Flush() // used for record logger printer + + logger := log.WithName("test") + logger.Infow("Hello world!", "foo", "bar") // structed logger +} + +func Test_WithValues(t *testing.T) { + defer log.Flush() // used for record logger printer + + logger := log.WithValues("key", "value") // used for record context + logger.Info("Hello world!") + logger.Info("Hello world!") +} + +func Test_V(t *testing.T) { + defer log.Flush() // used for record logger printer + + log.V(0).Infow("Hello world!", "key", "value") + log.V(1).Infow("Hello world!", "key", "value") +} + +func Test_Option(t *testing.T) { + fs := pflag.NewFlagSet("test", pflag.ExitOnError) + opt := log.NewOptions() + opt.AddFlags(fs) + + args := []string{"--log.level=debug"} + err := fs.Parse(args) + assert.Nil(t, err) + + assert.Equal(t, "debug", opt.Level) +} diff --git a/logrus/hook.go b/logrus/hook.go new file mode 100644 index 0000000..ef1b25f --- /dev/null +++ b/logrus/hook.go @@ -0,0 +1,59 @@ +package logrus + +import ( + "runtime" + + "github.com/sirupsen/logrus" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type hook struct { + logger *zap.Logger +} + +func (h *hook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *hook) Fire(entry *logrus.Entry) error { + fields := make([]zap.Field, 0, 10) + + for key, value := range entry.Data { + if key == logrus.ErrorKey { + fields = append(fields, zap.Error(value.(error))) + } else { + fields = append(fields, zap.Any(key, value)) + } + } + + switch entry.Level { + case logrus.PanicLevel: + h.Write(zapcore.PanicLevel, entry.Message, fields, entry.Caller) + case logrus.FatalLevel: + h.Write(zapcore.FatalLevel, entry.Message, fields, entry.Caller) + case logrus.ErrorLevel: + h.Write(zapcore.ErrorLevel, entry.Message, fields, entry.Caller) + case logrus.WarnLevel: + h.Write(zapcore.WarnLevel, entry.Message, fields, entry.Caller) + case logrus.InfoLevel: + h.Write(zapcore.InfoLevel, entry.Message, fields, entry.Caller) + case logrus.DebugLevel, logrus.TraceLevel: + h.Write(zapcore.DebugLevel, entry.Message, fields, entry.Caller) + } + + return nil +} + +func (h *hook) Write(lvl zapcore.Level, msg string, fields []zap.Field, caller *runtime.Frame) { + if ce := h.logger.Check(lvl, msg); ce != nil { + if caller != nil { + ce.Caller = zapcore.NewEntryCaller(caller.PC, caller.File, caller.Line, caller.PC != 0) + } + ce.Write(fields...) + } +} + +func newHook(logger *zap.Logger) logrus.Hook { + return &hook{logger: logger} +} diff --git a/logrus/logger.go b/logrus/logger.go new file mode 100644 index 0000000..7aff60d --- /dev/null +++ b/logrus/logger.go @@ -0,0 +1,17 @@ +package logrus + +import ( + "io/ioutil" + + "github.com/sirupsen/logrus" + "go.uber.org/zap" +) + +// NewLogger create a logrus logger, add hook to it and return it. +func NewLogger(zapLogger *zap.Logger) *logrus.Logger { + logger := logrus.New() + logger.SetOutput(ioutil.Discard) + logger.AddHook(newHook(zapLogger)) + + return logger +} diff --git a/options.go b/options.go new file mode 100644 index 0000000..e422f8f --- /dev/null +++ b/options.go @@ -0,0 +1,143 @@ +package log + +import ( + "encoding/json" + "fmt" + "github.com/spf13/pflag" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "strings" +) + +const ( + flagLevel = "log.level" + flagDisableCaller = "log.disable-caller" + flagDisableStacktrace = "log.disable-stacktrace" + flagFormat = "log.format" + flagEnableColor = "log.enable-color" + flagOutputPaths = "log.output-paths" + flagErrorOutputPaths = "log.error-output-paths" + flagDevelopment = "log.development" + flagName = "log.name" + + ConsoleFormat = "console" + JsonFormat = "json" +) + +// Options 日志有关的配置. 设置参考zap的配置 +type Options struct { + OutputPaths []string `json:"output-paths" mapstructure:"output-paths"` + ErrorOutputPaths []string `json:"error-output-paths" mapstructure:"error-output-paths"` + Level string `json:"level" mapstructure:"level"` + Format string `json:"format" mapstructure:"format"` + DisableCaller bool `json:"disable-caller" mapstructure:"disable-caller"` + DisableStacktrace bool `json:"disable-stacktrace" mapstructure:"disable-stacktrace"` + EnableColor bool `json:"enable-color" mapstructure:"enable-color"` + Development bool `json:"development" mapstructure:"development"` + Name string `json:"name" mapstructure:"name"` +} + +// NewOptions 新建带默认参数的日志配置 +func NewOptions() *Options { + return &Options{ + Level: zapcore.InfoLevel.String(), + DisableCaller: false, + DisableStacktrace: false, + Format: ConsoleFormat, + EnableColor: false, + Development: false, + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + } +} + +// Validate 验证日志配置. +func (o *Options) Validate() []error { + var errs []error + + var zapLevel zapcore.Level + if err := zapLevel.UnmarshalText([]byte(o.Level)); err != nil { + errs = append(errs, err) + } + + format := strings.ToLower(o.Format) + if format != ConsoleFormat && format != JsonFormat { + errs = append(errs, fmt.Errorf("not a valid log format: %q", o.Format)) + } + + return errs +} + +// AddFlags 读取命令行参数设置日志配置 +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.Level, flagLevel, o.Level, "Minimum log output `LEVEL`.") + fs.BoolVar(&o.DisableCaller, flagDisableCaller, o.DisableCaller, "Disable output of caller information in the log.") + fs.BoolVar(&o.DisableStacktrace, flagDisableStacktrace, + o.DisableStacktrace, "Disable the log to record a stack trace for all messages at or above panic level.") + fs.StringVar(&o.Format, flagFormat, o.Format, "Log output `FORMAT`, support plain or json format.") + fs.BoolVar(&o.EnableColor, flagEnableColor, o.EnableColor, "Enable output ansi colors in plain format logs.") + fs.StringSliceVar(&o.OutputPaths, flagOutputPaths, o.OutputPaths, "Output paths of log.") + fs.StringSliceVar(&o.ErrorOutputPaths, flagErrorOutputPaths, o.ErrorOutputPaths, "Error output paths of log.") + fs.BoolVar( + &o.Development, + flagDevelopment, + o.Development, + "Development puts the logger in development mode, which changes "+ + "the behavior of DPanicLevel and takes stacktraces more liberally.", + ) + fs.StringVar(&o.Name, flagName, o.Name, "The name of the logger.") +} + +func (o *Options) String() string { + data, _ := json.Marshal(o) + + return string(data) +} + +// Build 将设置安装到全局zap logger +func (o *Options) Build() error { + var zapLevel zapcore.Level + if err := zapLevel.UnmarshalText([]byte(o.Level)); err != nil { + zapLevel = zapcore.InfoLevel + } + encodeLevel := zapcore.CapitalLevelEncoder + if o.Format == ConsoleFormat && o.EnableColor { + encodeLevel = zapcore.CapitalColorLevelEncoder + } + + zc := &zap.Config{ + Level: zap.NewAtomicLevelAt(zapLevel), + Development: o.Development, + DisableCaller: o.DisableCaller, + DisableStacktrace: o.DisableStacktrace, + Sampling: &zap.SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: o.Format, + EncoderConfig: zapcore.EncoderConfig{ + MessageKey: "message", + LevelKey: "level", + TimeKey: "timestamp", + NameKey: "logger", + CallerKey: "caller", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: encodeLevel, + EncodeTime: timeEncoder, + EncodeDuration: autoDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeName: zapcore.FullNameEncoder, + }, + OutputPaths: o.OutputPaths, + ErrorOutputPaths: o.ErrorOutputPaths, + } + logger, err := zc.Build(zap.AddStacktrace(zapcore.PanicLevel)) + if err != nil { + return err + } + zap.RedirectStdLog(logger.Named(o.Name)) + zap.ReplaceGlobals(logger) + + return nil +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..ccc9886 --- /dev/null +++ b/options_test.go @@ -0,0 +1,23 @@ +package log_test + +import ( + "cynking.com/pkg/log" + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_Options_Validate(t *testing.T) { + opts := &log.Options{ + Level: "test", + Format: "test", + EnableColor: true, + DisableCaller: false, + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + } + + errs := opts.Validate() + expected := `[unrecognized level: "test" not a valid log format: "test"]` + assert.Equal(t, expected, fmt.Sprintf("%s", errs)) +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..c525ecf --- /dev/null +++ b/types.go @@ -0,0 +1,93 @@ +package log + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// 这里配置全局log字段 +const ( + KeyRequestID string = "requestID" + KeyUsername string = "username" + KeyWatcherName string = "watcher" +) + +// 下面是zap包的别名 + +// Field is an alias for the field structure in the underlying log frame. +type Field = zapcore.Field + +// Level is an alias for the level structure in the underlying log frame. +type Level = zapcore.Level + +var ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel = zapcore.DebugLevel + // InfoLevel is the default logging priority. + InfoLevel = zapcore.InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel = zapcore.WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel = zapcore.ErrorLevel + // PanicLevel logs a message, then panics. + PanicLevel = zapcore.PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel = zapcore.FatalLevel +) + +// Alias for zap type functions. +var ( + Any = zap.Any + Array = zap.Array + Object = zap.Object + Binary = zap.Binary + Bool = zap.Bool + Bools = zap.Bools + ByteString = zap.ByteString + ByteStrings = zap.ByteStrings + Complex64 = zap.Complex64 + Complex64s = zap.Complex64s + Complex128 = zap.Complex128 + Complex128s = zap.Complex128s + Duration = zap.Duration + Durations = zap.Durations + Err = zap.Error + Errors = zap.Errors + Float32 = zap.Float32 + Float32s = zap.Float32s + Float64 = zap.Float64 + Float64s = zap.Float64s + Int = zap.Int + Ints = zap.Ints + Int8 = zap.Int8 + Int8s = zap.Int8s + Int16 = zap.Int16 + Int16s = zap.Int16s + Int32 = zap.Int32 + Int32s = zap.Int32s + Int64 = zap.Int64 + Int64s = zap.Int64s + Namespace = zap.Namespace + Reflect = zap.Reflect + Stack = zap.Stack + String = zap.String + Stringer = zap.Stringer + Strings = zap.Strings + Time = zap.Time + Times = zap.Times + Uint = zap.Uint + Uints = zap.Uints + Uint8 = zap.Uint8 + Uint8s = zap.Uint8s + Uint16 = zap.Uint16 + Uint16s = zap.Uint16s + Uint32 = zap.Uint32 + Uint32s = zap.Uint32s + Uint64 = zap.Uint64 + Uint64s = zap.Uint64s + Uintptr = zap.Uintptr + Uintptrs = zap.Uintptrs +)