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  }