1. Le rôle de faro.receiver #
Le pont entre navigateur et backend.
Le principe #
Le SDK Grafana Faro collecte dans le navigateur les erreurs JavaScript, les événements, les Core Web Vitals et les traces des appels réseau. Il les envoie en HTTP à faro.receiver, qui écoute par défaut sur le port 12347 et transforme chaque élément en ligne de log (vers Loki) ou en span (vers Tempo).
2. Une configuration prête pour la production #
Sécuriser un point d’entrée public.
config.alloy #
faro.receiver "frontend" {
server {
listen_address = "0.0.0.0"
listen_port = 12347
cors_allowed_origins = ["https://shop.example.com"]
api_key = sys.env("FARO_API_KEY")
rate_limiting {
strategy = "per_app"
rate = 50
}
}
sourcemaps {
download = false
location {
path = "/srv/sourcemaps/{{ .Release }}"
minified_path_prefix = "https://shop.example.com/assets/"
}
}
extra_log_labels = {service_name = "shop-frontend"}
output {
logs = [loki.write.default.receiver]
traces = [otelcol.exporter.otlp.tempo.input]
}
}
loki.write "default" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo:4317"
tls {
insecure = true
}
}
}Configuration validée avec alloy validate (Alloy v1.21.3, novembre 2026).
Les réglages qui comptent #
| Réglage | Défaut | Pourquoi le changer |
|---|---|---|
listen_address | 127.0.0.1 | Le navigateur doit joindre le récepteur : écoutez sur 0.0.0.0 derrière un proxy TLS. |
cors_allowed_origins | vide (CORS désactivé) | Listez vos domaines ; ["*"] accepte n’importe quel site. |
api_key | vide | Exige l’en-tête X-API-Key ; sinon HTTP 401. |
rate_limiting | 50 req/s, rafale 100, global | per_app isole chaque application et environnement ; au-delà, HTTP 429. |
sourcemaps.download | true | Le récepteur télécharge les source maps depuis l’URL envoyée par le client : désactivez-le pour un trafic non maîtrisé et servez-les depuis le disque. |
max_allowed_payload_size | 5MiB | Limite la taille d’une requête. |
Ne laissez pas download = true sur un récepteur public : n'importe quel client pourrait faire télécharger à Alloy l'URL de son choix.3. Ce que reçoit Alloy : un test réel #
Payload, réponses, lignes produites.
Les réponses HTTP #
Nous avons envoyé un payload Faro (un log, un événement, des Web Vitals, une exception et une trace) à Alloy, avec loki.echo et otelcol.exporter.debug pour afficher le résultat :
curl -X POST http://127.0.0.1:12347/collect -H 'Content-Type: application/json' --data @payload.json
# → 401 (clé absente)
curl -X POST http://127.0.0.1:12347/collect -H 'X-API-Key: secret-demo' -H 'Origin: http://127.0.0.1:8099' --data @payload.json
# → HTTP/1.1 202 Accepted
# Access-Control-Allow-Origin: http://127.0.0.1:8099Les lignes envoyées à Loki #
Chaque élément devient une ligne logfmt avec un champ kind et le contexte de l'application, de la session, de la page et du navigateur (extrait) :
labels={service_name="shop-frontend"}
kind=log message="panier validé" level=info app_name=shop-frontend app_version=1.4.2 app_environment=production session_id=s-7f3a page_url=https://shop.example.com/checkout browser_name=Firefox
kind=event event_name=checkout_step event_domain=shop event_data_step=payment …
kind=measurement type=web-vitals cls=0.040000 inp=180.000000 lcp=2310.000000 …
kind=exception type=TypeError value="Cannot read properties of undefined (reading 'price')" stacktrace="TypeError: …\n at computeTotal (https://shop.example.com/assets/app.min.js:1:4821)" …La trace arrive dans Tempo comme span OpenTelemetry : HTTP POST /api/orders, service.name=shop-frontend, scope @grafana/faro-web-tracing. Dans Loki, filtrez les erreurs :
{service_name="shop-frontend"} | logfmt | kind="exception"
quantile_over_time(0.75, {service_name="shop-frontend"} | logfmt | kind="measurement" | unwrap lcp [5m])