Log page changes
This commit is contained in:
@@ -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)}'
|
||||
|
||||
+114
-85
@@ -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):
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
+1093
-760
File diff suppressed because it is too large
Load Diff
+3
-5
@@ -24,7 +24,7 @@
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="{{ url_for('static', filename='favicon.ico') }}"
|
||||
href="{{ url_for('static', filename='images/favicon.ico') }}"
|
||||
/>
|
||||
</head>
|
||||
<body class="login-layout">
|
||||
@@ -52,9 +52,7 @@
|
||||
{% endif %} {% endwith %}
|
||||
|
||||
<!-- Page Content -->
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<div class="container">{% block content %}{% endblock %}</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
@@ -88,4 +86,4 @@
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user