feat: add logging and SMTP error handling

Logging:
- Add structured logging with timestamps
- Log successful and failed login attempts
- Log new orders and order completions
- Log email sending success/failures

SMTP Error Handling:
- Add try/except block around SMTP operations
- Catch authentication errors, SMTP exceptions, and general errors
- Log all email failures with detailed error messages
- Ensure orders are saved even if email fails

This allows monitoring of critical operations and troubleshooting
email delivery issues through systemd journal.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-06 08:11:40 +01:00
parent 630595bce9
commit a8b26a25da

View File

@@ -14,6 +14,7 @@ import secrets
import smtplib import smtplib
import time import time
import threading import threading
import logging
from uuid import uuid4 from uuid import uuid4
from email.message import EmailMessage from email.message import EmailMessage
from functools import wraps from functools import wraps
@@ -31,6 +32,14 @@ DB_PATH = BASE_DIR / "hellas.db"
UPLOAD_DIR = BASE_DIR / "static" / "uploads" UPLOAD_DIR = BASE_DIR / "static" / "uploads"
ALLOWED_EXT = {".png", ".jpg", ".jpeg", ".webp", ".gif"} ALLOWED_EXT = {".png", ".jpg", ".jpeg", ".webp", ".gif"}
# Logging konfigurieren
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
# Optionaler Prefix (z. B. /wawi), wenn die App hinter einem SubPfad läuft. # Optionaler Prefix (z. B. /wawi), wenn die App hinter einem SubPfad läuft.
URL_PREFIX = os.environ.get("URL_PREFIX", "").strip().rstrip("/") URL_PREFIX = os.environ.get("URL_PREFIX", "").strip().rstrip("/")
STATIC_URL_PATH = f"{URL_PREFIX}/static" if URL_PREFIX else "/static" STATIC_URL_PATH = f"{URL_PREFIX}/static" if URL_PREFIX else "/static"
@@ -490,8 +499,10 @@ def login():
).fetchone() ).fetchone()
if row and check_password_hash(row["password_hash"], password): if row and check_password_hash(row["password_hash"], password):
session["user"] = user session["user"] = user
logger.info(f"Erfolgreicher Login: {user}")
nxt = request.args.get("next") or url_for("bp.index") nxt = request.args.get("next") or url_for("bp.index")
return redirect(nxt) return redirect(nxt)
logger.warning(f"Fehlgeschlagener Login-Versuch: {user}")
return render_template("login.html", error=True) return render_template("login.html", error=True)
return render_template("login.html", error=False) return render_template("login.html", error=False)
@@ -653,6 +664,7 @@ def order():
), ),
) )
db.commit() db.commit()
logger.info(f"Neue Bestellung: {data.get('artikel')} ({data.get('groesse')}) x{data.get('menge')} von {data.get('name')}")
to_addr = os.environ.get("ORDER_TO", "bjoern@welker.me") to_addr = os.environ.get("ORDER_TO", "bjoern@welker.me")
smtp_host = os.environ.get("SMTP_HOST") smtp_host = os.environ.get("SMTP_HOST")
@@ -683,10 +695,19 @@ def order():
msg.set_content(body) msg.set_content(body)
def _send(): def _send():
with smtplib.SMTP(smtp_host, smtp_port) as server: """Sendet Bestellungs-Email asynchron mit Error Handling."""
server.starttls() try:
server.login(smtp_user, smtp_pass) with smtplib.SMTP(smtp_host, smtp_port) as server:
server.send_message(msg, to_addrs=recipients) server.starttls()
server.login(smtp_user, smtp_pass)
server.send_message(msg, to_addrs=recipients)
logger.info(f"Bestellungs-Email erfolgreich versendet an {', '.join(recipients)}")
except smtplib.SMTPAuthenticationError as e:
logger.error(f"SMTP-Authentifizierung fehlgeschlagen: {e}")
except smtplib.SMTPException as e:
logger.error(f"SMTP-Fehler beim Email-Versand: {e}")
except Exception as e:
logger.error(f"Unerwarteter Fehler beim Email-Versand: {e}", exc_info=True)
threading.Thread(target=_send, daemon=True).start() threading.Thread(target=_send, daemon=True).start()
@@ -758,6 +779,7 @@ def complete_order(order_id: int):
(user, now_iso(), order_id), (user, now_iso(), order_id),
) )
db.commit() db.commit()
logger.info(f"Bestellung #{order_id} abgeschlossen von {user}")
return redirect(url_for("bp.orders")) return redirect(url_for("bp.orders"))