Files
rar-autopass/app/observ.py
Claude Agent 4a2afc68bf chore: curatare agresiva comentarii — scoatere referinte US/PRD din cod si template-uri
Eliminat zgomotul de trasabilitate (US-xxx, PRD x.x, Rn, OV-x, Tn, decizii/naratiune
istorica) din 41 fisiere app/ + template-uri. Pastrate comentariile care documenteaza
invarianti si logica ne-evidenta (idempotenta/hash, reconciliere anti-duplicat, RAR 500
esec definitiv, creds per cont, WAF User-Agent, 422 fara echo de parola, scope NULL->1),
curatate doar de tokeni.

Verificare: pentru cele 27 module .py curatate, structura de cod (tokeni non-comentariu/
non-string) e IDENTICA fata de HEAD -> doar comentarii/docstring-uri schimbate. Singura
schimbare de cod e in tests/test_web_responsive.py (scos 3 assert pe markeri US-006/007/008,
inlocuite de asertiunile structurale alaturate). 0 tokeni US/PRD reziduali in app/.
Regresie: 896 passed, 1 deselected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:44:24 +00:00

147 lines
5.1 KiB
Python

"""Logger structurat central.
Singurul punct prin care se emit evenimente de aplicatie: garanteaza format,
redactare si dublul canal (app_events in DB + log text rotativ) consistente si
imposibil de ocolit. Best-effort: o cadere a jurnalului NU doboara cererea/worker-ul.
Redactare la SCRIERE (nu la afisare): toate valorile trec prin `redact_pii`
(creds/token mascate integral, VIN/nr partial) inainte de persistare.
"""
from __future__ import annotations
import contextvars
import json
import logging
import logging.handlers
from datetime import datetime, timedelta, timezone
from typing import Any
from .config import get_settings
from .db import get_connection, insert_app_event
from .security import redact_pii, scrub_text
# request_id al cererii curente. Setat de middleware-ul HTTP; disponibil in
# handlerul de erori si aici, fara a polua semnaturile de functii.
request_id_var: contextvars.ContextVar[str | None] = contextvars.ContextVar(
"request_id", default=None
)
_LEVELS = {"DEBUG": 10, "INFO": 20, "WARNING": 30, "WARN": 30, "ERROR": 40, "CRITICAL": 50}
# Sursa implicita a evenimentelor pentru procesul curent. API = 'api' (default);
# worker-ul cheama set_source('worker') la pornire (fisier per-proces).
_DEFAULT_SOURCE = "api"
_loggers: dict[str, logging.Logger] = {}
def set_source(sursa: str) -> None:
"""Fixeaza sursa implicita a evenimentelor (apelata o data de worker la start)."""
global _DEFAULT_SOURCE
_DEFAULT_SOURCE = sursa
def _text_logger(sursa: str) -> logging.Logger:
"""Logger cu RotatingFileHandler pe fisier per-proces (app-<sursa>.log).
Rotatia pe dimensiune e in aplicatie — nu depindem de deploy. Cheia de cache
include calea: la schimbarea log_dir (teste) se creeaza un logger nou, fara a
acumula handlere duplicate pe acelasi fisier.
"""
settings = get_settings()
path = settings.log_dir / f"app-{sursa}.log"
key = str(path)
lg = _loggers.get(key)
if lg is not None:
return lg
lg = logging.getLogger(f"autopass.events::{key}")
lg.setLevel(logging.DEBUG)
lg.propagate = False
try:
settings.log_dir.mkdir(parents=True, exist_ok=True)
handler = logging.handlers.RotatingFileHandler(
path,
maxBytes=settings.log_file_max_bytes,
backupCount=settings.log_file_backup_count,
encoding="utf-8",
)
handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
lg.addHandler(handler)
except Exception: # noqa: BLE001 — fisier indisponibil nu trebuie sa doboare logul DB
pass
_loggers[key] = lg
return lg
def _purge_after(days: int) -> str:
"""now (UTC) + days, in formatul SQLite datetime('now') ('YYYY-MM-DD HH:MM:SS')."""
return (datetime.now(timezone.utc) + timedelta(days=days)).strftime("%Y-%m-%d %H:%M:%S")
def log_event(
tip: str,
*,
nivel: str = "INFO",
account_id: int | None = None,
cod: str | None = None,
mesaj: str | None = None,
context: dict | None = None,
sursa: str | None = None,
request_id: str | None = None,
conn: Any = None,
) -> None:
"""Emite un eveniment: un rand `app_events` + o linie in logul text (acelasi continut redactat).
- `tip`: text liber documentat (lista extensibila).
- `nivel`: DEBUG|INFO|WARNING|ERROR|CRITICAL. Sub `AUTOPASS_LOG_LEVEL` -> ignorat.
- `context`: metadate (submission_id, count, status...) — NU payload PII integral.
- `conn`: reutilizeaza conexiunea apelantului pe calea fierbinte (evita contentie WAL);
None -> deschide/inchide o conexiune proprie.
Best-effort: orice exceptie e inghitita (jurnalul nu trebuie sa rupa fluxul).
"""
try:
settings = get_settings()
min_lvl = _LEVELS.get((settings.log_level or "INFO").upper(), 20)
lvl = (nivel or "INFO").upper()
if _LEVELS.get(lvl, 20) < min_lvl:
return
src = sursa or _DEFAULT_SOURCE
rid = request_id if request_id is not None else request_id_var.get()
mesaj_red = scrub_text(mesaj) if isinstance(mesaj, str) else mesaj
ctx_red = redact_pii(context) if context else None
ctx_json = (
json.dumps(ctx_red, ensure_ascii=False, default=str) if ctx_red is not None else None
)
purge_after = _purge_after(int(settings.log_retention_days))
own = conn is None
c = conn or get_connection()
try:
insert_app_event(
c,
request_id=rid,
account_id=account_id,
sursa=src,
tip=tip,
nivel=lvl,
cod=cod,
mesaj=mesaj_red,
context_json=ctx_json,
purge_after=purge_after,
)
finally:
if own:
c.close()
line = (
f"[{src}] tip={tip} nivel={lvl} cont={account_id} cod={cod} "
f"rid={rid} {mesaj_red or ''}"
)
if ctx_json:
line += f" ctx={ctx_json}"
_text_logger(src).info(scrub_text(line))
except Exception: # noqa: BLE001 — jurnal best-effort (ca notify_signup)
pass