diff --git a/app/__init__.py b/app/__init__.py index 168263d..9ff7e26 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -6,6 +6,42 @@ from app.extensions import db, login_manager, migrate, csrf from app.utils.formatters import format_currency, format_percent, format_large_number +def _setup_logging(app): + """ + Ensure app.* module loggers emit at INFO level. + + Under Gunicorn the root logger already has handlers (pointing to Gunicorn's + error log / stderr) but its level is WARNING, so INFO records are dropped + before they reach any handler. We fix that by: + 1. Reusing Gunicorn's handlers on the 'app' namespace logger so records + go to the same destination as Gunicorn's own logs. + 2. Falling back to a plain stderr StreamHandler in dev / direct-run mode. + """ + import sys + + app_log = logging.getLogger('app') + app_log.setLevel(logging.INFO) + + if app_log.handlers: + return # already configured (e.g. running tests) + + gunicorn_handlers = logging.getLogger('gunicorn.error').handlers + if gunicorn_handlers: + # Running under Gunicorn — attach its handlers so our logs land in the + # same error log file that Gunicorn writes to. + for h in gunicorn_handlers: + app_log.addHandler(h) + else: + # Dev / direct python run — stderr is fine. + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(logging.Formatter( + '%(asctime)s %(levelname)s %(name)s: %(message)s' + )) + app_log.addHandler(handler) + + app_log.propagate = False # avoid double-printing via root + + def create_app(config_name=None): if config_name is None: config_name = os.environ.get('FLASK_ENV', 'development') @@ -14,13 +50,11 @@ def create_app(config_name=None): app = Flask(__name__) app.config.from_object(config[config_name]) - # Ensure app-level loggers (services, routes) emit INFO+ to stderr/Gunicorn - if not app.debug and not logging.root.handlers: - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s %(levelname)s %(name)s: %(message)s', - ) - logging.getLogger('app').setLevel(logging.INFO) + # Wire app.* loggers into Gunicorn's error log (or stderr in dev). + # basicConfig is a no-op under Gunicorn because Gunicorn already + # installed root handlers before our app is imported — so we must + # attach handlers explicitly. + _setup_logging(app) db.init_app(app) login_manager.init_app(app)