Skip to content

Commit b4d0da5

Browse files
ddustinrustyrussell
authored andcommitted
logs: A basic javascript log viewer
A basic javascript tool for filtering through large CLN log files. Changelog-Added: A new tool for rendering CLN log files in the browser.
1 parent cc0f66f commit b4d0da5

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed

contrib/log_visualizer.html

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
<html>
2+
<head>
3+
<script>
4+
function cssClean(str)
5+
{
6+
return str.replaceAll(/[^A-Za-z0-9]/g, "_")
7+
}
8+
function firstPubkey(str)
9+
{
10+
if (!str)
11+
return null;
12+
13+
var res = str.match(/0[23][0-9a-fA-F]{64}/)
14+
15+
return res ? res.at(0) : null;
16+
}
17+
function parseDaemon(daemon)
18+
{
19+
var res = {};
20+
21+
var items = daemon.split('-');
22+
23+
if (daemon == "plugin-manager")
24+
items = ["plugin manager"];
25+
26+
res.str = daemon;
27+
res.name = items.pop();
28+
res.peer = firstPubkey(items[0]);
29+
res.short_peer = null;
30+
31+
if (res.peer) {
32+
items.shift();
33+
res.short_peer = res.peer.substring(0, 4);
34+
}
35+
36+
res.parents = items;
37+
38+
if(res.name.startsWith("chan#"))
39+
if(res.parents.length)
40+
res.name = res.parents.pop() + " " + res.name;
41+
else
42+
res.name = "lightningd " + res.name;
43+
44+
return res;
45+
}
46+
function parseLogLine(line)
47+
{
48+
var res = {};
49+
50+
function eat_upto(re)
51+
{
52+
details = line.match(re);
53+
if(!details)
54+
return null;
55+
value = line.substring(0, details.index);
56+
line = line.substring(details.index + details[0].length);
57+
return value;
58+
}
59+
60+
res.date = new Date(eat_upto(/\s+/));
61+
res.type = eat_upto(/\s+/);
62+
res.daemon = parseDaemon(eat_upto(/: /));
63+
res.msg = line;
64+
65+
return res;
66+
}
67+
function parseUs(lineInfo)
68+
{
69+
return {
70+
pubkey: firstPubkey(lineInfo.msg),
71+
alias: (lineInfo.msg.match(/, alias (.*) \(color /) || []).at(1),
72+
color: (lineInfo.msg.match(/ \(color ([#][0-9a-fA-F]{6})\)/) || []).at(1),
73+
lightngind: (lineInfo.msg.match(/\) and lightningd ([0-9a-fA-F]+)/) || []).at(1),
74+
}
75+
}
76+
function toggle_type()
77+
{
78+
var sheet = document.getElementById('logStyleSheet').sheet;
79+
var rule = "";
80+
let selector = ".type-" + cssClean(this.type_str);
81+
if(this.className == 'type_on') {
82+
this.className = 'type_off';
83+
rule = "display:none";
84+
}
85+
else {
86+
this.className = 'type_on';
87+
}
88+
89+
for(let i = 0; i < sheet.cssRules.length; i++) {
90+
if (sheet.cssRules[i].selectorText != selector)
91+
continue;
92+
sheet.deleteRule(i);
93+
sheet.insertRule(selector + " {" + rule + "}", i);
94+
break;
95+
}
96+
}
97+
function togggle_all_type()
98+
{
99+
for (b of this.buttons)
100+
if(b.className == this.className)
101+
b.click();
102+
103+
if(this.className == 'type_on')
104+
this.className = 'type_off';
105+
else
106+
this.className = 'type_on';
107+
}
108+
function toggle_daemon()
109+
{
110+
var sheet = document.getElementById('logStyleSheet').sheet;
111+
var rule = "";
112+
let selector = ".daemon-" + cssClean(this.daemon.name);
113+
if(this.className == 'daemon_on') {
114+
this.className = 'daemon_off';
115+
rule = "display:none";
116+
}
117+
else {
118+
this.className = 'daemon_on';
119+
}
120+
121+
for(let i = 0; i < sheet.cssRules.length; i++) {
122+
if (sheet.cssRules[i].selectorText != selector)
123+
continue;
124+
sheet.deleteRule(i);
125+
sheet.insertRule(selector + " {" + rule + "}", i);
126+
break;
127+
}
128+
}
129+
function togggle_all_daemon()
130+
{
131+
for (b of this.buttons)
132+
if(b.className == this.className)
133+
b.click();
134+
135+
if(this.className == 'daemon_on')
136+
this.className = 'daemon_off';
137+
else
138+
this.className = 'daemon_on';
139+
}
140+
function filter_messages()
141+
{
142+
lines = document.getElementsByClassName("logLine");
143+
144+
try {
145+
for(line of lines) {
146+
if(line.info.msg.match(this.value))
147+
line.style.display = "";
148+
else
149+
line.style.display = "none";
150+
}
151+
document.getElementById('filter_error').innerText = "";
152+
}
153+
catch (error) {
154+
document.getElementById('filter_error').innerText = error.message;
155+
}
156+
}
157+
function do_render(logs, area)
158+
{
159+
var d = document;
160+
var sheet = d.getElementById('logStyleSheet').sheet;
161+
var types = {};
162+
var daemons = {};
163+
var us = null;
164+
165+
while(area.firstChild)
166+
area.removeChild(area.firstChild);
167+
168+
while(sheet.cssRules.length)
169+
sheet.deleteRule(0);
170+
171+
for(line of logs.split("\n")) {
172+
line = line.trim()
173+
if(!line.length)
174+
continue;
175+
176+
info = parseLogLine(line);
177+
178+
if(info.msg.startsWith('Server started with public key'))
179+
us = parseUs(info);
180+
181+
p = d.createElement('p');
182+
p.info = info;
183+
p.className = "logLine type-" + cssClean(info.type) + " daemon-" + cssClean(info.daemon.name);
184+
185+
if (info.daemon.peer)
186+
p.className += " peer-" + info.daemon.peer;
187+
188+
types[info.type] = (types[info.type] || 0) + 1
189+
daemons[info.daemon.str] = (daemons[info.daemon.str] || 0) + 1
190+
191+
var s = d.createElement('span');
192+
s.className = "daemon"
193+
s.title = info.daemon.parents;
194+
s.innerText = info.daemon.name;
195+
if (info.daemon.short_peer)
196+
s.innerText += " " + info.daemon.short_peer;
197+
s.innerText += " ";
198+
p.appendChild(s);
199+
200+
var s = d.createElement('span');
201+
s.className = "logMsg";
202+
s.title = info.date;
203+
s.innerText = info.msg;
204+
p.appendChild(s);
205+
206+
area.appendChild(p);
207+
}
208+
209+
types = Object.fromEntries(
210+
Object.entries(types).sort(([,a],[,b]) => b-a)
211+
);
212+
213+
daemons = Object.fromEntries(
214+
Object.entries(daemons).sort(([,a],[,b]) => b-a)
215+
);
216+
217+
var controls = d.createElement('p');
218+
controls.className = "controls";
219+
var type_buttons = [];
220+
var daemon_buttons = [];
221+
222+
for(type in types) {
223+
var b = d.createElement('button');
224+
b.className = 'type_on';
225+
b.type_str = type;
226+
b.innerText = type + " " + types[type];
227+
b.onclick = toggle_type;
228+
type_buttons.push(b);
229+
controls.appendChild(b);
230+
231+
sheet.insertRule(".type-" + cssClean(type) + "{}", 0);
232+
}
233+
234+
var b = d.createElement('button');
235+
b.className = 'type_on';
236+
b.innerText = "All"
237+
b.onclick = togggle_all_type;
238+
b.buttons = type_buttons;
239+
controls.appendChild(b);
240+
241+
controls.appendChild(d.createElement('hr'));
242+
243+
for(daemon_str in daemons) {
244+
var daemon = parseDaemon(daemon_str)
245+
var b = d.createElement('button');
246+
b.className = 'daemon_on';
247+
b.daemon = daemon;
248+
b.innerText = (daemon.short_peer || "") + " " + daemon.name + " " + daemons[daemon_str];
249+
b.onclick = toggle_daemon;
250+
daemon_buttons.push(b);
251+
controls.appendChild(b);
252+
253+
sheet.insertRule(".daemon-" + cssClean(daemon.name) + "{}", 0);
254+
}
255+
256+
var b = d.createElement('button');
257+
b.className = 'daemon_on';
258+
b.innerText = "All"
259+
b.onclick = togggle_all_daemon;
260+
b.buttons = daemon_buttons;
261+
controls.appendChild(b);
262+
263+
controls.appendChild(d.createElement('hr'));
264+
265+
266+
var t = d.createElement('input');
267+
t.type = 'text';
268+
t.placeholder = 'message filter regex';
269+
t.onchange = filter_messages;
270+
t.onkeyup = filter_messages;
271+
272+
controls.appendChild(t);
273+
controls.appendChild(document.createTextNode(" "));
274+
275+
var s = d.createElement('span');
276+
s.id = 'filter_error';
277+
controls.appendChild(s);
278+
279+
area.prepend(controls);
280+
}
281+
</script>
282+
<style id="logStyleSheet">
283+
</style>
284+
<style>
285+
.logLine {
286+
font-family: Cascadia, Hack, monospace;
287+
}
288+
.type_on:after, .daemon_on:after {
289+
content: " (on)";
290+
}
291+
.type_off:after, .daemon_off:after {
292+
content: " (off)";
293+
}
294+
.type_on, .daemon_on {
295+
}
296+
.type_off, .daemon_off {
297+
background-color: #FF6961;
298+
}
299+
.controls {
300+
position: sticky;
301+
top: 0;
302+
background-color: #aaa9;
303+
padding: 1em;
304+
}
305+
.logLine .daemon {
306+
color: #AEC6CF;
307+
}
308+
.type-IO:before {
309+
content: "io ";
310+
}
311+
.type-TRACE:before {
312+
content: "trace ";
313+
}
314+
.type-DEBUG:before {
315+
content: "debug ";
316+
color: #Ffd1dc;
317+
}
318+
.type-INFO:before {
319+
content: "info ";
320+
color: #AEC6CF;
321+
}
322+
.type-UNUSUAL:before {
323+
content: "unusual ";
324+
color: #E9D502;
325+
}
326+
.type-BROKEN:before {
327+
content: "broken ";
328+
color: #FF6961;
329+
}
330+
</style>
331+
</head>
332+
<body>
333+
<div id="area">
334+
<h3>Enter Logs:</h3>
335+
<textarea id="logs"></textarea>
336+
<button onclick="do_render(document.getElementById('logs').value, document.getElementById('area'))">Render</button>
337+
</div>
338+
</body>
339+
</html>

0 commit comments

Comments
 (0)