diff options
author | Valery Piashchynski <[email protected]> | 2020-12-26 00:47:21 +0300 |
---|---|---|
committer | GitHub <[email protected]> | 2020-12-26 00:47:21 +0300 |
commit | 566d7f4c95eb5dedcb2da5afcda4bbea8eba077f (patch) | |
tree | 0007a6b8c8ac9e7d31b8a5f3f7f27669c860d261 /plugins/logger | |
parent | 1bc3db2ea9b95edd0101676d7bfd75df3782c3bd (diff) | |
parent | 7a0dee1a416705c621edbf50e1f43fb39845348f (diff) |
Merge pull request #463 from spiral/experiment/core_pluginsv2.0.0-beta1
[RR2] Plugins
Diffstat (limited to 'plugins/logger')
-rw-r--r-- | plugins/logger/config.go | 94 | ||||
-rw-r--r-- | plugins/logger/encoder.go | 66 | ||||
-rw-r--r-- | plugins/logger/interface.go | 16 | ||||
-rw-r--r-- | plugins/logger/plugin.go | 69 | ||||
-rw-r--r-- | plugins/logger/zap_adapter.go | 56 |
5 files changed, 301 insertions, 0 deletions
diff --git a/plugins/logger/config.go b/plugins/logger/config.go new file mode 100644 index 00000000..f7a5742c --- /dev/null +++ b/plugins/logger/config.go @@ -0,0 +1,94 @@ +package logger + +import ( + "strings" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// ChannelConfig configures loggers per channel. +type ChannelConfig struct { + // Dedicated channels per logger. By default logger allocated via named logger. + Channels map[string]Config `json:"channels" yaml:"channels"` +} + +type Config struct { + // Mode configures logger based on some default template (development, production, off). + Mode string `json:"mode" yaml:"mode"` + + // Level is the minimum enabled logging level. Note that this is a dynamic + // level, so calling ChannelConfig.Level.SetLevel will atomically change the log + // level of all loggers descended from this config. + Level string `json:"level" yaml:"level"` + + // Encoding sets the logger's encoding. Valid values are "json" and + // "console", as well as any third-party encodings registered via + // RegisterEncoder. + Encoding string `json:"encoding" yaml:"encoding"` + + // Output is a list of URLs or file paths to write logging output to. + // See Open for details. + Output []string `json:"output" yaml:"output"` + + // ErrorOutput is a list of URLs to write internal logger errors to. + // The default is standard error. + // + // Note that this setting only affects internal errors; for sample code that + // sends error-level logs to a different location from info- and debug-level + // logs, see the package-level AdvancedConfiguration example. + ErrorOutput []string `json:"errorOutput" yaml:"errorOutput"` +} + +// ZapConfig converts config into Zap configuration. +func (cfg *Config) BuildLogger() (*zap.Logger, error) { + var zCfg zap.Config + switch strings.ToLower(cfg.Mode) { + case "off", "none": + return zap.NewNop(), nil + case "production": + zCfg = zap.NewProductionConfig() + case "development": + zCfg = zap.NewDevelopmentConfig() + default: + zCfg = zap.Config{ + Level: zap.NewAtomicLevelAt(zap.DebugLevel), + Encoding: "console", + EncoderConfig: zapcore.EncoderConfig{ + MessageKey: "message", + LevelKey: "level", + TimeKey: "time", + NameKey: "name", + EncodeName: ColoredHashedNameEncoder, + EncodeLevel: ColoredLevelEncoder, + EncodeTime: UTCTimeEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + }, + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } + } + + if cfg.Level != "" { + level := zap.NewAtomicLevel() + if err := level.UnmarshalText([]byte(cfg.Level)); err == nil { + zCfg.Level = level + } + } + + if cfg.Encoding != "" { + zCfg.Encoding = cfg.Encoding + } + + if len(cfg.Output) != 0 { + zCfg.OutputPaths = cfg.Output + } + + if len(cfg.ErrorOutput) != 0 { + zCfg.ErrorOutputPaths = cfg.ErrorOutput + } + + // todo: https://github.com/uber-go/zap/blob/master/FAQ.md#does-zap-support-log-rotation + + return zCfg.Build() +} diff --git a/plugins/logger/encoder.go b/plugins/logger/encoder.go new file mode 100644 index 00000000..4ff583c4 --- /dev/null +++ b/plugins/logger/encoder.go @@ -0,0 +1,66 @@ +package logger + +import ( + "hash/fnv" + "strings" + "time" + + "github.com/fatih/color" + "go.uber.org/zap/zapcore" +) + +var colorMap = []func(string, ...interface{}) string{ + color.HiYellowString, + color.HiGreenString, + color.HiBlueString, + color.HiRedString, + color.HiCyanString, + color.HiMagentaString, +} + +// ColoredLevelEncoder colorizes log levels. +func ColoredLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { + switch level { + case zapcore.DebugLevel: + enc.AppendString(color.HiWhiteString(level.CapitalString())) + case zapcore.InfoLevel: + enc.AppendString(color.HiCyanString(level.CapitalString())) + case zapcore.WarnLevel: + enc.AppendString(color.HiYellowString(level.CapitalString())) + case zapcore.ErrorLevel, zapcore.DPanicLevel: + enc.AppendString(color.HiRedString(level.CapitalString())) + case zapcore.PanicLevel, zapcore.FatalLevel: + enc.AppendString(color.HiMagentaString(level.CapitalString())) + } +} + +// ColoredNameEncoder colorizes service names. +func ColoredNameEncoder(s string, enc zapcore.PrimitiveArrayEncoder) { + if len(s) < 12 { + s += strings.Repeat(" ", 12-len(s)) + } + + enc.AppendString(color.HiGreenString(s)) +} + +// ColoredHashedNameEncoder colorizes service names and assigns different colors to different names. +func ColoredHashedNameEncoder(s string, enc zapcore.PrimitiveArrayEncoder) { + if len(s) < 12 { + s += strings.Repeat(" ", 12-len(s)) + } + + colorID := stringHash(s, len(colorMap)) + enc.AppendString(colorMap[colorID](s)) +} + +// UTCTimeEncoder encodes time into short UTC specific timestamp. +func UTCTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.UTC().Format("2006/01/02 15:04:05")) +} + +// returns string hash +func stringHash(name string, base int) int { + h := fnv.New32a() + _, _ = h.Write([]byte(name)) + return int(h.Sum32()) % base +} diff --git a/plugins/logger/interface.go b/plugins/logger/interface.go new file mode 100644 index 00000000..876629a9 --- /dev/null +++ b/plugins/logger/interface.go @@ -0,0 +1,16 @@ +package logger + +type ( + // Logger is an general RR log interface + Logger interface { + Debug(msg string, keyvals ...interface{}) + Info(msg string, keyvals ...interface{}) + Warn(msg string, keyvals ...interface{}) + Error(msg string, keyvals ...interface{}) + } +) + +// With creates a child logger and adds structured context to it +type WithLogger interface { + With(keyvals ...interface{}) Logger +} diff --git a/plugins/logger/plugin.go b/plugins/logger/plugin.go new file mode 100644 index 00000000..01bf5cc0 --- /dev/null +++ b/plugins/logger/plugin.go @@ -0,0 +1,69 @@ +package logger + +import ( + "github.com/spiral/endure" + "github.com/spiral/errors" + "github.com/spiral/roadrunner/v2/plugins/config" + "go.uber.org/zap" +) + +// PluginName declares plugin name. +const PluginName = "logs" + +// ZapLogger manages zap logger. +type ZapLogger struct { + base *zap.Logger + cfg Config + channels ChannelConfig +} + +// Init logger service. +func (z *ZapLogger) Init(cfg config.Configurer) error { + const op = errors.Op("zap logger init") + err := cfg.UnmarshalKey(PluginName, &z.cfg) + if err != nil { + return errors.E(op, errors.Disabled, err) + } + + err = cfg.UnmarshalKey(PluginName, &z.channels) + if err != nil { + return errors.E(op, errors.Disabled, err) + } + + z.base, err = z.cfg.BuildLogger() + if err != nil { + return errors.E(op, errors.Disabled, err) + } + return nil +} + +// DefaultLogger returns default logger. +func (z *ZapLogger) DefaultLogger() (Logger, error) { + return NewZapAdapter(z.base), nil +} + +// NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params. +func (z *ZapLogger) NamedLogger(name string) (Logger, error) { + if cfg, ok := z.channels.Channels[name]; ok { + l, err := cfg.BuildLogger() + if err != nil { + return nil, err + } + return NewZapAdapter(l), nil + } + + return NewZapAdapter(z.base.Named(name)), nil +} + +// NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params. +func (z *ZapLogger) ServiceLogger(n endure.Named) (Logger, error) { + return z.NamedLogger(n.Name()) +} + +// Provides declares factory methods. +func (z *ZapLogger) Provides() []interface{} { + return []interface{}{ + z.ServiceLogger, + z.DefaultLogger, + } +} diff --git a/plugins/logger/zap_adapter.go b/plugins/logger/zap_adapter.go new file mode 100644 index 00000000..0a0855b8 --- /dev/null +++ b/plugins/logger/zap_adapter.go @@ -0,0 +1,56 @@ +package logger + +import ( + "fmt" + + "go.uber.org/zap" +) + +type ZapAdapter struct { + zl *zap.Logger +} + +// Create NewZapAdapter which uses general log interface +func NewZapAdapter(zapLogger *zap.Logger) *ZapAdapter { + return &ZapAdapter{ + zl: zapLogger.WithOptions(zap.AddCallerSkip(1)), + } +} + +func (log *ZapAdapter) fields(keyvals []interface{}) []zap.Field { + // we should have even number of keys and values + if len(keyvals)%2 != 0 { + return []zap.Field{zap.Error(fmt.Errorf("odd number of keyvals pairs: %v", keyvals))} + } + + var fields []zap.Field + for i := 0; i < len(keyvals); i += 2 { + key, ok := keyvals[i].(string) + if !ok { + key = fmt.Sprintf("%v", keyvals[i]) + } + fields = append(fields, zap.Any(key, keyvals[i+1])) + } + + return fields +} + +func (log *ZapAdapter) Debug(msg string, keyvals ...interface{}) { + log.zl.Debug(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) Info(msg string, keyvals ...interface{}) { + log.zl.Info(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) Warn(msg string, keyvals ...interface{}) { + log.zl.Warn(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) Error(msg string, keyvals ...interface{}) { + log.zl.Error(msg, log.fields(keyvals)...) +} + +func (log *ZapAdapter) With(keyvals ...interface{}) Logger { + return NewZapAdapter(log.zl.With(log.fields(keyvals)...)) +} |