Architektura wielowarstwowa: gdzie należy wdrożyć rejestrowanie błędów / obsługę?

17

Obecnie refaktoryzuję duży podsystem o architekturze wielowarstwowej i staram się zaprojektować skuteczną strategię rejestrowania błędów / obsługi.

Powiedzmy, że moja architektura składa się z następujących trzech warstw:

  • Interfejs publiczny (IE i kontroler MVC)
  • Warstwa Domeny
  • Warstwa dostępu do danych

Moim źródłem zamieszania jest to, gdzie powinienem zaimplementować rejestrowanie błędów / obsługę:

  1. Najłatwiejszym rozwiązaniem byłoby zaimplementowanie rejestrowania na najwyższym poziomie (np. Sterownik interfejsu publicznego \ MVC). Jednak wydaje się to niewłaściwe, ponieważ oznacza przepuszczenie wyjątku przez różne warstwy, a następnie zalogowanie go; zamiast rejestrować wyjątek u źródła.

  2. Rejestrowanie wyjątku u źródła jest oczywiście najlepszym rozwiązaniem, ponieważ mam najwięcej informacji. Mój problem polega na tym, że nie mogę wychwycić każdego wyjątku u źródła bez przechwytywania WSZYSTKICH wyjątków, aw warstwie domena / interfejs publiczny doprowadzi to do wychwycenia wyjątków, które zostały już wychwycone, zarejestrowane i ponownie rzucone przez warstwę poniżej .

  3. Inną możliwą strategią jest połączenie # 1 i # 2; dzięki czemu wychwytuję określone wyjątki na warstwie, które najprawdopodobniej zostaną wyrzucone (IE Łapanie, rejestrowanie i ponowne rzucanie SqlExceptionsw warstwie dostępu do danych), a następnie rejestrowanie dalszych nieprzechwyconych wyjątków na najwyższym poziomie. Wymagałoby to jednak ode mnie złapania i ponownego zarejestrowania każdego wyjątku na najwyższym poziomie, ponieważ nie mogę odróżnić błędów, które już zostały zarejestrowane \ obsługiwane od tych, które nie zostały zarejestrowane.

Oczywiście jest to problem występujący w większości aplikacji, dlatego musi istnieć standardowe rozwiązanie tego problemu, w wyniku którego wyjątki są wychwytywane u źródła i logowane raz; jednak po prostu nie widzę, jak to zrobić.

Uwaga: tytuł tego pytania jest bardzo podobny do „ Rejestrowanie wyjątków w aplikacji wielowarstwowej” ”, jednak odpowiedzi w tym poście nie zawierają szczegółów i nie są wystarczające, aby odpowiedzieć na moje pytanie.

KidCode
źródło
1
Jednym podejściem, którego czasami używam, jest utworzenie własnej podklasy wyjątków (lub RuntimeException) i wyrzucenie jej, w tym oryginalnego wyjątku jako zagnieżdżonego. Oczywiście oznacza to, że podczas przechwytywania wyjątków na wyższych poziomach muszę sprawdzić rodzaj wyjątku (moje własne wyjątki po prostu są ponownie wprowadzane, inne wyjątki są rejestrowane i włączane do nowych instancji moich wyjątków). Ale pracuję solo od dłuższego czasu, więc nie mogę udzielać „oficjalnych” porad.
SJuan76,
4
The easiest solution would be to implement the logging at the top level- Zrób to. Rejestrowanie wyjątków u źródła nie jest dobrym pomysłem, a każda aplikacja, z którą się zetknąłem, była PITA do debugowania. Osoba odpowiedzialna za wywołanie wyjątku powinna ponosić odpowiedzialność.
Justin
Wydaje się, że masz na myśli konkretną technikę / język implementacji, w przeciwnym razie trudno jest zrozumieć twoje stwierdzenie „zarejestrowane wyjątki od tych, które nie były”. Czy możesz podać więcej kontekstu?
Vroomfondel
@Vroomfondel - Masz rację. Używam C #, a zawijanie kodu w każdej warstwie try{ ... } catch(Exception ex) { Log(ex); }spowoduje zapisanie tego samego wyjątku w każdej warstwie. (Wydaje się również, że złym zwyczajem jest wychwytywanie każdego wyjątku na każdej warstwie w bazie kodu.)
KidCode,

Odpowiedzi:

18

Na twoje pytania:

Najłatwiejszym rozwiązaniem byłoby wdrożenie rejestrowania na najwyższym poziomie

Posiadanie wyjątkowej bańki na najwyższym poziomie jest absolutnie poprawnym i wiarygodnym podejściem. Żadna z metod wyższej warstwy nie próbuje kontynuować jakiegoś procesu po awarii, który zwykle nie może zakończyć się sukcesem. Dobrze wyposażony wyjątek zawiera wszystkie informacje niezbędne do logowania. A nie robienie niczego z wyjątkami pomaga utrzymać kod w czystości i skupić się na głównym zadaniu zamiast na błędach.

Rejestrowanie wyjątku u źródła jest oczywiście najlepszym rozwiązaniem, ponieważ mam najwięcej informacji.

To w połowie poprawne. Tak, najbardziej przydatne informacje są tam dostępne. Ale zaleciłbym umieszczenie tego wszystkiego w obiekcie wyjątku (jeśli jeszcze go nie ma) zamiast natychmiastowego logowania. Jeśli logujesz się na niskim poziomie, nadal musisz zgłosić wyjątek, aby poinformować dzwoniących, że nie ukończyłeś pracy. To kończy się w wielu dziennikach tego samego zdarzenia.

Wyjątki

Moją główną wskazówką jest wychwytywanie i rejestrowanie wyjątków tylko na najwyższym poziomie. Wszystkie poniższe warstwy powinny zapewnić, że wszystkie niezbędne informacje o awariach zostaną przeniesione na najwyższy poziom. W aplikacji jednoprocesowej, np. W Javie, oznacza to w większości przypadków, że nie próbuj / nie łapaj ani nie loguj się poza najwyższym poziomem.

Czasami chcesz, aby niektóre informacje kontekstowe były zawarte w dzienniku wyjątków, które nie są dostępne w oryginalnym wyjątku, np. Instrukcja SQL i parametry, które zostały wykonane, gdy wyjątek został zgłoszony. Następnie możesz złapać oryginalny wyjątek i ponownie rzucić nowy, zawierający oryginalny plus kontekst.

Oczywiście prawdziwe życie czasami przeszkadza:

  • W Javie czasami trzeba złapać wyjątek i zawinąć go w inny typ wyjątku, aby zastosować się do niektórych podpisów ustalonych metod. Ale jeśli ponownie rzucisz wyjątek, upewnij się, że ponownie zgłoszony wyjątek zawiera wszystkie informacje potrzebne do późniejszego logowania.

  • Jeśli przekraczasz granicę między procesami, technicznie często nie możesz przenieść pełnego obiektu wyjątku, w tym śledzenia stosu. I oczywiście połączenie może zostać utracone. Oto punkt, w którym usługa powinna rejestrować wyjątki, a następnie dołożyć wszelkich starań, aby przekazać jak najwięcej informacji o awarii przez linię do swojego klienta. Usługa musi upewnić się, że klient otrzyma powiadomienie o awarii, albo otrzymując odpowiedź na awarię, albo uruchamiając limit czasu w przypadku zerwania połączenia. Zwykle spowoduje to, że ten sam błąd nie zostanie zarejestrowany dwukrotnie, raz w usłudze (bardziej szczegółowo) i raz na najwyższym poziomie klienta.

Logowanie

Dodam kilka zdań o logowaniu ogólnie, nie tylko logowaniu wyjątków.

Oprócz wyjątkowych sytuacji, chcesz, aby ważne działania aplikacji również były zapisywane w dzienniku. Tak więc użyj struktury rejestrowania.

Uważaj na poziomy dzienników (czytanie dzienników, w których informacje debugowania i poważne błędy nie są oznaczane odpowiednio, jest uciążliwe!). Typowe poziomy dziennika to:

  • BŁĄD: niektóre funkcje zawiodły nieodwracalnie. Nie musi to oznaczać awarii całego programu, ale niektóre zadania nie mogły zostać wykonane. Zazwyczaj masz obiekt wyjątku opisujący awarię.
  • OSTRZEŻENIE: Wydarzyło się coś dziwnego, ale nie spowodowało niepowodzenia żadnego zadania (wykryto dziwną konfigurację, tymczasowe zerwanie połączenia powodujące ponawianie prób itp.)
  • INFORMACJE: Chcesz przekazać administratorowi systemu jakieś istotne działanie programu (uruchomienie usługi z jej konfiguracją i wersją oprogramowania, importowanie plików danych do bazy danych, logowanie użytkowników do systemu, masz pomysł ...).
  • DEBUG: Rzeczy, które jako programista chcesz zobaczyć podczas debugowania jakiegoś problemu (ale nigdy nie dowiesz się z góry, czego naprawdę potrzebujesz w przypadku tego lub tego konkretnego błędu - jeśli możesz to przewidzieć, naprawisz błąd ). Jedną rzeczą, która zawsze się przydaje, jest rejestrowanie działań na zewnętrznych interfejsach.

W produkcji ustaw poziom dziennika na INFO. Wyniki powinny być przydatne dla administratora systemu, aby wiedział, co się dzieje. Spodziewaj się, że zadzwoni do ciebie po pomoc lub naprawę błędu dla każdego BŁĘDU w dzienniku i połowy OSTRZEŻEŃ.

Włącz poziom DEBUG tylko podczas prawdziwych sesji debugowania.

Pogrupuj wpisy dziennika w odpowiednie kategorie (np. Według w pełni kwalifikowanej nazwy klasy kodu, który generuje wpis), umożliwiając włączenie dzienników debugowania dla określonych części programu.

Ralf Kleberhoff
źródło
Dzięki za tak szczegółową odpowiedź, naprawdę doceniam także część logowania.
KidCode,
-1

Przygotowuję się na przegłosowanie, ale zamierzam wyjść na całość i powiedzieć, że nie jestem pewien, czy mogę się z tym zgodzić.

Spieniężanie wyjątków, a tym bardziej rejestrowanie ich ponownie, to dodatkowy wysiłek przynoszący niewielkie korzyści. Złap wyjątek u źródła (tak, najłatwiej), zaloguj go, ale nie rzucaj ponownie wyjątku, po prostu zgłoś dzwoniącemu „błąd”. „-1”, null, pusty ciąg, trochę wyliczenia, cokolwiek. Dzwoniący musi tylko wiedzieć, że połączenie nie powiodło się, prawie nigdy makabryczne szczegóły. A te będą w twoim dzienniku, prawda? W rzadkim przypadku, gdy osoba dzwoniąca potrzebuje szczegółów, śmiało i bąbelkuj, ​​ale nie jako automatyczne, bezmyślne domyślne.

Bezwarunkowo Przywróć Monikę
źródło
3
Problem z raportowaniem błędu według wartości zwracanych jest następujący: 1. Jeśli rozmówca dba o awarie, musi sprawdzić wartość specjalną. Ale czekaj, nullczy to pusty ciąg? Czy to -1, czy jakaś liczba ujemna? 2. Jeśli rozmówca nie dba (tj. Nie sprawdza), prowadzi to do błędów następczych niezwiązanych z pierwotną przyczyną, np NullPointerException. Lub gorzej: obliczenia trwają z nieprawidłowymi wartościami. 3. Jeśli rozmówca by się przejmował, ale programista nie uważa, że ​​ta metoda zawodzi, kompilator nie przypomina mu. Wyjątki nie mają tego problemu, albo łapiesz, albo rzucasz.
siegi
1
Przepraszam, musiałem to zagłosować. Podejście, które opisujesz (zastąp wyjątki specjalnymi wartościami zwracanymi) było najnowocześniejsze w latach 70., kiedy powstał język C, i istnieje wiele dobrych powodów, dla których współczesne języki mają wyjątki, a dla mnie głównym jest to, że prawidłowe stosowanie wyjątków znacznie ułatwia pisanie solidnego kodu. A wasze „bulgotanie wyjątków w górę [...] to dodatkowy wysiłek [...]” jest po prostu błędne: po prostu nie róbcie nic z wyjątków, a one same się rozwalą.
Ralf Kleberhoff