github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/shared/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 := newFilter(t.Levels)
    79  
    80  	if t.MaxQueueSize == 0 {
    81  		t.MaxQueueSize = DefaultMaxTargetQueue
    82  	}
    83  
    84  	switch t.Type {
    85  	case "console":
    86  		return newConsoleTarget(name, t, filter, formatter)
    87  	case "file":
    88  		return newFileTarget(name, t, filter, formatter)
    89  	case "syslog":
    90  		return newSyslogTarget(name, t, filter, formatter)
    91  	case "tcp":
    92  		return newTCPTarget(name, t, filter, formatter)
    93  	case "none":
    94  		return nil, nil
    95  	}
    96  	return nil, fmt.Errorf("invalid type '%s' for target %s", t.Type, name)
    97  }
    98  
    99  func newFilter(levels []LogLevel) logr.Filter {
   100  	filter := &logr.CustomFilter{}
   101  	for _, lvl := range levels {
   102  		filter.Add(logr.Level(lvl))
   103  	}
   104  	return filter
   105  }
   106  
   107  func newFormatter(name string, format string) (logr.Formatter, error) {
   108  	switch format {
   109  	case "json", "":
   110  		return &logrFmt.JSON{}, nil
   111  	case "plain":
   112  		return &logrFmt.Plain{Delim: " | "}, nil
   113  	default:
   114  		return nil, fmt.Errorf("invalid format '%s' for target %s", format, name)
   115  	}
   116  }
   117  
   118  func newConsoleTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
   119  	type consoleOptions struct {
   120  		Out string `json:"Out"`
   121  	}
   122  	options := &consoleOptions{}
   123  	if err := json.Unmarshal(t.Options, options); err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	var w io.Writer
   128  	switch options.Out {
   129  	case "stdout", "":
   130  		w = os.Stdout
   131  	case "stderr":
   132  		w = os.Stderr
   133  	default:
   134  		return nil, fmt.Errorf("invalid out '%s' for target %s", options.Out, name)
   135  	}
   136  
   137  	newTarget := target.NewWriterTarget(filter, formatter, w, t.MaxQueueSize)
   138  	return newTarget, nil
   139  }
   140  
   141  func newFileTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
   142  	type fileOptions struct {
   143  		Filename   string `json:"Filename"`
   144  		MaxSize    int    `json:"MaxSizeMB"`
   145  		MaxAge     int    `json:"MaxAgeDays"`
   146  		MaxBackups int    `json:"MaxBackups"`
   147  		Compress   bool   `json:"Compress"`
   148  	}
   149  	options := &fileOptions{}
   150  	if err := json.Unmarshal(t.Options, options); err != nil {
   151  		return nil, err
   152  	}
   153  	return newFileTargetWithOpts(name, t, target.FileOptions(*options), filter, formatter)
   154  }
   155  
   156  func newFileTargetWithOpts(name string, t *LogTarget, opts target.FileOptions, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
   157  	if opts.Filename == "" {
   158  		return nil, fmt.Errorf("missing 'Filename' option for target %s", name)
   159  	}
   160  	if err := checkFileWritable(opts.Filename); err != nil {
   161  		return nil, fmt.Errorf("error writing to 'Filename' for target %s: %w", name, err)
   162  	}
   163  
   164  	newTarget := target.NewFileTarget(filter, formatter, opts, t.MaxQueueSize)
   165  	return newTarget, nil
   166  }
   167  
   168  func newSyslogTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
   169  	options := &SyslogParams{}
   170  	if err := json.Unmarshal(t.Options, options); err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	if options.IP == "" {
   175  		return nil, fmt.Errorf("missing 'IP' option for target %s", name)
   176  	}
   177  	if options.Port == 0 {
   178  		options.Port = DefaultSysLogPort
   179  	}
   180  	return NewSyslogTarget(filter, formatter, options, t.MaxQueueSize)
   181  }
   182  
   183  func newTCPTarget(name string, t *LogTarget, filter logr.Filter, formatter logr.Formatter) (logr.Target, error) {
   184  	options := &TcpParams{}
   185  	if err := json.Unmarshal(t.Options, options); err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	if options.IP == "" {
   190  		return nil, fmt.Errorf("missing 'IP' option for target %s", name)
   191  	}
   192  	if options.Port == 0 {
   193  		return nil, fmt.Errorf("missing 'Port' option for target %s", name)
   194  	}
   195  	return NewTcpTarget(filter, formatter, options, t.MaxQueueSize)
   196  }
   197  
   198  func checkFileWritable(filename string) error {
   199  	// try opening/creating the file for writing
   200  	file, err := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	file.Close()
   205  	return nil
   206  }
   207  
   208  func isLevelEnabled(logger *logr.Logger, level logr.Level) bool {
   209  	if logger == nil || logger.Logr() == nil {
   210  		return false
   211  	}
   212  
   213  	status := logger.Logr().IsLevelEnabled(level)
   214  	return status.Enabled
   215  }
   216  
   217  // zapToLogr converts Zap fields to Logr fields.
   218  // This will not be needed once Logr is used for all logging.
   219  func zapToLogr(zapFields []Field) logr.Fields {
   220  	encoder := zapcore.NewMapObjectEncoder()
   221  	for _, zapField := range zapFields {
   222  		zapField.AddTo(encoder)
   223  	}
   224  	return logr.Fields(encoder.Fields)
   225  }
   226  
   227  // mlogLevelToLogrLevel converts a mlog logger level to
   228  // an array of discrete Logr levels.
   229  func mlogLevelToLogrLevels(level string) []LogLevel {
   230  	levels := make([]LogLevel, 0)
   231  	levels = append(levels, LvlError, LvlPanic, LvlFatal, LvlStdLog)
   232  
   233  	switch level {
   234  	case LevelDebug:
   235  		levels = append(levels, LvlDebug)
   236  		fallthrough
   237  	case LevelInfo:
   238  		levels = append(levels, LvlInfo)
   239  		fallthrough
   240  	case LevelWarn:
   241  		levels = append(levels, LvlWarn)
   242  	}
   243  	return levels
   244  }