github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/console.go (about)

     1  package tlog
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io"
     8  	"path/filepath"
     9  	"runtime/debug"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  	"unsafe"
    14  
    15  	"github.com/nikandfor/errors"
    16  	"github.com/nikandfor/hacked/hfmt"
    17  	"github.com/nikandfor/hacked/htime"
    18  	"github.com/nikandfor/loc"
    19  	"golang.org/x/term"
    20  
    21  	"github.com/nikandfor/tlog/low"
    22  	"github.com/nikandfor/tlog/tlwire"
    23  )
    24  
    25  type (
    26  	ConsoleWriter struct { //nolint:maligned
    27  		io.Writer
    28  		Flags int
    29  
    30  		d tlwire.Decoder
    31  
    32  		addpad   int     // padding for the next pair
    33  		b, h     low.Buf // buf, header
    34  		lasttime low.Buf
    35  
    36  		ls, lastls []byte
    37  
    38  		Colorize        bool
    39  		PadEmptyMessage bool
    40  		AllLabels       bool
    41  		AllCallers      bool
    42  
    43  		LevelWidth   int
    44  		MessageWidth int
    45  		IDWidth      int
    46  		Shortfile    int
    47  		Funcname     int
    48  		MaxValPad    int
    49  
    50  		TimeFormat     string
    51  		TimeLocation   *time.Location
    52  		DurationFormat string
    53  		DurationDiv    time.Duration
    54  		FloatFormat    string
    55  		FloatChar      byte
    56  		FloatPrecision int
    57  		CallerFormat   string
    58  		BytesFormat    string
    59  
    60  		StringOnNewLineMinLen int
    61  
    62  		PairSeparator string
    63  		KVSeparator   string
    64  
    65  		QuoteChars      string
    66  		QuoteAnyValue   bool
    67  		QuoteEmptyValue bool
    68  
    69  		ColorScheme
    70  
    71  		pad map[string]int
    72  	}
    73  
    74  	ColorScheme struct {
    75  		TimeColor       []byte
    76  		TimeChangeColor []byte // if different from TimeColor
    77  		FileColor       []byte
    78  		FuncColor       []byte
    79  		MessageColor    []byte
    80  		KeyColor        []byte
    81  		ValColor        []byte
    82  		LevelColor      struct {
    83  			Info  []byte
    84  			Warn  []byte
    85  			Error []byte
    86  			Fatal []byte
    87  			Debug []byte
    88  		}
    89  	}
    90  )
    91  
    92  const ( // console writer flags
    93  	Ldate = 1 << iota
    94  	Ltime
    95  	Lseconds
    96  	Lmilliseconds
    97  	Lmicroseconds
    98  	Lshortfile
    99  	Llongfile
   100  	Ltypefunc // pkg.(*Type).Func
   101  	Lfuncname // Func
   102  	LUTC
   103  	Lloglevel // log level
   104  
   105  	LstdFlags = Ldate | Ltime
   106  	LdetFlags = Ldate | Ltime | Lmicroseconds | Lshortfile | Lloglevel
   107  
   108  	Lnone = 0
   109  )
   110  
   111  const (
   112  	cfHex = 1 << iota
   113  )
   114  
   115  var (
   116  	ResetColor = Color(0)
   117  
   118  	DefaultColorScheme = ColorScheme{
   119  		TimeColor:       Color(90),
   120  		TimeChangeColor: Color(38, 5, 244, 1),
   121  		FileColor:       Color(90),
   122  		FuncColor:       Color(90),
   123  		KeyColor:        Color(36),
   124  		LevelColor: struct {
   125  			Info  []byte
   126  			Warn  []byte
   127  			Error []byte
   128  			Fatal []byte
   129  			Debug []byte
   130  		}{
   131  			Info:  Color(90),
   132  			Warn:  Color(31),
   133  			Error: Color(31, 1),
   134  			Fatal: Color(31, 1),
   135  			Debug: Color(90),
   136  		},
   137  	}
   138  )
   139  
   140  func NewConsoleWriter(w io.Writer, f int) *ConsoleWriter {
   141  	fd := -1
   142  
   143  	switch f := w.(type) {
   144  	case interface{ Fd() uintptr }:
   145  		fd = int(f.Fd())
   146  	case interface{ Fd() int }:
   147  		fd = f.Fd()
   148  	}
   149  
   150  	colorize := term.IsTerminal(fd)
   151  
   152  	return &ConsoleWriter{
   153  		Writer: w,
   154  		Flags:  f,
   155  
   156  		Colorize:        colorize,
   157  		PadEmptyMessage: true,
   158  
   159  		LevelWidth:   3,
   160  		Shortfile:    18,
   161  		Funcname:     16,
   162  		MessageWidth: 30,
   163  		IDWidth:      8,
   164  		MaxValPad:    24,
   165  
   166  		TimeFormat: "2006-01-02_15:04:05.000Z0700",
   167  		//DurationFormat: "%v",
   168  		FloatChar:      'f',
   169  		FloatPrecision: 5,
   170  		CallerFormat:   "%v",
   171  
   172  		StringOnNewLineMinLen: 71,
   173  
   174  		PairSeparator: "  ",
   175  		KVSeparator:   "=",
   176  
   177  		QuoteChars:      "`\"' ()[]{}*",
   178  		QuoteEmptyValue: true,
   179  
   180  		ColorScheme: DefaultColorScheme,
   181  
   182  		pad: make(map[string]int),
   183  	}
   184  }
   185  
   186  func (w *ConsoleWriter) Write(p []byte) (i int, err error) {
   187  	defer func() {
   188  		perr := recover()
   189  
   190  		if err == nil && perr == nil {
   191  			return
   192  		}
   193  
   194  		if perr != nil {
   195  			fmt.Fprintf(w.Writer, "panic: %v (pos %x)\n", perr, i)
   196  		} else {
   197  			fmt.Fprintf(w.Writer, "parse error: %+v (pos %x)\n", err, i)
   198  		}
   199  		fmt.Fprintf(w.Writer, "dump\n%v", tlwire.Dump(p))
   200  		fmt.Fprintf(w.Writer, "hex dump\n%v", hex.Dump(p))
   201  
   202  		s := debug.Stack()
   203  		fmt.Fprintf(w.Writer, "%s", s)
   204  	}()
   205  
   206  	if w.PairSeparator == "" {
   207  		w.PairSeparator = "  "
   208  	}
   209  
   210  	if w.KVSeparator == "" {
   211  		w.KVSeparator = "="
   212  	}
   213  
   214  	h := w.h
   215  
   216  more:
   217  	w.addpad = 0
   218  
   219  	var t time.Time
   220  	var pc loc.PC
   221  	var lv LogLevel
   222  	var tp EventKind
   223  	var m []byte
   224  	w.ls = w.ls[:0]
   225  	b := w.b
   226  
   227  	tag, els, i := w.d.Tag(p, i)
   228  	if tag != tlwire.Map {
   229  		return 0, errors.New("expected map")
   230  	}
   231  
   232  	var k []byte
   233  	var sub int64
   234  	for el := 0; els == -1 || el < int(els); el++ {
   235  		if els == -1 && w.d.Break(p, &i) {
   236  			break
   237  		}
   238  
   239  		pairst := i
   240  
   241  		k, i = w.d.Bytes(p, i)
   242  		if len(k) == 0 {
   243  			return 0, errors.New("empty key")
   244  		}
   245  
   246  		st := i
   247  
   248  		tag, sub, i = w.d.Tag(p, i)
   249  		if tag != tlwire.Semantic {
   250  			b, i = w.appendPair(b, p, k, st)
   251  			continue
   252  		}
   253  
   254  		//	println(fmt.Sprintf("key %s  tag %x %x", k, tag, sub))
   255  
   256  		switch {
   257  		case sub == tlwire.Time && string(k) == KeyTimestamp:
   258  			t, i = w.d.Time(p, st)
   259  		case sub == tlwire.Caller && string(k) == KeyCaller:
   260  			var pcs loc.PCs
   261  
   262  			pc, pcs, i = w.d.Callers(p, st)
   263  
   264  			if w.AllCallers && pcs != nil {
   265  				b, i = w.appendPair(b, p, k, st)
   266  			}
   267  		case sub == WireMessage && string(k) == KeyMessage:
   268  			m, i = w.d.Bytes(p, i)
   269  		case sub == WireLogLevel && string(k) == KeyLogLevel && w.Flags&Lloglevel != 0:
   270  			i = lv.TlogParse(p, st)
   271  		case sub == WireEventKind && string(k) == KeyEventKind:
   272  			_ = tp.TlogParse(p, st)
   273  
   274  			b, i = w.appendPair(b, p, k, st)
   275  		case sub == WireLabel:
   276  			i = w.d.Skip(p, st)
   277  			w.ls = append(w.ls, p[pairst:i]...)
   278  		default:
   279  			b, i = w.appendPair(b, p, k, st)
   280  		}
   281  	}
   282  
   283  	h = w.appendHeader(h, t, lv, pc, m, len(b))
   284  
   285  	h = append(h, b...)
   286  
   287  	if w.AllLabels || !bytes.Equal(w.lastls, w.ls) {
   288  		h = w.convertLabels(h, w.ls)
   289  		w.lastls = append(w.lastls[:0], w.ls...)
   290  	}
   291  
   292  	h.NewLine()
   293  
   294  	if i < len(p) {
   295  		goto more
   296  	}
   297  
   298  	w.b = b[:0]
   299  	w.h = h[:0]
   300  
   301  	_, err = w.Writer.Write(h)
   302  
   303  	return len(p), err
   304  }
   305  
   306  func (w *ConsoleWriter) convertLabels(b, p []byte) []byte {
   307  	var k []byte
   308  	i := 0
   309  
   310  	for i != len(p) {
   311  		k, i = w.d.Bytes(p, i)
   312  		if len(k) == 0 {
   313  			panic("empty key")
   314  		}
   315  
   316  		b, i = w.appendPair(b, p, k, i)
   317  	}
   318  
   319  	return b
   320  }
   321  
   322  func (w *ConsoleWriter) appendHeader(b []byte, t time.Time, lv LogLevel, pc loc.PC, m []byte, blen int) []byte {
   323  	var fname, file string
   324  	line := -1
   325  
   326  	if w.Flags&(Ldate|Ltime|Lmilliseconds|Lmicroseconds) != 0 {
   327  		b = w.appendTime(b, t)
   328  
   329  		b = append(b, ' ', ' ')
   330  	}
   331  
   332  	if w.Flags&Lloglevel != 0 {
   333  		var col []byte
   334  		switch {
   335  		case !w.Colorize:
   336  			// break
   337  		case lv == Info:
   338  			col = w.LevelColor.Info
   339  		case lv == Warn:
   340  			col = w.LevelColor.Warn
   341  		case lv == Error:
   342  			col = w.LevelColor.Error
   343  		case lv >= Fatal:
   344  			col = w.LevelColor.Fatal
   345  		default:
   346  			col = w.LevelColor.Debug
   347  		}
   348  
   349  		if col != nil {
   350  			b = append(b, col...)
   351  		}
   352  
   353  		i := len(b)
   354  		b = append(b, low.Spaces[:w.LevelWidth]...)
   355  
   356  		switch lv {
   357  		case Info:
   358  			copy(b[i:], "INFO")
   359  		case Warn:
   360  			copy(b[i:], "WARN")
   361  		case Error:
   362  			copy(b[i:], "ERROR")
   363  		case Fatal:
   364  			copy(b[i:], "FATAL")
   365  		default:
   366  			b = hfmt.Appendf(b[:i], "%*x", w.LevelWidth, lv)
   367  		}
   368  
   369  		end := len(b)
   370  
   371  		if col != nil {
   372  			b = append(b, ResetColor...)
   373  		}
   374  
   375  		if pad := i + w.LevelWidth + 2 - end; pad > 0 {
   376  			b = append(b, low.Spaces[:pad]...)
   377  		}
   378  	}
   379  
   380  	if w.Flags&(Llongfile|Lshortfile) != 0 {
   381  		fname, file, line = pc.NameFileLine()
   382  
   383  		if w.Colorize && len(w.FileColor) != 0 {
   384  			b = append(b, w.FileColor...)
   385  		}
   386  
   387  		if w.Flags&Lshortfile != 0 {
   388  			file = filepath.Base(file)
   389  
   390  			n := 1
   391  			for q := line; q != 0; q /= 10 {
   392  				n++
   393  			}
   394  
   395  			i := len(b)
   396  
   397  			b = append(b, low.Spaces[:w.Shortfile]...)
   398  			b = append(b[:i], file...)
   399  
   400  			e := len(b)
   401  			b = b[:i+w.Shortfile]
   402  
   403  			if len(file)+n > w.Shortfile {
   404  				i = i + w.Shortfile - n
   405  			} else {
   406  				i = e
   407  			}
   408  
   409  			b[i] = ':'
   410  			for q, j := line, n-1; j >= 1; j-- {
   411  				b[i+j] = byte(q%10) + '0'
   412  				q /= 10
   413  			}
   414  		} else {
   415  			b = append(b, file...)
   416  
   417  			n := 1
   418  			for q := line; q != 0; q /= 10 {
   419  				n++
   420  			}
   421  
   422  			i := len(b)
   423  			b = append(b, ":           "[:n]...)
   424  
   425  			for q, j := line, n-1; j >= 1; j-- {
   426  				b[i+j] = byte(q%10) + '0'
   427  				q /= 10
   428  			}
   429  		}
   430  
   431  		if w.Colorize && len(w.FileColor) != 0 {
   432  			b = append(b, ResetColor...)
   433  		}
   434  
   435  		b = append(b, ' ', ' ')
   436  	}
   437  
   438  	if w.Flags&(Ltypefunc|Lfuncname) != 0 {
   439  		if line == -1 {
   440  			fname, _, _ = pc.NameFileLine()
   441  		}
   442  		fname = filepath.Base(fname)
   443  
   444  		if w.Colorize && len(w.FuncColor) != 0 {
   445  			b = append(b, w.FuncColor...)
   446  		}
   447  
   448  		if w.Flags&Lfuncname != 0 {
   449  			p := strings.Index(fname, ").")
   450  			if p == -1 {
   451  				p = strings.IndexByte(fname, '.')
   452  				fname = fname[p+1:]
   453  			} else {
   454  				fname = fname[p+2:]
   455  			}
   456  
   457  			if l := len(fname); l <= w.Funcname {
   458  				i := len(b)
   459  				b = append(b, low.Spaces[:w.Funcname]...)
   460  				b = append(b[:i], fname...)
   461  				b = b[:i+w.Funcname]
   462  			} else {
   463  				b = append(b, fname[:w.Funcname]...)
   464  				j := 1
   465  				for {
   466  					q := fname[l-j]
   467  					if q < '0' || '9' < q {
   468  						break
   469  					}
   470  					b[len(b)-j] = q
   471  					j++
   472  				}
   473  			}
   474  		} else {
   475  			b = append(b, fname...)
   476  		}
   477  
   478  		if w.Colorize && len(w.FuncColor) != 0 {
   479  			b = append(b, ResetColor...)
   480  		}
   481  
   482  		b = append(b, ' ', ' ')
   483  	}
   484  
   485  	if len(m) != 0 {
   486  		if w.Colorize && len(w.MessageColor) != 0 {
   487  			b = append(b, w.MessageColor...)
   488  		}
   489  
   490  		b = append(b, m...)
   491  
   492  		if w.Colorize && len(w.MessageColor) != 0 {
   493  			b = append(b, ResetColor...)
   494  		}
   495  	}
   496  
   497  	if len(m) >= w.MessageWidth && blen != 0 {
   498  		b = append(b, ' ', ' ')
   499  	}
   500  
   501  	if (w.PadEmptyMessage || len(m) != 0) && len(m) < w.MessageWidth && blen != 0 {
   502  		b = append(b, low.Spaces[:w.MessageWidth-len(m)]...)
   503  	}
   504  
   505  	return b
   506  }
   507  
   508  func (w *ConsoleWriter) appendTime(b []byte, t time.Time) []byte {
   509  	if w.Flags&LUTC != 0 {
   510  		t = t.UTC()
   511  	}
   512  
   513  	var Y, M, D, h, m, s int
   514  	if w.Flags&(Ldate|Ltime) != 0 {
   515  		Y, M, D, h, m, s = htime.DateClock(t)
   516  	}
   517  
   518  	if w.Colorize && len(w.TimeColor) != 0 {
   519  		b = append(b, w.TimeColor...)
   520  	}
   521  
   522  	ts := len(b)
   523  
   524  	if w.Flags&Ldate != 0 {
   525  		b = append(b,
   526  			byte(Y/1000)+'0',
   527  			byte(Y/100%10)+'0',
   528  			byte(Y/10%10)+'0',
   529  			byte(Y/1%10)+'0',
   530  			'-',
   531  			byte(M/10)+'0',
   532  			byte(M%10)+'0',
   533  			'-',
   534  			byte(D/10)+'0',
   535  			byte(D%10)+'0',
   536  		)
   537  	}
   538  	if w.Flags&Ltime != 0 {
   539  		if w.Flags&Ldate != 0 {
   540  			b = append(b, '_')
   541  		}
   542  
   543  		b = append(b,
   544  			byte(h/10)+'0',
   545  			byte(h%10)+'0',
   546  			':',
   547  			byte(m/10)+'0',
   548  			byte(m%10)+'0',
   549  			':',
   550  			byte(s/10)+'0',
   551  			byte(s%10)+'0',
   552  		)
   553  	}
   554  	if w.Flags&(Lmilliseconds|Lmicroseconds) != 0 {
   555  		if w.Flags&(Ldate|Ltime) != 0 {
   556  			b = append(b, '.')
   557  		}
   558  
   559  		ns := t.Nanosecond() / 1e3
   560  		if w.Flags&Lmilliseconds != 0 {
   561  			ns /= 1000
   562  
   563  			b = append(b,
   564  				byte(ns/100%10)+'0',
   565  				byte(ns/10%10)+'0',
   566  				byte(ns/1%10)+'0',
   567  			)
   568  		} else {
   569  			b = append(b,
   570  				byte(ns/100000%10)+'0',
   571  				byte(ns/10000%10)+'0',
   572  				byte(ns/1000%10)+'0',
   573  				byte(ns/100%10)+'0',
   574  				byte(ns/10%10)+'0',
   575  				byte(ns/1%10)+'0',
   576  			)
   577  		}
   578  	}
   579  
   580  	if w.Colorize && len(w.TimeChangeColor) != 0 {
   581  		c := common(b[ts:], w.lasttime)
   582  		ts += c
   583  		w.lasttime = append(w.lasttime[:c], b[ts:]...)
   584  
   585  		if c != 0 && ts != len(b) {
   586  			b = append(b, w.TimeChangeColor...)
   587  
   588  			copy(b[ts+len(w.TimeChangeColor):], b[ts:])
   589  			copy(b[ts:], w.TimeChangeColor)
   590  		}
   591  	}
   592  
   593  	if w.Colorize && (len(w.TimeColor) != 0 || len(w.TimeChangeColor) != 0) {
   594  		b = append(b, ResetColor...)
   595  	}
   596  
   597  	return b
   598  }
   599  
   600  func (w *ConsoleWriter) appendPair(b, p, k []byte, st int) (_ []byte, i int) {
   601  	i = st
   602  
   603  	if w.addpad != 0 {
   604  		b = append(b, low.Spaces[:w.addpad]...)
   605  		w.addpad = 0
   606  	}
   607  
   608  	if len(b) != 0 {
   609  		b = append(b, w.PairSeparator...)
   610  	}
   611  
   612  	if w.Colorize && len(w.KeyColor) != 0 {
   613  		b = append(b, w.KeyColor...)
   614  	}
   615  
   616  	b = append(b, k...)
   617  
   618  	b = append(b, w.KVSeparator...)
   619  
   620  	if w.Colorize && len(w.ValColor) != 0 {
   621  		b = append(b, w.ValColor...)
   622  	} else if w.Colorize && len(w.KeyColor) != 0 {
   623  		b = append(b, ResetColor...)
   624  	}
   625  
   626  	vst := len(b)
   627  
   628  	b, i = w.ConvertValue(b, p, i, 0)
   629  
   630  	vw := len(b) - vst
   631  
   632  	// NOTE: Value width can be incorrect for non-ascii symbols.
   633  	// We can calc it by iterating utf8.DecodeRune() but should we?
   634  
   635  	if w.Colorize && len(w.ValColor) != 0 {
   636  		b = append(b, ResetColor...)
   637  	}
   638  
   639  	nw := w.pad[low.UnsafeBytesToString(k)]
   640  
   641  	if vw < nw {
   642  		w.addpad = nw - vw
   643  	}
   644  
   645  	if nw < vw && vw <= w.MaxValPad {
   646  		if vw > w.MaxValPad {
   647  			vw = w.MaxValPad
   648  		}
   649  
   650  		w.pad[string(k)] = vw
   651  	}
   652  
   653  	return b, i
   654  }
   655  
   656  func (w *ConsoleWriter) ConvertValue(b, p []byte, st, ff int) (_ []byte, i int) {
   657  	tag, sub, i := w.d.Tag(p, st)
   658  
   659  	switch tag {
   660  	case tlwire.Int, tlwire.Neg:
   661  		var v uint64
   662  		v, i = w.d.Unsigned(p, st)
   663  
   664  		base := 10
   665  		if tag == tlwire.Neg {
   666  			b = append(b, '-')
   667  		}
   668  
   669  		if ff&cfHex != 0 {
   670  			b = append(b, "0x"...)
   671  			base = 16
   672  		}
   673  
   674  		b = strconv.AppendUint(b, v, base)
   675  	case tlwire.Bytes, tlwire.String:
   676  		var s []byte
   677  		s, i = w.d.Bytes(p, st)
   678  
   679  		if w.StringOnNewLineMinLen != 0 && len(s) >= w.StringOnNewLineMinLen {
   680  			b = append(b, '\\', '\n')
   681  
   682  			if tag == tlwire.Bytes {
   683  				h := hex.Dumper(noescapeByteWriter(&b))
   684  
   685  				_, _ = h.Write(s)
   686  				_ = h.Close()
   687  			} else {
   688  				b = append(b, s...)
   689  
   690  				if s[len(s)-1] != '\n' {
   691  					b = append(b, '\n')
   692  				}
   693  			}
   694  
   695  			break
   696  		}
   697  
   698  		if tag == tlwire.Bytes {
   699  			if w.BytesFormat != "" {
   700  				b = hfmt.Appendf(b, w.BytesFormat, s)
   701  				break
   702  			}
   703  
   704  			if ff&cfHex != 0 {
   705  				b = hfmt.Appendf(b, "%x", s)
   706  				break
   707  			}
   708  		}
   709  
   710  		quote := tag == tlwire.Bytes || w.QuoteAnyValue || len(s) == 0 && w.QuoteEmptyValue
   711  		if !quote {
   712  			for _, c := range s {
   713  				if c < 0x20 || c >= 0x80 {
   714  					quote = true
   715  					break
   716  				}
   717  				for _, q := range w.QuoteChars {
   718  					if byte(q) == c {
   719  						quote = true
   720  						break
   721  					}
   722  				}
   723  			}
   724  		}
   725  
   726  		if quote {
   727  			ss := low.UnsafeBytesToString(s)
   728  			b = strconv.AppendQuote(b, ss)
   729  		} else {
   730  			b = append(b, s...)
   731  		}
   732  	case tlwire.Array:
   733  		b = append(b, '[')
   734  
   735  		for el := 0; sub == -1 || el < int(sub); el++ {
   736  			if sub == -1 && w.d.Break(p, &i) {
   737  				break
   738  			}
   739  
   740  			if el != 0 {
   741  				b = append(b, ' ')
   742  			}
   743  
   744  			b, i = w.ConvertValue(b, p, i, ff)
   745  		}
   746  
   747  		b = append(b, ']')
   748  	case tlwire.Map:
   749  		b = append(b, '{')
   750  
   751  		for el := 0; sub == -1 || el < int(sub); el++ {
   752  			if sub == -1 && w.d.Break(p, &i) {
   753  				break
   754  			}
   755  
   756  			if el != 0 {
   757  				b = append(b, ' ')
   758  			}
   759  
   760  			b, i = w.ConvertValue(b, p, i, ff)
   761  
   762  			b = append(b, ':')
   763  
   764  			b, i = w.ConvertValue(b, p, i, ff)
   765  		}
   766  
   767  		b = append(b, '}')
   768  	case tlwire.Special:
   769  		switch sub {
   770  		case tlwire.False:
   771  			b = append(b, "false"...)
   772  		case tlwire.True:
   773  			b = append(b, "true"...)
   774  		case tlwire.Nil:
   775  			b = append(b, "<nil>"...)
   776  		case tlwire.Undefined:
   777  			b = append(b, "<undef>"...)
   778  		case tlwire.None:
   779  			// none
   780  		case tlwire.Hidden:
   781  			b = append(b, "<hidden>"...)
   782  		case tlwire.SelfRef:
   783  			b = append(b, "<self_ref>"...)
   784  		case tlwire.Float64, tlwire.Float32, tlwire.Float8:
   785  			var f float64
   786  			f, i = w.d.Float(p, st)
   787  
   788  			if w.FloatFormat != "" {
   789  				b = hfmt.Appendf(b, w.FloatFormat, f)
   790  			} else {
   791  				b = strconv.AppendFloat(b, f, w.FloatChar, w.FloatPrecision, 64)
   792  			}
   793  		default:
   794  			panic(sub)
   795  		}
   796  	case tlwire.Semantic:
   797  		switch sub {
   798  		case tlwire.Time:
   799  			var t time.Time
   800  			t, i = w.d.Time(p, st)
   801  
   802  			if w.TimeFormat == "" {
   803  				b = strconv.AppendInt(b, t.UnixNano(), 10)
   804  				break
   805  			}
   806  
   807  			if w.TimeLocation != nil {
   808  				t = t.In(w.TimeLocation)
   809  			}
   810  
   811  			b = t.AppendFormat(b, w.TimeFormat)
   812  		case tlwire.Duration:
   813  			var d time.Duration
   814  			d, i = w.d.Duration(p, st)
   815  
   816  			switch {
   817  			case w.DurationFormat != "" && w.DurationDiv != 0:
   818  				b = hfmt.Appendf(b, w.DurationFormat, float64(d/w.DurationDiv))
   819  			case w.DurationFormat != "":
   820  				b = hfmt.Appendf(b, w.DurationFormat, d)
   821  			default:
   822  				b = w.AppendDuration(b, d)
   823  			}
   824  		case WireID:
   825  			var id ID
   826  			i = id.TlogParse(p, st)
   827  
   828  			st := len(b)
   829  			b = append(b, "123456789_123456789_123456789_12"[:w.IDWidth]...)
   830  			id.FormatTo(b[st:], 'v')
   831  		case tlwire.Hex:
   832  			b, i = w.ConvertValue(b, p, i, ff|cfHex)
   833  		case tlwire.Caller:
   834  			var pc loc.PC
   835  			var pcs loc.PCs
   836  
   837  			pc, pcs, i = w.d.Callers(p, st)
   838  
   839  			if pcs == nil {
   840  				b = hfmt.Appendf(b, w.CallerFormat, pc)
   841  				break
   842  			}
   843  
   844  			b = append(b, '[')
   845  			for i, pc := range pcs {
   846  				if i != 0 {
   847  					b = append(b, ' ')
   848  				}
   849  
   850  				b = hfmt.Appendf(b, w.CallerFormat, pc)
   851  			}
   852  			b = append(b, ']')
   853  		default:
   854  			b, i = w.ConvertValue(b, p, i, ff)
   855  		}
   856  	default:
   857  		panic(tag)
   858  	}
   859  
   860  	return b, i
   861  }
   862  
   863  func (w *ConsoleWriter) AppendDuration(b []byte, d time.Duration) []byte {
   864  	if d == 0 {
   865  		return append(b, ' ', ' ', '0', 's')
   866  	}
   867  
   868  	var buf [32]byte
   869  
   870  	if d >= 99*time.Second {
   871  		const MaxGroups = 2
   872  		group := 0
   873  		i := 0
   874  
   875  		if d < 0 {
   876  			d = -d
   877  			b = append(b, '-')
   878  		}
   879  
   880  		add := func(d, unit time.Duration, suff byte) time.Duration {
   881  			if group == 0 && d < unit && unit > time.Second || group >= MaxGroups {
   882  				return d
   883  			}
   884  
   885  			x := int(d / unit)
   886  			d = d % unit
   887  			group++
   888  
   889  			if group == MaxGroups && d >= unit/2 {
   890  				x++
   891  			}
   892  
   893  			w := width(x)
   894  			i += w
   895  			for j := 1; j <= w; j++ {
   896  				buf[i-j] = byte(x%10) + '0'
   897  				x /= 10
   898  			}
   899  
   900  			buf[i] = suff
   901  			i++
   902  
   903  			return d
   904  		}
   905  
   906  		d = add(d, 24*time.Hour, 'd')
   907  		d = add(d, time.Hour, 'h')
   908  		d = add(d, time.Minute, 'm')
   909  		d = add(d, time.Second, 's')
   910  
   911  		return append(b, buf[:i]...)
   912  	}
   913  
   914  	neg := d < 0
   915  	if neg {
   916  		d = -d
   917  	}
   918  
   919  	end := len(buf) - 4
   920  	i := end
   921  
   922  	for d != 0 {
   923  		i--
   924  		buf[i] = byte(d%10) + '0'
   925  		d /= 10
   926  	}
   927  
   928  	buf[i-1] = '0' // leading zero for possible carry
   929  
   930  	j := i + 3
   931  	buf[j] += 5 // round
   932  
   933  	for j >= i-1 && buf[j] > '9' { // move carry
   934  		buf[j] = '0'
   935  		j--
   936  		buf[j]++
   937  	}
   938  
   939  	j = end
   940  	u := -1
   941  
   942  	for buf[j-2] != 0 || buf[j-1] == '1' { // find suitable units
   943  		j -= 3
   944  		u++
   945  	}
   946  
   947  	i = j // beginning
   948  
   949  	for buf[j] == '0' || buf[j] == 0 { // leading spaces
   950  		buf[j] = ' '
   951  		j++
   952  	}
   953  
   954  	digit := j
   955  
   956  	j += 3
   957  
   958  	// insert point
   959  	if j-1 > i+3 {
   960  		buf[j-1] = buf[j-2]
   961  	}
   962  	if j-2 > i+3 {
   963  		buf[j-2] = buf[j-3]
   964  	}
   965  
   966  	buf[i+3] = '.'
   967  
   968  	if j > end {
   969  		j = end
   970  	}
   971  
   972  	for j > i+3 && (buf[j-1] == '0' || buf[j-1] == '.') { // trailing zeros
   973  		j--
   974  	}
   975  
   976  	suff := []string{"ns", "µs", "ms", "s", "m"}
   977  	j += copy(buf[j:], suff[u])
   978  
   979  	if neg {
   980  		buf[digit-1] = '-'
   981  
   982  		if digit == i {
   983  			i--
   984  		}
   985  	}
   986  
   987  	return append(b, buf[i:j]...)
   988  }
   989  
   990  func width(n int) (w int) {
   991  	q := 10
   992  	w = 1
   993  
   994  	for q <= n {
   995  		w++
   996  		q *= 10
   997  	}
   998  
   999  	return w
  1000  }
  1001  
  1002  func Color(c ...int) (r []byte) {
  1003  	if len(c) == 0 {
  1004  		return nil
  1005  	}
  1006  
  1007  	r = append(r, '\x1b', '[')
  1008  
  1009  	for i, c := range c {
  1010  		if i != 0 {
  1011  			r = append(r, ';')
  1012  		}
  1013  
  1014  		switch {
  1015  		case c < 10:
  1016  			r = append(r, '0'+byte(c%10))
  1017  		case c < 100:
  1018  			r = append(r, '0'+byte(c/10), '0'+byte(c%10))
  1019  		default:
  1020  			r = append(r, '0'+byte(c/100), '0'+byte(c/10%10), '0'+byte(c%10))
  1021  		}
  1022  	}
  1023  
  1024  	r = append(r, 'm')
  1025  
  1026  	return r
  1027  }
  1028  
  1029  func common(x, y []byte) (n int) {
  1030  	for n < len(y) && x[n] == y[n] {
  1031  		n++
  1032  	}
  1033  
  1034  	return
  1035  }
  1036  
  1037  func noescapeByteWriter(b *[]byte) *low.Buf {
  1038  	//	return (*low.Buf)(b)
  1039  	return (*low.Buf)(noescape(unsafe.Pointer(b)))
  1040  }