github.com/nikandfor/tlog@v0.21.3/convert/logfmt.go (about) 1 package convert 2 3 import ( 4 "errors" 5 "io" 6 "path/filepath" 7 "strconv" 8 "time" 9 10 "github.com/nikandfor/hacked/hfmt" 11 "github.com/nikandfor/loc" 12 "golang.org/x/term" 13 14 "github.com/nikandfor/tlog" 15 "github.com/nikandfor/tlog/low" 16 "github.com/nikandfor/tlog/tlio" 17 "github.com/nikandfor/tlog/tlwire" 18 ) 19 20 type ( 21 Logfmt struct { //nolint:maligned 22 io.Writer 23 24 TimeFormat string 25 TimeZone *time.Location 26 27 FloatFormat string 28 FloatChar byte 29 FloatPrecision int 30 31 QuoteChars string 32 QuoteAnyValue bool 33 QuoteEmptyValue bool 34 35 PairSeparator string 36 KVSeparator string 37 ArrSeparator string 38 MapSeparator string 39 MapKVSeparator string 40 41 MaxValPad int 42 43 AppendKeySafe bool 44 SubObjects bool 45 46 Rename RenameFunc 47 48 Colorize bool 49 KeyColor []byte 50 ValColor []byte 51 52 d tlwire.Decoder 53 54 b low.Buf 55 56 addpad int 57 pad map[string]int 58 } 59 ) 60 61 func NewLogfmt(w io.Writer) *Logfmt { 62 fd := tlio.Fd(w) 63 colorize := term.IsTerminal(int(fd)) 64 65 return &Logfmt{ 66 Writer: w, 67 68 TimeFormat: "2006-01-02T15:04:05.000000000Z07:00", 69 TimeZone: time.Local, 70 FloatChar: 'f', 71 FloatPrecision: 5, 72 QuoteChars: "`\"' ()[]{}*", 73 74 PairSeparator: " ", 75 KVSeparator: "=", 76 ArrSeparator: " ", 77 MapSeparator: " ", 78 MapKVSeparator: "=", 79 80 MaxValPad: 24, 81 82 Colorize: colorize, 83 KeyColor: tlog.Color(36), 84 85 AppendKeySafe: true, 86 87 pad: make(map[string]int), 88 } 89 } 90 91 func (w *Logfmt) Write(p []byte) (i int, err error) { 92 b := w.b[:0] 93 94 more: 95 tag, els, i := w.d.Tag(p, i) 96 if tag != tlwire.Map { 97 return i, errors.New("map expected") 98 } 99 100 w.addpad = 0 101 102 var k []byte 103 for el := 0; els == -1 || el < int(els); el++ { 104 if els == -1 && w.d.Break(p, &i) { 105 break 106 } 107 108 k, i = w.d.Bytes(p, i) 109 110 b, i = w.appendPair(b, p, k, i, el == 0) 111 } 112 113 b = append(b, '\n') 114 115 if i < len(p) { 116 goto more 117 } 118 119 w.b = b[:0] 120 121 _, err = w.Writer.Write(b) 122 if err != nil { 123 return 0, err 124 } 125 126 return len(p), nil 127 } 128 129 func (w *Logfmt) appendPair(b, p, k []byte, st int, first bool) (_ []byte, i int) { 130 if w.addpad != 0 { 131 b = append(b, low.Spaces[:w.addpad]...) 132 w.addpad = 0 133 } 134 135 if !w.SubObjects { 136 tag := w.d.TagOnly(p, st) 137 138 if tag == tlwire.Array || tag == tlwire.Map { 139 return w.convertArray(b, p, k, st, first) 140 } 141 } 142 143 if !first { 144 b = append(b, w.PairSeparator...) 145 } 146 147 if w.Colorize && len(w.KeyColor) != 0 { 148 b = append(b, w.KeyColor...) 149 } 150 151 var renamed bool 152 153 if w.Rename != nil { 154 b, renamed = w.Rename(b, p, k, st) 155 } 156 157 if !renamed { 158 b = w.appendAndQuote(b, k, tlwire.String) 159 } 160 161 b = append(b, w.KVSeparator...) 162 163 if w.Colorize && len(w.ValColor) != 0 { 164 b = append(b, w.ValColor...) 165 } else if w.Colorize && len(w.KeyColor) != 0 { 166 b = append(b, tlog.ResetColor...) 167 } 168 169 vst := len(b) 170 171 b, i = w.ConvertValue(b, p, k, st) 172 173 vw := len(b) - vst 174 175 // NOTE: Value width can be incorrect for non-ascii symbols. 176 // We can calc it by iterating utf8.DecodeRune() but should we? 177 178 if w.Colorize && len(w.ValColor) != 0 { 179 b = append(b, tlog.ResetColor...) 180 } 181 182 nw := w.pad[low.UnsafeBytesToString(k)] 183 184 if vw < nw { 185 w.addpad = nw - vw 186 } 187 188 if nw < vw && vw <= w.MaxValPad { 189 if vw > w.MaxValPad { 190 vw = w.MaxValPad 191 } 192 193 w.pad[string(k)] = vw 194 } 195 196 return b, i 197 } 198 199 func (w *Logfmt) ConvertValue(b, p, k []byte, st int) (_ []byte, i int) { 200 tag, sub, i := w.d.Tag(p, st) 201 202 switch tag { 203 case tlwire.Int: 204 b = strconv.AppendUint(b, uint64(sub), 10) 205 case tlwire.Neg: 206 b = strconv.AppendInt(b, sub, 10) 207 case tlwire.Bytes, tlwire.String: 208 var s []byte 209 s, i = w.d.Bytes(p, st) 210 211 b = w.appendAndQuote(b, s, tag) 212 case tlwire.Array: 213 b, i = w.convertArray(b, p, k, st, false) 214 case tlwire.Map: 215 b, i = w.convertArray(b, p, k, st, false) 216 case tlwire.Semantic: 217 switch sub { 218 case tlwire.Time: 219 var t time.Time 220 t, i = w.d.Time(p, st) 221 222 if w.TimeZone != nil { 223 t = t.In(w.TimeZone) 224 } 225 226 if w.TimeFormat != "" { 227 b = append(b, '"') 228 b = t.AppendFormat(b, w.TimeFormat) 229 b = append(b, '"') 230 } else { 231 b = strconv.AppendInt(b, t.UnixNano(), 10) 232 } 233 case tlog.WireID: 234 var id tlog.ID 235 i = id.TlogParse(p, st) 236 237 bst := len(b) + 1 238 b = append(b, `"123456789_123456789_123456789_12"`...) 239 240 id.FormatTo(b[bst:], 'x') 241 case tlwire.Caller: 242 var pc loc.PC 243 var pcs loc.PCs 244 pc, pcs, i = w.d.Callers(p, st) 245 246 if pcs != nil { 247 b = append(b, '[') 248 for i, pc := range pcs { 249 if i != 0 { 250 b = append(b, ',') 251 } 252 253 _, file, line := pc.NameFileLine() 254 b = hfmt.Appendf(b, `"%v:%d"`, filepath.Base(file), line) 255 } 256 b = append(b, ']') 257 } else { 258 _, file, line := pc.NameFileLine() 259 260 b = hfmt.Appendf(b, `"%v:%d"`, filepath.Base(file), line) 261 } 262 default: 263 b, i = w.ConvertValue(b, p, k, i) 264 } 265 case tlwire.Special: 266 switch sub { 267 case tlwire.False: 268 b = append(b, "false"...) 269 case tlwire.True: 270 b = append(b, "true"...) 271 case tlwire.Nil: 272 b = append(b, "<nil>"...) 273 case tlwire.Undefined: 274 b = append(b, "<undef>"...) 275 case tlwire.None: 276 b = append(b, "<none>"...) 277 case tlwire.Float64, tlwire.Float32, tlwire.Float8: 278 var f float64 279 f, i = w.d.Float(p, st) 280 281 if w.FloatFormat != "" { 282 b = hfmt.Appendf(b, w.FloatFormat, f) 283 } else { 284 b = strconv.AppendFloat(b, f, w.FloatChar, w.FloatPrecision, 64) 285 } 286 default: 287 panic(sub) 288 } 289 default: 290 panic(tag) 291 } 292 293 return b, i 294 } 295 296 func (w *Logfmt) appendAndQuote(b, s []byte, tag byte) []byte { 297 quote := tag == tlwire.Bytes || w.QuoteAnyValue || len(s) == 0 && w.QuoteEmptyValue 298 if !quote { 299 for _, c := range s { 300 if c < 0x20 || c >= 0x80 { 301 quote = true 302 break 303 } 304 for _, q := range w.QuoteChars { 305 if byte(q) == c { 306 quote = true 307 break 308 } 309 } 310 } 311 } 312 313 switch { 314 case quote: 315 ss := low.UnsafeBytesToString(s) 316 b = strconv.AppendQuote(b, ss) 317 case w.AppendKeySafe: 318 b = low.AppendSafe(b, s) 319 default: 320 b = append(b, s...) 321 } 322 323 return b 324 } 325 326 func (w *Logfmt) convertArray(b, p, k []byte, st int, first bool) (_ []byte, i int) { 327 tag, sub, i := w.d.Tag(p, st) 328 329 subk := k[:len(k):len(k)] 330 331 if w.SubObjects { 332 if tag == tlwire.Map { 333 b = append(b, '{') 334 } else { 335 b = append(b, '[') 336 } 337 } 338 339 for el := 0; sub == -1 || el < int(sub); el++ { 340 if sub == -1 && w.d.Break(p, &i) { 341 break 342 } 343 344 if !w.SubObjects { 345 if tag == tlwire.Map { 346 var kk []byte 347 348 kk, i = w.d.Bytes(p, i) 349 350 subk = append(subk[:len(k)], '.') 351 subk = append(subk, kk...) 352 } 353 354 b, i = w.appendPair(b, p, subk, i, first && el == 0) 355 356 continue 357 } 358 359 if tag == tlwire.Map { 360 if el != 0 { 361 b = append(b, w.MapSeparator...) 362 } 363 364 k, i = w.d.Bytes(p, i) 365 366 if w.AppendKeySafe { 367 b = low.AppendSafe(b, k) 368 } else { 369 b = append(b, k...) 370 } 371 372 b = append(b, w.MapKVSeparator...) 373 } else if el != 0 { 374 b = append(b, w.ArrSeparator...) 375 } 376 377 b, i = w.ConvertValue(b, p, subk, i) 378 } 379 380 if w.SubObjects { 381 if tag == tlwire.Map { 382 b = append(b, '}') 383 } else { 384 b = append(b, ']') 385 } 386 } 387 388 return b, i 389 }