github.com/aquanetwork/aquachain@v1.7.8/common/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" 19 floatFormat = 'f' 20 termMsgJust = 40 21 ) 22 23 // locationTrims are trimmed for display to avoid unwieldy log lines. 24 var locationTrims = []string{ 25 "gitlab.com/aquachain/aquachain/", 26 } 27 28 // PrintOrigins sets or unsets log location (file:line) printing for terminal 29 // format output. 30 func PrintOrigins(print bool) { 31 if print { 32 atomic.StoreUint32(&locationEnabled, 1) 33 } else { 34 atomic.StoreUint32(&locationEnabled, 0) 35 } 36 } 37 38 // locationEnabled is an atomic flag controlling whether the terminal formatter 39 // should append the log locations too when printing entries. 40 var locationEnabled uint32 41 42 // locationLength is the maxmimum path length encountered, which all logs are 43 // padded to to aid in alignment. 44 var locationLength uint32 45 46 // fieldPadding is a global map with maximum field value lengths seen until now 47 // to allow padding log contexts in a bit smarter way. 48 var fieldPadding = make(map[string]int) 49 50 // fieldPaddingLock is a global mutex protecting the field padding map. 51 var fieldPaddingLock sync.RWMutex 52 53 type Format interface { 54 Format(r *Record) []byte 55 } 56 57 // FormatFunc returns a new Format object which uses 58 // the given function to perform record formatting. 59 func FormatFunc(f func(*Record) []byte) Format { 60 return formatFunc(f) 61 } 62 63 type formatFunc func(*Record) []byte 64 65 func (f formatFunc) Format(r *Record) []byte { 66 return f(r) 67 } 68 69 // TerminalStringer is an analogous interface to the stdlib stringer, allowing 70 // own types to have custom shortened serialization formats when printed to the 71 // screen. 72 type TerminalStringer interface { 73 TerminalString() string 74 } 75 76 // TerminalFormat formats log records optimized for human readability on 77 // a terminal with color-coded level output and terser human friendly timestamp. 78 // This format should only be used for interactive programs or while developing. 79 // 80 // [TIME] [LEVEL] MESAGE key=value key=value ... 81 // 82 // Example: 83 // 84 // [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002 85 // 86 func TerminalFormat(usecolor bool) Format { 87 return FormatFunc(func(r *Record) []byte { 88 var color = 0 89 if usecolor { 90 switch r.Lvl { 91 case LvlCrit: 92 color = 35 93 case LvlError: 94 color = 31 95 case LvlWarn: 96 color = 33 97 case LvlInfo: 98 color = 32 99 case LvlDebug: 100 color = 36 101 case LvlTrace: 102 color = 34 103 } 104 } 105 106 b := &bytes.Buffer{} 107 lvl := r.Lvl.AlignedString() 108 if atomic.LoadUint32(&locationEnabled) != 0 { 109 // Log origin printing was requested, format the location path and line number 110 location := fmt.Sprintf("%+v", r.Call) 111 for _, prefix := range locationTrims { 112 location = strings.TrimPrefix(location, prefix) 113 } 114 // Maintain the maximum location length for fancyer alignment 115 align := int(atomic.LoadUint32(&locationLength)) 116 if align < len(location) { 117 align = len(location) 118 atomic.StoreUint32(&locationLength, uint32(align)) 119 } 120 padding := strings.Repeat(" ", align-len(location)) 121 122 // Assemble and print the log heading 123 if color > 0 { 124 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) 125 } else { 126 fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) 127 } 128 } else { 129 if color > 0 { 130 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) 131 } else { 132 fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) 133 } 134 } 135 // try to justify the log output for short messages 136 length := utf8.RuneCountInString(r.Msg) 137 if len(r.Ctx) > 0 && length < termMsgJust { 138 b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length)) 139 } 140 // print the keys logfmt style 141 logfmt(b, r.Ctx, color, true) 142 return b.Bytes() 143 }) 144 } 145 146 // LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable 147 // format for key/value pairs. 148 // 149 // For more details see: http://godoc.org/github.com/kr/logfmt 150 // 151 func LogfmtFormat() Format { 152 return FormatFunc(func(r *Record) []byte { 153 common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} 154 buf := &bytes.Buffer{} 155 logfmt(buf, append(common, r.Ctx...), 0, false) 156 return buf.Bytes() 157 }) 158 } 159 160 func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) { 161 for i := 0; i < len(ctx); i += 2 { 162 if i != 0 { 163 buf.WriteByte(' ') 164 } 165 166 k, ok := ctx[i].(string) 167 v := formatLogfmtValue(ctx[i+1], term) 168 if !ok { 169 k, v = errorKey, formatLogfmtValue(k, term) 170 } 171 172 // XXX: we should probably check that all of your key bytes aren't invalid 173 fieldPaddingLock.RLock() 174 padding := fieldPadding[k] 175 fieldPaddingLock.RUnlock() 176 177 length := utf8.RuneCountInString(v) 178 if padding < length { 179 padding = length 180 181 fieldPaddingLock.Lock() 182 fieldPadding[k] = padding 183 fieldPaddingLock.Unlock() 184 } 185 if color > 0 { 186 fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k) 187 } else { 188 buf.WriteString(k) 189 buf.WriteByte('=') 190 } 191 buf.WriteString(v) 192 if i < len(ctx)-2 { 193 buf.Write(bytes.Repeat([]byte{' '}, padding-length)) 194 } 195 } 196 buf.WriteByte('\n') 197 } 198 199 // JsonFormat formats log records as JSON objects separated by newlines. 200 // It is the equivalent of JsonFormatEx(false, true). 201 func JsonFormat() Format { 202 return JsonFormatEx(false, true) 203 } 204 205 // JsonFormatEx formats log records as JSON objects. If pretty is true, 206 // records will be pretty-printed. If lineSeparated is true, records 207 // will be logged with a new line between each record. 208 func JsonFormatEx(pretty, lineSeparated bool) Format { 209 jsonMarshal := json.Marshal 210 if pretty { 211 jsonMarshal = func(v interface{}) ([]byte, error) { 212 return json.MarshalIndent(v, "", " ") 213 } 214 } 215 216 return FormatFunc(func(r *Record) []byte { 217 props := make(map[string]interface{}) 218 219 props[r.KeyNames.Time] = r.Time 220 props[r.KeyNames.Lvl] = r.Lvl.String() 221 props[r.KeyNames.Msg] = r.Msg 222 223 for i := 0; i < len(r.Ctx); i += 2 { 224 k, ok := r.Ctx[i].(string) 225 if !ok { 226 props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) 227 } 228 props[k] = formatJsonValue(r.Ctx[i+1]) 229 } 230 231 b, err := jsonMarshal(props) 232 if err != nil { 233 b, _ = jsonMarshal(map[string]string{ 234 errorKey: err.Error(), 235 }) 236 return b 237 } 238 239 if lineSeparated { 240 b = append(b, '\n') 241 } 242 243 return b 244 }) 245 } 246 247 func formatShared(value interface{}) (result interface{}) { 248 defer func() { 249 if err := recover(); err != nil { 250 if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { 251 result = "nil" 252 } else { 253 panic(err) 254 } 255 } 256 }() 257 258 switch v := value.(type) { 259 case time.Time: 260 return v.Format(timeFormat) 261 262 case error: 263 return v.Error() 264 265 case fmt.Stringer: 266 return v.String() 267 268 default: 269 return v 270 } 271 } 272 273 func formatJsonValue(value interface{}) interface{} { 274 value = formatShared(value) 275 switch value.(type) { 276 case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: 277 return value 278 default: 279 return fmt.Sprintf("%+v", value) 280 } 281 } 282 283 // formatValue formats a value for serialization 284 func formatLogfmtValue(value interface{}, term bool) string { 285 if value == nil { 286 return "nil" 287 } 288 289 if t, ok := value.(time.Time); ok { 290 // Performance optimization: No need for escaping since the provided 291 // timeFormat doesn't have any escape characters, and escaping is 292 // expensive. 293 return t.Format(timeFormat) 294 } 295 if term { 296 if s, ok := value.(TerminalStringer); ok { 297 // Custom terminal stringer provided, use that 298 return escapeString(s.TerminalString()) 299 } 300 } 301 value = formatShared(value) 302 switch v := value.(type) { 303 case bool: 304 return strconv.FormatBool(v) 305 case float32: 306 return strconv.FormatFloat(float64(v), floatFormat, 3, 64) 307 case float64: 308 return strconv.FormatFloat(v, floatFormat, 3, 64) 309 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: 310 return fmt.Sprintf("%d", value) 311 case string: 312 return escapeString(v) 313 default: 314 return escapeString(fmt.Sprintf("%+v", value)) 315 } 316 } 317 318 var stringBufPool = sync.Pool{ 319 New: func() interface{} { return new(bytes.Buffer) }, 320 } 321 322 func escapeString(s string) string { 323 needsQuotes := false 324 needsEscape := false 325 for _, r := range s { 326 if r <= ' ' || r == '=' || r == '"' { 327 needsQuotes = true 328 } 329 if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { 330 needsEscape = true 331 } 332 } 333 if !needsEscape && !needsQuotes { 334 return s 335 } 336 e := stringBufPool.Get().(*bytes.Buffer) 337 e.WriteByte('"') 338 for _, r := range s { 339 switch r { 340 case '\\', '"': 341 e.WriteByte('\\') 342 e.WriteByte(byte(r)) 343 case '\n': 344 e.WriteString("\\n") 345 case '\r': 346 e.WriteString("\\r") 347 case '\t': 348 e.WriteString("\\t") 349 default: 350 e.WriteRune(r) 351 } 352 } 353 e.WriteByte('"') 354 var ret string 355 if needsQuotes { 356 ret = e.String() 357 } else { 358 ret = string(e.Bytes()[1 : e.Len()-1]) 359 } 360 e.Reset() 361 stringBufPool.Put(e) 362 return ret 363 }