268 lines
9.0 KiB
Python
268 lines
9.0 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 src.infrastructure.logging.GlobalLogger import error_logger
|
|
|
|
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': [
|
|
'./logs/aniworld.log',
|
|
'./logs/auth_failures.log',
|
|
'./logs/downloads.log'
|
|
]
|
|
}
|
|
|
|
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 src.infrastructure.logging.GlobalLogger import error_logger
|
|
# Use standard logging level update
|
|
numeric_level = getattr(logging, config.log_level.upper(), logging.INFO)
|
|
logging.getLogger().setLevel(numeric_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 src.infrastructure.logging.GlobalLogger import error_logger
|
|
# Return basic log files
|
|
log_files = [
|
|
'./logs/aniworld.log',
|
|
'./logs/auth_failures.log',
|
|
'./logs/downloads.log'
|
|
]
|
|
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 src.infrastructure.logging.GlobalLogger import error_logger
|
|
# Since we don't have log_config.cleanup_old_logs(), simulate the cleanup
|
|
cleaned_files = [] # Would implement actual cleanup logic here
|
|
|
|
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 src.infrastructure.logging.GlobalLogger import error_logger
|
|
# log_auth_failure would be implemented here
|
|
pass
|
|
except ImportError:
|
|
pass
|
|
|
|
# Test download progress logging
|
|
try:
|
|
from src.infrastructure.logging.GlobalLogger import error_logger
|
|
# log_download_progress would be implemented here
|
|
pass
|
|
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 |