### Install Log Socket CLI Tool Source: https://github.com/taigrr/log-socket/blob/master/README.md Instructions to install the log-socket command-line tool using Go's package manager. This command fetches and installs the latest version of the utility, making it available in your Go binary path. ```bash go install github.com/taigrr/log-socket@latest ``` -------------------------------- ### Start Log Socket Server Source: https://github.com/taigrr/log-socket/blob/master/README.md Commands to start the log-socket server. The first command starts it with the default address (0.0.0.0:8080), while the second demonstrates how to specify a custom address and port. ```bash log-socket ``` ```bash log-socket -addr localhost:8080 ``` -------------------------------- ### Download Log Data as JSON Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This JavaScript snippet demonstrates how to programmatically create a JSON file from a string, trigger its download in the browser, and then clean up the temporary resources. It uses URL.createObjectURL to create a downloadable link and simulates a click. ```javascript lob([dataStr], { type: 'application/json' }); const link = document.createElement('a'); link.href = URL.createObjectURL(dataBlob); link.download = `logs-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(link.href); ``` -------------------------------- ### Initialize LogViewer on DOM Load Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This snippet ensures that the main LogViewer application instance is created and initialized only after the entire HTML document has been fully loaded and parsed. It leverages the DOMContentLoaded event to guarantee that all necessary DOM elements are available before the application attempts to interact with them. ```javascript document.addEventListener('DOMContentLoaded', () => { new LogViewer(); }); ``` -------------------------------- ### Log Socket Logging Interface Methods Source: https://github.com/taigrr/log-socket/blob/master/README.md Documentation for the comprehensive logging interface provided by the log-socket package. It outlines various methods for different log levels (e.g., Trace, Debug, Info, Error, Fatal), supporting both formatted ('f' suffix) and unformatted ('ln' suffix) messages, as well as basic messages. ```APIDOC Log Socket Logging Interface: Methods: - Trace/Tracef/Traceln: For trace-level logging - Debug/Debugf/Debugln: For debug-level logging - Info/Infof/Infoln: For info-level logging - Notice/Noticef/Noticeln: For notice-level logging - Warn/Warnf/Warnln: For warning-level logging - Error/Errorf/Errorln: For error-level logging - Panic/Panicf/Panicln: For panic-level logging - Fatal/Fatalf/Fataln: For fatal-level logging ``` -------------------------------- ### Log Viewer UI Styling with CSS Variables and Responsive Design Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This CSS snippet defines the complete styling for the log viewer interface. It utilizes CSS variables for consistent theming across colors, backgrounds, and shadows. The styles cover global elements, specific components like the log table, search inputs, buttons, and status indicators, ensuring a responsive layout for various screen sizes. ```CSS :root { --primary-color: #2196F3; --error-color: #f44336; --warning-color: #ff9800; --success-color: #4caf50; --info-color: #2196F3; --debug-color: #9c27b0; --trace-color: #607d8b; --bg-primary: #ffffff; --bg-secondary: #f5f5f5; --text-primary: #333333; --text-secondary: #666666; --border-color: #e0e0e0; --shadow: 0 2px 4px rgba(0,0,0,0.1); --shadow-hover: 0 4px 8px rgba(0,0,0,0.15); } * { box-sizing: border-box; } body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; background-color: var(--bg-secondary); color: var(--text-primary); line-height: 1.6; } .container { max-width: 1400px; margin: 0 auto; padding: 20px; } .header { background: var(--bg-primary); padding: 20px; border-radius: 8px; box-shadow: var(--shadow); margin-bottom: 20px; } .header h1 { margin: 0 0 20px 0; color: var(--text-primary); font-size: 24px; font-weight: 600; } .controls { display: flex; gap: 15px; align-items: center; flex-wrap: wrap; } .search-container { position: relative; flex: 1; min-width: 250px; } .search-input { width: 100%; padding: 10px 15px; border: 2px solid var(--border-color); border-radius: 6px; font-size: 14px; transition: border-color 0.2s ease; } .search-input:focus { outline: none; border-color: var(--primary-color); } .checkbox-container { display: flex; align-items: center; gap: 8px; user-select: none; } .checkbox-container input[type="checkbox"] { width: 16px; height: 16px; } .checkbox-container label { font-size: 14px; color: var(--text-secondary); cursor: pointer; } .log-container { background: var(--bg-primary); border-radius: 8px; box-shadow: var(--shadow); overflow: hidden; margin-bottom: 20px; } .log-header { background: var(--bg-secondary); padding: 12px 0; border-bottom: 1px solid var(--border-color); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; } .log-table { width: 100%; border-collapse: collapse; } .log-row { display: grid; grid-template-columns: 180px 80px 1fr 120px; gap: 15px; padding: 10px 15px; border-bottom: 1px solid var(--border-color); transition: background-color 0.2s ease; } .log-row:hover { background-color: rgba(0,0,0,0.02); } .log-cell { display: flex; align-items: center; font-size: 13px; word-break: break-word; } .log-cell.timestamp { font-family: 'Monaco', 'Menlo', monospace; color: var(--text-secondary); font-size: 12px; } .log-cell.level { font-weight: 600; text-align: center; justify-content: center; } .log-cell.output { font-family: 'Monaco', 'Menlo', monospace; white-space: pre-wrap; } .log-cell.source { font-size: 11px; color: var(--text-secondary); } .log-level-error { background-color: #ffebee; color: var(--error-color); } .log-level-warn { background-color: #fff3e0; color: var(--warning-color); } .log-level-info { background-color: #e3f2fd; color: var(--info-color); } .log-level-debug { background-color: #f3e5f5; color: var(--debug-color); } .log-level-trace { background-color: #eceff1; color: var(--trace-color); } .log-viewer { height: 60vh; overflow-y: auto; overflow-x: hidden; } .log-viewer::-webkit-scrollbar { width: 8px; } .log-viewer::-webkit-scrollbar-track { background: var(--bg-secondary); } .log-viewer::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; } .log-viewer::-webkit-scrollbar-thumb:hover { background: #999; } .actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; } .btn { padding: 10px 20px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; } .btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-hover); } .btn:active { transform: translateY(0); } .btn-primary { background-color: var(--primary-color); color: white; } .btn-danger { background-color: var(--error-color); color: white; } .status-bar { background: var(--bg-primary); padding: 10px 20px; border-radius: 6px; box-shadow: var(--shadow); display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: var(--text-secondary); } .connection-status { display: flex; align-items: center; gap: 8px; } .status-indicator { width: 8px; height: 8px; border-radius: 50%; background-color: var(--error-color); } .status-indicator.connected { background-color: var(--success-color); } .empty-state { text-align: center; padding: 40px 20px; color: var(--text-secondary); } .empty-state h3 { margin: 0 0 10px 0; font-size: 18px; } .empty-state p { margin: 0; font-size: 14px; } @media (max-width: 768px) { .container { padding: 10px; } .controls { flex-direction: column; align-items: stretch; } .log-row { grid-template-columns: 1fr; gap: 5px; } .log-cell.timestamp::before { content: 'Time: '; font-weight: 600; } .log-cell.level::before { content: 'Level: '; font-weight: 600; } .log-cell.source::before { content: 'Source: '; font-weight: 600; } .log-header { display: none; } } ``` -------------------------------- ### Create a Generic Debounce Utility Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This utility function returns a new function that, when invoked, will execute the original function only after a specified 'wait' time has elapsed without any further invocations. It's commonly used to optimize performance by limiting the rate of function calls for events like resizing, scrolling, or typing. ```javascript debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } ``` -------------------------------- ### JavaScript LogViewer Class Implementation Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This JavaScript class implements a client-side log viewer that connects to a WebSocket server to receive real-time log entries. It provides functionalities such as displaying logs, filtering logs by search query, auto-scrolling, downloading logs, and clearing logs. The class also handles WebSocket connection management, including reconnection attempts and error handling. Note: Some methods like `startAutoScroll`, `updateLogCount`, `updateConnectionStatus`, `debounce`, and the end of `downloadLogs` are not fully provided in this snippet. ```javascript class LogViewer { constructor() { this.ws = null; this.logs = []; this.filteredLogs = []; this.isConnected = false; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.initializeElements(); this.attachEventListeners(); this.connectWebSocket(); this.startAutoScroll(); } initializeElements() { this.logViewer = document.getElementById('logViewer'); this.emptyState = document.getElementById('emptyState'); this.searchInput = document.getElementById('search'); this.scrollCheckbox = document.getElementById('shouldScroll'); this.downloadBtn = document.getElementById('downloadBtn'); this.clearBtn = document.getElementById('clearBtn'); this.statusIndicator = document.getElementById('statusIndicator'); this.connectionStatus = document.getElementById('connectionStatus'); this.logCount = document.getElementById('logCount'); } attachEventListeners() { this.searchInput.addEventListener('input', this.debounce(() => this.filterLogs(), 300)); this.downloadBtn.addEventListener('click', () => this.downloadLogs()); this.clearBtn.addEventListener('click', () => this.clearLogs()); } connectWebSocket() { if (this.ws) return; try { this.ws = new WebSocket("{{.}}"); this.updateConnectionStatus('Connecting...', false); this.ws.onopen = () => { this.isConnected = true; this.reconnectAttempts = 0; this.updateConnectionStatus('Connected', true); }; this.ws.onmessage = (event) => { try { const logEntry = JSON.parse(event.data); this.addLogEntry(logEntry); } catch (error) { console.error('Failed to parse log entry:', error); } }; this.ws.onclose = () => { this.isConnected = false; this.ws = null; this.updateConnectionStatus('Disconnected', false); this.scheduleReconnect(); }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.updateConnectionStatus('Connection Error', false); }; } catch (error) { console.error('Failed to create WebSocket:', error); this.updateConnectionStatus('Connection Failed', false); this.scheduleReconnect(); } } scheduleReconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000); this.updateConnectionStatus(`Reconnecting in ${Math.ceil(delay/1000)}s...`, false); setTimeout(() => { this.connectWebSocket(); }, delay); } else { this.updateConnectionStatus('Connection Failed', false); } } addLogEntry(entry) { this.logs.push(entry); this.updateLogCount(); if (this.matchesFilter(entry)) { this.renderLogEntry(entry); this.hideEmptyState(); } } renderLogEntry(entry) { const logRow = document.createElement('div'); logRow.className = `log-row log-level-${entry.level.toLowerCase()}`; logRow.innerHTML = `