<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Server Sent Event</title> <style> .main-container { display: flex; flex-direction: column; gap: 10px; font-family: 'Hack', monospace; font-size: 12px; } #loading { font-style: italic; } .log-entry { font-size: 12px; line-height: 1.2; } .log-entry > span { min-width: 0; overflow-wrap: break-word; word-break: break-word; margin-right: 10px; } .log-entry > span:last-child { margin-right: 0; } .log-entry.log-entry-trace .log-level { color: blue; } .log-entry.log-entry-debug .log-level { color: gray; } .log-entry.log-entry-info .log-level { color: green; } .log-entry.log-entry-warn .log-level { color: yellow; } .log-entry.log-entry-error .log-level, .log-entry.log-entry-fatal .log-level, .log-entry.log-entry-panic .log-level { color: red; } .log-entry.log-entry-info .log-message, .log-entry.log-entry-warn .log-message, .log-entry.log-entry-error .log-message, .log-entry.log-entry-fatal .log-message, .log-entry.log-entry-panic .log-message { font-weight: bold; } .log-timestamp { color: #666; min-width: 150px; } .log-level { font-size: 12px; min-width: 50px; } .log-scope { font-size: 12px; min-width: 40px; } .log-component { font-size: 12px; min-width: 80px; } .log-message { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 500px; } .log-extras { color: #000; } .log-extras .log-extras-header { font-weight: bold; color:cornflowerblue; } </style> </head> <body> <div class="main-container"> <div id="header"> <span id="loading"> Connecting to log stream... </span> <span id="stats"> </span> </div> <div id="event-data"> </div> </div> </body> <script> class LogStream { constructor(url, eventDataElement, loadingElement, statsElement) { this.url = url; this.eventDataElement = eventDataElement; this.loadingElement = loadingElement; this.statsElement = statsElement; this.stream = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 10; this.reconnectDelay = 1000; // Start with 1 second this.maxReconnectDelay = 30000; // Max 30 seconds this.isConnecting = false; this.totalMessages = 0; this.connect(); } connect() { if (this.isConnecting) return; this.isConnecting = true; this.loadingElement.innerText = "Connecting to log stream..."; this.stream = new EventSource(this.url); this.stream.onopen = () => { this.isConnecting = false; this.reconnectAttempts = 0; this.reconnectDelay = 1000; this.loadingElement.innerText = "Log stream connected."; this.totalMessages = 0; this.totalBytes = 0; }; this.stream.onmessage = (event) => { this.totalBytes += event.data.length; this.totalMessages++; const data = JSON.parse(event.data); this.addLogEntry(data); this.updateStats(); }; this.stream.onerror = () => { this.isConnecting = false; this.loadingElement.innerText = "Log stream disconnected."; this.stream.close(); this.handleReconnect(); }; } updateStats() { this.statsElement.innerHTML = `Messages: <strong>${this.totalMessages}</strong>, Bytes: <strong>${this.totalBytes}</strong> `; } handleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { this.loadingElement.innerText = "Failed to reconnect after multiple attempts"; return; } this.reconnectAttempts++; this.reconnectDelay = Math.min(this.reconnectDelay * 1, this.maxReconnectDelay); this.loadingElement.innerText = `Reconnecting in ${this.reconnectDelay/1000} seconds... (Attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`; setTimeout(() => { this.connect(); }, this.reconnectDelay); } addLogEntry(data) { const el = document.createElement("div"); el.className = "log-entry log-entry-" + data.level; const timestamp = document.createElement("span"); timestamp.className = "log-timestamp"; timestamp.innerText = data.time; el.appendChild(timestamp); const level = document.createElement("span"); level.className = "log-level"; level.innerText = this.shortLogLevel(data.level); el.appendChild(level); const scope = document.createElement("span"); scope.className = "log-scope"; scope.innerText = data.scope; el.appendChild(scope); const component = document.createElement("span"); component.className = "log-component"; component.innerText = data.component; el.appendChild(component); const message = document.createElement("span"); message.className = "log-message"; message.innerText = data.message; el.appendChild(message); this.addLogExtras(el, data); this.eventDataElement.appendChild(el); window.scrollTo(0, document.body.scrollHeight); } shortLogLevel(level) { switch (level) { case "trace": return "TRC"; case "debug": return "DBG"; case "info": return "INF"; case "warn": return "WRN"; case "error": return "ERR"; case "fatal": return "FTL"; case "panic": return "PNC"; default: return level; } } addLogExtras(el, data) { const excludeKeys = [ "timestamp", "time", "level", "scope", "component", "message", ]; const extras = {}; for (const key in data) { if (excludeKeys.includes(key)) { continue; } extras[key] = data[key]; } for (const key in extras) { const extra = document.createElement("span"); extra.className = "log-extras log-extras-" + key; const extraKey = document.createElement("span"); extraKey.className = "log-extras-header"; extraKey.innerText = key + '='; extra.appendChild(extraKey); const extraValue = document.createElement("span"); extraValue.className = "log-extras-value"; let value = extras[key]; if (typeof value === 'object') { value = JSON.stringify(value); } extraValue.innerText = value; extra.appendChild(extraValue); el.appendChild(extra); } } disconnect() { if (this.stream) { this.stream.close(); this.stream = null; } } } // Initialize the log stream when the page loads document.addEventListener('DOMContentLoaded', () => { const logStream = new LogStream( "/developer/log-stream", document.getElementById("event-data"), document.getElementById("loading"), document.getElementById("stats"), ); // Clean up when the page is unloaded window.addEventListener('beforeunload', () => { logStream.disconnect(); }); }); </script> </html>