github.com/nikandfor/tlog@v0.21.3/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.None:
   771  			// none
   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.Float64, tlwire.Float32, tlwire.Float8:
   781  			var f float64
   782  			f, i = w.d.Float(p, st)
   783  
   784  			if w.FloatFormat != "" {
   785  				b = hfmt.Appendf(b, w.FloatFormat, f)
   786  			} else {
   787  				b = strconv.AppendFloat(b, f, w.FloatChar, w.FloatPrecision, 64)
   788  			}
   789  		default:
   790  			panic(sub)
   791  		}
   792  	case tlwire.Semantic:
   793  		switch sub {
   794  		case tlwire.Time:
   795  			var t time.Time
   796  			t, i = w.d.Time(p, st)
   797  
   798  			if w.TimeFormat == "" {
   799  				b = strconv.AppendInt(b, t.UnixNano(), 10)
   800  				break
   801  			}
   802  
   803  			if w.TimeLocation != nil {
   804  				t = t.In(w.TimeLocation)
   805  			}
   806  
   807  			b = t.AppendFormat(b, w.TimeFormat)
   808  		case tlwire.Duration:
   809  			var d time.Duration
   810  			d, i = w.d.Duration(p, st)
   811  
   812  			switch {
   813  			case w.DurationFormat != "" && w.DurationDiv != 0:
   814  				b = hfmt.Appendf(b, w.DurationFormat, float64(d/w.DurationDiv))
   815  			case w.DurationFormat != "":
   816  				b = hfmt.Appendf(b, w.DurationFormat, d)
   817  			default:
   818  				b = w.AppendDuration(b, d)
   819  			}
   820  		case WireID:
   821  			var id ID
   822  			i = id.TlogParse(p, st)
   823  
   824  			st := len(b)
   825  			b = append(b, "123456789_123456789_123456789_12"[:w.IDWidth]...)
   826  			id.FormatTo(b[st:], 'v')
   827  		case tlwire.Hex:
   828  			b, i = w.ConvertValue(b, p, i, ff|cfHex)
   829  		case tlwire.Caller:
   830  			var pc loc.PC
   831  			var pcs loc.PCs
   832  
   833  			pc, pcs, i = w.d.Callers(p, st)
   834  
   835  			if pcs == nil {
   836  				b = hfmt.Appendf(b, w.CallerFormat, pc)
   837  				break
   838  			}
   839  
   840  			b = append(b, '[')
   841  			for i, pc := range pcs {
   842  				if i != 0 {
   843  					b = append(b, ' ')
   844  				}
   845  
   846  				b = hfmt.Appendf(b, w.CallerFormat, pc)
   847  			}
   848  			b = append(b, ']')
   849  		default:
   850  			b, i = w.ConvertValue(b, p, i, ff)
   851  		}
   852  	default:
   853  		panic(tag)
   854  	}
   855  
   856  	return b, i
   857  }
   858  
   859  func (w *ConsoleWriter) AppendDuration(b []byte, d time.Duration) []byte {
   860  	if d == 0 {
   861  		return append(b, ' ', ' ', '0', 's')
   862  	}
   863  
   864  	var buf [32]byte
   865  
   866  	if d >= 99*time.Second {
   867  		const MaxGroups = 2
   868  		group := 0
   869  		i := 0
   870  
   871  		if d < 0 {
   872  			d = -d
   873  			b = append(b, '-')
   874  		}
   875  
   876  		add := func(d, unit time.Duration, suff byte) time.Duration {
   877  			if group == 0 && d < unit && unit > time.Second || group >= MaxGroups {
   878  				return d
   879  			}
   880  
   881  			x := int(d / unit)
   882  			d = d % unit
   883  			group++
   884  
   885  			if group == MaxGroups && d >= unit/2 {
   886  				x++
   887  			}
   888  
   889  			w := width(x)
   890  			i += w
   891  			for j := 1; j <= w; j++ {
   892  				buf[i-j] = byte(x%10) + '0'
   893  				x /= 10
   894  			}
   895  
   896  			buf[i] = suff
   897  			i++
   898  
   899  			return d
   900  		}
   901  
   902  		d = add(d, 24*time.Hour, 'd')
   903  		d = add(d, time.Hour, 'h')
   904  		d = add(d, time.Minute, 'm')
   905  		d = add(d, time.Second, 's')
   906  
   907  		return append(b, buf[:i]...)
   908  	}
   909  
   910  	neg := d < 0
   911  	if neg {
   912  		d = -d
   913  	}
   914  
   915  	end := len(buf) - 4
   916  	i := end
   917  
   918  	for d != 0 {
   919  		i--
   920  		buf[i] = byte(d%10) + '0'
   921  		d /= 10
   922  	}
   923  
   924  	buf[i-1] = '0' // leading zero for possible carry
   925  
   926  	j := i + 3
   927  	buf[j] += 5 // round
   928  
   929  	for j >= i-1 && buf[j] > '9' { // move carry
   930  		buf[j] = '0'
   931  		j--
   932  		buf[j]++
   933  	}
   934  
   935  	j = end
   936  	u := -1
   937  
   938  	for buf[j-2] != 0 || buf[j-1] == '1' { // find suitable units
   939  		j -= 3
   940  		u++
   941  	}
   942  
   943  	i = j // beginning
   944  
   945  	for buf[j] == '0' || buf[j] == 0 { // leading spaces
   946  		buf[j] = ' '
   947  		j++
   948  	}
   949  
   950  	digit := j
   951  
   952  	j += 3
   953  
   954  	// insert point
   955  	if j-1 > i+3 {
   956  		buf[j-1] = buf[j-2]
   957  	}
   958  	if j-2 > i+3 {
   959  		buf[j-2] = buf[j-3]
   960  	}
   961  
   962  	buf[i+3] = '.'
   963  
   964  	if j > end {
   965  		j = end
   966  	}
   967  
   968  	for j > i+3 && (buf[j-1] == '0' || buf[j-1] == '.') { // trailing zeros
   969  		j--
   970  	}
   971  
   972  	suff := []string{"ns", "µs", "ms", "s", "m"}
   973  	j += copy(buf[j:], suff[u])
   974  
   975  	if neg {
   976  		buf[digit-1] = '-'
   977  
   978  		if digit == i {
   979  			i--
   980  		}
   981  	}
   982  
   983  	return append(b, buf[i:j]...)
   984  }
   985  
   986  func width(n int) (w int) {
   987  	q := 10
   988  	w = 1
   989  
   990  	for q <= n {
   991  		w++
   992  		q *= 10
   993  	}
   994  
   995  	return w
   996  }
   997  
   998  func Color(c ...int) (r []byte) {
   999  	if len(c) == 0 {
  1000  		return nil
  1001  	}
  1002  
  1003  	r = append(r, '\x1b', '[')
  1004  
  1005  	for i, c := range c {
  1006  		if i != 0 {
  1007  			r = append(r, ';')
  1008  		}
  1009  
  1010  		switch {
  1011  		case c < 10:
  1012  			r = append(r, '0'+byte(c%10))
  1013  		case c < 100:
  1014  			r = append(r, '0'+byte(c/10), '0'+byte(c%10))
  1015  		default:
  1016  			r = append(r, '0'+byte(c/100), '0'+byte(c/10%10), '0'+byte(c%10))
  1017  		}
  1018  	}
  1019  
  1020  	r = append(r, 'm')
  1021  
  1022  	return r
  1023  }
  1024  
  1025  func common(x, y []byte) (n int) {
  1026  	for n < len(y) && x[n] == y[n] {
  1027  		n++
  1028  	}
  1029  
  1030  	return
  1031  }
  1032  
  1033  func noescapeByteWriter(b *[]byte) *low.Buf {
  1034  	//	return (*low.Buf)(b)
  1035  	return (*low.Buf)(noescape(unsafe.Pointer(b)))
  1036  }