New group logging
This commit is contained in:
1 parent
400837b41a
commit
cded4a0200
14 files changed
+324
-75
No files matched your search
@@ -15,7 +15,6 @@
|
||||
const saved = vscode.getState() || {};
|
||||
let entries = [], latest, appliedMessage, displayZone = 'Europe/Berlin', paused = false, selected = saved.selected, mode = saved.mode || 'graph', origin = saved.origin || saved.selected;
|
||||
const expanded = new Set(saved.expanded || []);
|
||||
const expandedInit = new Set(saved.expandedInit || []);
|
||||
const visibleEventGroups = new Map();
|
||||
const recentEvents = new Set();
|
||||
const recentApps = new Set();
|
||||
@@ -25,7 +24,7 @@
|
||||
const $ = id => document.getElementById(id);
|
||||
const node = (tag, text, cls) => { const e = document.createElement(tag); if (text !== undefined) e.textContent = text; if (cls) e.className = cls; return e; };
|
||||
const normalLevel = e => e.level === 'CRITICAL' ? 'ERROR' : e.level;
|
||||
function save() { vscode.setState({ selected, mode, origin, expanded: [...expanded], expandedInit: [...expandedInit], sort: $('sort').value, groupInit: $('group-init').checked }); }
|
||||
function save() { vscode.setState({ selected, mode, origin, expanded: [...expanded], sort: $('sort').value }); }
|
||||
function choose(id, fromList = true) { if (fromList) origin = id; selected = id; save(); renderApps(); renderDetail(); }
|
||||
function filtered() {
|
||||
const q = $('search').value.toLowerCase(), level = $('level').value, minutes = Number($('period').value);
|
||||
@@ -34,7 +33,7 @@
|
||||
&& (level === 'all' || normalLevel(e) === level)
|
||||
&& `${e.app} ${e.entity || ''} ${e.message}`.toLowerCase().includes(q));
|
||||
}
|
||||
const cleanMessage = e => e.message.replace(/ \[event=[\w-]+ trace=[\w-]+\]/g, '').replace(/ \[init=[\w-]+\]/g, '');
|
||||
const cleanMessage = e => e.message.replace(/ \[event=[\w-]+ trace=[\w-]+\]/g, '').replace(/ \[init=[\w-]+\]/g, '').replace(/ \[group=(?:initialization|smartcondition|value_selector):[a-f0-9]{12}\]/gi, '');
|
||||
const formatTime = (entry, includeYear = false) => {
|
||||
if (!Number.isFinite(entry.time)) return entry.timestamp;
|
||||
try {
|
||||
@@ -57,6 +56,22 @@
|
||||
return `${years} year${years === 1 ? '' : 's'} ago`;
|
||||
}
|
||||
function copy(text) { if (text) vscode.postMessage({ type: 'copy', text }); }
|
||||
function logMessages(items, cls) {
|
||||
const container = node('div', undefined, cls);
|
||||
items.forEach((entry, index) => {
|
||||
if (index) container.append(document.createTextNode('\n'));
|
||||
container.append(node('span', cleanMessage(entry), `log-message level-${normalLevel(entry)}`));
|
||||
});
|
||||
return container;
|
||||
}
|
||||
function blockTitle(items, groupKey) {
|
||||
const first = items[0];
|
||||
const kind = groupKey?.startsWith('initialization:') || first.initialization_id ? 'initialization' : items.find(entry => entry.group_kind)?.group_kind;
|
||||
const names = { initialization: 'Initialization', smartcondition: 'Smart condition', value_selector: 'Value selector' };
|
||||
const name = names[kind] || (items.length > 1 || first.trace_id ? 'Event' : 'Log');
|
||||
const label = kind === 'initialization' ? first.app : items.map(entry => entry.group_label || cleanMessage(entry).match(/^\[([^\]]+)\]/)?.[1]).find(Boolean) || first.entity || first.app;
|
||||
return node('div', `${name} · ${label}`, 'block-title');
|
||||
}
|
||||
function row(e) {
|
||||
const item = node('div', undefined, `row${selected === e.event_id ? ' selected' : ''}${recentEvents.has(e.event_id) ? ' recent-log' : ''}`);
|
||||
item.dataset.eventId = e.event_id; item.dataset.eventIds = e.event_id;
|
||||
@@ -67,23 +82,23 @@
|
||||
relative.title = `${formatTime(e, true)} · Exact: ${e.timestamp}`;
|
||||
meta.append(relative, node('span', e.level, `badge ${normalLevel(e)}`));
|
||||
if (e.cause_ids.length) meta.append(node('span', 'View causes ↗', 'link-hint'));
|
||||
item.append(meta, node('div', cleanMessage(e), 'row-message'));
|
||||
item.append(blockTitle([e]), meta, logMessages([e], 'row-message'));
|
||||
item.onclick = () => { if (!window.getSelection()?.toString()) choose(e.event_id); };
|
||||
item.onkeydown = event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); choose(e.event_id); } };
|
||||
return item;
|
||||
}
|
||||
function groupedByEvent(items) {
|
||||
function groupedByEvent(items, groupKey) {
|
||||
const groups = [];
|
||||
items.forEach(e => {
|
||||
const trace = e.kind === 'log' ? e.trace_id : undefined;
|
||||
const key = groupKey || (e.group_id ? `group:${e.group_id}` : e.kind === 'log' && e.trace_id ? `trace:${e.trace_id}` : undefined);
|
||||
const previous = groups[groups.length - 1];
|
||||
if (trace && previous?.trace === trace) previous.entries.push(e);
|
||||
else groups.push({ trace, entries: [e] });
|
||||
if (key && previous?.key === key) previous.entries.push(e);
|
||||
else groups.push({ key, entries: [e] });
|
||||
});
|
||||
return groups;
|
||||
}
|
||||
function appendEventRows(parent, items) {
|
||||
groupedByEvent(items).forEach(unit => {
|
||||
function appendEventRows(parent, items, groupKey) {
|
||||
groupedByEvent(items, groupKey).forEach(unit => {
|
||||
if (unit.entries.length === 1) { parent.append(row(unit.entries[0])); return; }
|
||||
const representative = [...unit.entries].reverse().find(e => e.cause_ids?.length) || unit.entries[unit.entries.length - 1];
|
||||
const ids = unit.entries.map(e => e.event_id);
|
||||
@@ -102,7 +117,7 @@
|
||||
if (count) meta.append(node('span', count === 1 ? level : `${level} ${count}`, `badge ${level}`));
|
||||
}
|
||||
if (unit.entries.some(e => e.cause_ids.length)) meta.append(node('span', 'View causes ↗', 'link-hint'));
|
||||
item.append(meta, node('div', unit.entries.map(cleanMessage).join('\n'), 'row-message'));
|
||||
item.append(blockTitle(unit.entries, groupKey), meta, logMessages(unit.entries, 'row-message'));
|
||||
item.onclick = () => { if (!window.getSelection()?.toString()) choose(representative.event_id); };
|
||||
item.onkeydown = event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); choose(representative.event_id); } };
|
||||
parent.append(item);
|
||||
@@ -145,14 +160,14 @@
|
||||
const rows = node('div', undefined, 'rows');
|
||||
const units = [], initGroups = new Map();
|
||||
logs.forEach(e => {
|
||||
if ($('group-init').checked && e.initialization_id) {
|
||||
if (e.initialization_id) {
|
||||
if (!initGroups.has(e.initialization_id)) { const unit = { id: e.initialization_id, entries: [] }; initGroups.set(e.initialization_id, unit); units.push(unit); }
|
||||
initGroups.get(e.initialization_id).entries.push(e);
|
||||
} else {
|
||||
const trace = e.kind === 'log' ? e.trace_id : undefined;
|
||||
const key = e.group_id ? `group:${e.group_id}` : e.kind === 'log' && e.trace_id ? `trace:${e.trace_id}` : undefined;
|
||||
const previous = units[units.length - 1];
|
||||
if (trace && previous && !previous.id && previous.trace === trace) previous.entries.push(e);
|
||||
else units.push({ trace, entries: [e] });
|
||||
if (key && previous && !previous.id && previous.key === key) previous.entries.push(e);
|
||||
else units.push({ key, entries: [e] });
|
||||
}
|
||||
});
|
||||
const startTime = unit => unit.id
|
||||
@@ -160,25 +175,7 @@
|
||||
: unit.entries[0].time;
|
||||
units.sort((a, b) => startTime(a) - startTime(b));
|
||||
units.slice(-150).forEach(unit => {
|
||||
if (!unit.id) { appendEventRows(rows, unit.entries); return; }
|
||||
const group = node('details', undefined, 'init-group'), key = app + ':' + unit.id;
|
||||
group.open = expandedInit.has(key);
|
||||
const first = entries.find(e => e.app === app && e.initialization_id === unit.id && e.kind === 'initialization_started') || unit.entries[0], last = unit.entries[unit.entries.length - 1];
|
||||
const header = node('summary', undefined, 'init-summary');
|
||||
const failed = unit.entries.some(e => normalLevel(e) === 'ERROR' || e.status === 'failed');
|
||||
header.append(node('span', `Initialization · ${formatTime(first, true)}`, 'name'), node('span', `${unit.entries.length} entries`));
|
||||
if (failed) header.append(node('span', 'ERROR', 'badge ERROR'));
|
||||
header.title = `Initialization ${unit.id} · Last entry: ${formatTime(last, true)} · Exact: ${last.timestamp}`;
|
||||
group.append(header);
|
||||
const body = node('div', undefined, 'init-rows');
|
||||
const copyGroup = node('button', 'Copy initialization');
|
||||
copyGroup.onclick = () => copy(unit.entries.map(copyText).join('\n'));
|
||||
body.append(copyGroup);
|
||||
appendEventRows(body, unit.entries.slice(-150));
|
||||
if (unit.entries.length > 150) body.append(node('div', 'Showing the last 150 entries. Copy initialization includes all loaded matches.', 'more'));
|
||||
group.append(body);
|
||||
group.addEventListener('toggle', event => { event.stopPropagation(); group.open ? expandedInit.add(key) : expandedInit.delete(key); save(); });
|
||||
rows.append(group);
|
||||
appendEventRows(rows, unit.entries, unit.id ? `initialization:${unit.id}` : undefined);
|
||||
});
|
||||
if (units.length > 150) rows.append(node('div', `${units.length - 150} older rows/groups: narrow the time range or search.`, 'more'));
|
||||
detail.append(rows);
|
||||
@@ -194,7 +191,7 @@
|
||||
$('graph-mode').parentElement.hidden = !e || !e.cause_ids.length;
|
||||
if (!e) { container.replaceChildren(node('div', selected ? 'This entry is no longer in the loaded history.' : 'Select a log to view its details.', 'empty')); return; }
|
||||
const selectedGroup = visibleEventGroups.get(e.event_id) || [e];
|
||||
container.replaceChildren(node('div', selectedGroup.map(cleanMessage).join('\n'), 'selected-message'), node('div', `${e.app} · ${e.timestamp} · ${selectedGroup.length} log lines`, 'metadata'));
|
||||
container.replaceChildren(logMessages(selectedGroup, 'selected-message'), node('div', `${e.app} · ${e.timestamp} · ${selectedGroup.length} log lines`, 'metadata'));
|
||||
const raw = node('button', 'Open raw log'); raw.onclick = () => vscode.postMessage({ type: 'open', id: e.event_id }); container.append(raw);
|
||||
if (e.source) { const source = node('button', 'Open source'); source.onclick = () => vscode.postMessage({ type: 'source', id: e.event_id }); container.append(source); }
|
||||
if (origin && origin !== selected) { const back = node('button', 'Back to selected log'); back.onclick = () => choose(origin); container.append(back); }
|
||||
@@ -344,13 +341,12 @@
|
||||
}
|
||||
});
|
||||
$('copy').onclick = () => { const e = entries.find(e => e.event_id === selected); copy(window.getSelection()?.toString() || (e ? (visibleEventGroups.get(e.event_id) || [e]).map(copyText).join('\n') : '')); };
|
||||
['search', 'period', 'level', 'group-init', 'sort'].forEach(id => $(id).addEventListener('input', () => { save(); renderApps(); }));
|
||||
['search', 'period', 'level', 'sort'].forEach(id => $(id).addEventListener('input', () => { save(); renderApps(); }));
|
||||
$('pause').onclick = () => { paused = !paused; $('pause').textContent = paused ? 'Resume' : 'Pause'; if (!paused && latest) apply(latest); else if (paused) $('status').textContent = 'Display paused · New events are still being collected'; };
|
||||
$('import').onclick = () => vscode.postMessage({ type: 'import' });
|
||||
$('graph-mode').onclick = () => { mode = 'graph'; save(); renderDetail(); };
|
||||
$('tree-mode').onclick = () => { mode = 'tree'; save(); renderDetail(); };
|
||||
$('sort').value = saved.sort || 'latest';
|
||||
$('group-init').checked = saved.groupInit !== false;
|
||||
vscode.postMessage({ type: 'ready' });
|
||||
if (window.LOG_REVIEW_DATA) { apply({ timezone: 'Europe/Berlin', entries: window.LOG_REVIEW_DATA, warnings: ['Interface preview: sample data, no AppDaemon connection.'], limit: 10000 }); expanded.add('automation'); selected = 'review-5'; renderApps(); renderDetail(); }
|
||||
})();
|
||||
Reference in new issue
Block a user