github.com/Aoi-hosizora/ahlib-more@v1.5.1-0.20230404072844-256112befaf6/xlogrus/simple_formatter.go (about)

     1  package xlogrus
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/Aoi-hosizora/ahlib/xcolor"
     7  	"github.com/sirupsen/logrus"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  )
    14  
    15  // simpleFormatterOptions is a type of SimpleFormatter's option, each field can be set by SimpleFormatterOption function type.
    16  type simpleFormatterOptions struct {
    17  	timestampFormat  string
    18  	timeLocation     *time.Location
    19  	disableColor     bool
    20  	callerFormatter  func(*runtime.Frame) (filename string, funcname string)
    21  	levelFormatter   func(logrus.Level) string
    22  	messageFormatter func(level, time, caller, message string) string
    23  }
    24  
    25  // SimpleFormatterOption represents an option type for SimpleFormatter's option, can be created by WithXXX functions.
    26  type SimpleFormatterOption func(*simpleFormatterOptions)
    27  
    28  // WithTimestampFormat creates an SimpleFormatterOption to specify timestamp format, defaults to time.RFC3339.
    29  func WithTimestampFormat(f string) SimpleFormatterOption {
    30  	return func(o *simpleFormatterOptions) {
    31  		o.timestampFormat = f
    32  	}
    33  }
    34  
    35  // WithTimeLocation creates an SimpleFormatterOption to specify the time.Location for entry's time, defaults to time.Local.
    36  func WithTimeLocation(loc *time.Location) SimpleFormatterOption {
    37  	return func(o *simpleFormatterOptions) {
    38  		o.timeLocation = loc
    39  	}
    40  }
    41  
    42  // WithDisableColor creates an SimpleFormatterOption to disable the colored format, defaults to false, and means defaults to enable colored format.
    43  func WithDisableColor(disable bool) SimpleFormatterOption {
    44  	return func(o *simpleFormatterOptions) {
    45  		o.disableColor = disable
    46  	}
    47  }
    48  
    49  // WithCallerFormatter creates an SimpleFormatterOption to specify the caller's runtime.Frame formatter, defaults to use filename without path and function's shortname.
    50  func WithCallerFormatter(formatter func(*runtime.Frame) (filename string, funcname string)) SimpleFormatterOption {
    51  	return func(o *simpleFormatterOptions) {
    52  		o.callerFormatter = formatter
    53  	}
    54  }
    55  
    56  // WithLevelFormatter creates an SimpleFormatterOption to specify the logrus.Level formatter, defaults to use the first four character in capital of the level.
    57  func WithLevelFormatter(formatter func(logrus.Level) string) SimpleFormatterOption {
    58  	return func(o *simpleFormatterOptions) {
    59  		o.levelFormatter = formatter
    60  	}
    61  }
    62  
    63  // WithMessageFormatter creates an SimpleFormatterOption to specify the logger formatter.
    64  //
    65  // The default format logs like:
    66  // 	WARN [2021-08-29T05:56:25+08:00] test
    67  // 	INFO [2021-08-29T05:56:25+08:00] filename.go:123 funcname() > test
    68  func WithMessageFormatter(formatter func(level, time, caller, message string) string) SimpleFormatterOption {
    69  	return func(o *simpleFormatterOptions) {
    70  		o.messageFormatter = formatter
    71  	}
    72  }
    73  
    74  // SimpleFormatter represents a simple formatter for logrus.Logger, it only formats level, time, caller and message information with color or without color. Note that
    75  // the logrus.Fields data will not be formatted unlink what logrus.TextFormatter does.
    76  type SimpleFormatter struct {
    77  	option       *simpleFormatterOptions
    78  	terminalOnce sync.Once
    79  }
    80  
    81  var _ logrus.Formatter = (*SimpleFormatter)(nil)
    82  
    83  // NewSimpleFormatter creates an SimpleFormatter with given SimpleFormatterOption-s.
    84  //
    85  // Example:
    86  // 	l := logrus.New()
    87  // 	l.SetLevel(logrus.TraceLevel)
    88  // 	l.SetReportCaller(true)
    89  // 	l.SetFormatter(NewSimpleFormatter(
    90  // 		WithTimestampFormat("2006-01-02 15:04:05"),
    91  // 		WithTimeLocation(time.UTC),
    92  // 		WithDisableColor(false),
    93  // 		WithCallerFormatter(func(*runtime.Frame) (string, string) { return "", "" }),
    94  // 		WithLevelFormatter(func(l logrus.Level) string { return strings.ToUpper(l.String())[:1] }),
    95  // 	))
    96  func NewSimpleFormatter(options ...SimpleFormatterOption) *SimpleFormatter {
    97  	opt := &simpleFormatterOptions{}
    98  	for _, o := range options {
    99  		if o != nil {
   100  			o(opt)
   101  		}
   102  	}
   103  	if opt.timestampFormat == "" {
   104  		opt.timestampFormat = time.RFC3339
   105  	}
   106  	if opt.timeLocation == nil {
   107  		opt.timeLocation = time.Local
   108  	}
   109  	if opt.callerFormatter == nil {
   110  		opt.callerFormatter = func(frame *runtime.Frame) (filename string, funcname string) {
   111  			_, filename = filepath.Split(frame.File)
   112  			_, funcname = filepath.Split(frame.Function)
   113  			filename = fmt.Sprintf("%s:%d", filename, frame.Line)
   114  			funcname = fmt.Sprintf("%s()", funcname)
   115  			return filename, funcname
   116  		}
   117  	}
   118  	if opt.levelFormatter == nil {
   119  		opt.levelFormatter = func(level logrus.Level) string {
   120  			return strings.ToUpper(level.String()[0:4])
   121  		}
   122  	}
   123  	if opt.messageFormatter == nil {
   124  		opt.messageFormatter = func(level, time, caller, message string) string {
   125  			if caller == "" {
   126  				return fmt.Sprintf("%s [%s] %s\n", level, time, message)
   127  			}
   128  			return fmt.Sprintf("%s [%s] %s > %s\n", level, time, caller, message)
   129  		}
   130  	}
   131  	return &SimpleFormatter{option: opt}
   132  }
   133  
   134  // Format formats a single logrus.Entry, this method implements logrus.Formatter interface.
   135  func (s *SimpleFormatter) Format(entry *logrus.Entry) ([]byte, error) {
   136  	s.terminalOnce.Do(func() {
   137  		// 0. initialize the terminal io.Writer for color supported
   138  		if entry.Logger != nil && !s.option.disableColor {
   139  			xcolor.InitTerminal(entry.Logger.Out)
   140  		}
   141  	})
   142  
   143  	// 1. level and time
   144  	level := s.option.levelFormatter(entry.Level)
   145  	now := entry.Time.In(s.option.timeLocation).Format(s.option.timestampFormat)
   146  
   147  	// 2. runtime caller
   148  	caller := ""
   149  	if entry.HasCaller() {
   150  		filename, funcname := s.option.callerFormatter(entry.Caller)
   151  		parts := make([]string, 0, 2)
   152  		if filename != "" {
   153  			parts = append(parts, filename)
   154  		}
   155  		if funcname != "" {
   156  			parts = append(parts, funcname)
   157  		}
   158  		caller = strings.Join(parts, " ") // "filename:line funcname()"
   159  	}
   160  
   161  	// 3. color and message
   162  	if !s.option.disableColor {
   163  		level = s.levelColor(entry.Level).Sprintf(level)
   164  		if caller != "" {
   165  			caller = s.callerStyle().Sprint(caller)
   166  		}
   167  	}
   168  	message := strings.TrimSuffix(entry.Message, "\n")
   169  	message = s.option.messageFormatter(level, now, caller, message)
   170  
   171  	// *. write to buffer
   172  	buf := entry.Buffer
   173  	if entry.Buffer == nil {
   174  		// nil only when fire hooks
   175  		buf = &bytes.Buffer{}
   176  	}
   177  	_, _ = buf.WriteString(message)
   178  	return buf.Bytes(), nil
   179  }
   180  
   181  // levelColor returns the xcolor.Color from logrus.Level.
   182  func (s *SimpleFormatter) levelColor(level logrus.Level) xcolor.Color {
   183  	switch level {
   184  	case logrus.InfoLevel:
   185  		return xcolor.Blue
   186  	case logrus.WarnLevel:
   187  		return xcolor.Yellow
   188  	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
   189  		return xcolor.Red
   190  	default: // debug, trace
   191  		return xcolor.White
   192  	}
   193  }
   194  
   195  // callerStyle returns the xcolor.Style for caller.
   196  func (s *SimpleFormatter) callerStyle() xcolor.Style {
   197  	return xcolor.Faint // you may not see this style in Windows's cmd
   198  }
   199  
   200  // RFC3339JsonFormatter returns a logrus.JSONFormatter with time.RFC3339Nano timestamp format, "entries" data key and custom logrus.FieldMap.
   201  func RFC3339JsonFormatter() *logrus.JSONFormatter {
   202  	return &logrus.JSONFormatter{
   203  		TimestampFormat:   time.RFC3339Nano,
   204  		DisableTimestamp:  false,
   205  		DisableHTMLEscape: false,
   206  		PrettyPrint:       false,
   207  		DataKey:           "entries",
   208  		FieldMap: logrus.FieldMap{
   209  			logrus.FieldKeyTime:  "@@time",
   210  			logrus.FieldKeyLevel: "@level",
   211  			logrus.FieldKeyMsg:   "@message",
   212  			logrus.FieldKeyFile:  "file",
   213  			logrus.FieldKeyFunc:  "function",
   214  			// @@time -> @level -> @message -> entries -> file -> function
   215  		},
   216  	}
   217  }
   218  
   219  // RFC3339ColoredTextFormatter returns a logrus.TextFormatter with time.RFC3339 timestamp format, full timestamp format, force color and quote.
   220  func RFC3339ColoredTextFormatter() *logrus.TextFormatter {
   221  	return &logrus.TextFormatter{
   222  		ForceColors:      true,
   223  		ForceQuote:       true,
   224  		DisableTimestamp: false,
   225  		FullTimestamp:    true,
   226  		TimestampFormat:  time.RFC3339,
   227  		DisableSorting:   false,
   228  		CallerPrettyfier: func(frame *runtime.Frame) (function string, file string) {
   229  			_, funcname := filepath.Split(frame.Function)
   230  			_, filename := filepath.Split(frame.File)
   231  			funcname = fmt.Sprintf("%s()", funcname)
   232  			filename = fmt.Sprintf("%s:%d", filename, frame.Line)
   233  			return funcname, filename
   234  		},
   235  	}
   236  }