接口实现

为了方便业务自适配不同的 log 接入使用,Logger 只包含了最简单的 Log 接口。当业务需要在 Kratos 框架内部使用自定义的 log 的时候,只需要简单实现 Log 方法即可。同时 kratos log 也提供了一些日志的辅助功能,如 value、helper、 filter 等,当我们需要用到这些功能时可以直接使用框架的内置实现,或自行实现相应的功能。

  1. type Logger interface {
  2. Log(level Level, keyvals ...interface{}) error
  3. }

如何实现

实现 logger

  1. // https://github.com/go-kratos/kratos/blob/main/log/std.go
  2. var _ Logger = (*stdLogger)(nil)
  3. type stdLogger struct {
  4. log *log.Logger
  5. pool *sync.Pool
  6. }
  7. // NewStdLogger new a logger with writer.
  8. func NewStdLogger(w io.Writer) Logger {
  9. return &stdLogger{
  10. log: log.New(w, "", 0),
  11. pool: &sync.Pool{
  12. New: func() interface{} {
  13. return new(bytes.Buffer)
  14. },
  15. },
  16. }
  17. }
  18. // Log print the kv pairs log.
  19. func (l *stdLogger) Log(level Level, keyvals ...interface{}) error {
  20. if len(keyvals) == 0 {
  21. return nil
  22. }
  23. if len(keyvals)%2 != 0 {
  24. keyvals = append(keyvals, "")
  25. }
  26. buf := l.pool.Get().(*bytes.Buffer)
  27. buf.WriteString(level.String())
  28. for i := 0; i < len(keyvals); i += 2 {
  29. fmt.Fprintf(buf, " %s=%v", keyvals[i], keyvals[i+1])
  30. }
  31. l.log.Output(4, buf.String())~~~~
  32. buf.Reset()
  33. l.pool.Put(buf)
  34. return nil
  35. }

实现 valuer

  1. func Timestamp(layout string) Valuer {
  2. return func(context.Context) interface{} {
  3. return time.Now().Format(layout)
  4. }
  5. }

使用方式

使用 Logger 打印日志

  1. logger := log.DefaultLogger
  2. logger.Log(LevelInfo, "key1", "value1")

使用 Helper 打印日志

  1. log := log.NewHelper(DefaultLogger)
  2. log.Debug("test debug")
  3. log.Info("test info")
  4. log.Warn("test warn")
  5. log.Error("test error")

使用 valuer

  1. logger := DefaultLogger
  2. logger = With(logger, "ts", DefaultTimestamp, "caller", DefaultCaller)
  3. logger.Log(LevelInfo, "msg", "helloworld")

同时打印多个 logger

  1. out := log.NewStdLogger(os.Stdout)
  2. err := log.NewStdLogger(os.Stderr)
  3. l := log.With(MultiLogger(out, err))
  4. l.Log(LevelInfo, "msg", "test")

使用 context

  1. logger := log.With(NewStdLogger(os.Stdout),
  2. "trace", Trace(),
  3. )
  4. log := log.NewHelper(logger)
  5. ctx := context.WithValue(context.Background(), "trace_id", "2233")
  6. log.WithContext(ctx).Info("got trace!")

使用 filter 过滤日志

如果需要过滤日志中某些不应该被打印明文的字段如 password 等,可以通过 log.NewFilter() 来实现过滤功能

通过 level 过滤日志
  1. l := log.NewHelper(log.NewFilter(log.DefaultLogger, log.FilterLevel(log.LevelWarn)))
  2. l.Log(LevelDebug, "msg1", "te1st debug")
  3. l.Debug("test debug")
  4. l.Debugf("test %s", "debug")
  5. l.Debugw("log", "test debug")
  6. l.Warn("warn log")
通过 key 过滤日志
  1. l := log.NewHelper(log.NewFilter(log.DefaultLogger, log.FilterKey("password")))
  2. l.Debugw("password", "123456")
通过 value 过滤日志
  1. l := log.NewHelper(log.NewFilter(log.DefaultLogger, log.FilterValue("kratos")))
  2. l.Debugw("name", "kratos")
通过 hook func 过滤日志
  1. l := log.NewHelper(log.NewFilter(log.DefaultLogger, log.FilterFunc(testFilterFunc)))
  2. l.Debug("debug level")
  3. l.Infow("password", "123456")
  4. func testFilterFunc(level Level, keyvals ...interface{}) bool {
  5. if level == LevelWarn {
  6. return true
  7. }
  8. for i := 0; i < len(keyvals); i++ {
  9. if keyvals[i] == "password" {
  10. keyvals[i+1] = "***"
  11. }
  12. }
  13. return false
  14. }

输出日志到stdout

使用自带的 StdLogger 可以创建标准输出日志对象. 通过 NewHelper 构造日志模块,Helper 生成的日志模块可以提供不同等级的日志输出。

  1. logger := log.NewStdLogger()
  2. log := log.NewHelper(logger)
  3. // Levels
  4. log.Info("some log")
  5. log.Infof("format %s", "some log")
  6. log.Infow("field_name", "some log")

输出日志到fluentd

引入 fluent sdk

  1. import "github.com/go-kratos/fluent"
  2. addr := "unix:///var/run/fluent/fluent.sock"
  3. logger,err := fluent.NewLogger(addr)
  4. if err != nil {
  5. return
  6. }
  7. log := log.NewHelper(logger)
  8. // Levels
  9. log.Info("some log")
  10. log.Infof("format %s", "some log")
  11. log.Infow("field_name", "some log")

扩展阅读

使用 Zap 实现 Logger

  1. var _ log.Logger = (*ZapLogger)(nil)
  2. type ZapLogger struct {
  3. log *zap.Logger
  4. Sync func() error
  5. }
  6. // NewZapLogger return ZapLogger
  7. func NewZapLogger(encoder zapcore.EncoderConfig, level zap.AtomicLevel, opts ...zap.Option) *ZapLogger {
  8. core := zapcore.NewCore(
  9. zapcore.NewConsoleEncoder(encoder),
  10. zapcore.NewMultiWriteSyncer(
  11. zapcore.AddSync(os.Stdout),
  12. ), level)
  13. zapLogger := zap.New(core, opts...)
  14. return &ZapLogger{log: zapLogger, Sync: zapLogger.Sync}
  15. }
  16. // Log Implementation of logger interface
  17. func (l *ZapLogger) Log(level log.Level, keyvals ...interface{}) error {
  18. if len(keyvals) == 0 || len(keyvals)%2 != 0 {
  19. l.log.Warn(fmt.Sprint("Keyvalues must appear in pairs: ", keyvals))
  20. return nil
  21. }
  22. // Zap.Field is used when keyvals pairs appear
  23. var data []zap.Field
  24. for i := 0; i < len(keyvals); i += 2 {
  25. data = append(data, zap.Any(fmt.Sprint(keyvals[i]), fmt.Sprint(keyvals[i+1])))
  26. }
  27. switch level {
  28. case log.LevelDebug:
  29. l.log.Debug("", data...)
  30. case log.LevelInfo:
  31. l.log.Info("", data...)
  32. case log.LevelWarn:
  33. l.log.Warn("", data...)
  34. case log.LevelError:
  35. l.log.Error("", data...)
  36. }
  37. return nil
  38. }