For å forstå hvordan løsningene våre fungerer i virkeligheten, hva brukerne er mest interessert i, og hva de ikke gidder å bruke — så må vi ha noen form for analyse. Vi trenger også diagnostikk som forteller oss hvorvidt frontenden oppfører seg på enheter “in the wild”. Dessverre er de fleste kommersielle løsningene for dette lite opptatt av personvern, mange lagrer data i USA, og de fleste er dels eller helt på kant med GDPR. Løsningen er å lage sitt eget mottak.

Ved å lage vårt eget mottak har vi kontroll på hvilke data som samles inn om brukeren. Ved å sende til vår egen backend blir det vanskelig for nettleserne å skille trafikken fra annen trafikk, og adblockers utgjør ikke lengre en like stor trussel. Dersom vi passer på å kun samle inn anonyme data trenger vi heller ikke å be brukeren om samtykke med svære, fæle dialoger fulle av “legalese”.

“Er det ikke fryktelig mye jobb å kopiere Google Analytics” lurer du sikker på? Slapp av, vi skal ikke kopiere Google Analytics. Vi skal kun lage et mottak. Dersom dataene er anonyme kan vi ubekymret sende dem til et hvilket som helst analyseverktøy for å få tilgang til alle de gode verktøyene for å lage rapporter, gjøre trakt-analyse, osv. Mixpanel, Google Analytics, Sentry.io og andre verktøy har godt dokumenterte API-er for å sende inn data.

sendBeacon

Nettleseren har en innebygget funksjon dedikert til å samle inn analyse og diagnostikk. sendBeacon(url, data) gjør en asynkron POST til url med “a small amount of data” (MDN). Det fine med sendBeacon er at du trygt kan kalle den selvom brukeren er på vei ut av siden, for nettleseren vil fullføre requesten i bakgrunnen — i skarp kontrast til XMLHttpRequest, som i samme tilfelle blir kansellert av nettleseren.

Så for å samle inn hendelser som beskriver bruken av nettsiden din samler du opp litt kontekst og fyrer avgårde et kall til sendBeacon:

sendBeacon("/stats", JSON.stringify({
  "type": "download-csv",
  "context": {
    "build-date": "2023-04-24T12:36:58.429635Z",
    "git-sha": "af53678a56",
    "language": "en-US",
    "referrer": "http://localhost:8084/",
    "screen-size": "390x844",
    "tracking-id": "59f1e4bf-69eb-41d3-b45d-68a24d46793a",
    "url": "http://localhost:8084/...",
    "viewport-size": "390x844"
  }
}));

Eksempelet viser et event fra appen jeg jobber i til daglig. Vi samler opp noe kontekst, men ikke nok til å fingerprinte brukeren. Vi har også type: "page-view" som fyrer på all navigering i appen.

Mottaket

Å sende eventet fra klienten er bare halve jobben. Du må også ha et endepunkt som tar imot dataene, og enten tar vare på dem — eller sender dem videre til et egnet verktøy.

Google Analytics er et populært verktøy for web-analyse. De har et godt dokumentert API for mottak. En mulig implementasjon av mottaket på serveren er å mappe om dataene fra klienten til noe som passer spec-en til Google, og sende det videre. Deretter kan man bruke mange av Google Analytics sine verktøy som vanlig — med unntak av re-targeting og andre ting som skjender brukernes personvern.

Feil-rapporter

Samme tilnærming kan med hell brukes for å samle diagnostikk fra frontenden. Verktøy som sentry.io er nyttige, men kan være i konflikt med personvernet — og ikke minst kan blokkeres av adblockers og tilsvarende verktøy. Hvis det er én ting du ikke ønsker å miste til en adblocker så er det rapporter om at frontenden din kræsjer.

For å samle opp uventede feil trenger du en lytter på error-eventet som trigges på window:

window.addEventListener("error", function (e) {
  sendBeacon("/errors", JSON.stringify({
    // ...
  }));
});

Her har du error-objektet, informasjon om nettleseren, og all mulig annen nyttig informasjon om appen din. På serveren kan du sende dataene videre til mottaks-API-et til sentry, eller finne på noe eget. På prosjektet mitt har vi denne handleren:

(defn handle-log [{:keys [body] :as request}]
  (let [user-agent (get-in request [:headers "user-agent"])]
    (when-not (exclude-ua? user-agent)
      (doseq [{:keys [level message data]} body]
        (log/log (get-log-level level)
                 message
                 (cond-> data
                   user-agent (assoc :userAgent user-agent)
                   (:stack data) (update :stack stacktrace/improve-stacktrace)
                   (:exception data) (assoc :stack (-> data :exception :stack stacktrace/improve-stacktrace))))))
    {:status 202}))

TLDR: Vi tar imot data fra klienten og logger dem. Dataene havner da i Datadog sammen med resten av loggene våre, og vi finner logg-data og feil fra frontenden på samme sted som vi har data fra backenden. Og det tok noen få timer å smelle opp.

Å lage gode “first-party” løsninger for web-analyse og diagnostikk er ikke krevende. Når du først har mottaket på plass og kan gå god for at det kun er anonyme data er det uproblematisk å sende data videre til tredjeparts verktøy for å se på dem. sendBeacon fungerer i alle moderne nettlesere, så her det ingen grunn til å nøle!