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 }