updated structure and app

This commit is contained in:
Quality System Admin
2025-11-03 19:48:53 +02:00
parent 7fd4b7449d
commit 8d47e6e82d
14 changed files with 3914 additions and 142 deletions

View File

@@ -1,72 +1,165 @@
# Gunicorn Configuration File for Trasabilitate Application
# Production-ready WSGI server configuration
# Docker-optimized Production WSGI server configuration
import multiprocessing
import os
# Server socket
bind = "0.0.0.0:8781"
backlog = 2048
# ============================================================================
# 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
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
# ============================================================================
# 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))
# Restart workers after this many requests, to prevent memory leaks
max_requests = 1000
max_requests_jitter = 50
# Worker class - 'sync' is stable for most use cases
# Alternative: 'gevent' or 'gthread' for better concurrency
worker_class = os.getenv("GUNICORN_WORKER_CLASS", "sync")
# Logging
accesslog = "/srv/quality_recticel/logs/access.log"
errorlog = "/srv/quality_recticel/logs/error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
# Max simultaneous connections per worker
worker_connections = int(os.getenv("GUNICORN_WORKER_CONNECTIONS", "1000"))
# Process naming
proc_name = 'trasabilitate_app'
# Workers silent for more than this many seconds are killed and restarted
# Increase for long-running requests (file uploads, reports)
timeout = int(os.getenv("GUNICORN_TIMEOUT", "120"))
# Daemon mode (set to True for production deployment)
# 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
accesslog = os.getenv("GUNICORN_ACCESS_LOG", "/srv/quality_recticel/logs/access.log")
errorlog = os.getenv("GUNICORN_ERROR_LOG", "/srv/quality_recticel/logs/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
# User/group to run worker processes
# user = "www-data"
# group = "www-data"
# ============================================================================
# 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"))
# Preload application for better performance
preload_app = True
# ============================================================================
# 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"
# Enable automatic worker restarts
max_requests = 1000
max_requests_jitter = 100
# Pseudo-random number for load balancing
worker_tmp_dir = os.getenv("GUNICORN_WORKER_TMP_DIR", "/dev/shm")
# SSL Configuration (uncomment if using HTTPS)
# keyfile = "/path/to/ssl/private.key"
# certfile = "/path/to/ssl/certificate.crt"
# ============================================================================
# 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)
# Security
limit_request_line = 4094
limit_request_fields = 100
limit_request_field_size = 8190
def when_ready(server):
"""Called just after the server is started."""
server.log.info("Trasabilitate Application server is ready. Listening on: %s", server.address)
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 received INT or QUIT signal")
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("Worker spawned (pid: %s)", worker.pid)
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 (pid: %s)", worker.pid)
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.info("Worker received 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 (exit code: %s)", worker.pid, worker.tmp.last_mtime)