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 }