Organizacja błędów aplikacji w środowisku produkcyjnym

Błędy są nieodłącznym elementem procesu wytwarzania oprogramowania. Mamy wypracowane metody zapobiegania, detekcji oraz ich rozwiązywania. Mimo wszystko zdarzają się w świecie produkcyjnego systemu. Trafiłem niedawno na ciekawy cytat, który zainspirował mnie do napisania kilku słów związanych z tematyką błędów.

The best error message is the one that never shows up.
~ Thomas Fuchs

Cytat ten możemy rozpatrywać na dwóch płaszczyznach:

  • popełniania błędów i sposobu ich niwelowania,
  • tuszowania błędów w momencie kiedy już wystąpią.

Ja skupię się na tym drugim punkcie. Ze względu na fakt, że projektując rozwiązania zapominamy o tym, że coś może pójść nie po naszej myśli, a co gorsza prezentując potem całemu światu wnętrzności systemu. Widziałem już różne formy tego jak błędy „wracały” do użytkownika, dlatego też chcę podzielić się prostymi sposobami na ich „zapudrowanie”.

Moim kontekstem wyjściowym jest oprogramowanie z którym mamy do czynienia na co dzień – aplikacja webowa, dostępna przez przeglądarkę internetową.

Brak błędów

Stan idealny. Komunikat o błędzie się nie pojawia ponieważ nie ma błędów w aplikacji, które powodują ich wyświetlanie. Takie oto proste rozwiązanie 🙂

Jako osoby odpowiedzialne za tworzenie oprogramowania powinniśmy dążyć do tego aby w naszym kodzie występowała jak najmniejsza ilość defektów. Czas i koszt ich naprawy rośnie wraz z tym jak późno je wykrywamy oraz jak wielkie zawiłości posiadamy w kodzie (mam tu na myśli chociażby silne powiązania czy duplikację błędnego kodu). Zdarza się, że są błędy których się nie poprawia, ponieważ koszt ich naprawy wielokrotnie przekracza wartość dodaną do aplikacji – uznając, że da się z tym żyć.

Mam tylko pytanie do Ciebie drogi czytelniku – czy znasz (sensowne, nie Hello World) oprogramowanie w pełni wolne od błędów?

Nieraz byliśmy świadkami potężnych konsekwencji, które wynikły z drobnych pomyłek. Lista błędów w oprogramowaniu, które niosły za sobą negatywny skutki robi wrażenie, ale jednocześnie udowadnia, że nawet dysponując grubą kasą, procedurami, powtarzalnymi testami, puszczenie na produkcję błędu jest bardzo prawdopodobne.

Przykładem może być chociażby start rakiety Ariane 5. Próba przekonwertowania 64 bitowej liczby na 16 bitową spowodowała błąd przepełnienia, który doprowadził do zmiany trajektorii lotu, a finalnie do samozniszczenia rakiety. Ponad 7 miliardów (!) dolarów poszło z dymem.

Nie jest łatwo ustrzec się od wszelkich błędów. Nawet przeznaczając miliony dolarów na rozwój oprogramowania i jego silną weryfikację może:

  • przekręcić się licznik,
  • przepełnić bufor,
  • czy zawieść interfejs białkowy.

W omawianym kontekście zazwyczaj konsekwencje wystąpienia błędu nie są aż tak tragiczne jak przedstawione wyżej. Jednak zróbmy coś, co da nam informację zwrotną, a użytkownika końcowego zostawi w przeświadczeniu – oni nad wszystkim panują, co za świetny zespół!

Błędy ujawnione użytkownikowi końcowemu

Istnieją pewne typy informacji, które mogą zostać odebrane przez użytkownika jako błąd. Do takiej grupy zaliczam m.in. brak uprawnień, brak podstrony, link z tokenem którego ważność wygasła.

Często świadomie informujemy użytkownika o tym, że coś poszło nie tak, jednak czy odbierane jest to zgodnie z naszymi intencjami? To też pierwsza linia styku: użytkownik, a potencjalny błąd. Dlatego powinniśmy dać odczuć, że wszystko jest pod naszą kontrolą – prezentując jasny komunikat.

StackOverflow - 404 Page

StackOverflow nie tylko informuje o braku strony ale podaje cztery przydatne linki.

Pierwszym krokiem jest nie tylko wybranie błędów o których będziemy informować użytkownika, ale także zdecydowanie co zostanie sprytnie przed nim ukryte – może strony do których posiada dostęp jedynie administrator nie powinny zwracać statusu 403? Po co zwykły użytkownik ma wiedzieć, że prawidłowo rozwiązujemy w naszej aplikacji adres admin/news, tylko brakuje użytkownikowi uprawnień? Zastanów się i przyjmij określoną politykę postępowania z konkretnymi przypadkami, co więcej zapisz ją i edukuj zespół w tym zakresie.

Następnie należy przygotować dostosowane do aplikacji strony błędów z odpowiednim komunikatem, aby informował użytkownika o tym co się stało. W przypadku błędu 404 istnieje sporo porad w jaki sposób je projektować. Ważne aby domyślne strony błędów z Apache/nginx zostały zastąpione przygotowanymi na tę okoliczność widokami. Zwróć też zgodny z przygotowaną podstroną kod odpowiedzi HTTP.

Apache 404 Page

Pamiętaj, że domyślna podstrona 4xx (np. w serwerze Apache) może być wartościowa dla domorosłego pentestera – po prostu zdradza zbyt wiele szczegółów technicznych, a dzięki nim można szukać odpowiedniego exploita.

Błędy ukryte przed użytkownikiem końcowym

Co zrobić i jak się ustrzec gdy aplikacja zaczyna zawodzić np. poprzez nie przechwycony w odpowiedni sposób wyjątek?

Na pewno nie wyświetlaj widoku tzw. exception handlera z wykorzystanego frameworka. Mówiąc w skrócie, to najbardziej obnażający widok jaki można zaprezentować użytkownikowi. Taka podstrona zdradza architekturę kodu, nazewnictwo zmienny i metod, może wyświetlać dane przekazywane do metod, a nawet wyświetlić pełną konfigurację aplikacji.

Posłużę się przykładem popularnej biblioteki Whoops! w świecie aplikacji opartych o język PHP. Rozwiązanie ma za zadanie pomagać programistom w radzeniu sobie z błędami i wyjątkami.

Whoops!

Możesz także podejrzeć wersję demo.

Wyobraź sobie, że na wdrożenie produkcyjne wkrada się taka forma prezentowania błędów występujących w Twojej aplikacji. W ten sposób przekazujesz ogrom informacji, zazwyczaj niedostępnych dla osób postronnych.

Osoby nietechniczne nie będą wiedzieć co się stało, co to wogóle za hieroglify. Natomiast osoba techniczna może znaleźć niekorzystne dla Ciebie zastosowanie.

Dlatego na środowiskach produkcyjnych lub udostępnionych w ramach beta testów dla użytkowników końcowych wyłączasz szczegóły z exception handlera! Koniec. Zwykle można to zrealizować za pomocą odpowiedniej flagi w konfiguracji.

Oczywiście, widok ten można ograniczać dla wybranych adresów IP, jednak łatwo zapomnieć o zakomentowanym na szybko warunku sprawdzającym dostęp tylko dla wybranych 🙂 Zdecydowanie odradzam takie podejście.

Lepiej zostawić białą stronę (choć i jej powinieneś się pozbyć) niż wyświetlić zestaw informacji, tak jak to robi wspomniana biblioteka Whoops! Idealnym natomiast rozwiązaniem jest omówiona w poprzednim punkcie dedykowana strona błędu. Lakonicznie czy też w sposób humorystyczny prezentująca, że faktycznie coś po stronie aplikacji się nie udało. Może to być problem z nawiązaniem połączenia do usługi zewnętrznej czy brakiem przechwycenia wyjątku z logiki biznesowej – na ten moment, bez znaczenia. Nie zdradzajmy z czym mamy problem.

Error 500

Źródło: daxiongmao.eu

Przy tak skonstruowanej stronie błędu można się śmiało pokusić o ironicznie stwierdzenie – przewidzieliśmy błędy 🙂

Netflix 404 Error

Dodatkowe informacje w postaci identyfikatora, na pierwszy rzut oka niewidoczne dla użytkownika i też nie zdradzające za wiele. Do tego tekst jest tego samego koloru co tło – sprytna sztuczka Netflix 😉 Takie dane mogą być ułatwieniem w późniejszym dochodzeniu tego co się stało.

Błędy odkryte dla zespołu

Ukryliśmy szczegóły błędów, użytkownik nie jest świadomy tego co faktycznie mogło się wydarzyć – wie co najwyżej, że coś zawiodło. Nie oznacza to jednak, że zapominamy o fakcie wystąpienia błędu. Wręcz przeciwnie, jako zespół często odpowiadaliśmy za wdrożenie uruchomione produkcyjnie. Musieliśmy wiedzieć co się dzieje z naszą aplikacją, weryfikować niepożądane zachowania i je eliminować. Dlatego o ile techniczne aspekty błędu ukryte są przed użytkownikiem aplikacji to pełny zakres posiadanej wiedzy musiał być dostępny dla zespołu programistycznego.

Aby nie szukać błędów po omacku wykorzystywaliśmy różne formy gromadzenia informacji o błędach. Były to pliki z stack trace, zewnętrzne narzędzia agregujące logi (ELK Stack), aż po rozwiązania oferowane w modelu SaaS.

Jednym z takich rozwiązań o którym warto wspomnieć jest Sentry, oferujące m.in wsparcie dla najpopularniejszych platform programistycznych (backend & frontend) czy integrację z zewnętrznymi usługami.

Błedy prezentowane w Sentry

Prosty interfejs umożliwia przyjemną pracę z przechwyconymi błędami – przeszukiwanie, analizę i podgląd szczegółów. Na screenie powyżej widać akurat błędy zebrane z części frontendowej.

Warte rozważenia są też wszelkiego rodzaju formy notyfikowania zespołu wybranym kanałem komunikacyjnym (komunikator, mail, sms). Należy jednak uważać z ilością i ważnością przesyłanych komunikatów. Pamiętam jak w pewnym projekcie wszystkie nie przechwycone wyjątki i błędy (nawet każdy notice z PHP) były wysyłane mailowo do wszystkich członków zespołu. W pewnym momencie byliśmy tak nieczuli na te wiadomości, że za pomocą odpowiedniego filtru w kliencie pocztowym kierowaliśmy je bezpośrednio do kosza. Nie chcę rozwijać tutaj wątku przyczyn takiego zachowania, ale jedynie zwrócić uwagę, że nadmiarowość bezpośrednich komunikatów powoduje swego rodzaju znieczulicę na błędy – coś jak teoria rozbitych okien.

Podsumowanie

Chciałbym abyś zapamiętał trzy najważniejsze rzeczy z tego artykułu:

  1. Przygotuj dedykowane podstrony błędów.
  2. Ukryj szczegóły techniczne błędu.
  3. Niech zespół wie jakie błędy pojawiają się w aplikacji.

Jednak od samego zapamiętania za wiele się nie zmieni – spróbuj zastosować w swojej aplikacji sugerowane rozwiązania.

A może masz jeszcze jakąś dodatkową uwagę o której warto wspomnieć?

PS. Rzuć koniecznie okiem na te dwa artykuły tłumaczące dobre praktyki tworzenia error messages:

Zdjęcia: