-
-
-
-
- | Time |
- Level |
- Message |
-
-
-
-
+
+
+
+
No log file loaded
+
Drag & drop a log file anywhere on the page or click below to browse
+
+
+
+
+
+
+
+
+ | Time |
+ Level |
+ Message |
+
+
+
+
+
+
+
+
+
+ 0 entries
+ ·
+ 0 total
+ ·
+ no file
-
-
-
0 entries
-
·
-
0 total
-
·
-
no file
+
@@ -804,6 +862,7 @@
let expandedRows = new Set();
let chart = null;
let chartBuckets = [];
+ const msg_len_limit = 100;
// ---- Demo data generator ----
function generateDemoLogs() {
@@ -864,7 +923,7 @@
];
let i = 0;
- debugger
+ //debugger
while (i < lines.length) {
const line = lines[i].trim();
let matched = false;
@@ -899,7 +958,14 @@
j++;
}
i = j - 1;
- logs.push({ time, level: levelStr, message: msg.trim(), props: null, exception: exception || null });
+ logs.push({
+ time,
+ level: levelStr,
+ message: msg.length > msg_len_limit ? msg.slice(0, msg_len_limit) + "..." : msg.trim(),
+ fullmessage: msg.length > msg_len_limit ? msg : null,
+ props: null,
+ exception: exception || null
+ });
matched = true;
break;
}
@@ -932,7 +998,12 @@
const usedKeys = new Set([...timeKeys, ...levelKeys, ...msgKeys].filter(k => obj[k] !== undefined));
const props = Object.fromEntries(Object.entries(obj).filter(([k]) => !usedKeys.has(k)));
const exception = obj.exception || obj.error || obj.stack || null;
- logs.push({ time, level: ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'].includes(level) ? level : 'INFO', message: String(msgVal), props: Object.keys(props).length ? props : null, exception: exception ? String(exception) : null });
+ logs.push({
+ time,
+ level: ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'].includes(level) ? level : 'INFO',
+ message: String(msgVal), props: Object.keys(props).length ? props : null,
+ exception: exception ? String(exception) : null
+ });
} catch { }
}
return logs;
@@ -1016,7 +1087,39 @@
x: {
type: 'category',
stacked: true,
- ticks: { display: false },
+ ticks: {
+ // Grouping: Limit the number of labels shown to prevent overlapping
+ maxTicksLimit: 15,
+ // Formatting
+ // callback: function (val, index) {
+ // // Assuming your data labels are Date objects or Date strings
+ // const date = new Date(this.getLabelForValue(val));
+
+ // // Returns "HH:mm:ss" in local time
+ // return date.toLocaleTimeString('sv-SE');
+ // }
+ callback: function (val, index) {
+ const labels = this.chart.data.labels;
+ const date = new Date(labels[index]);
+
+ // Check if the first and last labels are on the same day
+ const firstDate = new Date(labels[0]).toDateString();
+ const lastDate = new Date(labels[labels.length - 1]).toDateString();
+ const isSameDay = firstDate === lastDate;
+
+ // Format based on the result
+ if (isSameDay) {
+ // Returns: HH:mm:ss
+ return date.toLocaleTimeString('sv-SE');
+ } else {
+ // Returns: DD/MM HH:mm:ss
+ const day = String(date.getDate()).padStart(2, '0');
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const time = date.toLocaleTimeString('sv-SE');
+ return `${day}/${month} ${time}`;
+ }
+ }
+ },
grid: { color: '#ffffff08', drawBorder: false },
border: { color: '#ffffff10' }
},
@@ -1032,6 +1135,7 @@
canvas.addEventListener('contextmenu', e => {
e.preventDefault();
+ showLoading()
clearTimeFilter();
});
}
@@ -1043,19 +1147,25 @@
// ---- Filtering ----
function applyTimeFilter(start, end) {
+ showLoading()
timeFilter = { start, end };
const startD = new Date(start), endD = new Date(end);
document.getElementById('time-badge-label').textContent =
`${fmtShort(startD)} – ${fmtShort(endD)}`;
document.getElementById('time-badge').classList.add('visible');
applyFilters();
+ updateFilteredLevelCounts();
+ hideLoading()
}
function clearTimeFilter() {
+ showLoading()
timeFilter = null;
document.getElementById('time-badge').classList.remove('visible');
if (chart) chart.resetZoom();
applyFilters();
+ updateLevelCounts();
+ hideLoading()
}
function applyFilters() {
@@ -1103,9 +1213,9 @@
tr.className = 'fade-in';
tr.dataset.idx = idx;
tr.innerHTML = `
-
${fmtTime(log.time)} |
+
${fmtTimeLocal(log.time)} |
${log.level} |
-
${esc(log.message)}${log.exception || log.props ? ' ▸ details' : ''} |
+
${esc(log.message)}${log.exception || log.props || log.fullmessage ? ' ▸ details' : ''} |
`;
tr.addEventListener('click', () => toggleDetail(tr, log, idx));
tbody.appendChild(tr);
@@ -1129,6 +1239,9 @@
const detailTr = document.createElement('tr');
detailTr.className = 'row-detail';
let sections = '';
+ if (log.fullmessage) {
+ sections += `
Full Message
${esc(log.fullmessage)}
`;
+ }
if (log.exception) {
sections += `
Exception / Stack Trace
${esc(log.exception)}
`;
}
@@ -1149,6 +1262,14 @@
});
}
+ function updateFilteredLevelCounts() {
+ const counts = { TRACE: 0, DEBUG: 0, INFO: 0, WARN: 0, ERROR: 0, FATAL: 0 };
+ filteredLogs.forEach(l => { if (counts[l.level] !== undefined) counts[l.level]++; });
+ Object.entries(counts).forEach(([lv, cnt]) => {
+ document.getElementById('cnt-' + lv).textContent = cnt.toLocaleString();
+ });
+ }
+
// ---- Status bar ----
function updateStatus() {
const s = document.getElementById('status-showing');
@@ -1161,6 +1282,9 @@
function fmtTime(d) {
return d.toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
}
+ function fmtTimeLocal(d) {
+ return d.toLocaleString('sv-SE').replace('T', ' ').replace(/\.\d+Z$/, '');
+ }
function fmtShort(d) {
return d.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
@@ -1171,6 +1295,7 @@
// ---- Load file ----
function loadFile(file) {
//debugger
+ showLoading();
const reader = new FileReader();
reader.onload = e => {
const text = e.target.result;
@@ -1248,8 +1373,19 @@
buildChart(allLogs);
renderTable();
updateStatus();
+ hideLoading();
}
+ const loader = document.getElementById('loader');
+ function showLoading() {
+ if (!loader.classList.contains('is-active')) {
+ loader.classList.add('is-active');
+ }
+ }
+ function hideLoading() {
+ loader.classList.remove('is-active');
+ }
+
// ---- Events ----
document.getElementById('file-input').addEventListener('change', e => {
if (e.target.files[0]) loadFile(e.target.files[0]);
@@ -1274,6 +1410,7 @@
document.getElementById('time-badge').addEventListener('click', () => clearTimeFilter());
document.getElementById('clear-filters').addEventListener('click', () => {
+ //showLoading()
levelFilter = null;
document.getElementById('search').value = '';
document.querySelectorAll('.level-pill').forEach(p => p.classList.remove('active'));
@@ -1314,7 +1451,7 @@
});
// Load demo on start
- loadDemo();
+ //loadDemo();