2025-09-29 14:53:25 +02:00

256 lines
8.7 KiB
Python

"""
API endpoints for logging configuration and management.
"""
from flask import Blueprint, jsonify, request, send_file
from web.controllers.auth_controller import require_auth
from config import config
import logging
import os
from datetime import datetime
logger = logging.getLogger(__name__)
logging_bp = Blueprint('logging', __name__, url_prefix='/api/logging')
@logging_bp.route('/config', methods=['GET'])
@require_auth
def get_logging_config():
"""Get current logging configuration."""
try:
# Import here to avoid circular imports
from server.infrastructure.logging.config import logging_config as log_config
config_data = {
'log_level': config.log_level,
'enable_console_logging': config.enable_console_logging,
'enable_console_progress': config.enable_console_progress,
'enable_fail2ban_logging': config.enable_fail2ban_logging,
'log_files': log_config.get_log_files() if hasattr(log_config, 'get_log_files') else []
}
return jsonify({
'success': True,
'config': config_data
})
except Exception as e:
logger.error(f"Error getting logging config: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500
@logging_bp.route('/config', methods=['POST'])
@require_auth
def update_logging_config():
"""Update logging configuration."""
try:
data = request.get_json() or {}
# Update log level
log_level = data.get('log_level', config.log_level)
if log_level in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
config.log_level = log_level
# Update console logging settings
if 'enable_console_logging' in data:
config.enable_console_logging = bool(data['enable_console_logging'])
if 'enable_console_progress' in data:
config.enable_console_progress = bool(data['enable_console_progress'])
if 'enable_fail2ban_logging' in data:
config.enable_fail2ban_logging = bool(data['enable_fail2ban_logging'])
# Save configuration
config.save_config()
# Update runtime logging level
try:
from server.infrastructure.logging.config import logging_config as log_config
log_config.update_log_level(config.log_level)
except ImportError:
# Fallback for basic logging
numeric_level = getattr(logging, config.log_level.upper(), logging.INFO)
logging.getLogger().setLevel(numeric_level)
logger.info(f"Logging configuration updated: level={config.log_level}, console={config.enable_console_logging}")
return jsonify({
'success': True,
'message': 'Logging configuration updated successfully',
'config': {
'log_level': config.log_level,
'enable_console_logging': config.enable_console_logging,
'enable_console_progress': config.enable_console_progress,
'enable_fail2ban_logging': config.enable_fail2ban_logging
}
})
except Exception as e:
logger.error(f"Error updating logging config: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500
@logging_bp.route('/files', methods=['GET'])
@require_auth
def list_log_files():
"""Get list of available log files."""
try:
from server.infrastructure.logging.config import logging_config as log_config
log_files = log_config.get_log_files()
return jsonify({
'success': True,
'files': log_files
})
except Exception as e:
logger.error(f"Error listing log files: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500
@logging_bp.route('/files/<filename>/download', methods=['GET'])
@require_auth
def download_log_file(filename):
"""Download a specific log file."""
try:
# Security: Only allow log files
if not filename.endswith('.log'):
return jsonify({
'success': False,
'error': 'Invalid file type'
}), 400
log_directory = "logs"
file_path = os.path.join(log_directory, filename)
# Security: Check if file exists and is within log directory
if not os.path.exists(file_path) or not os.path.abspath(file_path).startswith(os.path.abspath(log_directory)):
return jsonify({
'success': False,
'error': 'File not found'
}), 404
return send_file(
file_path,
as_attachment=True,
download_name=f"{filename}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
)
except Exception as e:
logger.error(f"Error downloading log file {filename}: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500
@logging_bp.route('/files/<filename>/tail', methods=['GET'])
@require_auth
def tail_log_file(filename):
"""Get the last N lines from a log file."""
try:
# Security: Only allow log files
if not filename.endswith('.log'):
return jsonify({
'success': False,
'error': 'Invalid file type'
}), 400
lines = int(request.args.get('lines', 100))
lines = min(lines, 1000) # Limit to 1000 lines max
log_directory = "logs"
file_path = os.path.join(log_directory, filename)
# Security: Check if file exists and is within log directory
if not os.path.exists(file_path) or not os.path.abspath(file_path).startswith(os.path.abspath(log_directory)):
return jsonify({
'success': False,
'error': 'File not found'
}), 404
# Read last N lines
with open(file_path, 'r', encoding='utf-8') as f:
all_lines = f.readlines()
tail_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
return jsonify({
'success': True,
'lines': [line.rstrip('\n\r') for line in tail_lines],
'total_lines': len(all_lines),
'showing_lines': len(tail_lines)
})
except Exception as e:
logger.error(f"Error tailing log file {filename}: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500
@logging_bp.route('/cleanup', methods=['POST'])
@require_auth
def cleanup_logs():
"""Clean up old log files."""
try:
data = request.get_json() or {}
days = int(data.get('days', 30))
days = max(1, min(days, 365)) # Limit between 1-365 days
from server.infrastructure.logging.config import logging_config as log_config
cleaned_files = log_config.cleanup_old_logs(days)
logger.info(f"Cleaned up {len(cleaned_files)} old log files (older than {days} days)")
return jsonify({
'success': True,
'message': f'Cleaned up {len(cleaned_files)} log files',
'cleaned_files': cleaned_files
})
except Exception as e:
logger.error(f"Error cleaning up logs: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500
@logging_bp.route('/test', methods=['POST'])
@require_auth
def test_logging():
"""Test logging at different levels."""
try:
test_message = "Test log message from web interface"
# Test different log levels
logger.debug(f"DEBUG: {test_message}")
logger.info(f"INFO: {test_message}")
logger.warning(f"WARNING: {test_message}")
logger.error(f"ERROR: {test_message}")
# Test fail2ban logging
try:
from server.infrastructure.logging.config import log_auth_failure
log_auth_failure("127.0.0.1", "test_user")
except ImportError:
pass
# Test download progress logging
try:
from server.infrastructure.logging.config import log_download_progress
log_download_progress("Test Series", "S01E01", 50.0, "1.2 MB/s", "5m 30s")
except ImportError:
pass
return jsonify({
'success': True,
'message': 'Test messages logged successfully'
})
except Exception as e:
logger.error(f"Error testing logging: {e}")
return jsonify({
'success': False,
'error': str(e)
}), 500