### 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 = `
${this.formatTimestamp(entry.timestamp)}
${entry.level}
${this.escapeHtml(entry.output)}
${this.escapeHtml(entry.file || 'N/A')}
`; this.logViewer.appendChild(logRow); } formatTimestamp(timestamp) { try { const date = new Date(timestamp); return date.toLocaleString(); } catch { return timestamp; } } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } filterLogs() { const query = this.searchInput.value.toLowerCase().trim(); // Clear current display this.clearLogDisplay(); if (!query) { // Show all logs this.logs.forEach(log => this.renderLogEntry(log)); } else { // Show filtered logs const filtered = this.logs.filter(log => this.matchesQuery(log, query)); filtered.forEach(log => this.renderLogEntry(log)); } if (this.logViewer.children.length === 0 || (this.logViewer.children.length === 1 && this.logViewer.contains(this.emptyState))) { this.showEmptyState(); } else { this.hideEmptyState(); } } matchesFilter(entry) { const query = this.searchInput.value.toLowerCase().trim(); return !query || this.matchesQuery(entry, query); } matchesQuery(entry, query) { return ( entry.output.toLowerCase().includes(query) || entry.level.toLowerCase().includes(query) || (entry.file && entry.file.toLowerCase().includes(query)) || entry.timestamp.toLowerCase().includes(query) ); } clearLogDisplay() { while (this.logViewer.firstChild && this.logViewer.firstChild !== this.emptyState) { this.logViewer.removeChild(this.logViewer.firstChild); } } showEmptyState() { if (!this.logViewer.contains(this.emptyState)) { this.logViewer.appendChild(this.emptyState); } } hideEmptyState() { if (this.logViewer.contains(this.emptyState)) { this.logViewer.removeChild(this.emptyState); } } clearLogs() { if (!confirm('Are you sure you want to clear all logs?')) return; this.logs = []; this.clearLogDisplay(); this.showEmptyState(); this.updateLogCount(); } downloadLogs() { if (this.logs.length === 0) { alert('No logs to download'); return; } const dataStr = JSON.stringify(this.logs, null, 2); const dataBlob = new B ``` -------------------------------- ### Display Current Log Entry Count Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This method calculates the total number of log entries and updates a dedicated UI element to display this count. It includes logic for correct pluralization of 'log' based on the count, ensuring a grammatically accurate display for the user. ```javascript updateLogCount() { const count = this.logs.length; this.logCount.textContent = `${count} log${count !== 1 ? 's' : ''}`; } ``` -------------------------------- ### Update UI for Connection Status Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This function dynamically updates two UI elements to reflect the current connection status. It sets the text content of a status display and toggles a CSS class on an indicator, providing immediate visual feedback to the user about the network connection state. ```javascript updateConnectionStatus(status, connected) { this.connectionStatus.textContent = status; this.statusIndicator.classList.toggle('connected', connected); } ``` -------------------------------- ### Implement Continuous Auto-Scrolling Source: https://github.com/taigrr/log-socket/blob/master/browser/viewer.html This function sets up a continuous auto-scrolling behavior for a log viewer. It uses requestAnimationFrame to efficiently scroll the viewer to its bottom whenever a specific checkbox is enabled and the connection is active, ensuring that the latest log entries are always visible. ```javascript startAutoScroll() { const autoScroll = () => { if (this.scrollCheckbox.checked && this.isConnected) { this.logViewer.scrollTop = this.logViewer.scrollHeight; } requestAnimationFrame(autoScroll); }; autoScroll(); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.