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  }