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