190 Zeilen
6.1 KiB
Python
190 Zeilen
6.1 KiB
Python
import logging
|
|
import logging.handlers
|
|
import json
|
|
import sys
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Dict, Any
|
|
from flask import g, request, has_request_context
|
|
|
|
|
|
class StructuredFormatter(logging.Formatter):
|
|
def format(self, record):
|
|
log_data = {
|
|
'timestamp': datetime.utcnow().isoformat(),
|
|
'level': record.levelname,
|
|
'logger': record.name,
|
|
'message': record.getMessage(),
|
|
'module': record.module,
|
|
'function': record.funcName,
|
|
'line': record.lineno
|
|
}
|
|
|
|
if has_request_context():
|
|
log_data['request'] = {
|
|
'method': request.method,
|
|
'path': request.path,
|
|
'remote_addr': request.remote_addr,
|
|
'user_agent': request.user_agent.string,
|
|
'request_id': getattr(g, 'request_id', 'unknown')
|
|
}
|
|
|
|
if hasattr(record, 'request_id'):
|
|
log_data['request_id'] = record.request_id
|
|
|
|
if hasattr(record, 'error_code'):
|
|
log_data['error_code'] = record.error_code
|
|
|
|
if hasattr(record, 'details') and record.details:
|
|
log_data['details'] = self._sanitize_details(record.details)
|
|
|
|
if record.exc_info:
|
|
log_data['exception'] = {
|
|
'type': record.exc_info[0].__name__,
|
|
'message': str(record.exc_info[1]),
|
|
'traceback': self.formatException(record.exc_info)
|
|
}
|
|
|
|
return json.dumps(log_data, ensure_ascii=False)
|
|
|
|
def _sanitize_details(self, details: Dict[str, Any]) -> Dict[str, Any]:
|
|
sensitive_fields = {
|
|
'password', 'secret', 'token', 'api_key', 'authorization',
|
|
'credit_card', 'ssn', 'pin'
|
|
}
|
|
|
|
sanitized = {}
|
|
for key, value in details.items():
|
|
if any(field in key.lower() for field in sensitive_fields):
|
|
sanitized[key] = '[REDACTED]'
|
|
elif isinstance(value, dict):
|
|
sanitized[key] = self._sanitize_details(value)
|
|
else:
|
|
sanitized[key] = value
|
|
|
|
return sanitized
|
|
|
|
|
|
class ErrorLevelFilter(logging.Filter):
|
|
def __init__(self, min_level=logging.ERROR):
|
|
self.min_level = min_level
|
|
|
|
def filter(self, record):
|
|
return record.levelno >= self.min_level
|
|
|
|
|
|
def setup_logging(app):
|
|
log_level = os.getenv('LOG_LEVEL', 'INFO').upper()
|
|
log_dir = os.getenv('LOG_DIR', 'logs')
|
|
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
|
|
root_logger = logging.getLogger()
|
|
root_logger.setLevel(getattr(logging, log_level))
|
|
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
console_handler.setLevel(logging.INFO)
|
|
|
|
if app.debug:
|
|
console_formatter = logging.Formatter(
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
else:
|
|
console_formatter = StructuredFormatter()
|
|
|
|
console_handler.setFormatter(console_formatter)
|
|
root_logger.addHandler(console_handler)
|
|
|
|
app_log_handler = logging.handlers.RotatingFileHandler(
|
|
os.path.join(log_dir, 'app.log'),
|
|
maxBytes=10 * 1024 * 1024,
|
|
backupCount=10
|
|
)
|
|
app_log_handler.setLevel(logging.DEBUG)
|
|
app_log_handler.setFormatter(StructuredFormatter())
|
|
root_logger.addHandler(app_log_handler)
|
|
|
|
error_log_handler = logging.handlers.RotatingFileHandler(
|
|
os.path.join(log_dir, 'errors.log'),
|
|
maxBytes=10 * 1024 * 1024,
|
|
backupCount=10
|
|
)
|
|
error_log_handler.setLevel(logging.ERROR)
|
|
error_log_handler.setFormatter(StructuredFormatter())
|
|
error_log_handler.addFilter(ErrorLevelFilter())
|
|
root_logger.addHandler(error_log_handler)
|
|
|
|
security_logger = logging.getLogger('security')
|
|
security_handler = logging.handlers.RotatingFileHandler(
|
|
os.path.join(log_dir, 'security.log'),
|
|
maxBytes=10 * 1024 * 1024,
|
|
backupCount=20
|
|
)
|
|
security_handler.setFormatter(StructuredFormatter())
|
|
security_logger.addHandler(security_handler)
|
|
security_logger.setLevel(logging.INFO)
|
|
|
|
werkzeug_logger = logging.getLogger('werkzeug')
|
|
werkzeug_logger.setLevel(logging.WARNING)
|
|
|
|
@app.before_request
|
|
def log_request_info():
|
|
logger = logging.getLogger('request')
|
|
logger.info(
|
|
'Request started',
|
|
extra={
|
|
'request_id': getattr(g, 'request_id', 'unknown'),
|
|
'details': {
|
|
'method': request.method,
|
|
'path': request.path,
|
|
'query_params': dict(request.args),
|
|
'content_length': request.content_length
|
|
}
|
|
}
|
|
)
|
|
|
|
@app.after_request
|
|
def log_response_info(response):
|
|
logger = logging.getLogger('request')
|
|
logger.info(
|
|
'Request completed',
|
|
extra={
|
|
'request_id': getattr(g, 'request_id', 'unknown'),
|
|
'details': {
|
|
'status_code': response.status_code,
|
|
'content_length': response.content_length or 0
|
|
}
|
|
}
|
|
)
|
|
return response
|
|
|
|
|
|
def get_logger(name: str) -> logging.Logger:
|
|
return logging.getLogger(name)
|
|
|
|
|
|
def log_error(logger: logging.Logger, message: str, error: Exception = None, **kwargs):
|
|
extra = kwargs.copy()
|
|
|
|
if error:
|
|
extra['error_type'] = type(error).__name__
|
|
extra['error_message'] = str(error)
|
|
|
|
if hasattr(error, 'code'):
|
|
extra['error_code'] = error.code
|
|
if hasattr(error, 'details'):
|
|
extra['details'] = error.details
|
|
|
|
logger.error(message, exc_info=error, extra=extra)
|
|
|
|
|
|
def log_security_event(event_type: str, message: str, **details):
|
|
logger = logging.getLogger('security')
|
|
logger.warning(
|
|
f"Security Event: {event_type} - {message}",
|
|
extra={
|
|
'security_event': event_type,
|
|
'details': details,
|
|
'request_id': getattr(g, 'request_id', 'unknown') if has_request_context() else None
|
|
}
|
|
) |