Files
quality_app/py_app/gunicorn.conf.py
Quality System Admin 9c19379810 updated backups solution
2025-11-03 22:18:56 +02:00

168 lines
7.1 KiB
Python

# Gunicorn Configuration File for Trasabilitate Application
# Docker-optimized Production WSGI server configuration
import multiprocessing
import os
# ============================================================================
# SERVER SOCKET CONFIGURATION
# ============================================================================
# Bind to all interfaces on port from environment or default
bind = os.getenv("GUNICORN_BIND", "0.0.0.0:8781")
backlog = int(os.getenv("GUNICORN_BACKLOG", "2048"))
# ============================================================================
# WORKER PROCESSES CONFIGURATION
# ============================================================================
# Calculate workers: For Docker, use CPU count * 2 + 1 (but allow override)
# In Docker, cpu_count() returns container CPU limit if set
workers = int(os.getenv("GUNICORN_WORKERS", multiprocessing.cpu_count() * 2 + 1))
# Worker class - 'sync' is stable for most use cases
# Alternative: 'gevent' or 'gthread' for better concurrency
worker_class = os.getenv("GUNICORN_WORKER_CLASS", "sync")
# Max simultaneous connections per worker
worker_connections = int(os.getenv("GUNICORN_WORKER_CONNECTIONS", "1000"))
# Workers silent for more than this many seconds are killed and restarted
# Increase for long-running requests (file uploads, reports, large backups)
# For 5GB+ database operations, allow up to 30 minutes
timeout = int(os.getenv("GUNICORN_TIMEOUT", "1800")) # 30 minutes
# Keep-alive for reusing connections
keepalive = int(os.getenv("GUNICORN_KEEPALIVE", "5"))
# Graceful timeout - time to wait for workers to finish during shutdown
graceful_timeout = int(os.getenv("GUNICORN_GRACEFUL_TIMEOUT", "30"))
# ============================================================================
# WORKER LIFECYCLE - PREVENT MEMORY LEAKS
# ============================================================================
# Restart workers after this many requests to prevent memory leaks
max_requests = int(os.getenv("GUNICORN_MAX_REQUESTS", "1000"))
max_requests_jitter = int(os.getenv("GUNICORN_MAX_REQUESTS_JITTER", "100"))
# ============================================================================
# LOGGING CONFIGURATION
# ============================================================================
# Docker-friendly: logs to stdout/stderr by default, but allow file logging
# Automatically detect the correct log path based on current directory
default_log_dir = "/srv/quality_app/logs" if "/srv/quality_app" in os.getcwd() else "/srv/quality_recticel/logs"
accesslog = os.getenv("GUNICORN_ACCESS_LOG", f"{default_log_dir}/access.log")
errorlog = os.getenv("GUNICORN_ERROR_LOG", f"{default_log_dir}/error.log")
# For pure Docker logging (12-factor app), use:
# accesslog = "-" # stdout
# errorlog = "-" # stderr
loglevel = os.getenv("GUNICORN_LOG_LEVEL", "info")
# Enhanced access log format with timing and user agent
access_log_format = (
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s '
'"%(f)s" "%(a)s" %(D)s µs'
)
# Capture stdout/stderr in log (useful for print statements)
capture_output = os.getenv("GUNICORN_CAPTURE_OUTPUT", "true").lower() == "true"
# ============================================================================
# PROCESS NAMING & DAEMON
# ============================================================================
proc_name = os.getenv("GUNICORN_PROC_NAME", "trasabilitate_app")
# CRITICAL FOR DOCKER: Never use daemon mode in containers
# Docker needs the process to run in foreground
daemon = False
# ============================================================================
# SECURITY & LIMITS
# ============================================================================
# Request line size limit (protect against large headers)
limit_request_line = int(os.getenv("GUNICORN_LIMIT_REQUEST_LINE", "4094"))
limit_request_fields = int(os.getenv("GUNICORN_LIMIT_REQUEST_FIELDS", "100"))
limit_request_field_size = int(os.getenv("GUNICORN_LIMIT_REQUEST_FIELD_SIZE", "8190"))
# ============================================================================
# PERFORMANCE OPTIMIZATION
# ============================================================================
# Preload application before forking workers
# Pros: Faster worker spawn, less memory if using copy-on-write
# Cons: Code changes require full restart
preload_app = os.getenv("GUNICORN_PRELOAD_APP", "true").lower() == "true"
# Pseudo-random number for load balancing
worker_tmp_dir = os.getenv("GUNICORN_WORKER_TMP_DIR", "/dev/shm")
# ============================================================================
# SSL CONFIGURATION (if needed)
# ============================================================================
# Uncomment and set environment variables if using HTTPS
# keyfile = os.getenv("SSL_KEY_FILE")
# certfile = os.getenv("SSL_CERT_FILE")
# ca_certs = os.getenv("SSL_CA_CERTS")
# ============================================================================
# SERVER HOOKS - LIFECYCLE CALLBACKS
# ============================================================================
def on_starting(server):
"""Called just before the master process is initialized."""
server.log.info("=" * 60)
server.log.info("🚀 Trasabilitate Application - Starting Server")
server.log.info("=" * 60)
server.log.info("📍 Configuration:")
server.log.info(f" • Workers: {workers}")
server.log.info(f" • Worker Class: {worker_class}")
server.log.info(f" • Timeout: {timeout}s")
server.log.info(f" • Bind: {bind}")
server.log.info(f" • Preload App: {preload_app}")
server.log.info(f" • Max Requests: {max_requests} (+/- {max_requests_jitter})")
server.log.info("=" * 60)
def when_ready(server):
"""Called just after the server is started."""
server.log.info("=" * 60)
server.log.info("✅ Trasabilitate Application Server is READY!")
server.log.info(f"📡 Listening on: {server.address}")
server.log.info(f"🌐 Access the application at: http://{bind}")
server.log.info("=" * 60)
def on_exit(server):
"""Called just before exiting Gunicorn."""
server.log.info("=" * 60)
server.log.info("👋 Trasabilitate Application - Shutting Down")
server.log.info("=" * 60)
def worker_int(worker):
"""Called just after a worker exited on SIGINT or SIGQUIT."""
worker.log.info("⚠️ Worker %s received INT or QUIT signal", worker.pid)
def pre_fork(server, worker):
"""Called just before a worker is forked."""
server.log.info("🔄 Forking new worker (pid: %s)", worker.pid)
def post_fork(server, worker):
"""Called just after a worker has been forked."""
server.log.info("✨ Worker spawned successfully (pid: %s)", worker.pid)
def pre_exec(server):
"""Called just before a new master process is forked."""
server.log.info("🔄 Master process forking...")
def worker_abort(worker):
"""Called when a worker received the SIGABRT signal."""
worker.log.warning("🚨 Worker %s received SIGABRT signal - ABORTING!", worker.pid)
def child_exit(server, worker):
"""Called just after a worker has been exited, in the master process."""
server.log.info("👋 Worker %s exited", worker.pid)