2025-07-18 12:34:28 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# shared/monitoring/logging.py
|
|
|
|
|
# ================================================================
|
2025-07-17 13:09:24 +02:00
|
|
|
"""
|
|
|
|
|
Centralized logging configuration for microservices
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
import logging.config
|
|
|
|
|
import os
|
2025-07-18 12:34:28 +02:00
|
|
|
import sys
|
2025-12-14 19:05:37 +01:00
|
|
|
import resource
|
2025-07-17 13:09:24 +02:00
|
|
|
from typing import Dict, Any
|
|
|
|
|
|
2025-07-18 12:34:28 +02:00
|
|
|
def setup_logging(service_name: str, log_level: str = "INFO",
|
|
|
|
|
enable_json: bool = False, enable_file: bool = True) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Set up logging configuration for a microservice with improved error handling.
|
2025-07-17 13:09:24 +02:00
|
|
|
|
2025-07-18 12:34:28 +02:00
|
|
|
Args:
|
|
|
|
|
service_name: Name of the service for log identification
|
|
|
|
|
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
|
|
|
enable_json: Whether to use JSON formatting
|
|
|
|
|
enable_file: Whether to enable file logging
|
|
|
|
|
"""
|
|
|
|
|
|
2025-12-14 19:05:37 +01:00
|
|
|
# Check file descriptor limits
|
|
|
|
|
try:
|
|
|
|
|
soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
|
|
|
if soft_limit < 1024:
|
|
|
|
|
print(f"Warning: Low file descriptor limit ({soft_limit}). Consider increasing with 'ulimit -n'")
|
|
|
|
|
if soft_limit < 256:
|
|
|
|
|
print("Critical: File descriptor limit is very low. File logging may fail.")
|
|
|
|
|
enable_file = False
|
|
|
|
|
except Exception:
|
|
|
|
|
# resource module might not be available on all platforms
|
|
|
|
|
pass
|
|
|
|
|
|
2025-07-18 12:34:28 +02:00
|
|
|
# Create logs directory if it doesn't exist and file logging is enabled
|
|
|
|
|
log_dir = "/var/log"
|
|
|
|
|
if enable_file:
|
|
|
|
|
try:
|
2025-12-14 19:05:37 +01:00
|
|
|
# First try to create/write to /var/log
|
|
|
|
|
test_file = os.path.join(log_dir, f".{service_name}_test")
|
|
|
|
|
with open(test_file, 'w') as f:
|
|
|
|
|
f.write("test")
|
|
|
|
|
os.remove(test_file)
|
|
|
|
|
except (PermissionError, OSError):
|
2025-07-18 12:34:28 +02:00
|
|
|
# Fallback to local directory if can't write to /var/log
|
|
|
|
|
log_dir = "./logs"
|
|
|
|
|
print(f"Warning: Could not write to /var/log, using {log_dir}")
|
2025-12-14 19:05:37 +01:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Warning: Could not create log directory {log_dir}: {e}")
|
|
|
|
|
enable_file = False # Disable file logging if we can't create directory
|
2025-07-18 12:34:28 +02:00
|
|
|
|
|
|
|
|
# Define formatters
|
|
|
|
|
formatters = {
|
|
|
|
|
"standard": {
|
|
|
|
|
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
|
|
|
"datefmt": "%Y-%m-%d %H:%M:%S"
|
|
|
|
|
},
|
|
|
|
|
"detailed": {
|
|
|
|
|
"format": "%(asctime)s [%(levelname)s] %(name)s [%(filename)s:%(lineno)d] %(funcName)s(): %(message)s",
|
|
|
|
|
"datefmt": "%Y-%m-%d %H:%M:%S"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Add JSON formatter if requested and available
|
|
|
|
|
if enable_json:
|
|
|
|
|
try:
|
|
|
|
|
import pythonjsonlogger.jsonlogger
|
|
|
|
|
formatters["json"] = {
|
2025-07-17 13:09:24 +02:00
|
|
|
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
|
2025-07-18 12:34:28 +02:00
|
|
|
"format": "%(asctime)s %(name)s %(levelname)s %(message)s %(filename)s %(lineno)d"
|
2025-07-17 13:09:24 +02:00
|
|
|
}
|
2025-07-18 12:34:28 +02:00
|
|
|
except ImportError:
|
|
|
|
|
print("Warning: pythonjsonlogger not available, falling back to standard formatting")
|
|
|
|
|
enable_json = False
|
|
|
|
|
|
|
|
|
|
# Define handlers
|
|
|
|
|
handlers = {
|
|
|
|
|
"console": {
|
|
|
|
|
"class": "logging.StreamHandler",
|
|
|
|
|
"level": log_level,
|
|
|
|
|
"formatter": "json" if enable_json else "standard",
|
|
|
|
|
"stream": "ext://sys.stdout"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Add file handler if enabled
|
|
|
|
|
if enable_file:
|
2025-12-14 19:05:37 +01:00
|
|
|
try:
|
|
|
|
|
# Test if we can actually write to the log file location
|
|
|
|
|
test_filename = f"{log_dir}/{service_name}.log"
|
|
|
|
|
test_dir = os.path.dirname(test_filename)
|
|
|
|
|
if not os.access(test_dir, os.W_OK):
|
|
|
|
|
print(f"Warning: Cannot write to log directory {test_dir}, disabling file logging")
|
|
|
|
|
enable_file = False
|
|
|
|
|
else:
|
|
|
|
|
handlers["file"] = {
|
|
|
|
|
"class": "logging.FileHandler",
|
|
|
|
|
"level": log_level,
|
|
|
|
|
"formatter": "detailed",
|
|
|
|
|
"filename": test_filename,
|
|
|
|
|
"mode": "a",
|
|
|
|
|
"encoding": "utf-8"
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Warning: Could not configure file handler: {e}")
|
|
|
|
|
enable_file = False
|
2025-07-18 12:34:28 +02:00
|
|
|
|
|
|
|
|
# Add logstash handler if in production
|
|
|
|
|
logstash_host = os.getenv("LOGSTASH_HOST")
|
|
|
|
|
if logstash_host and os.getenv("ENVIRONMENT") == "production":
|
|
|
|
|
try:
|
|
|
|
|
handlers["logstash"] = {
|
2025-07-17 13:09:24 +02:00
|
|
|
"class": "logstash.TCPLogstashHandler",
|
2025-07-18 12:34:28 +02:00
|
|
|
"host": logstash_host,
|
2025-07-17 13:09:24 +02:00
|
|
|
"port": int(os.getenv("LOGSTASH_PORT", "5000")),
|
|
|
|
|
"version": 1,
|
|
|
|
|
"message_type": "logstash",
|
|
|
|
|
"fqdn": False,
|
|
|
|
|
"tags": [service_name]
|
|
|
|
|
}
|
2025-07-18 12:34:28 +02:00
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Warning: Could not setup logstash handler: {e}")
|
|
|
|
|
|
|
|
|
|
# Define root logger configuration
|
|
|
|
|
root_handlers = ["console"]
|
|
|
|
|
if enable_file:
|
|
|
|
|
root_handlers.append("file")
|
|
|
|
|
if "logstash" in handlers:
|
|
|
|
|
root_handlers.append("logstash")
|
|
|
|
|
|
|
|
|
|
# Complete logging configuration
|
|
|
|
|
config: Dict[str, Any] = {
|
|
|
|
|
"version": 1,
|
|
|
|
|
"disable_existing_loggers": False,
|
|
|
|
|
"formatters": formatters,
|
|
|
|
|
"handlers": handlers,
|
2025-07-17 13:09:24 +02:00
|
|
|
"loggers": {
|
2025-07-18 12:34:28 +02:00
|
|
|
"": { # Root logger
|
|
|
|
|
"handlers": root_handlers,
|
2025-07-17 13:09:24 +02:00
|
|
|
"level": log_level,
|
|
|
|
|
"propagate": False
|
|
|
|
|
},
|
|
|
|
|
"uvicorn": {
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"level": log_level,
|
|
|
|
|
"propagate": False
|
|
|
|
|
},
|
|
|
|
|
"uvicorn.access": {
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"level": log_level,
|
|
|
|
|
"propagate": False
|
2025-07-18 12:34:28 +02:00
|
|
|
},
|
|
|
|
|
"sqlalchemy": {
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"level": "WARNING", # Reduce SQL logging noise
|
|
|
|
|
"propagate": False
|
|
|
|
|
},
|
|
|
|
|
"httpx": {
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"level": "WARNING", # Reduce HTTP client logging
|
|
|
|
|
"propagate": False
|
2025-07-17 13:09:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-18 12:34:28 +02:00
|
|
|
try:
|
|
|
|
|
logging.config.dictConfig(config)
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
logger.info(f"Logging configured for {service_name} at level {log_level}")
|
2025-12-14 19:05:37 +01:00
|
|
|
if enable_file:
|
|
|
|
|
logger.info(f"File logging enabled at {log_dir}/{service_name}.log")
|
|
|
|
|
else:
|
|
|
|
|
logger.info("File logging disabled")
|
2025-07-18 12:34:28 +02:00
|
|
|
except Exception as e:
|
|
|
|
|
# Fallback to basic logging if configuration fails
|
|
|
|
|
logging.basicConfig(
|
|
|
|
|
level=getattr(logging, log_level.upper()),
|
|
|
|
|
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
|
|
|
handlers=[logging.StreamHandler(sys.stdout)]
|
|
|
|
|
)
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
logger.error(f"Failed to configure advanced logging for {service_name}: {e}")
|
|
|
|
|
logger.info(f"Using basic logging configuration for {service_name}")
|
2025-12-14 19:05:37 +01:00
|
|
|
|
|
|
|
|
# Additional debugging for file handler issues
|
|
|
|
|
if "file" in str(e).lower() or "handler" in str(e).lower():
|
|
|
|
|
logger.error(f"File handler configuration failed. Check permissions for {log_dir}")
|
|
|
|
|
logger.error(f"Current working directory: {os.getcwd()}")
|
|
|
|
|
logger.error(f"Attempting to write to: {log_dir}/{service_name}.log")
|
2025-07-18 12:34:28 +02:00
|
|
|
|