Skip to content

fix(bpp): nie raportuj do Rollbara spodziewanej wiszącej referencji w __str__#677

Open
mpasternak wants to merge 2 commits into
devfrom
fix/autor-jednostka-str-wiszacy-fk
Open

fix(bpp): nie raportuj do Rollbara spodziewanej wiszącej referencji w __str__#677
mpasternak wants to merge 2 commits into
devfrom
fix/autor-jednostka-str-wiszacy-fk

Conversation

@mpasternak

Copy link
Copy Markdown
Member

Problem

Autor_Jednostka.__str__ miał już fallback na wypadek błędów podczas
usuwania — komentarz w kodzie mówił o tym wprost:

except Exception:
    zaloguj_polkniety_wyjatek(...)
    # Fallback w przypadku jakichkolwiek błędów podczas usuwania
    return f"Autor_Jednostka #{...}"

Problem: łapał gołe except Exception i raportował wszystko do Rollbara —
łącznie z przypadkiem, dla którego ten fallback w ogóle powstał. Podczas
kaskadowego kasowania Django buduje str() obiektu, który wciąż żyje
w pamięci, choć jego wiersz (i wiersz po drugiej stronie FK) już zniknął:

bpp/models/autor.py:__str__
  → related_descriptors.__get__      (KeyError 'autor' — brak cache'u FK)
  → cacheops/query.py:get
  → DoesNotExist: Autor matching query does not exist.

Efekt: itemy #1098
i #450 w produkcyjnym
strumieniu błędów, mimo że aplikacja zachowywała się poprawnie.

Rozwiązanie

Rozdzielenie dwóch przypadków:

  • ObjectDoesNotExist → log bez Rollbara. Parametr do_rollbar=False
    istnieje w zaloguj_polkniety_wyjatek dokładnie dla takich benignych
    fallbacków (patrz jego docstring).
  • cokolwiek innego → raportuj jak dotąd, bo to naprawdę nieoczekiwane.

Zachowanie widoczne dla użytkownika (tekst fallbacku, brak wyjątku) bez zmian.

Testy

Nowy src/bpp/tests/test_models/test_autor_jednostka_str.py — TDD, test
raportowania najpierw padał (report.called == True).

  • razem z test_autor_jednostka_unikalnosc.py i test_autor_scope.py
    34 passed

🤖 Generated with Claude Code

https://claude.ai/code/session_01NcAqeqyqBzNEkkVnhpHDaH

mpasternak and others added 2 commits July 25, 2026 00:01
… __str__

Autor_Jednostka.__str__ miał już fallback na wypadek błędów "podczas
usuwania" — komentarz wprost o tym mówił. Ale łapał gołe `except Exception`
i raportował WSZYSTKO do Rollbara, łącznie z przypadkiem, dla którego ten
fallback powstał: podczas kaskadowego kasowania Django buduje str() obiektu,
który wciąż żyje w pamięci, choć jego wiersz i wiersz po drugiej stronie FK
już zniknął.

Efekt: itemy DoesNotExist: Autor matching query does not exist (#1098, #450)
w produkcyjnym strumieniu błędów, mimo że aplikacja działała poprawnie.

Rozdzielamy: ObjectDoesNotExist → log bez Rollbara (do_rollbar=False,
parametr istniał już w zaloguj_polkniety_wyjatek właśnie dla benignych
fallbacków); cokolwiek innego → raportuj jak dotąd.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NcAqeqyqBzNEkkVnhpHDaH
- Brakował test kierunku odwrotnego: cały sens tej zmiany to rozdzielenie
  dwóch gałęzi, a pokryta była jedna. Nowy test pilnuje, że wyjątek INNY niż
  ObjectDoesNotExist nadal trafia do Rollbara — zweryfikowany mutacją
  (cofnięcie `except ObjectDoesNotExist` do `except Exception` wywala test).

- Dołożona asercja caplog: wyciszamy Rollbara, NIE diagnostykę.

- Sprostowana skala w komentarzu i newsfragmencie. To nie był "strumień" —
  to 9 wystąpień w 2 itemach przez 10 dni. Prawdziwy powód zmiany jest inny
  i teraz jest opisany: hash itemu obejmuje numer linii, więc KAŻDY deploy
  zakładał nowy item i alert szedł od nowa.

- Komentarz nie twierdzi już, że to "kaskadowe kasowanie" jako fakt —
  traceback z Rollbara nie zawiera ramek wywołującego. Wskazujemy easyaudit
  (object_repr liczony w transaction.on_commit) jako najpewniejszy znany
  mechanizm, ten sam, który opisuje komentarz przy Jednostka.__str__.

- Odnotowane wprost: logger `bpp.*` nie ma dziś handlera w LOGGING, więc
  ślad ląduje na stderr przez logging.lastResort. Diagnostyka jest słaba,
  ale to osobny temat — nie powód, by zostawiać fałszywy alarm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NcAqeqyqBzNEkkVnhpHDaH
@mpasternak

Copy link
Copy Markdown
Member Author

Poprawki po self-review

Naniesione w 4d223a336. Review był tu szczególnie przydatny — wychwycił, że
testowałem tylko jedną z dwóch gałęzi, które ten PR wprowadza.

1. Brakujący test kierunku odwrotnego. Cały sens zmiany to rozdzielenie
ObjectDoesNotExist (nie raportuj) od reszty (raportuj). Nic nie broniło przed
cofnięciem except ObjectDoesNotExist z powrotem do except Exception
testy byłyby zielone. Nowy test_str_autor_jednostka_nadal_raportuje_nieoczekiwane_bledy
to zamyka; zweryfikowany mutacją (po cofnięciu rozdzielenia: 1 failed).

2. Asercja caplog. Komentarz obiecywał „log zostaje" — nietestowane.
Teraz jest.

3. Sprostowana skala — przesadziłem. Sprawdziłem w Rollbarze: to 9
wystąpień w 2 itemach przez 10 dni
, a nie „strumień" czy „zaśmiecanie", jak
pisałem w komentarzu i newsfragmencie. Prawdziwy powód zmiany jest inny i wart
zapisania: hash itemu obejmuje numer linii (655 w 1397rc1, 690 w 1398),
więc każdy deploy zakłada nowy item i alert idzie od nowa — mimo że
aplikacja zachowuje się poprawnie. To jest właściwe uzasadnienie i ono jest
teraz w komentarzu.

4. „Kaskadowe kasowanie" nie jest faktem. report_exc_info(sys.exc_info())
daje traceback zaczynający się w ramce z try, więc w itemie nie ma ani
jednej ramki wywołującego
. Mechanizm jest potwierdzony —
easyaudit/signals/model_signals.py odracza post_delete przez
transaction.on_commit, a crud_flows.py liczy object_repr: str(instance)
po commicie; ten sam komentarz jest już przy Jednostka.__str__
(bpp/models/jednostka.py:239) — ale to hipoteza, nie dowód, i komentarz tak
to teraz formułuje.

5. Newsfragment skrócony do dwóch linijek. Review słusznie: klient nie ma
Rollbara, a fragment kończył się zdaniem „nic się dla Ciebie nie zmienia" —
definicja szumu w changelogu.

Odnotowane, poza zakresem tego PR-a

  • bpp.* nie ma handlera w LOGGING (settings/base.py, skonfigurowane są
    tylko django.security, django.request, celery, pbn_import, pbn_api,
    oidc_integration, weasyprint; gunicorn też nie rusza roota). Ślad ląduje
    na stderr przez logging.lastResort, bez timestampu i nazwy loggera. Czyli
    „log zostaje" to dziś słaba pociecha — dopisałem to wprost w komentarzu, żeby
    się tym nie zasłaniać. Naprawa to osobny PR (wpis loggera bpp z handlerem
    console).
  • Autor_Dyscyplina.__str__ (bpp/models/dyscyplina_naukowa.py:151) ma
    bliźniaczo identyczny antywzorzec i ten sam CASCADE od Autor — czeka tam ten
    sam item. Nie rozszerzam tego PR-a.
  • CRUDEvent.object_repr dla kaskadowo skasowanych powiązań jest zapewne
    bezwartościowy
    Collector.delete() zeruje pk (deletion.py:516) przed
    on_commit, więc audyt widzi "Autor_Jednostka #nowy". Preexisting; docelowo
    __str__ w ogóle nie powinien chodzić do bazy.
  • Niespójność konwencji: Jednostka.__str__ rozwiązuje ten sam problem
    wąskimi except Uczelnia.DoesNotExist + gołym pass. Dwie konwencje na jeden
    problem — warto kiedyś ujednolicić.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant