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

     1  package convert
     2  
     3  import (
     4  	_ "embed"
     5  	"errors"
     6  	"io"
     7  	"time"
     8  
     9  	"github.com/nikandfor/hacked/hfmt"
    10  	"github.com/nikandfor/loc"
    11  	"github.com/nikandfor/tlog"
    12  	"github.com/nikandfor/tlog/tlio"
    13  	"github.com/nikandfor/tlog/tlwire"
    14  )
    15  
    16  type (
    17  	Web struct {
    18  		io.Writer
    19  
    20  		EventTimeFormat string
    21  		TimeFormat      string
    22  
    23  		PickTime    bool
    24  		PickCaller  bool
    25  		PickMessage bool
    26  
    27  		d tlwire.Decoder
    28  		l Logfmt
    29  		j JSON
    30  		c *tlog.ConsoleWriter
    31  
    32  		s []tlog.ID
    33  		m []byte
    34  
    35  		time, last []byte
    36  
    37  		bb, b, ls []byte
    38  	}
    39  )
    40  
    41  //go:embed webstyles.css
    42  var webstyles []byte
    43  
    44  func NewWeb(w io.Writer) *Web {
    45  	ww := &Web{
    46  		Writer:          w,
    47  		EventTimeFormat: "2006-01-02 15:04:05.000",
    48  		TimeFormat:      "2006-01-02 15:04:05.000",
    49  		PickTime:        true,
    50  		//PickCaller:      true,
    51  		PickMessage: true,
    52  
    53  		c: tlog.NewConsoleWriter(nil, 0),
    54  	}
    55  
    56  	ww.c.Colorize = false
    57  
    58  	return ww
    59  }
    60  
    61  func (w *Web) Write(p []byte) (i int, err error) {
    62  	if w.last == nil {
    63  		w.bb = w.buildHeader(w.bb[:0])
    64  	}
    65  
    66  more:
    67  	tag, els, i := w.d.Tag(p, i)
    68  	if tag != tlwire.Map {
    69  		return i, errors.New("map expected")
    70  	}
    71  
    72  	var t time.Time
    73  	var c loc.PC
    74  	var ek tlog.EventKind
    75  	var m []byte
    76  
    77  	var k []byte
    78  	var sub int64
    79  
    80  	for el := 0; els == -1 || el < int(els); el++ {
    81  		if els == -1 && w.d.Break(p, &i) {
    82  			break
    83  		}
    84  
    85  		k, i = w.d.Bytes(p, i)
    86  		if len(k) == 0 {
    87  			return 0, errors.New("empty key")
    88  		}
    89  
    90  		st := i
    91  
    92  		tag, sub, i = w.d.Tag(p, i)
    93  		if tag != tlwire.Semantic {
    94  			w.b, i = w.appendPair(w.b, p, k, st)
    95  			continue
    96  		}
    97  
    98  		switch {
    99  		case w.PickTime && sub == tlwire.Time && string(k) == tlog.KeyTimestamp:
   100  			t, i = w.d.Time(p, st)
   101  		case w.PickCaller && sub == tlwire.Caller && string(k) == tlog.KeyCaller && c == 0:
   102  			c, i = w.d.Caller(p, st)
   103  		case w.PickMessage && sub == tlog.WireMessage && string(k) == tlog.KeyMessage:
   104  			m, i = w.d.Bytes(p, i)
   105  		case sub == tlog.WireID:
   106  			var id tlog.ID
   107  			_ = id.TlogParse(p, st)
   108  
   109  			w.s = append(w.s, id)
   110  			w.b, i = w.appendPair(w.b, p, k, st)
   111  		case sub == tlog.WireEventKind && string(k) == tlog.KeyEventKind:
   112  			_ = ek.TlogParse(p, st)
   113  
   114  			w.b, i = w.appendPair(w.b, p, k, st)
   115  		case sub == tlog.WireLabel:
   116  			w.ls, i = w.appendPair(w.ls, p, k, st)
   117  		default:
   118  			w.b, i = w.appendPair(w.b, p, k, st)
   119  		}
   120  	}
   121  
   122  	w.bb = w.buildEvent(w.bb, t, c, m)
   123  
   124  	w.s = w.s[:0]
   125  	w.b = w.b[:0]
   126  	w.ls = w.ls[:0]
   127  
   128  	if i < len(p) {
   129  		goto more
   130  	}
   131  
   132  	bb := w.bb
   133  	w.bb = w.bb[:0]
   134  
   135  	_, err = w.Writer.Write(bb)
   136  	if err != nil {
   137  		return 0, err
   138  	}
   139  
   140  	return len(p), nil
   141  }
   142  
   143  func (w *Web) Close() error {
   144  	w.bb = w.buildFooter(w.bb[:0])
   145  	_, err := w.Writer.Write(w.bb)
   146  
   147  	e := tlio.Close(w.Writer)
   148  	if err == nil {
   149  		err = e
   150  	}
   151  
   152  	return err
   153  }
   154  
   155  func (w *Web) buildHeader(b []byte) []byte {
   156  	b = append(b, `<html>
   157  <head>
   158  </head>
   159  <style>`...)
   160  
   161  	b = append(b, webstyles...)
   162  
   163  	b = append(b, `</style>
   164  <body>
   165  <table class=events>
   166  `...)
   167  
   168  	return b
   169  }
   170  
   171  func (w *Web) buildFooter(b []byte) []byte {
   172  	b = append(b, `</table>
   173  </body>
   174  </html>
   175  `...)
   176  
   177  	return b
   178  }
   179  
   180  func (w *Web) buildEvent(b []byte, t time.Time, c loc.PC, m []byte) []byte {
   181  	b = append(b, `<tr class="event`...)
   182  
   183  	for _, s := range w.s {
   184  		b = hfmt.Appendf(b, " id%08v", s)
   185  	}
   186  
   187  	b = append(b, "\">\n"...)
   188  
   189  	if w.PickTime {
   190  		b = w.buildTime(b, t)
   191  	}
   192  
   193  	if w.PickCaller {
   194  		b = hfmt.Appendf(b, "<td class=caller>%s</td>\n", c.String())
   195  	}
   196  
   197  	if w.PickMessage {
   198  		b = hfmt.Appendf(b, "<td class=msg>%s</td>\n", m)
   199  	}
   200  
   201  	b = hfmt.Appendf(b, "<td class=kvs>%s</td>\n", w.b)
   202  
   203  	b = append(b, "</tr>\n"...)
   204  
   205  	return b
   206  }
   207  
   208  func (w *Web) buildTime(b []byte, t time.Time) []byte {
   209  	w.time = t.AppendFormat(w.time[:0], w.TimeFormat)
   210  	c := common(w.time, w.last)
   211  
   212  	b = append(b, "<td class=ev-time>"...)
   213  
   214  	if c != 0 {
   215  		b = hfmt.Appendf(b, `<span class=ev-time-pref>%s</span>`, w.time[:c])
   216  	}
   217  	if c != len(w.time) {
   218  		b = hfmt.Appendf(b, `<span class=ev-time-suff>%s</span>`, w.time[c:])
   219  	}
   220  
   221  	b = append(b, "</td>\n"...)
   222  
   223  	w.last, w.time = w.time, w.last
   224  
   225  	return b
   226  }
   227  
   228  func (w *Web) appendPair(b, p, k []byte, st int) (_ []byte, i int) {
   229  	b = hfmt.Appendf(b, `<div class=kv><span class=key>%s=</span><div class=val>`, k)
   230  
   231  	b, i = w.c.ConvertValue(b, p, st, 0)
   232  
   233  	b = append(b, "</div></div>\n"...)
   234  
   235  	return b, i
   236  }
   237  
   238  func common(a, b []byte) (n int) {
   239  	for n < len(b) && a[n] == b[n] {
   240  		n++
   241  	}
   242  
   243  	return
   244  }