github.com/adacta-ru/mattermost-server/v5@v5.31.1/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  }