diff --git a/app.py b/app.py index 9a312fc..578bdbc 100644 --- a/app.py +++ b/app.py @@ -1979,57 +1979,64 @@ def admin_logs(): @app.route('/api/logs/recent') @admin_required def api_recent_logs(): - """Enhanced API endpoint to get recent log entries with filtering""" + """API endpoint to get recent log entries with full details""" try: - # Get parameters - days = request.args.get('days', 7, type=int) + days = request.args.get('days', 1, type=int) limit = request.args.get('limit', 50, type=int) - category_filter = request.args.get('category', None) - severity_filter = request.args.get('severity', None) - search_term = request.args.get('search', None) - # Verify log table exists - if not logger_handler.verify_log_table_exists(): - return jsonify({ - 'success': False, - 'error': 'Log table not found or inaccessible', - 'logs': [], - 'total': 0 - }), 500 + cutoff_date = datetime.now() - timedelta(days=days) - # Get logs using the enhanced method - logs = logger_handler.get_recent_logs( - days=days, - limit=limit, - category_filter=category_filter, - severity_filter=severity_filter, - search_term=search_term - ) + # Enhanced SQL to get all available fields + logs_sql = """ + SELECT + event_id, + event_type, + event_category, + event_description, + event_data, + severity_level, + created_timestamp, + username, + user_id, + ip_address + FROM log_events + WHERE created_timestamp >= :cutoff_date + ORDER BY created_timestamp DESC + LIMIT :limit + """ - # Log the API access for audit purposes - logger_handler.log_security_event( - event_type="admin_logs_accessed", - description=f"Admin {session.get('username', 'unknown')} accessed log data (days={days}, limit={limit})", - severity="LOW", - additional_data={ - 'filters': { - 'category': category_filter, - 'severity': severity_filter, - 'search': search_term - } - } - ) + result = db.session.execute(text(logs_sql), { + 'cutoff_date': cutoff_date, + 'limit': limit + }).fetchall() + + logs = [] + for row in result: + # Parse event_data if it's JSON + event_data = None + if row.event_data: + try: + event_data = json.loads(row.event_data) if isinstance(row.event_data, str) else row.event_data + except (json.JSONDecodeError, TypeError): + event_data = row.event_data + + logs.append({ + 'event_id': row.event_id, + 'event_type': row.event_type, + 'event_category': row.event_category, + 'description': row.event_description, + 'event_data': event_data, + 'severity': row.severity_level, + 'timestamp': row.created_timestamp.isoformat(), + 'username': row.username or 'System', + 'user_id': row.user_id, + 'ip_address': row.ip_address or '-' + }) return jsonify({ 'success': True, 'logs': logs, - 'total': len(logs), - 'filters_applied': { - 'days': days, - 'category': category_filter, - 'severity': severity_filter, - 'search': search_term - } + 'total': len(logs) }) except Exception as e: @@ -2037,111 +2044,95 @@ def api_recent_logs(): print(f"Error in api_recent_logs: {e}") return jsonify({ 'success': False, - 'error': f'Failed to fetch recent logs: {str(e)}', - 'logs': [], - 'total': 0 + 'error': f'Failed to fetch recent logs: {str(e)}' }), 500 @app.route('/api/logs/stats') @admin_required def api_log_stats(): - """Enhanced API endpoint to get logging statistics""" + """API endpoint to get logging statistics""" try: days = request.args.get('days', 7, type=int) + print(f"๐Ÿ“Š Getting log statistics for last {days} days") - # Verify log table exists - if not logger_handler.verify_log_table_exists(): - return jsonify({ - 'success': False, - 'error': 'Log table not found or inaccessible', - 'stats': {} - }), 500 - - # Get statistics + # Get statistics from logger handler stats = logger_handler.get_log_statistics(days=days) + print(f"๐Ÿ“ˆ Retrieved stats: {stats}") - # Add some additional metadata - enhanced_stats = { + # Ensure all expected keys exist + expected_stats = { 'total_events': stats.get('total_events', 0), 'security_events': stats.get('security_events', 0), 'database_errors': stats.get('database_errors', 0), 'user_activities': stats.get('user_activities', 0), - 'system_events': stats.get('system_events', 0), - 'unique_users': stats.get('unique_users', 0), - 'severity_breakdown': { - 'high': stats.get('high_severity', 0), - 'medium': stats.get('medium_severity', 0), - 'low': stats.get('low_severity', 0), - 'info': stats.get('info_severity', 0) - }, - 'category_breakdown': { - 'security': stats.get('security_events', 0), - 'database': stats.get('database_errors', 0), - 'user_activity': stats.get('user_activities', 0), - 'system': stats.get('system_events', 0) - } + 'system_events': stats.get('system_events', 0) } return jsonify({ 'success': True, - 'stats': enhanced_stats, + 'stats': expected_stats, 'days': days, - 'generated_at': datetime.now().isoformat() + 'timestamp': datetime.now().isoformat() }) except Exception as e: logger_handler.log_database_error('api_log_stats', e) - print(f"Error in api_log_stats: {e}") + print(f"โŒ Error in api_log_stats: {e}") return jsonify({ 'success': False, 'error': f'Failed to fetch log statistics: {str(e)}', - 'stats': {} + 'stats': { + 'total_events': 0, + 'security_events': 0, + 'database_errors': 0, + 'user_activities': 0, + 'system_events': 0 + } }), 500 @app.route('/api/logs/cleanup', methods=['POST']) @admin_required def api_cleanup_logs(): - """Enhanced API endpoint to cleanup old log entries""" + """API endpoint to cleanup old log entries""" try: - # Get parameters from request + # Get JSON data data = request.get_json() if not data: + print("โŒ No JSON data provided") return jsonify({ 'success': False, 'error': 'No JSON data provided' }), 400 days_to_keep = data.get('days_to_keep', 90) + print(f"๐Ÿงน Cleanup request: keep last {days_to_keep} days") # Validate input if not isinstance(days_to_keep, int) or days_to_keep < 7: + print(f"โŒ Invalid days_to_keep: {days_to_keep}") return jsonify({ 'success': False, 'error': 'days_to_keep must be an integer >= 7' }), 400 if days_to_keep > 365: + print(f"โŒ days_to_keep too large: {days_to_keep}") return jsonify({ 'success': False, 'error': 'days_to_keep cannot exceed 365 days' }), 400 - # Verify log table exists - if not logger_handler.verify_log_table_exists(): - return jsonify({ - 'success': False, - 'error': 'Log table not found or inaccessible' - }), 500 - - # Perform cleanup + # Perform cleanup using logger handler deleted_count = logger_handler.cleanup_old_logs(days_to_keep=days_to_keep) - # Enhanced logging for audit trail admin_username = session.get('username', 'unknown') + print(f"โœ… Cleanup completed by {admin_username}: {deleted_count} records deleted") + + # Log the admin action logger_handler.log_security_event( event_type="admin_log_cleanup", description=f"Admin {admin_username} performed log cleanup: {deleted_count} entries removed (keeping last {days_to_keep} days)", - severity="HIGH", # High because this is a data deletion operation + severity="HIGH", additional_data={ 'admin_user': admin_username, 'days_to_keep': days_to_keep, @@ -2161,7 +2152,7 @@ def api_cleanup_logs(): except Exception as e: logger_handler.log_database_error('api_cleanup_logs', e) - print(f"Error in api_cleanup_logs: {e}") + print(f"โŒ Error in api_cleanup_logs: {e}") return jsonify({ 'success': False, 'error': f'Failed to cleanup old logs: {str(e)}' diff --git a/logger_handler.py b/logger_handler.py index 9a1d91e..d363973 100644 --- a/logger_handler.py +++ b/logger_handler.py @@ -23,7 +23,7 @@ import logging.handlers import json import os import traceback -from datetime import datetime, date +from datetime import datetime, date, timedelta from functools import wraps from flask import request, session, g from sqlalchemy import text @@ -594,63 +594,82 @@ class AppLogger: ) # UTILITY METHODS - def get_log_statistics(self, days=7): """Get logging statistics for the specified number of days""" try: + from datetime import datetime, timedelta # Import here as backup cutoff_date = datetime.now() - timedelta(days=days) - # Get total events - total_sql = """ - SELECT COUNT(*) as total_events - FROM log_events - WHERE created_timestamp >= :cutoff_date - """ - - total_result = self.db.session.execute(text(total_sql), { - 'cutoff_date': cutoff_date - }).fetchone() - - # Get events by category - category_sql = """ - SELECT - event_category, - COUNT(*) as event_count - FROM log_events - WHERE created_timestamp >= :cutoff_date - GROUP BY event_category - """ - - category_result = self.db.session.execute(text(category_sql), { - 'cutoff_date': cutoff_date - }).fetchall() - - # Build simple statistics dictionary (not nested) + # Initialize default stats stats = { - 'total_events': total_result.total_events if total_result else 0, + 'total_events': 0, 'security_events': 0, 'database_errors': 0, 'user_activities': 0, 'system_events': 0 } - # Process category results - for row in category_result: - if row.event_category == 'security': - stats['security_events'] = row.event_count - elif row.event_category == 'database': - stats['database_errors'] = row.event_count - elif row.event_category == 'user_activity': - stats['user_activities'] = row.event_count - elif row.event_category == 'system': - stats['system_events'] = row.event_count + # Check if table exists first + try: + table_check = self.db.session.execute(text("SHOW TABLES LIKE 'log_events'")).fetchone() + if not table_check: + print("โš ๏ธ log_events table does not exist") + return stats + except Exception as table_error: + print(f"โš ๏ธ Cannot check if log_events table exists: {table_error}") + return stats + # Get total events count + try: + total_sql = """ + SELECT COUNT(*) as total_events + FROM log_events + WHERE created_timestamp >= :cutoff_date + """ + + total_result = self.db.session.execute(text(total_sql), {'cutoff_date': cutoff_date}).fetchone() + if total_result: + stats['total_events'] = total_result.total_events + print(f"โœ… Found {stats['total_events']} total events in last {days} days") + except Exception as total_error: + print(f"โš ๏ธ Error getting total events: {total_error}") + + # Get events by category + try: + category_sql = """ + SELECT + event_category, + COUNT(*) as event_count + FROM log_events + WHERE created_timestamp >= :cutoff_date + GROUP BY event_category + """ + + category_result = self.db.session.execute(text(category_sql), {'cutoff_date': cutoff_date}).fetchall() + + for row in category_result: + category = row.event_category + count = row.event_count + print(f"โœ… Found {count} events in category: {category}") + + if category == 'security': + stats['security_events'] = count + elif category == 'database': + stats['database_errors'] = count + elif category == 'user_activity': + stats['user_activities'] = count + elif category == 'system': + stats['system_events'] = count + + except Exception as category_error: + print(f"โš ๏ธ Error getting category stats: {category_error}") + + print(f"๐Ÿ“Š Final stats: {stats}") return stats except Exception as e: + print(f"โŒ Error in get_log_statistics: {e}") self.log_database_error('get_log_statistics', e) - print(f"Error in get_log_statistics: {e}") - # Return default stats structure return { 'total_events': 0, 'security_events': 0, @@ -662,60 +681,70 @@ class AppLogger: def cleanup_old_logs(self, days_to_keep=90): """Clean up old log entries from database""" try: + from datetime import datetime, timedelta # Import here as backup cutoff_date = datetime.now() - timedelta(days=days_to_keep) + print(f"๐Ÿงน Starting log cleanup: removing entries older than {cutoff_date}") + + # Check if table exists first + try: + table_check = self.db.session.execute(text("SHOW TABLES LIKE 'log_events'")).fetchone() + if not table_check: + print("โš ๏ธ log_events table does not exist") + return 0 + except Exception as table_error: + print(f"โš ๏ธ Cannot check if log_events table exists: {table_error}") + return 0 # First, count how many records will be deleted - count_sql = """ - SELECT COUNT(*) as count_to_delete - FROM log_events - WHERE created_timestamp < :cutoff_date - AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH') - """ - - count_result = self.db.session.execute(text(count_sql), { - 'cutoff_date': cutoff_date - }).fetchone() - - count_to_delete = count_result.count_to_delete if count_result else 0 - - if count_to_delete == 0: + try: + count_sql = """ + SELECT COUNT(*) as count_to_delete + FROM log_events + WHERE created_timestamp < :cutoff_date + AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH') + """ + + count_result = self.db.session.execute(text(count_sql), {'cutoff_date': cutoff_date}).fetchone() + count_to_delete = count_result.count_to_delete if count_result else 0 + + print(f"๐Ÿ“Š Found {count_to_delete} records to delete") + + if count_to_delete == 0: + print("โœ… No old records found to cleanup") + return 0 + + except Exception as count_error: + print(f"โš ๏ธ Error counting records to delete: {count_error}") return 0 # Perform the cleanup - exclude critical logs - cleanup_sql = """ - DELETE FROM log_events - WHERE created_timestamp < :cutoff_date - AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH') - """ - - result = self.db.session.execute(text(cleanup_sql), { - 'cutoff_date': cutoff_date - }) - - deleted_count = result.rowcount - self.db.session.commit() - - # Log the cleanup operation - self.logger.info(f"Log cleanup completed: {deleted_count} entries removed (keeping entries older than {days_to_keep} days)") - - # Also log to security log for audit - self.log_security_event( - event_type="log_cleanup", - description=f"Admin cleaned up {deleted_count} old log entries (keeping last {days_to_keep} days)", - severity="MEDIUM", - additional_data={ - 'days_to_keep': days_to_keep, - 'deleted_count': deleted_count, - 'cutoff_date': cutoff_date.isoformat() - } - ) - - return deleted_count + try: + cleanup_sql = """ + DELETE FROM log_events + WHERE created_timestamp < :cutoff_date + AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH') + """ + + result = self.db.session.execute(text(cleanup_sql), {'cutoff_date': cutoff_date}) + deleted_count = result.rowcount + self.db.session.commit() + + print(f"๐Ÿ—‘๏ธ Successfully deleted {deleted_count} old log entries") + + # Log the cleanup operation + self.logger.info(f"Log cleanup completed: {deleted_count} entries removed (keeping entries newer than {days_to_keep} days)") + + return deleted_count + + except Exception as delete_error: + print(f"โŒ Error during deletion: {delete_error}") + self.db.session.rollback() + return 0 except Exception as e: + print(f"โŒ Error in cleanup_old_logs: {e}") self.db.session.rollback() self.log_database_error('cleanup_old_logs', e) - print(f"Error in cleanup_old_logs: {e}") return 0 def get_recent_logs(self, days=7, limit=100, category_filter=None, severity_filter=None, search_term=None): diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..66280b0 Binary files /dev/null and b/static/favicon.ico differ diff --git a/templates/admin_logs.html b/templates/admin_logs.html index 0b7d907..f229c7c 100644 --- a/templates/admin_logs.html +++ b/templates/admin_logs.html @@ -1,13 +1,12 @@ -{% extends "base_authenticated.html" %} -{% block title %}System Logs - QR Code Management{% endblock %} - -{% block extra_head %} +{% extends "base_authenticated.html" %} {% block title %}System Logs - QR Code +Management{% endblock %} {% block extra_head %} - -{% endblock %} - -{% block content %} + +{% endblock %} {% block content %}
@@ -22,6 +21,10 @@

Monitor system activities and security events

+
- {% if log_stats %}
-

{{ log_stats.total_events or 0 }}

+

+ {{ log_stats.total_events if log_stats else 0 }} +

Total Events (7 days)

@@ -51,7 +55,9 @@
-

{{ log_stats.security_events or 0 }}

+

+ {{ log_stats.security_events if log_stats else 0 }} +

Security Events

@@ -61,7 +67,9 @@
-

{{ log_stats.database_errors or 0 }}

+

+ {{ log_stats.database_errors if log_stats else 0 }} +

Database Errors

@@ -71,11 +79,36 @@
-

{{ log_stats.user_activities or 0 }}

+

+ {{ log_stats.user_activities if log_stats else 0 }} +

User Activities

+ + + {% if config.DEBUG %} +
+ Debug Info (Development Mode):
+ Log Stats Available: {{ log_stats is not none }}
+ {% if log_stats %} Log Stats Keys: {{ log_stats.keys() | list }}
+ Total Events: {{ log_stats.total_events }}
+ Security Events: {{ log_stats.security_events }}
+ Database Errors: {{ log_stats.database_errors }}
+ User Activities: {{ log_stats.user_activities }}
+ {% endif %} +
{% endif %} @@ -131,14 +164,14 @@

Loading log entries...

- -