Custom loader GTM — cicha awaria całej analityki przez sGTM
Dodawałem do GTM zdarzenie rejestrujące otwarcie czatu AI na własnej stronie. Trigger, tag, publikacja — rutyna na kwadrans. Otworzyłem stronę, kliknąłem w widżet i zacząłem czekać na request. Po czterdziestu sekundach było jasne, że nie przyjdzie.
Odruch numer jeden: zepsułem coś przy nowym tagu. Odruch numer dwa, po dziesięciu minutach: nie zepsułem nic, bo od wczoraj nie działała cała analityka na tej stronie. Strona ładowała się bez zarzutu, nic na niej nie wyglądało na zepsute.
Gdybym tamtego dnia nie miał do zrobienia akurat tego jednego eventu, nie zauważyłbym niczego.
Czterdzieści sekund, po których zacząłem szukać
Do podglądu zdarzeń używam wtyczki Adswerve dataLayer Inspector+ — wygodniejsza niż zakładka Network, bo pokazuje od razu strukturę pushy zamiast surowych żądań. Po kliknięciu w czat nie pokazała nic.
Konsola nie pomogła, i tu jest sedno. Awaria tego rodzaju nie rzuca wyjątku JavaScript — nie ma czerwonego stack trace’u, nie ma nazwy funkcji, nie ma linii do kliknięcia. Zostaje po niej najwyżej jeden wpis o nieudanym żądaniu zasobu, wśród ostrzeżeń, które i tak masz w konsoli każdego dnia: blokad z Content Security Policy, komunikatów wstrzykiwanych przez wtyczki przeglądarki, hałasu z cudzego kodu. Nikt z tego nie czyta „twoja analityka nie istnieje od wczoraj”.
Konsekwencja jest praktyczna i wykracza poza mój przypadek: skoro nie ma wyjątku, to żadne narzędzie do monitoringu błędów front-endu tego nie zgłosi. Sentry i podobne łapią rzucone wyjątki, nie brak pliku, po który nikt się nie upomniał.
Drugie narzędzie, po które sięgnąłem, to inspektor Consent Mode. Pokazał Inactive, siedem sygnałów w stanie Unknown i gcs | None. Wtedy hipoteza się przesunęła: to nie jest problem jednego tagu, to jest problem całej warstwy.
Objaw, który wskazywał nie na tę warstwę co trzeba
Pierwsze podejrzenie padło na mój własny kod — dzień wcześniej wdrażałem czat AI, więc zmiany były świeże. Sprawdzenie zajęło chwilę i wykluczyło tę ścieżkę:
window.dataLayer
// [['consent','default',{…}],
// ['set','ads_data_redaction',true],
// ['set','url_passthrough',true],
// [{'gtm.start':…, event:'gtm.js'}]]
window.google_tag_data
// undefined
Pierwsza linia mówi, że inline skrypt zgód w <head> wykonał się poprawnie i wepchnął do kolejki wszystko, co miał wepchnąć. Druga mówi, że nikt tej kolejki nie odebrał.
google_tag_data to obiekt, który tworzy dopiero sam GTM po załadowaniu. Consent Mode nie mieszka w dataLayer — dataLayer to tylko tablica z poleceniami czekającymi na konsumenta. Stany zgód, które czyta inspektor, powstają w google_tag_data.ics w momencie, gdy gtm.js przetworzy tę tablicę. Bez GTM nie ma czego odczytać, stąd siedem razy Unknown. I stąd gcs | None: parametr gcs doklejają do żądań tagi Google, a żaden tag się nie odpalił, bo żaden tag nie istniał.
Zostało sprawdzić, dlaczego GTM nie wystartował:
GET https://[moja-domena]/[ścieżka-loadera].js
→ 404, 19 bajtów
Nie 500, nie timeout. Zwykłe „404 page not found” w text/plain — odpowiedź serwera, który żyje, ale nie ma pod tym adresem niczego do oddania. Kontener server-side został wyłączony po stronie dostawcy hostingu sGTM.
Dlaczego padło wszystko, a nie tylko server-side
Tu wchodzi rzecz, którą warto rozumieć przed wdrożeniem, a nie po awarii. Mam włączony custom loader GTM — mechanizm, w którym plik gtm.js nie jest pobierany z googletagmanager.com, tylko serwuje go mój własny kontener sGTM, pod nierozpoznawalną ścieżką. To standardowy element setupu odpornego na blokery treści, opisywałem go przy okazji server-side GTM przez proxy.
Zysk jest realny: w kodzie strony nie ma ani jednej wzmianki o domenach Google, a listy filtrów nie mają czego dopasować. Koszt jest równie realny i o nim mówi się znacznie rzadziej — kontener przestaje być odbiorcą danych, a staje się warunkiem startu całej warstwy tagowania.
proxy same-origin — działała
wyłączony — tu pękł łańcuch
nigdy nie pobrany
nigdy nie wystartowały
<head> wykonał się jako jedyny, bo nie zależy od niczego.
W klasycznym wdrożeniu server-side ten sam incydent wyglądałby zupełnie inaczej: gtm.js przyszedłby z CDN Google, GTM wystartowałby normalnie, Consent Mode działałby poprawnie, tagi odpaliłyby się — i dopiero hity dostałyby 404. Straciłbym dane, ale zachował całą warstwę sterującą, w tym zgody.
Im mocniej domykasz tracking do własnej domeny, tym dłuższy łańcuch zależności bierzesz na siebie. To nie jest argument przeciwko first-party — to argument za tym, żeby wiedzieć, co dokładnie kupujesz.
Dwie awarie, które w raportach wyglądają identycznie
Praktyczna część tej historii: rozróżnienie objawów. W GA4 obie sytuacje dają ten sam płaski wykres, ale w przeglądarce wyglądają zupełnie inaczej i wymagają innej naprawy.
| Co sprawdzasz | Padł loader | Padł transport hitów |
|---|---|---|
window.google_tag_manager | nie istnieje | istnieje, kontener widoczny |
window.google_tag_data | undefined | obiekt ze stanami zgód |
| Inspektor Consent Mode | Inactive, sygnały Unknown | Active, poprawne stany |
| Network | jedno żądanie 404, potem cisza | tagi strzelają, żądania z kodem 4xx/5xx |
| Konsola | bez wyjątku, najwyżej wpis o nieudanym zasobie | bez wyjątku, najwyżej wpisy o nieudanych żądaniach |
| Gdzie szukać | dostępność kontenera, ścieżka loadera, CSP | konfiguracja transport_url, endpoint, certyfikat |
Wiersz „konsola” wygląda w obu kolumnach podobnie i to nie jest przeoczenie. Nieudane żądanie nie jest wyjątkiem, więc nie trafia tam, gdzie szukasz błędów odruchowo — a gdy w konsoli i tak siedzi kilka ostrzeżeń CSP i komunikatów od wtyczek, jeden wpis więcej nie zwraca niczyjej uwagi.
Czego nie złapie alert w GA4
Odruchowa odpowiedź na pytanie „jak się o tym dowiedzieć” brzmi: ustawić alert w GA4 na spadek liczby zdarzeń. Sam bym tak odpowiedział przed tym incydentem.
Problem w tym, że mój kontener był wyłączony przez jeden dzień. Custom insight w GA4 ustawiony na wykrywanie anomalii porównuje okresy i reaguje na odchylenie od tego, co model uznaje za normę. Jeden słaby dzień mieści się w zakresie, który dla większości serwisów jest zwykłym szumem: weekend, święto, koniec kampanii, awaria dostawcy płatności. Alert ze stałym progiem — „zdarzeń mniej niż 1 w ciągu dnia”, a dla strony www nawet w ciągu godziny — zero złapie. Tyle że dowiesz się o nim dopiero wtedy, gdy GA4 przetworzy dane, czyli z opóźnieniem liczonym w godzinach, i tylko o tym, że danych brak, nie o tym, dlaczego.
Stąd wniosek, który uważam za najbardziej użyteczny z całej tej historii: narzędzie, po które sięga się odruchowo, ma okno detekcji dłuższe niż typowa awaria. Alert na dane odpowiada na pytanie „czy dane wyglądają normalnie”. Na pytanie „czy warstwa tagowania w ogóle żyje” odpowiada wyłącznie uptime check uderzający w konkretny endpoint — ścieżkę loadera i endpoint kontenera osobno. To dwa różne pytania i dwa różne narzędzia.
Ile monitoringu jest warte
Nie zbudowałem po tym incydencie żadnej infrastruktury alertowej i uważam, że dobrze zrobiłem. Ta strona to wizytówka i poligon doświadczalny. Dzień bez danych nie kosztuje mnie ani złotówki, a jedynym poszkodowanym jest wykres, na który patrzę z ciekawości.
Kalkulacja odwraca się całkowicie przy wdrożeniu komercyjnym. Tam awaria trackingu nie kończy się dziurą w raporcie — kończy się kampaniami optymalizowanymi na ślepo, zepsutą atrybucją i decyzjami budżetowymi podjętymi na danych, których nie ma. Przy dużej marce dochodzi warstwa, o której łatwo zapomnieć: raport, który się nie zgadza, kosztuje zaufanie do całego zespołu danych, a odbudowa tego zaufania trwa dłużej niż naprawa kontenera.
Ta sama logika zadziałała u mnie przy czacie AI na tej stronie. Ma on identyczny profil cichej awarii: gdy skończą się środki na koncie API, odwiedzający zobaczy komunikat o niedostępności, a prawdziwa przyczyna wyląduje w logach funkcji, których nikt nie czyta. Do wyboru miałem automatyczne doładowanie z karty albo ręczną kontrolę: alerty mailowe przy progach miesięcznych wydatków i przypomnienie w kalendarzu, żeby sprawdzić saldo. Wybrałem ręczną kontrolę i świadomie odrzuciłem automat — nie chcę mechanizmu, który sam sięga po moje pieniądze bez mojej decyzji. Przy wdrożeniu komercyjnym wybrałbym odwrotnie: tam kilkugodzinna przerwa w działaniu widocznej funkcji kosztuje więcej niż ryzyko niekontrolowanego obciążenia karty.
Zasada, którą z tego wyciągam, jest krótka: koszt monitoringu ma odpowiadać kosztowi awarii, a nie ambicjom architekta. Wizytówka nie potrzebuje pagera. Sklep robiący obrót w każdej godzinie potrzebuje go bardziej niż połowy funkcji, które ma w kontenerze.
Co bym wdrożył u klienta
Konkret zamiast listy życzeń — trzy warstwy, każda na inne pytanie:
- Uptime check na ścieżkę loadera i na endpoint kontenera, osobno. Kilka minut konfiguracji w dowolnym zewnętrznym monitorze. To jedyna warstwa, która złapałaby mój przypadek w ciągu kwadransa, a nie po godzinach przetwarzania danych.
- Alert w GA4 na spadek liczby zdarzeń. Nie na awarie krótkie, tylko na powolne wykrwawianie się wdrożenia — tag wyłączony przy deployu, zmieniona nazwa zdarzenia, przypadkowo zawężony trigger.
- Sprawdzenie warstwy sterującej przy każdej publikacji kontenera. Dwadzieścia sekund: czy
google_tag_dataistnieje i czy inspektor pokazujeActive. Ja robię to teraz odruchowo po każdej publikacji — bo nauczyłem się, że publikacja i działanie to dwie różne rzeczy.
Trzy rzeczy warte zapamiętania z tego dnia: konsola bez wyjątku nie jest dowodem, że warstwa tagowania żyje; przy custom loaderze kontener sGTM stoi w łańcuchu wyżej niż GTM, więc jego awaria zabiera także Consent Mode; a alert na dane i uptime check na endpoint odpowiadają na dwa różne pytania i nie zastępują się nawzajem.
Analityka jest ostatnią warstwą produkcyjną, której nikt nie monitoruje. Gdy pada API, dzwoni pager. Gdy pada tracking, wykres robi się płaski i wygląda dokładnie jak spokojna niedziela.