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