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  }