github.com/adacta-ru/mattermost-server/v6@v6.0.0/mlog/logr.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package mlog 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io" 10 "os" 11 12 "github.com/hashicorp/go-multierror" 13 "github.com/mattermost/logr" 14 logrFmt "github.com/mattermost/logr/format" 15 "github.com/mattermost/logr/target" 16 "go.uber.org/zap/zapcore" 17 ) 18 19 const ( 20 DefaultMaxTargetQueue = 1000 21 DefaultSysLogPort = 514 22 ) 23 24 type LogLevel struct { 25 ID logr.LevelID 26 Name string 27 Stacktrace bool 28 } 29 30 type LogTarget struct { 31 Type string // one of "console", "file", "tcp", "syslog", "none". 32 Format string // one of "json", "plain" 33 Levels []LogLevel 34 Options json.RawMessage 35 MaxQueueSize int 36 } 37 38 type LogTargetCfg map[string]*LogTarget 39 type LogrCleanup func() error 40 41 func newLogr() *logr.Logger { 42 lgr := &logr.Logr{} 43 lgr.OnExit = func(int) {} 44 lgr.OnPanic = func(interface{}) {} 45 lgr.OnLoggerError = onLoggerError 46 lgr.OnQueueFull = onQueueFull 47 lgr.OnTargetQueueFull = onTargetQueueFull 48 49 logger := lgr.NewLogger() 50 return &logger 51 } 52 53 func logrAddTargets(logger *logr.Logger, targets LogTargetCfg) error { 54 lgr := logger.Logr() 55 var errs error 56 for name, t := range targets { 57 target, err := NewLogrTarget(name, t) 58 if err != nil { 59 errs = multierror.Append(err) 60 continue 61 } 62 if target != nil { 63 target.SetName(name) 64 lgr.AddTarget(target) 65 } 66 } 67 return errs 68 } 69 70 // NewLogrTarget creates a `logr.Target` based on a target config. 71 // Can be used when parsing custom config files, or when programmatically adding 72 // built-in targets. Use `mlog.AddTarget` to add custom targets. 73 func NewLogrTarget(name string, t *LogTarget) (logr.Target, error) { 74 formatter, err := newFormatter(name, t.Format) 75 if err != nil { 76 return nil, err 77 } 78 filter, err := newFilter(name, t.Levels) 79 if err != nil { 80 return nil, err 81 } 82 83 if t.MaxQueueSize == 0 { 84 t.MaxQueueSize = DefaultMaxTargetQueue 85 } 86 87 switch t.Type { 88 case "console": 89 return newConsoleTarget(name, t, filter, formatter) 90 case "file": 91 return newFileTarget(name, t, filter, formatter) 92 case "syslog": 93 return newSyslogTarget(name, t, filter, formatter) 94 case "tcp": 95 return newTCPTarget(name, t, filter, formatter) 96 case "none": 97 return nil, nil 98 } 99 return nil, fmt.Errorf("invalid type '%s' for target %s", t.Type, name) 100 } 101 102 func newFilter(name string, levels []LogLevel) (logr.Filter, error) { 103 filter := &logr.CustomFilter{} 104 for _, lvl := range levels { 105 filter.Add(logr.Level(lvl)) 106 } 107 return filter, nil 108 } 109 110 func newFormatter(name string, format string) (logr.Formatter, error) { 111 switch format { 112 case "json", "": 113 return &logrFmt.JSON{}, nil 114 case "plain": 115 return &logrFmt.Plain{Delim: " | "}, nil 116 default: 117 return nil, fmt.Errorf("invalid format '%s' for target %s", format, name) 118 } 119 } 120 121 func newConsoleTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) { 122 type consoleOptions struct { 123 Out string `json:"Out"` 124 } 125 options := &consoleOptions{} 126 if err := json.Unmarshal(t.Options, options); err != nil { 127 return nil, err 128 } 129 130 var w io.Writer 131 switch options.Out { 132 case "stdout", "": 133 w = os.Stdout 134 case "stderr": 135 w = os.Stderr 136 default: 137 return nil, fmt.Errorf("invalid out '%s' for target %s", options.Out, name) 138 } 139 140 newTarget := target.NewWriterTarget(filter, formatter, w, t.MaxQueueSize) 141 return newTarget, nil 142 } 143 144 func newFileTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) { 145 type fileOptions struct { 146 Filename string `json:"Filename"` 147 MaxSize int `json:"MaxSizeMB"` 148 MaxAge int `json:"MaxAgeDays"` 149 MaxBackups int `json:"MaxBackups"` 150 Compress bool `json:"Compress"` 151 } 152 options := &fileOptions{} 153 if err := json.Unmarshal(t.Options, options); err != nil { 154 return nil, err 155 } 156 return newFileTargetWithOpts(name, t, target.FileOptions(*options), filter, formatter) 157 } 158 159 func newFileTargetWithOpts(name string, t *LogTarget, opts target.FileOptions, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) { 160 if opts.Filename == "" { 161 return nil, fmt.Errorf("missing 'Filename' option for target %s", name) 162 } 163 if err := checkFileWritable(opts.Filename); err != nil { 164 return nil, fmt.Errorf("error writing to 'Filename' for target %s: %w", name, err) 165 } 166 167 newTarget := target.NewFileTarget(filter, formatter, opts, t.MaxQueueSize) 168 return newTarget, nil 169 } 170 171 func newSyslogTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) { 172 options := &SyslogParams{} 173 if err := json.Unmarshal(t.Options, options); err != nil { 174 return nil, err 175 } 176 177 if options.IP == "" { 178 return nil, fmt.Errorf("missing 'IP' option for target %s", name) 179 } 180 if options.Port == 0 { 181 options.Port = DefaultSysLogPort 182 } 183 return NewSyslogTarget(filter, formatter, options, t.MaxQueueSize) 184 } 185 186 func newTCPTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) { 187 options := &TcpParams{} 188 if err := json.Unmarshal(t.Options, options); err != nil { 189 return nil, err 190 } 191 192 if options.IP == "" { 193 return nil, fmt.Errorf("missing 'IP' option for target %s", name) 194 } 195 if options.Port == 0 { 196 return nil, fmt.Errorf("missing 'Port' option for target %s", name) 197 } 198 return NewTcpTarget(filter, formatter, options, t.MaxQueueSize) 199 } 200 201 func checkFileWritable(filename string) error { 202 // try opening/creating the file for writing 203 file, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) 204 if err != nil { 205 return err 206 } 207 file.Close() 208 return nil 209 } 210 211 func isLevelEnabled(logger *logr.Logger, level logr.Level) bool { 212 if logger == nil || logger.Logr() == nil { 213 return false 214 } 215 216 status := logger.Logr().IsLevelEnabled(level) 217 return status.Enabled 218 } 219 220 // zapToLogr converts Zap fields to Logr fields. 221 // This will not be needed once Logr is used for all logging. 222 func zapToLogr(zapFields []Field) logr.Fields { 223 encoder := zapcore.NewMapObjectEncoder() 224 for _, zapField := range zapFields { 225 zapField.AddTo(encoder) 226 } 227 return logr.Fields(encoder.Fields) 228 } 229 230 // mlogLevelToLogrLevel converts a mlog logger level to 231 // an array of discrete Logr levels. 232 func mlogLevelToLogrLevels(level string) []LogLevel { 233 levels := make([]LogLevel, 0) 234 levels = append(levels, LvlError, LvlPanic, LvlFatal, LvlStdLog) 235 236 switch level { 237 case LevelDebug: 238 levels = append(levels, LvlDebug) 239 fallthrough 240 case LevelInfo: 241 levels = append(levels, LvlInfo) 242 fallthrough 243 case LevelWarn: 244 levels = append(levels, LvlWarn) 245 } 246 return levels 247 }