diff --git a/log_parser.py b/log_parser.py index 330d489..5704e18 100644 --- a/log_parser.py +++ b/log_parser.py @@ -1,5 +1,6 @@ import re + def parse_log_file(log_file_path): """ Parse HAProxy syslog format and identify security threats. @@ -78,7 +79,7 @@ def parse_log_file(log_file_path): ip_address = ip_match.group(1) - # Extract date/time in brackets + # Extract date/time in brackets (preferred format) datetime_match = re.search(r'\[(\d{2}/\w+/\d{4}:\d{2}:\d{2}:\d{2})', line) if datetime_match: timestamp = datetime_match.group(1) @@ -95,10 +96,17 @@ def parse_log_file(log_file_path): # Extract HTTP method and URL http_match = re.search(r'"(\w+)\s+([^\s]+)\s+HTTP', line) if not http_match: - continue - - http_method = http_match.group(1) - requested_url = http_match.group(2) + # Fallback: extract entire request line + request_match = re.search(r'"([^"]*)"', line) + if request_match: + request_line = request_match.group(1).split() + http_method = request_line[0] if len(request_line) > 0 else 'UNKNOWN' + requested_url = request_line[1] if len(request_line) > 1 else '/' + else: + continue + else: + http_method = http_match.group(1) + requested_url = http_match.group(2) # Detect threats xss_alert = bool(xss_pattern.search(line)) @@ -107,6 +115,24 @@ def parse_log_file(log_file_path): put_method = http_method == 'PUT' illegal_resource = status_code == '403' + # Determine status class for UI coloring + status_class = 'secondary' + if status_code.startswith('2'): + status_class = 'success' + elif status_code.startswith('3'): + status_class = 'info' + elif status_code.startswith('4'): + status_class = 'warning' + if illegal_resource: + status_class = 'warning' + elif status_code.startswith('5'): + status_class = 'danger' + + # Add threat flag if any security issue detected + has_threat = xss_alert or sql_alert or webshell_alert or put_method or illegal_resource + if has_threat: + status_class = 'danger' + parsed_entries.append({ 'timestamp': timestamp, 'ip_address': ip_address, @@ -120,16 +146,20 @@ def parse_log_file(log_file_path): 'put_method': put_method, 'illegal_resource': illegal_resource, 'webshell_alert': webshell_alert, + 'status_class': status_class, + 'has_threat': has_threat, + 'message': f"{frontend}~ {backend} [{status_code}] {http_method} {requested_url}" }) except Exception as e: - print(f"Error parsing line: {e}") + print(f"[LOG_PARSER] Error parsing line: {e}", flush=True) continue except FileNotFoundError: - print(f"Log file not found: {log_file_path}") + print(f"[LOG_PARSER] Log file not found: {log_file_path}", flush=True) return [] except Exception as e: - print(f"Error reading log file: {e}") + print(f"[LOG_PARSER] Error reading log file: {e}", flush=True) return [] + print(f"[LOG_PARSER] Parsed {len(parsed_entries)} log entries", flush=True) return parsed_entries diff --git a/static/js/logs.js b/static/js/logs.js index a12af37..ff6d761 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -1,6 +1,5 @@ /** - * HAProxy Logs Management - * Pagination, filtering, and proper formatting + * HAProxy Logs Management with Security Alerts */ document.addEventListener('DOMContentLoaded', function() { @@ -115,8 +114,8 @@ document.addEventListener('DOMContentLoaded', function() { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - page: currentPage, - per_page: perPage + page: 1, + per_page: 200 // Load initial 200 }) }) .then(r => r.json()) @@ -124,6 +123,9 @@ document.addEventListener('DOMContentLoaded', function() { if (data.success) { allLoadedLogs = data.logs; loadedSpan.textContent = data.logs.length; + totalLogs = data.total; + document.getElementById('total_count').textContent = data.total; + console.log(`[Logs] Loaded ${data.logs.length}/${data.total}`, flush=true); applyFilters(); } else { showError(data.error); @@ -145,7 +147,7 @@ document.addEventListener('DOMContentLoaded', function() { if (searchFilter.value.trim()) { const query = searchFilter.value.toLowerCase(); filtered = filtered.filter(log => { - const text = `${log.timestamp || ''} ${log.source || ''} ${log.message || ''}`.toLowerCase(); + const text = `${log.timestamp} ${log.ip_address} ${log.http_method} ${log.requested_url} ${log.frontend} ${log.backend}`.toLowerCase(); return text.includes(query); }); } @@ -153,7 +155,7 @@ document.addEventListener('DOMContentLoaded', function() { // Apply exclude phrases if (excludePhrases.length > 0) { filtered = filtered.filter(log => { - const text = `${log.timestamp || ''} ${log.source || ''} ${log.message || ''}`; + const text = `${log.timestamp} ${log.ip_address} ${log.message}`; return !excludePhrases.some(phrase => text.includes(phrase)); }); } @@ -190,23 +192,27 @@ document.addEventListener('DOMContentLoaded', function() { return; } - logsContainer.innerHTML = logs.map((entry, idx) => { - const timestamp = entry.timestamp || '-'; - const source = entry.source || '-'; - const message = entry.message || '-'; + logsContainer.innerHTML = logs.map((entry) => { + const threat_badges = []; + if (entry.xss_alert) threat_badges.push('XSS'); + if (entry.sql_alert) threat_badges.push('SQL'); + if (entry.webshell_alert) threat_badges.push('SHELL'); + if (entry.put_method) threat_badges.push('PUT'); + if (entry.illegal_resource) threat_badges.push('403'); - // Color code by status code if present - let statusClass = ''; - if (message.includes('200')) statusClass = 'table-success'; - else if (message.includes('404')) statusClass = 'table-warning'; - else if (message.includes('500')) statusClass = 'table-danger'; + const threat_html = threat_badges.length > 0 ? `
${threat_badges.join('')}
` : ''; return ` - + - ${escapeHtml(timestamp)}
- ${escapeHtml(source)}
- ${escapeHtml(message)} + ${threat_html} + ${escapeHtml(entry.timestamp)}
+ ${escapeHtml(entry.ip_address)}
+ ${escapeHtml(entry.http_method)} + ${escapeHtml(entry.requested_url)} + ${escapeHtml(entry.status_code)} +
+ ${escapeHtml(entry.frontend)}~ ${escapeHtml(entry.backend)} `; @@ -214,7 +220,7 @@ document.addEventListener('DOMContentLoaded', function() { } /** - * Update exclude UI to show active filters + * Update exclude UI */ function updateExcludeUI() { if (excludePhrases.length > 0) { @@ -240,7 +246,7 @@ document.addEventListener('DOMContentLoaded', function() { } /** - * Global function to remove exclude phrase + * Remove exclude phrase */ window.removeExcludePhrase = function(idx) { excludePhrases.splice(idx, 1);