Enhanced tracking page
This commit is contained in:
-2721
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,725 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Enhanced Logging Handler for QR Attendance Management System
|
||||||
|
==========================================================
|
||||||
|
|
||||||
|
This module provides comprehensive logging functionality for:
|
||||||
|
- User login/logout activities with session details
|
||||||
|
- QR code creation, modification, and deletion operations
|
||||||
|
- Database transaction errors and connection issues
|
||||||
|
- Flask application errors and exceptions
|
||||||
|
- Security events and unauthorized access attempts
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Structured JSON logging for better analytics
|
||||||
|
- Rotating log files to prevent disk space issues
|
||||||
|
- Different log levels for various event types
|
||||||
|
- Database logging table for critical events
|
||||||
|
- Performance monitoring and error tracking
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime, date
|
||||||
|
from functools import wraps
|
||||||
|
from flask import request, session, g
|
||||||
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
class AppLogger:
|
||||||
|
"""
|
||||||
|
Enhanced application logger with multiple output formats and destinations
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app=None, db=None):
|
||||||
|
"""Initialize the logger with Flask app and database instances"""
|
||||||
|
self.app = app
|
||||||
|
self.db = db
|
||||||
|
self.logger = None
|
||||||
|
self.security_logger = None
|
||||||
|
|
||||||
|
if app:
|
||||||
|
self.init_app(app, db)
|
||||||
|
|
||||||
|
def init_app(self, app, db):
|
||||||
|
"""Initialize logging with Flask application context"""
|
||||||
|
self.app = app
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
# Create logs directory if it doesn't exist
|
||||||
|
log_dir = os.path.join(app.root_path, 'logs')
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Configure main application logger
|
||||||
|
self.logger = logging.getLogger('qr_attendance_app')
|
||||||
|
self.logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Configure security logger for sensitive events
|
||||||
|
self.security_logger = logging.getLogger('qr_attendance_security')
|
||||||
|
self.security_logger.setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# Remove existing handlers to avoid duplication
|
||||||
|
self.logger.handlers.clear()
|
||||||
|
self.security_logger.handlers.clear()
|
||||||
|
|
||||||
|
# Setup file handlers with rotation
|
||||||
|
self._setup_file_handlers(log_dir)
|
||||||
|
|
||||||
|
# Setup console handler for development
|
||||||
|
self._setup_console_handler()
|
||||||
|
|
||||||
|
# Create database logging table
|
||||||
|
self._create_log_table()
|
||||||
|
|
||||||
|
# Register error handlers with Flask
|
||||||
|
self._register_error_handlers()
|
||||||
|
|
||||||
|
app.logger_handler = self
|
||||||
|
|
||||||
|
def _setup_file_handlers(self, log_dir):
|
||||||
|
"""Setup rotating file handlers for different log types"""
|
||||||
|
|
||||||
|
# Main application log (rotates when 10MB, keeps 5 files)
|
||||||
|
app_handler = logging.handlers.RotatingFileHandler(
|
||||||
|
os.path.join(log_dir, 'application.log'),
|
||||||
|
maxBytes=10*1024*1024, # 10MB
|
||||||
|
backupCount=5
|
||||||
|
)
|
||||||
|
app_handler.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Error log (rotates when 5MB, keeps 10 files)
|
||||||
|
error_handler = logging.handlers.RotatingFileHandler(
|
||||||
|
os.path.join(log_dir, 'errors.log'),
|
||||||
|
maxBytes=5*1024*1024, # 5MB
|
||||||
|
backupCount=10
|
||||||
|
)
|
||||||
|
error_handler.setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
# Security log (rotates when 2MB, keeps 20 files for compliance)
|
||||||
|
security_handler = logging.handlers.RotatingFileHandler(
|
||||||
|
os.path.join(log_dir, 'security.log'),
|
||||||
|
maxBytes=2*1024*1024, # 2MB
|
||||||
|
backupCount=20
|
||||||
|
)
|
||||||
|
security_handler.setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# Create custom formatter with JSON structure
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
'%(asctime)s | %(levelname)s | %(name)s | %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
app_handler.setFormatter(formatter)
|
||||||
|
error_handler.setFormatter(formatter)
|
||||||
|
security_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# Add handlers to loggers
|
||||||
|
self.logger.addHandler(app_handler)
|
||||||
|
self.logger.addHandler(error_handler)
|
||||||
|
self.security_logger.addHandler(security_handler)
|
||||||
|
|
||||||
|
def _setup_console_handler(self):
|
||||||
|
"""Setup console handler for development environment"""
|
||||||
|
if self.app.debug:
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
console_formatter = logging.Formatter(
|
||||||
|
'%(asctime)s [%(levelname)s] %(message)s',
|
||||||
|
datefmt='%H:%M:%S'
|
||||||
|
)
|
||||||
|
console_handler.setFormatter(console_formatter)
|
||||||
|
|
||||||
|
self.logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
def _create_log_table(self):
|
||||||
|
"""Create database table for storing critical log events"""
|
||||||
|
try:
|
||||||
|
with self.app.app_context():
|
||||||
|
# Create log_events table if it doesn't exist
|
||||||
|
create_table_sql = """
|
||||||
|
CREATE TABLE IF NOT EXISTS log_events (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
event_id VARCHAR(36) UNIQUE NOT NULL,
|
||||||
|
event_type VARCHAR(50) NOT NULL,
|
||||||
|
event_category VARCHAR(30) NOT NULL,
|
||||||
|
user_id INT NULL,
|
||||||
|
username VARCHAR(80) NULL,
|
||||||
|
event_description TEXT NOT NULL,
|
||||||
|
event_data JSON NULL,
|
||||||
|
ip_address VARCHAR(45) NULL,
|
||||||
|
user_agent TEXT NULL,
|
||||||
|
request_path VARCHAR(500) NULL,
|
||||||
|
session_id VARCHAR(100) NULL,
|
||||||
|
severity_level VARCHAR(20) DEFAULT 'INFO',
|
||||||
|
created_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_event_type (event_type),
|
||||||
|
INDEX idx_event_category (event_category),
|
||||||
|
INDEX idx_user_id (user_id),
|
||||||
|
INDEX idx_created_timestamp (created_timestamp),
|
||||||
|
INDEX idx_severity_level (severity_level)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.session.execute(text(create_table_sql))
|
||||||
|
self.db.session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not create log_events table: {e}")
|
||||||
|
|
||||||
|
def _register_error_handlers(self):
|
||||||
|
"""Register Flask error handlers for automatic logging"""
|
||||||
|
|
||||||
|
@self.app.errorhandler(500)
|
||||||
|
def handle_internal_error(error):
|
||||||
|
"""Log internal server errors automatically"""
|
||||||
|
self.log_flask_error(
|
||||||
|
error_type="InternalServerError",
|
||||||
|
error_message=str(error),
|
||||||
|
stack_trace=traceback.format_exc()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return user-friendly error page
|
||||||
|
if self.app.debug:
|
||||||
|
return None # Let Flask handle debug errors
|
||||||
|
|
||||||
|
return '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Server Error</title></head>
|
||||||
|
<body style="font-family: Arial; text-align: center; margin-top: 100px;">
|
||||||
|
<h1>🔧 Something went wrong</h1>
|
||||||
|
<p>We're working to fix this issue. Please try again later.</p>
|
||||||
|
<a href="/" style="color: #2563eb;">← Back to Home</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''', 500
|
||||||
|
|
||||||
|
@self.app.errorhandler(404)
|
||||||
|
def handle_not_found(error):
|
||||||
|
"""Log 404 errors for security monitoring"""
|
||||||
|
self.log_security_event(
|
||||||
|
event_type="page_not_found",
|
||||||
|
description=f"404 error: {request.path}",
|
||||||
|
severity="LOW"
|
||||||
|
)
|
||||||
|
|
||||||
|
return '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Page Not Found</title></head>
|
||||||
|
<body style="font-family: Arial; text-align: center; margin-top: 100px;">
|
||||||
|
<h1>🔍 Page Not Found</h1>
|
||||||
|
<p>The page you're looking for doesn't exist.</p>
|
||||||
|
<a href="/" style="color: #2563eb;">← Back to Home</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''', 404
|
||||||
|
|
||||||
|
def _get_request_context(self):
|
||||||
|
"""Get current request context information"""
|
||||||
|
if not request:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'ip_address': request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr),
|
||||||
|
'user_agent': request.headers.get('User-Agent', ''),
|
||||||
|
'request_path': request.path,
|
||||||
|
'request_method': request.method,
|
||||||
|
'session_id': session.get('_id', 'anonymous'),
|
||||||
|
'user_id': session.get('user_id'),
|
||||||
|
'username': session.get('username')
|
||||||
|
}
|
||||||
|
|
||||||
|
def _log_to_database(self, event_type, event_category, description, event_data=None, severity='INFO'):
|
||||||
|
"""Log critical events to database table"""
|
||||||
|
try:
|
||||||
|
context = self._get_request_context()
|
||||||
|
event_id = str(uuid.uuid4())
|
||||||
|
|
||||||
|
insert_sql = """
|
||||||
|
INSERT INTO log_events (
|
||||||
|
event_id, event_type, event_category, user_id, username,
|
||||||
|
event_description, event_data, ip_address, user_agent,
|
||||||
|
request_path, session_id, severity_level
|
||||||
|
) VALUES (
|
||||||
|
:event_id, :event_type, :event_category, :user_id, :username,
|
||||||
|
:description, :event_data, :ip_address, :user_agent,
|
||||||
|
:request_path, :session_id, :severity
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.db.session.execute(text(insert_sql), {
|
||||||
|
'event_id': event_id,
|
||||||
|
'event_type': event_type,
|
||||||
|
'event_category': event_category,
|
||||||
|
'user_id': context.get('user_id'),
|
||||||
|
'username': context.get('username'),
|
||||||
|
'description': description,
|
||||||
|
'event_data': json.dumps(event_data) if event_data else None,
|
||||||
|
'ip_address': context.get('ip_address'),
|
||||||
|
'user_agent': context.get('user_agent'),
|
||||||
|
'request_path': context.get('request_path'),
|
||||||
|
'session_id': context.get('session_id'),
|
||||||
|
'severity': severity
|
||||||
|
})
|
||||||
|
|
||||||
|
self.db.session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Don't let logging errors break the application
|
||||||
|
print(f"Database logging error: {e}")
|
||||||
|
try:
|
||||||
|
self.db.session.rollback()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# USER LOGIN/LOGOUT LOGGING METHODS
|
||||||
|
|
||||||
|
def log_user_login(self, user_id, username, success=True, failure_reason=None):
|
||||||
|
"""Log user login attempts with detailed session information"""
|
||||||
|
context = self._get_request_context()
|
||||||
|
|
||||||
|
event_data = {
|
||||||
|
'user_id': user_id,
|
||||||
|
'username': username,
|
||||||
|
'success': success,
|
||||||
|
'login_timestamp': datetime.now().isoformat(),
|
||||||
|
'session_info': {
|
||||||
|
'session_id': context.get('session_id'),
|
||||||
|
'ip_address': context.get('ip_address'),
|
||||||
|
'user_agent': context.get('user_agent')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if failure_reason:
|
||||||
|
event_data['failure_reason'] = failure_reason
|
||||||
|
|
||||||
|
if success:
|
||||||
|
message = f"User login successful: {username} (ID: {user_id})"
|
||||||
|
self.logger.info(json.dumps({
|
||||||
|
'event': 'user_login_success',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database for security monitoring
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='user_login_success',
|
||||||
|
event_category='authentication',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data,
|
||||||
|
severity='INFO'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
message = f"User login failed: {username} - {failure_reason}"
|
||||||
|
self.security_logger.warning(json.dumps({
|
||||||
|
'event': 'user_login_failure',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log failed logins to database for security analysis
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='user_login_failure',
|
||||||
|
event_category='security',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data,
|
||||||
|
severity='WARNING'
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_user_logout(self, user_id, username, session_duration=None):
|
||||||
|
"""Log user logout events with session duration"""
|
||||||
|
context = self._get_request_context()
|
||||||
|
|
||||||
|
event_data = {
|
||||||
|
'user_id': user_id,
|
||||||
|
'username': username,
|
||||||
|
'logout_timestamp': datetime.now().isoformat(),
|
||||||
|
'session_duration_minutes': session_duration,
|
||||||
|
'session_info': {
|
||||||
|
'session_id': context.get('session_id'),
|
||||||
|
'ip_address': context.get('ip_address')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message = f"User logout: {username} (ID: {user_id})"
|
||||||
|
if session_duration:
|
||||||
|
message += f" - Session duration: {session_duration} minutes"
|
||||||
|
|
||||||
|
self.logger.info(json.dumps({
|
||||||
|
'event': 'user_logout',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='user_logout',
|
||||||
|
event_category='authentication',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data
|
||||||
|
)
|
||||||
|
|
||||||
|
# QR CODE LOGGING METHODS
|
||||||
|
|
||||||
|
def log_qr_code_created(self, qr_code_id, qr_code_name, created_by_user_id, qr_data):
|
||||||
|
"""Log QR code creation events"""
|
||||||
|
event_data = {
|
||||||
|
'qr_code_id': qr_code_id,
|
||||||
|
'qr_code_name': qr_code_name,
|
||||||
|
'created_by_user_id': created_by_user_id,
|
||||||
|
'created_timestamp': datetime.now().isoformat(),
|
||||||
|
'qr_code_details': {
|
||||||
|
'location': qr_data.get('location'),
|
||||||
|
'location_address': qr_data.get('location_address'),
|
||||||
|
'location_event': qr_data.get('location_event'),
|
||||||
|
'has_coordinates': qr_data.get('has_coordinates', False)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if qr_data.get('has_coordinates'):
|
||||||
|
event_data['qr_code_details']['coordinates'] = {
|
||||||
|
'latitude': qr_data.get('latitude'),
|
||||||
|
'longitude': qr_data.get('longitude'),
|
||||||
|
'accuracy': qr_data.get('coordinate_accuracy')
|
||||||
|
}
|
||||||
|
|
||||||
|
message = f"QR code created: {qr_code_name} (ID: {qr_code_id}) by user {created_by_user_id}"
|
||||||
|
|
||||||
|
self.logger.info(json.dumps({
|
||||||
|
'event': 'qr_code_created',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='qr_code_created',
|
||||||
|
event_category='qr_management',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_qr_code_updated(self, qr_code_id, qr_code_name, updated_by_user_id, changes):
|
||||||
|
"""Log QR code modification events"""
|
||||||
|
event_data = {
|
||||||
|
'qr_code_id': qr_code_id,
|
||||||
|
'qr_code_name': qr_code_name,
|
||||||
|
'updated_by_user_id': updated_by_user_id,
|
||||||
|
'updated_timestamp': datetime.now().isoformat(),
|
||||||
|
'changes': changes
|
||||||
|
}
|
||||||
|
|
||||||
|
message = f"QR code updated: {qr_code_name} (ID: {qr_code_id}) by user {updated_by_user_id}"
|
||||||
|
|
||||||
|
self.logger.info(json.dumps({
|
||||||
|
'event': 'qr_code_updated',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='qr_code_updated',
|
||||||
|
event_category='qr_management',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_qr_code_deleted(self, qr_code_id, qr_code_name, deleted_by_user_id):
|
||||||
|
"""Log QR code deletion events"""
|
||||||
|
event_data = {
|
||||||
|
'qr_code_id': qr_code_id,
|
||||||
|
'qr_code_name': qr_code_name,
|
||||||
|
'deleted_by_user_id': deleted_by_user_id,
|
||||||
|
'deleted_timestamp': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
message = f"QR code deleted: {qr_code_name} (ID: {qr_code_id}) by user {deleted_by_user_id}"
|
||||||
|
|
||||||
|
self.logger.warning(json.dumps({
|
||||||
|
'event': 'qr_code_deleted',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database with higher severity
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='qr_code_deleted',
|
||||||
|
event_category='qr_management',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data,
|
||||||
|
severity='WARNING'
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_qr_code_accessed(self, qr_code_id, qr_code_name, access_method='scan'):
|
||||||
|
"""Log QR code access/scan events"""
|
||||||
|
context = self._get_request_context()
|
||||||
|
|
||||||
|
event_data = {
|
||||||
|
'qr_code_id': qr_code_id,
|
||||||
|
'qr_code_name': qr_code_name,
|
||||||
|
'access_method': access_method,
|
||||||
|
'access_timestamp': datetime.now().isoformat(),
|
||||||
|
'access_info': {
|
||||||
|
'ip_address': context.get('ip_address'),
|
||||||
|
'user_agent': context.get('user_agent')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message = f"QR code accessed: {qr_code_name} (ID: {qr_code_id}) via {access_method}"
|
||||||
|
|
||||||
|
self.logger.info(json.dumps({
|
||||||
|
'event': 'qr_code_accessed',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# DATABASE ERROR LOGGING METHODS
|
||||||
|
|
||||||
|
def log_database_error(self, operation, error, query=None, parameters=None):
|
||||||
|
"""Log database operation errors"""
|
||||||
|
event_data = {
|
||||||
|
'operation': operation,
|
||||||
|
'error_type': type(error).__name__,
|
||||||
|
'error_message': str(error),
|
||||||
|
'error_timestamp': datetime.now().isoformat(),
|
||||||
|
'database_info': {
|
||||||
|
'query': query[:500] if query else None, # Truncate long queries
|
||||||
|
'parameters': str(parameters)[:200] if parameters else None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isinstance(error, SQLAlchemyError):
|
||||||
|
event_data['sqlalchemy_error'] = True
|
||||||
|
if hasattr(error, 'orig'):
|
||||||
|
event_data['original_error'] = str(error.orig)
|
||||||
|
|
||||||
|
message = f"Database error in {operation}: {type(error).__name__} - {str(error)}"
|
||||||
|
|
||||||
|
self.logger.error(json.dumps({
|
||||||
|
'event': 'database_error',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database if possible (try/catch to avoid recursive errors)
|
||||||
|
try:
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='database_error',
|
||||||
|
event_category='database',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data,
|
||||||
|
severity='ERROR'
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
# If database logging fails, just continue
|
||||||
|
pass
|
||||||
|
|
||||||
|
def log_database_connection_error(self, error):
|
||||||
|
"""Log database connection failures"""
|
||||||
|
event_data = {
|
||||||
|
'error_type': 'database_connection_failure',
|
||||||
|
'error_message': str(error),
|
||||||
|
'error_timestamp': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
message = f"Database connection error: {str(error)}"
|
||||||
|
|
||||||
|
self.logger.critical(json.dumps({
|
||||||
|
'event': 'database_connection_error',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# FLASK ERROR LOGGING METHODS
|
||||||
|
|
||||||
|
def log_flask_error(self, error_type, error_message, stack_trace=None, request_data=None):
|
||||||
|
"""Log Flask application errors"""
|
||||||
|
context = self._get_request_context()
|
||||||
|
|
||||||
|
event_data = {
|
||||||
|
'error_type': error_type,
|
||||||
|
'error_message': error_message,
|
||||||
|
'error_timestamp': datetime.now().isoformat(),
|
||||||
|
'request_context': context,
|
||||||
|
'stack_trace': stack_trace[:2000] if stack_trace else None # Truncate long traces
|
||||||
|
}
|
||||||
|
|
||||||
|
if request_data:
|
||||||
|
event_data['request_data'] = request_data
|
||||||
|
|
||||||
|
message = f"Flask error: {error_type} - {error_message}"
|
||||||
|
|
||||||
|
self.logger.error(json.dumps({
|
||||||
|
'event': 'flask_error',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='flask_error',
|
||||||
|
event_category='application',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data,
|
||||||
|
severity='ERROR'
|
||||||
|
)
|
||||||
|
|
||||||
|
# SECURITY EVENT LOGGING METHODS
|
||||||
|
|
||||||
|
def log_security_event(self, event_type, description, severity='MEDIUM', additional_data=None):
|
||||||
|
"""Log security-related events"""
|
||||||
|
context = self._get_request_context()
|
||||||
|
|
||||||
|
event_data = {
|
||||||
|
'security_event_type': event_type,
|
||||||
|
'severity': severity,
|
||||||
|
'event_timestamp': datetime.now().isoformat(),
|
||||||
|
'request_context': context
|
||||||
|
}
|
||||||
|
|
||||||
|
if additional_data:
|
||||||
|
event_data['additional_data'] = additional_data
|
||||||
|
|
||||||
|
message = f"Security event: {event_type} - {description}"
|
||||||
|
|
||||||
|
self.security_logger.warning(json.dumps({
|
||||||
|
'event': 'security_event',
|
||||||
|
'data': event_data
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Log to database with high priority
|
||||||
|
self._log_to_database(
|
||||||
|
event_type='security_event',
|
||||||
|
event_category='security',
|
||||||
|
description=message,
|
||||||
|
event_data=event_data,
|
||||||
|
severity='WARNING'
|
||||||
|
)
|
||||||
|
|
||||||
|
# UTILITY METHODS
|
||||||
|
|
||||||
|
def get_log_statistics(self, days=7):
|
||||||
|
"""Get logging statistics for the specified number of days"""
|
||||||
|
try:
|
||||||
|
cutoff_date = datetime.now() - timedelta(days=days)
|
||||||
|
|
||||||
|
stats_sql = """
|
||||||
|
SELECT
|
||||||
|
event_category,
|
||||||
|
severity_level,
|
||||||
|
COUNT(*) as event_count
|
||||||
|
FROM log_events
|
||||||
|
WHERE created_timestamp >= :cutoff_date
|
||||||
|
GROUP BY event_category, severity_level
|
||||||
|
ORDER BY event_count DESC
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = self.db.session.execute(text(stats_sql), {
|
||||||
|
'cutoff_date': cutoff_date
|
||||||
|
}).fetchall()
|
||||||
|
|
||||||
|
stats = {}
|
||||||
|
for row in result:
|
||||||
|
category = row.event_category
|
||||||
|
if category not in stats:
|
||||||
|
stats[category] = {}
|
||||||
|
stats[category][row.severity_level] = row.event_count
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_database_error('get_log_statistics', e)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def cleanup_old_logs(self, days_to_keep=90):
|
||||||
|
"""Clean up old log entries from database"""
|
||||||
|
try:
|
||||||
|
cutoff_date = datetime.now() - timedelta(days=days_to_keep)
|
||||||
|
|
||||||
|
cleanup_sql = """
|
||||||
|
DELETE FROM log_events
|
||||||
|
WHERE created_timestamp < :cutoff_date
|
||||||
|
AND severity_level NOT IN ('ERROR', 'CRITICAL')
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = self.db.session.execute(text(cleanup_sql), {
|
||||||
|
'cutoff_date': cutoff_date
|
||||||
|
})
|
||||||
|
|
||||||
|
deleted_count = result.rowcount
|
||||||
|
self.db.session.commit()
|
||||||
|
|
||||||
|
self.logger.info(f"Cleaned up {deleted_count} old log entries")
|
||||||
|
return deleted_count
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_database_error('cleanup_old_logs', e)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# DECORATOR FUNCTIONS FOR AUTOMATIC LOGGING
|
||||||
|
|
||||||
|
def log_user_activity(activity_type):
|
||||||
|
"""Decorator to automatically log user activities"""
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
result = f(*args, **kwargs)
|
||||||
|
|
||||||
|
# Log successful activity
|
||||||
|
if hasattr(g, 'app') and hasattr(g.app, 'logger_handler'):
|
||||||
|
logger = g.app.logger_handler
|
||||||
|
logger.logger.info(json.dumps({
|
||||||
|
'event': f'user_activity_{activity_type}',
|
||||||
|
'data': {
|
||||||
|
'user_id': session.get('user_id'),
|
||||||
|
'username': session.get('username'),
|
||||||
|
'activity': activity_type,
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Log error
|
||||||
|
if hasattr(g, 'app') and hasattr(g.app, 'logger_handler'):
|
||||||
|
logger = g.app.logger_handler
|
||||||
|
logger.log_flask_error(
|
||||||
|
error_type=f"activity_error_{activity_type}",
|
||||||
|
error_message=str(e),
|
||||||
|
stack_trace=traceback.format_exc()
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def log_database_operations(operation_name):
|
||||||
|
"""Decorator to automatically log database operations"""
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
result = f(*args, **kwargs)
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Log database error
|
||||||
|
if hasattr(g, 'app') and hasattr(g.app, 'logger_handler'):
|
||||||
|
logger = g.app.logger_handler
|
||||||
|
logger.log_database_error(
|
||||||
|
operation=operation_name,
|
||||||
|
error=e
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return decorated_function
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZATION FUNCTION
|
||||||
|
|
||||||
|
def init_logging(app, db):
|
||||||
|
"""Initialize the logging system with the Flask app"""
|
||||||
|
logger_handler = AppLogger(app, db)
|
||||||
|
return logger_handler
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
+719
-272
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user