06/01 Add log viewer
This commit is contained in:
+42
-24
@@ -8,38 +8,54 @@ from app.utils.formatters import format_currency, format_percent, format_large_n
|
||||
|
||||
def _setup_logging(app):
|
||||
"""
|
||||
Ensure app.* module loggers emit at INFO level.
|
||||
Configure the 'app' namespace logger to write to a rotating file AND stderr.
|
||||
|
||||
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.
|
||||
Uses a pipe-delimited format so the log viewer can parse each field easily:
|
||||
2026-06-01 12:00:00|INFO|app.services.teller_service|message text
|
||||
|
||||
The file path comes from LOG_FILE_PATH config (defaults to logs/app.log
|
||||
next to the project root). The directory is created automatically.
|
||||
"""
|
||||
import sys
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
app_log = logging.getLogger('app')
|
||||
app_log.setLevel(logging.INFO)
|
||||
|
||||
if app_log.handlers:
|
||||
return # already configured (e.g. running tests)
|
||||
return # already configured (avoids duplicate handlers on reload)
|
||||
|
||||
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.setLevel(logging.INFO)
|
||||
app_log.propagate = False # don't double-emit through the root logger
|
||||
|
||||
app_log.propagate = False # avoid double-printing via root
|
||||
fmt = logging.Formatter(
|
||||
'%(asctime)s|%(levelname)s|%(name)s|%(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
)
|
||||
|
||||
# --- rotating file handler (primary — always on) ---
|
||||
log_file = app.config.get('LOG_FILE_PATH', '')
|
||||
if log_file:
|
||||
try:
|
||||
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
||||
fh = RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=app.config.get('LOG_MAX_BYTES', 10 * 1024 * 1024),
|
||||
backupCount=app.config.get('LOG_BACKUP_COUNT', 5),
|
||||
encoding='utf-8',
|
||||
)
|
||||
fh.setLevel(logging.INFO)
|
||||
fh.setFormatter(fmt)
|
||||
app_log.addHandler(fh)
|
||||
except Exception as exc:
|
||||
# Can't open file (permissions, bad path) — fall through to stderr only
|
||||
print(f'[pfm] WARNING: could not open log file {log_file!r}: {exc}', file=sys.stderr)
|
||||
|
||||
# --- stderr handler (secondary — also always on so Gunicorn captures it) ---
|
||||
sh = logging.StreamHandler(sys.stderr)
|
||||
sh.setLevel(logging.INFO)
|
||||
sh.setFormatter(fmt)
|
||||
app_log.addHandler(sh)
|
||||
|
||||
app_log.info('Logging initialised — file=%s', log_file or '(none)')
|
||||
|
||||
|
||||
def create_app(config_name=None):
|
||||
@@ -73,6 +89,7 @@ def create_app(config_name=None):
|
||||
from app.routes.reports import reports_bp
|
||||
from app.routes.settings import settings_bp
|
||||
from app.routes.teller import teller_bp
|
||||
from app.routes.logs import logs_bp
|
||||
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(dashboard_bp)
|
||||
@@ -86,6 +103,7 @@ def create_app(config_name=None):
|
||||
app.register_blueprint(reports_bp)
|
||||
app.register_blueprint(settings_bp)
|
||||
app.register_blueprint(teller_bp)
|
||||
app.register_blueprint(logs_bp)
|
||||
|
||||
with app.app_context():
|
||||
from app.models import (
|
||||
|
||||
Reference in New Issue
Block a user