github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/chain/log/format.go (about) 1 package log 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strconv" 9 "strings" 10 "sync" 11 "sync/atomic" 12 "time" 13 "unicode/utf8" 14 ) 15 16 const ( 17 timeFormat = "2006-01-02T15:04:05-0700" 18 termTimeFormat = "01-02|15:04:05.000" 19 floatFormat = 'f' 20 termMsgJust = 40 21 ) 22 23 var locationTrims = []string{ 24 "github.com/neatlab/neatio/", 25 } 26 27 func PrintOrigins(print bool) { 28 if print { 29 atomic.StoreUint32(&locationEnabled, 1) 30 } else { 31 atomic.StoreUint32(&locationEnabled, 0) 32 } 33 } 34 35 var locationEnabled uint32 36 37 var locationLength uint32 38 39 var fieldPadding = make(map[string]int) 40 41 var fieldPaddingLock sync.RWMutex 42 43 type Format interface { 44 Format(r *Record) []byte 45 } 46 47 func FormatFunc(f func(*Record) []byte) Format { 48 return formatFunc(f) 49 } 50 51 type formatFunc func(*Record) []byte 52 53 func (f formatFunc) Format(r *Record) []byte { 54 return f(r) 55 } 56 57 type TerminalStringer interface { 58 TerminalString() string 59 } 60 61 func TerminalFormat(usecolor bool) Format { 62 return FormatFunc(func(r *Record) []byte { 63 var color = 0 64 if usecolor { 65 switch r.Lvl { 66 case LvlCrit: 67 color = 35 68 case LvlError: 69 color = 31 70 case LvlWarn: 71 color = 33 72 case LvlInfo: 73 color = 32 74 case LvlDebug: 75 color = 36 76 case LvlTrace: 77 color = 34 78 } 79 } 80 81 b := &bytes.Buffer{} 82 lvl := r.Lvl.AlignedString() 83 if atomic.LoadUint32(&locationEnabled) != 0 { 84 85 location := fmt.Sprintf("%+v", r.Call) 86 for _, prefix := range locationTrims { 87 location = strings.TrimPrefix(location, prefix) 88 } 89 90 align := int(atomic.LoadUint32(&locationLength)) 91 if align < len(location) { 92 align = len(location) 93 atomic.StoreUint32(&locationLength, uint32(align)) 94 } 95 padding := strings.Repeat(" ", align-len(location)) 96 97 if color > 0 { 98 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) 99 } else { 100 fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) 101 } 102 } else { 103 if color > 0 { 104 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) 105 } else { 106 fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) 107 } 108 } 109 110 length := utf8.RuneCountInString(r.Msg) 111 if len(r.Ctx) > 0 && length < termMsgJust { 112 b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length)) 113 } 114 115 logfmt(b, r.Ctx, color, true) 116 return b.Bytes() 117 }) 118 } 119 120 func LogfmtFormat() Format { 121 return FormatFunc(func(r *Record) []byte { 122 common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} 123 buf := &bytes.Buffer{} 124 logfmt(buf, append(common, r.Ctx...), 0, false) 125 return buf.Bytes() 126 }) 127 } 128 129 func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) { 130 for i := 0; i < len(ctx); i += 2 { 131 if i != 0 { 132 buf.WriteByte(' ') 133 } 134 135 k, ok := ctx[i].(string) 136 v := formatLogfmtValue(ctx[i+1], term) 137 if !ok { 138 k, v = errorKey, formatLogfmtValue(k, term) 139 } 140 141 fieldPaddingLock.RLock() 142 padding := fieldPadding[k] 143 fieldPaddingLock.RUnlock() 144 145 length := utf8.RuneCountInString(v) 146 if padding < length { 147 padding = length 148 149 fieldPaddingLock.Lock() 150 fieldPadding[k] = padding 151 fieldPaddingLock.Unlock() 152 } 153 if color > 0 { 154 fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k) 155 } else { 156 buf.WriteString(k) 157 buf.WriteByte('=') 158 } 159 buf.WriteString(v) 160 if i < len(ctx)-2 { 161 buf.Write(bytes.Repeat([]byte{' '}, padding-length)) 162 } 163 } 164 buf.WriteByte('\n') 165 } 166 167 func JSONFormat() Format { 168 return JSONFormatEx(false, true) 169 } 170 171 func JSONFormatOrderedEx(pretty, lineSeparated bool) Format { 172 jsonMarshal := json.Marshal 173 if pretty { 174 jsonMarshal = func(v interface{}) ([]byte, error) { 175 return json.MarshalIndent(v, "", " ") 176 } 177 } 178 return FormatFunc(func(r *Record) []byte { 179 props := make(map[string]interface{}) 180 181 props[r.KeyNames.Time] = r.Time 182 props[r.KeyNames.Lvl] = r.Lvl.String() 183 props[r.KeyNames.Msg] = r.Msg 184 185 ctx := make([]string, len(r.Ctx)) 186 for i := 0; i < len(r.Ctx); i += 2 { 187 k, ok := r.Ctx[i].(string) 188 if !ok { 189 props[errorKey] = fmt.Sprintf("%+v is not a string key,", r.Ctx[i]) 190 } 191 ctx[i] = k 192 ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true) 193 } 194 props[r.KeyNames.Ctx] = ctx 195 196 b, err := jsonMarshal(props) 197 if err != nil { 198 b, _ = jsonMarshal(map[string]string{ 199 errorKey: err.Error(), 200 }) 201 return b 202 } 203 if lineSeparated { 204 b = append(b, '\n') 205 } 206 return b 207 }) 208 } 209 210 func JSONFormatEx(pretty, lineSeparated bool) Format { 211 jsonMarshal := json.Marshal 212 if pretty { 213 jsonMarshal = func(v interface{}) ([]byte, error) { 214 return json.MarshalIndent(v, "", " ") 215 } 216 } 217 218 return FormatFunc(func(r *Record) []byte { 219 props := make(map[string]interface{}) 220 221 props[r.KeyNames.Time] = r.Time 222 props[r.KeyNames.Lvl] = r.Lvl.String() 223 props[r.KeyNames.Msg] = r.Msg 224 225 for i := 0; i < len(r.Ctx); i += 2 { 226 k, ok := r.Ctx[i].(string) 227 if !ok { 228 props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) 229 } 230 props[k] = formatJSONValue(r.Ctx[i+1]) 231 } 232 233 b, err := jsonMarshal(props) 234 if err != nil { 235 b, _ = jsonMarshal(map[string]string{ 236 errorKey: err.Error(), 237 }) 238 return b 239 } 240 241 if lineSeparated { 242 b = append(b, '\n') 243 } 244 245 return b 246 }) 247 } 248 249 func formatShared(value interface{}) (result interface{}) { 250 defer func() { 251 if err := recover(); err != nil { 252 if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { 253 result = "nil" 254 } else { 255 panic(err) 256 } 257 } 258 }() 259 260 switch v := value.(type) { 261 case time.Time: 262 return v.Format(timeFormat) 263 264 case error: 265 return v.Error() 266 267 case fmt.Stringer: 268 return v.String() 269 270 default: 271 return v 272 } 273 } 274 275 func formatJSONValue(value interface{}) interface{} { 276 value = formatShared(value) 277 switch value.(type) { 278 case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: 279 return value 280 default: 281 return fmt.Sprintf("%+v", value) 282 } 283 } 284 285 func formatLogfmtValue(value interface{}, term bool) string { 286 if value == nil { 287 return "nil" 288 } 289 290 if t, ok := value.(time.Time); ok { 291 292 return t.Format(timeFormat) 293 } 294 if term { 295 if s, ok := value.(TerminalStringer); ok { 296 297 return escapeString(s.TerminalString()) 298 } 299 } 300 value = formatShared(value) 301 switch v := value.(type) { 302 case bool: 303 return strconv.FormatBool(v) 304 case float32: 305 return strconv.FormatFloat(float64(v), floatFormat, 3, 64) 306 case float64: 307 return strconv.FormatFloat(v, floatFormat, 3, 64) 308 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: 309 return fmt.Sprintf("%d", value) 310 case string: 311 return escapeString(v) 312 default: 313 return escapeString(fmt.Sprintf("%+v", value)) 314 } 315 } 316 317 var stringBufPool = sync.Pool{ 318 New: func() interface{} { return new(bytes.Buffer) }, 319 } 320 321 func escapeString(s string) string { 322 needsQuotes := false 323 needsEscape := false 324 for _, r := range s { 325 if r <= ' ' || r == '=' || r == '"' { 326 needsQuotes = true 327 } 328 if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { 329 needsEscape = true 330 } 331 } 332 if !needsEscape && !needsQuotes { 333 return s 334 } 335 e := stringBufPool.Get().(*bytes.Buffer) 336 e.WriteByte('"') 337 for _, r := range s { 338 switch r { 339 case '\\', '"': 340 e.WriteByte('\\') 341 e.WriteByte(byte(r)) 342 case '\n': 343 e.WriteString("\\n") 344 case '\r': 345 e.WriteString("\\r") 346 case '\t': 347 e.WriteString("\\t") 348 default: 349 e.WriteRune(r) 350 } 351 } 352 e.WriteByte('"') 353 var ret string 354 if needsQuotes { 355 ret = e.String() 356 } else { 357 ret = string(e.Bytes()[1 : e.Len()-1]) 358 } 359 e.Reset() 360 stringBufPool.Put(e) 361 return ret 362 }