Przejdź do treści

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

Autor: Maciej Walczak
Spis treści
Robot AI przy laptopie analizuje listę zgłoszeń z Sentry – grupuje podobne błędy, wykrywa przyczyny, wskazuje priorytety i proponuje rozwiązania

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łoUdział
lokalne maszyny (zanim wyłączyłem raportowanie z dev)ok. 66%
raporty CSP z przeglądarek odwiedzającychok. 30%
błędy aplikacji z produkcjiok. 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ń:

  1. Lokalny nuxt dev przestał raportować. Błędy z HMR, z restartów serwera i z testowych danych zjadały limit i zasypywały listę.
  2. 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.
  3. Poprawne środowisko. Serwer brał nazwę środowiska z NODE_ENV. Tyle że każdy build produkcyjny ma NODE_ENV=production – więc staging udawał produkcję. Teraz źródłem jest VERCEL_ENV (production / preview), a przeglądarka dostaje to samo przez runtime config.
  4. 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:

  1. Stałe namiary – organizacja, region, projekt, środowiska – żeby agent nie szukał ich od zera.
  2. Tryb tylko do odczytu. Przegląd niczego w Sentry nie zmienia – zamykanie dopiero po mojej akceptacji, zawsze z powodem zapisanym w komentarzu.
  3. 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.
  4. 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.
  5. Weryfikacja kandydatów – czy stack ma ramki z kodu aplikacji, czy błąd był złapany, z którego wydania pochodzi.
  6. 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.
  7. Format raportu – od najważniejszego: Wymaga reakcji → Do sprawdzenia → Do zamknięcia → Szum → liczby.
  8. 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…

Dodaj komentarz

  • Artykuły

    GA4 i Claude Code – analityka www wsparta przez AI

    Statystyki sklepu potrafią pokazywać liczby, które nie mają nic wspólnego z rzeczywistością. Jak AI pomaga mi to sprawdzać – na przykładzie flora-art.pl.

    12 min czytania

  • Artykuły

    Programista w czasach AI

    Czy AI zabierze nam robotę? Nie sądzę. Ale zmieni ją tak, że za kilka lat sami jej nie poznamy – i mam na to dowód w postaci 543 commitów…

    13 min czytania