Mniej szumu, więcej wniosków – czyli jak AI przegląda mi zgłoszenia z Sentry

Spis treści

W jednym z ostatnich wpisów GA4 i Claude Code – analityka www wsparta przez AI wspomniałem jednym zdaniem, że sklep flora-art.pl (otwiera się w nowej karcie) korzysta jeszcze z kilku innych narzędzi, w tym z Sentry. Z kolei we wpisie Programista w czasach AI napisałem, że zgłoszenia z Sentry analizuję razem z AI. Obie te wzmianki aż się prosiły o rozwinięcie, więc dziś biorę ten temat na warsztat.
Kiedy pierwszy raz porządnie przejrzałem z Claude Code wszystkie zdarzenia z ostatnich 30 dni, okazało się, że ok. dwie trzecie „błędów” wygenerowałem sam – z własnego laptopa, podczas pracy. Z tego, co przychodziło od prawdziwych klientów, większość zresztą stanowiły raporty bezpieczeństwa z przeglądarek. Błędy samego sklepu z produkcji? Około 4% wszystkich zdarzeń…
Instrukcji instalacji SDK tu nie będzie – oficjalna dokumentacja modułu dla Nuxta zrobi to lepiej niż ja. Opiszę za to, jak z Sentry pracuję: co wysyłam świadomie, co Sentry zbiera sam, jak agent AI czyta zgłoszenia przez MCP, co z tego wynika i jak gaszę szum. No to lecimy…
Po co mi Sentry w małym sklepie?
Krótko o kontekście. Serwis flora-art.pl to strona mojej żony – jej wizytówka, realizacje, a także sklep online – wszystko w 3 wersjach językowych. Pod spodem Nuxt 4 (SSR, ISR, część stron prerenderowana), funkcje serverless na Vercelu, PostgreSQL w Neonie (przez Prismę), kolejka zadań po zamówieniu w Upstash QStash oraz cała gromada integracji: BaseLinker, bramki płatnicze Autopay i Revolut, SMS-y, maile, GA4.
A zespół? Jeden programista (czyli ja) i Claude Code, który pisze większość kodu, a od kilku miesięcy prowadzi też przegląd błędów. Nie ma dyżurów, nie ma działu ops, nie ma nikogo, kto o 3 w nocy patrzy w wykresy. Na wielu ścieżkach Sentry jest jedynym alarmem – np. zadania kolejki, które odpalają się po złożeniu zamówienia, poza Sentry zostawiają ślad tylko w dzienniku zamówienia w bazie.
Do tego dochodzą 2 ograniczenia. Pierwsze to darmowy plan – miesięcznie 5 tys. błędów, 50 nagrań sesji i 5 GB logów. Szału nie ma, więc tracing i nagrania trzeba próbkować, a resztę świadomie wybierać. Drugie – jedynym kanałem powiadomień są maile o nowych zgłoszeniach. Wszystko obsługuje jeden projekt w Sentry, wspólny dla przeglądarki i serwera (oficjalny moduł @sentry/nuxt, SDK 10.x).
Przy takim układzie Sentry nie jest dla mnie „monitoringiem dla zespołu ops”. To raczej skrzynka odbiorcza, którą czyta agent AI – i to zmienia sposób, w jaki trzeba go skonfigurować.
Co wysyłam świadomie
Wyjątki złapane w kodzie
W instrukcjach projektu (CLAUDE.md) jest prosta konwencja: w każdym catch dla błędu, którego się nie spodziewamy, ląduje para console.error + Sentry.captureException, a klient dostaje ogólny komunikat – treść błędu nigdy nie wraca w odpowiedzi HTTP. W uproszczeniu:
try {
await createPayment(order)
} catch (error) {
console.error(error)
Sentry.captureException(error)
throw createError({ statusCode: 500, message: 'Nie udało się utworzyć płatności' })
}
Dotyczy to przede wszystkim ścieżki zakupu i płatności, zadań kolejki (magazyn, PDF, maile, SMS-y, GA4), klientów API integracji oraz zadań cronowych.
Ciekawsze od samej konwencji są jednak 2 wzorce, które z niej wyrosły.
Pierwszy to Sentry jako alarm biznesowy. Gdy kwota w powiadomieniu z bramki płatniczej nie zgadza się z kwotą zamówienia, kod nie rzuca wyjątku – zapisuje status, dodaje wpis w dzienniku zamówienia i sam tworzy błąd, który wysyła do Sentry:
if (paidAmount !== order.total) {
await markOrderForManualCheck(order)
await addOrderLogEntry(order, /* co się nie zgadza */)
Sentry.captureException(new Error(/* opis niezgodności */))
}
Takie zgłoszenie znaczy po prostu „sprawdź tę płatność ręcznie”. Z perspektywy sklepu to jest ważniejsze niż niejeden stack trace. Podobnie z idempotencją: bramki potrafią wysłać to samo powiadomienie 2 razy i właśnie w Sentry widać, czy zabezpieczenie przed podwójnym przetworzeniem zadziałało i czy zadania kolejki dobrze zniosły powtórkę.
Drugi wzorzec: awaria kolejki nie blokuje odpowiedzi. Wysyłka zadań do kolejki siedzi we własnym try/catch – bramka płatnicza dostaje potwierdzenie, a błąd idzie do Sentry i do dziennika zamówienia. W Sentry lądują więc też rzeczy, które procesu nie przerwały, ale zostawiły go niedomkniętym. Bez tego zamówienie mogłoby po cichu nie dotrzeć do magazynu.
Do tego 2 drobiazgi, które spisałem w instrukcjach dla agenta, bo AI lubi o nich zapominać:
- przy opakowywaniu błędu zachowujemy oryginał jako
cause– wtedy w Sentry widać cały łańcuch przyczyn, a nie tylko ogólny komunikat warstwy wyżej - nigdy
JSON.stringify(error)– to gubi stack trace i w Sentry zostaje pusty obiekt
Logi
console.warn i console.error trafiają do Sentry Logs – i po stronie przeglądarki, i po stronie serwera. console.log – świadomie nie. Zadania kolejki zrzucają nim całe odpowiedzi z systemu magazynowego, a tam są dane osobowe z zamówień. Do tego każdy wpis liczy się do limitu planu, a w przeglądarce log to głównie szum ze skryptów zewnętrznych. Logi idą tylko z produkcji – na stagingu i lokalnie konsolę mam pod ręką.
Raporty CSP
Nagłówek Content-Security-Policy w buildzie produkcyjnym ma dyrektywę report-uri wskazującą na Sentry. Każde naruszenie polityki w przeglądarce odwiedzającego staje się zdarzeniem.
CSP wdrażałem 2-etapowo: najpierw w trybie Report-Only (przeglądarka nic nie blokuje, tylko raportuje), uzupełniając listę dozwolonych hostów na podstawie tego, co przyszło do Sentry – a dopiero potem włączyłem egzekwowanie. Kto wdrażał CSP w sklepie z kilkoma integracjami, ten wie, że to dobry pomysł 🙂 Sentry posłużył mi tu za narzędzie do strojenia polityki bezpieczeństwa, a nie tylko do łapania błędów.
Co Sentry daje „gratis”
Reszta danych przychodzi bez ani jednej linijki kodu po mojej stronie – albo prawie bez:
- błędy niezłapane po obu stronach – moduł Nuxta podpina się sam, nie trzeba owijać każdego handlera
- kontekst zdarzenia – przeglądarka, system, trasa, wydanie, środowisko i breadcrumbs (kliknięcia, nawigacja i żądania tuż przed błędem)
- wydania powiązane z commitami przez integrację z Vercelem – przy zgłoszeniu widać, od którego wdrożenia błąd występuje
- source mapy, ale tylko dla przeglądarki – build generuje je jako
hidden, moduł wysyła je do Sentry, więc stack trace pokazuje kod źródłowy (map serwera nie wysyłam – agent i tak trafia do źródeł po nazwach tras i funkcji)
Web Vitals z prawdziwych telefonów
Efekt uboczny tracingu, z którego cieszę się najbardziej: Web Vitals. Każde zmierzone wczytanie strony przynosi dane o LCP, FCP, CLS i TTFB, a INP przychodzi osobno ze spanów interakcji. Zero dodatkowego kodu – i to są dane od prawdziwych odwiedzających.
Wcześniejsze optymalizacje strony głównej (zdjęcia, LCP) mierzyłem w Lighthouse, który mówi tylko, jak strona wypada w teście – Sentry pokazuje, czy ten wynik trzyma się na telefonach klientów. Stan z ostatnich 30 dni (p75): wszystkie 5 metryk w zielonym zakresie Google. LCP i FCP z umiarkowanym zapasem, TTFB, CLS i INP z dużym. Czyli wydajność frontu nie jest dziś tematem do poprawek – i to też jest wniosek, tylko przyjemniejszy 🙂
Session Replay – ale leniwie
Nagrania sesji zbieram przede wszystkim dla zgłoszeń z błędem. Tu jednak był haczyk – integracja Replay waży ok. 190 KB przed kompresją. Po kompresji robi się z tego dużo mniej, ale i tak dorzucanie całej integracji do bundla każdej strony sklepu z kwiatami to średni pomysł, zwłaszcza przy tak ładnych Web Vitals.
Dlatego Replay nie siedzi w bundlu – doładowuje się leniwie z CDN Sentry, kiedy przeglądarka po wczytaniu strony nie ma nic lepszego do roboty (lazyLoadIntegration + requestIdleCallback).
Dwie trzecie błędów zrobiłem sobie sam
Wróćmy do liczb z początku. Wszystkie zdarzenia błędów z ostatnich 30 dni:
| Źródło | Udział |
|---|---|
| lokalne maszyny (zanim wyłączyłem raportowanie z dev) | ok. 66% |
| raporty CSP z przeglądarek odwiedzających | ok. 30% |
| błędy aplikacji z produkcji | ok. 4% |
Najwięcej szumu nie przyszło więc z internetu, tylko z mojego biurka. Poprawki szły po kolei, każda wynikła z przeglądu listy zgłoszeń:
- Lokalny
nuxt devprzestał raportować. Błędy z HMR, z restartów serwera i z testowych danych zjadały limit i zasypywały listę. - Lokalnie zbudowana wersja produkcyjna też przestała. Okazało się, że build odpalony na laptopie zgłaszał się w Sentry jako… produkcja. Teraz Sentry jest włączony tylko wtedy, gdy aplikacja działa na Vercelu (zmienna
VERCEL=1, którą ustawia platforma), a przeglądarka dostaje flagę wyliczoną w czasie budowania. - Poprawne środowisko. Serwer brał nazwę środowiska z
NODE_ENV. Tyle że każdy build produkcyjny maNODE_ENV=production– więc staging udawał produkcję. Teraz źródłem jestVERCEL_ENV(production/preview), a przeglądarka dostaje to samo przez runtime config. - Logi tylko z produkcji i bez poziomu
log(o czym wyżej).
W uproszczeniu wygląda to mniej więcej tak:
Sentry.init({
enabled: process.env.VERCEL === '1',
environment: process.env.VERCEL_ENV, // production / preview
// …reszta konfiguracji
})
Lekcja z tego jest prosta: środowisko w Sentry jest warte tyle, ile zmienna, z której je bierzesz. Najpierw napraw etykiety, potem filtruj. Na błąd z punktu 3 zresztą nie wpadłbym sam z siebie – wyszedł dopiero z tego, że w „produkcji” pojawiały się zdarzenia, których produkcja nie miała prawa wygenerować.
Claude Code i MCP – czyli agent widzi wszystko naraz
Claude Code łączy się z hostowanym serwerem MCP Sentry (mcp.sentry.dev) – transport HTTP, autoryzacja OAuth moim kontem. Konfiguracja siedzi po mojej stronie, a nie w repozytorium, więc w kodzie projektu nie ma żadnego tokenu.
Jedna pułapka: moja organizacja w Sentry jest w regionie UE, który trzeba przekazywać w każdym wywołaniu – bez tego część zapytań trafia pod zły adres. Niby banał, ale jeśli się tego nigdzie nie zapisze, agent odkrywa to od nowa w każdej sesji.
Agent przegląda listę zgłoszeń z filtrami w składni Sentry (is:unresolved, is:regressed, logger:csp i tak dalej), liczy agregaty – ile błędów na środowisko, wydanie, godzinę – zagląda w szczegóły zgłoszeń, logi i trace’y, a na moje polecenie zmienia statusy. Sentry ma też własną analizę AI (Seer), ale z niej jeszcze nie korzystam – całą robotę wykonuje Claude.
Ktoś zapyta: po co agent, skoro w panelu Sentry wszystko widać? Ano po to, że panel pokazuje, co się stało – a agent widzi jednocześnie zgłoszenie, kod, historię gita i dokumentację projektu. Może od razu sprawdzić, czy to już naprawiono, który commit to wprowadził i gdzie jest reguła, która powinna była temu zapobiec. Tak samo jak przy GA4 – liczy się zestawienie kilku źródeł naraz.
Skill zamiast promptu
Sposób pracy z Sentry spisałem jako skill dla agenta – zwykły plik Markdown w repozytorium (.claude/skills/sentry/SKILL.md). Odpala się, gdy pytam o błędy albo proszę o przegląd po wdrożeniu czy sprzątanie zgłoszeń. W skrócie, co jest w środku:
- Stałe namiary – organizacja, region, projekt, środowiska – żeby agent nie szukał ich od zera.
- Tryb tylko do odczytu. Przegląd niczego w Sentry nie zmienia – zamykanie dopiero po mojej akceptacji, zawsze z powodem zapisanym w komentarzu.
- Kolejność zapytań – najpierw błędy produkcyjne bez raportów CSP (inaczej to one zdominują listę), potem CSP osobno, potem wszystko, co nowe, wróciło albo przyspiesza.
- Tabela szumu i tabela rzeczy ważnych – co u nas jest szumem (i dlaczego) oraz czego nigdy nie wolno zbyć jedną linijką. Reguły z obu tabel opisuję niżej.
- Weryfikacja kandydatów – czy stack ma ramki z kodu aplikacji, czy błąd był złapany, z którego wydania pochodzi.
- Sprawdzenie „już naprawione” – zgłoszenia nie zamykają się same po commicie, więc agent porównuje datę ostatniego wystąpienia z datą commita (
git log -S) i proponuje zamknięcie z dowodem. - Format raportu – od najważniejszego: Wymaga reakcji → Do sprawdzenia → Do zamknięcia → Szum → liczby.
- Pułapki – np. zarchiwizowane zgłoszenia nie wchodzą do listy nierozwiązanych, a zgłoszenia mogą zawierać dane osobowe, więc raport tych danych nie cytuje.
Takich ogólnych „promptów do analizy błędów” jest w sieci pełno. Mój skill to po prostu wiedza o tym projekcie, spisana raz: co u nas jest szumem i dlaczego. Każdy wiersz tabeli szumu ma uzasadnienie powiązane z konkretną decyzją w kodzie – np. ze świadomie wąską listą domen w CSP.
Co z tego wyszło?
Bez wchodzenia w szczegóły – kilka kategorii wniosków z przeglądów:
- Problem infrastruktury, nie kodu. Seria błędów puli połączeń z bazą okazała się skutkiem splotu 3 czynników: zasypiającej bazy serverless, odświeżania ISR strony głównej napędzanego przez zewnętrzny monitor dostępności i cache’u, który przy wygaśnięciu sięgał do bazy w tle. Sentry pokazał gdzie – prawie wyłącznie przy rewalidacji jednej trasy. Skończyło się zmianami w cache’u, ISR i regionie funkcji.
- Cudza biblioteka na konkretnych urządzeniach. Wyjątek w kalendarzu do wyboru daty dostawy – tylko na mobilnych przeglądarkach, u pojedynczych klientów, a zamówienia i tak przechodziły. Biblioteka nie jest już rozwijana, więc zamiast owijać w
try/catch, wymieniliśmy ją na komponent z frameworka UI, którego sklep i tak używa. - „Nasz” błąd wywołany cudzym kodem. Rozszerzenia przeglądarki czytały dane strukturalne strony (JSON-LD), zakładając, że to obiekt – a u nas była tablica. Więcej o tym za chwilę.
Jak się tak patrzy na tę listę, to mam wrażenie, że w projekcie tej skali Sentry najwięcej daje, ujawniając złe założenia – o środowiskach, o cudzych skryptach, o tym, kto tak naprawdę generuje ruch na serwerze.
Jak powstaje poprawka
Zwykle przegląd skillem kończy się raportem, a ja wybieram z niego, co naprawiamy. Przy JSON-LD było inaczej – do skrzynki wpadł mi mail z Sentry o wyjątku przypisanym naszej stronie i to zgłoszenie przykuło moją uwagę. Zleciłem agentowi śledztwo. Agent otworzył zdarzenie, przejrzał stack z kodem źródłowym (dzięki source mapom) i znalazł winowajcę: skrypt rozszerzenia przeglądarki, który czyta nasze dane strukturalne i zakłada, że dostanie obiekt. A dostawał tablicę.
Najprościej byłoby dodać filtr i zapomnieć o sprawie. Tyle że w instrukcjach projektu mam zasadę „żadnych rozwiązań tymczasowych” – więc zmieniliśmy kształt danych na równoważny i zgodny ze specyfikacją (obiekt z @graph). Szum zniknął u źródła, a wyszukiwarki dostają dokładnie te same dane.
Potem już rutyna: test przed poprawką, tam, gdzie się da. Następnie weryfikacja w buildzie produkcyjnym i na serwerze deweloperskim, bo w tym projekcie łapią różne klasy błędów – plus jedno prawdziwe wczytanie strony. Commit dostaje identyfikator zgłoszenia i opis przyczyny, więc historia gita staje się drugim indeksem zgłoszeń Sentry. W tym samym kroku aktualizujemy docs/, a to, czego jeszcze nie potwierdziłem w Sentry, ląduje w roadmapie jako „niezweryfikowane”. Następny przegląd sprawdza, czy zgłoszenie nie wróciło (is:regressed).
Jak gaszę szum
Mam 3 poziomy. Najlepiej gasić szum u źródła – konfiguracją SDK (kto raportuje, z jakim środowiskiem, jakie poziomy logów) albo zmianą w kodzie, po której błąd przestaje powstawać, jak przy JSON-LD.
Jeśli się nie da, zostaje klasyfikacja w procedurze: skill zna klasy szumu i raportuje je zbiorczo, jedną linią. U mnie to m.in. raporty CSP od piksela Google z zagranicznych domen (lista domen w CSP jest świadomie bez wildcardu na TLD, a pomiar i tak działa), tłumaczenie strony w przeglądarce, rozszerzenia, pasek narzędzi Vercela, przeglądarki wbudowane w aplikacje społecznościowe i stare chunki JS po wdrożeniu.
Ostatni poziom to status w Sentry – i to dopiero po mojej akceptacji. Szum, który może urosnąć, archiwizuję „do eskalacji”, a na zawsze – tylko wzorce stałe. Realne problemy śledzone w roadmapie też archiwizuję, żeby nie zaśmiecały listy.
Przy okazji spisałem kilka reguł, które moim zdaniem przydadzą się w każdym projekcie:
- Szum liczy się, gdy rośnie. Jeden użytkownik, jedno zdarzenie – można odpuścić. Ten sam wzorzec nagle w dużej skali to sygnał (nowy skrypt zewnętrzny, zmieniony CSP) i trzeba go sprawdzić.
- Klasa szumu ma wyjątki. Błąd ładowania chunków to zwykle stara karta po wdrożeniu. Ale wielu różnych użytkowników w ciągu kilku minut po wydaniu to już rozjechany deploy.
- Raport CSP od hosta z naszej listy to błąd konfiguracji – coś, co ma działać, jest po cichu obcinane.
- „Naprawione” to nie to samo co „zamknięte”. Bez okresowego sprzątania lista nierozwiązanych przestaje cokolwiek znaczyć.
- Ścieżka zakupu ma wagę niezależnie od liczby zdarzeń. Jeden błąd w płatności znaczy więcej niż setki raportów z przeglądarek.
Czy warto?
Jak zwykle odpowiedź brzmi: to zależy. Jeśli ktoś podłączy Sentry „bo wypada” i będzie dostawał maila o każdym nowym zgłoszeniu – to po kilku tygodniach przestanie je otwierać, bo utonie w szumie. Sam bym przestał.
U mnie przełom przyszedł dopiero ze zmianą podejścia. O ile panel Sentry jest dobry do zaglądania w pojedyncze zgłoszenie, o tyle regularny przegląd – z sięganiem do kodu, gita i dokumentacji – to robota, którą agent wykonuje cierpliwiej niż ja. A ja zostaję przy decyzjach: co naprawiamy, co zamykamy, co uznajemy za szum. Sentry przestał być zbiornikiem alertów i stał się skrzynką odbiorczą, z której coś faktycznie wynika.
No i ta lekcja z dwiema trzecimi… Zanim zaczniesz filtrować szum z internetu, sprawdź, ile go robisz sobie sam.
Co dalej?
Porównanie Seera z Claude’em na tych samych zgłoszeniach to temat na osobny wpis – może następnym razem… A jeśli macie własne sposoby na szum w Sentry albo uważacie, że coś tu przekombinowałem – dajcie znać w komentarzach, chętnie skonfrontuję swoje podejście z Waszym 🙂
Komentarze
Ładowanie komentarzy…


