github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/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.Hidden: 278 b = append(b, "<hidden>"...) 279 case tlwire.SelfRef: 280 b = append(b, "<self_ref>"...) 281 case tlwire.Float64, tlwire.Float32, tlwire.Float8: 282 var f float64 283 f, i = w.d.Float(p, st) 284 285 if w.FloatFormat != "" { 286 b = hfmt.Appendf(b, w.FloatFormat, f) 287 } else { 288 b = strconv.AppendFloat(b, f, w.FloatChar, w.FloatPrecision, 64) 289 } 290 default: 291 panic(sub) 292 } 293 default: 294 panic(tag) 295 } 296 297 return b, i 298 } 299 300 func (w *Logfmt) appendAndQuote(b, s []byte, tag byte) []byte { 301 quote := tag == tlwire.Bytes || w.QuoteAnyValue || len(s) == 0 && w.QuoteEmptyValue 302 if !quote { 303 for _, c := range s { 304 if c < 0x20 || c >= 0x80 { 305 quote = true 306 break 307 } 308 for _, q := range w.QuoteChars { 309 if byte(q) == c { 310 quote = true 311 break 312 } 313 } 314 } 315 } 316 317 switch { 318 case quote: 319 ss := low.UnsafeBytesToString(s) 320 b = strconv.AppendQuote(b, ss) 321 case w.AppendKeySafe: 322 b = low.AppendSafe(b, s) 323 default: 324 b = append(b, s...) 325 } 326 327 return b 328 } 329 330 func (w *Logfmt) convertArray(b, p, k []byte, st int, first bool) (_ []byte, i int) { 331 tag, sub, i := w.d.Tag(p, st) 332 333 subk := k[:len(k):len(k)] 334 335 if w.SubObjects { 336 if tag == tlwire.Map { 337 b = append(b, '{') 338 } else { 339 b = append(b, '[') 340 } 341 } 342 343 for el := 0; sub == -1 || el < int(sub); el++ { 344 if sub == -1 && w.d.Break(p, &i) { 345 break 346 } 347 348 if !w.SubObjects { 349 if tag == tlwire.Map { 350 var kk []byte 351 352 kk, i = w.d.Bytes(p, i) 353 354 subk = append(subk[:len(k)], '.') 355 subk = append(subk, kk...) 356 } 357 358 b, i = w.appendPair(b, p, subk, i, first && el == 0) 359 360 continue 361 } 362 363 if tag == tlwire.Map { 364 if el != 0 { 365 b = append(b, w.MapSeparator...) 366 } 367 368 k, i = w.d.Bytes(p, i) 369 370 if w.AppendKeySafe { 371 b = low.AppendSafe(b, k) 372 } else { 373 b = append(b, k...) 374 } 375 376 b = append(b, w.MapKVSeparator...) 377 } else if el != 0 { 378 b = append(b, w.ArrSeparator...) 379 } 380 381 b, i = w.ConvertValue(b, p, subk, i) 382 } 383 384 if w.SubObjects { 385 if tag == tlwire.Map { 386 b = append(b, '}') 387 } else { 388 b = append(b, ']') 389 } 390 } 391 392 return b, i 393 }