Créer un dashboard Splunk d’audience avec les logs Nginx

La licorne Geekeries transforme d’anciens tableaux de bord en un dashboard Splunk d’audience qui sépare visiteurs humains et robots

Bonjour à tous, une fois les logs Nginx de Geekeries.org correctement ingérés dans Splunk après la dernière upgrade serveur, j’avais une question assez simple : est-ce que je pouvais faire un « vrai » dashboard Splunk de mesure d’audience, façon Google Analytics, mais sans ajouter de JavaScript, de cookie ou de nouveau tracker dans votre navigateur ?

Je voulais connaître les pages lues, les sources du trafic, les appareils utilisés et la place occupée par les robots. Je voulais aussi les visiteurs uniques, le nombre de sessions ou encore le taux de rebond. Tout en sachant que Nginx n’observe qu’une suite de requêtes HTTP. Il ne voit ni vos clics dans la page, ni votre scroll, ni le moment où vous partez faire un café en laissant l’onglet ouvert.

Le projet a commencé avec douze vieux (genre j’y avais pas touché depuis 4 ans) dashboards récupérés de mon précédent serveur. Il s’est terminé avec une vue Audience & Web Analytics de 26 panneaux, directement alimentée par les journaux du site. Entre les deux, le plus gros travail n’a pas été de dessiner des camemberts (Ca Splunk le fait très bien). Il a fallu décider ce que les données permettaient réellement d’affirmer.

Mon ancien dashboard cherchait dans un serveur qui n’existait plus

J’aurais pu importer les douze fichiers Simple XML, corriger deux couleurs et déclarer la migration terminée. Le problème, c’est qu’ils attendaient d’anciens index, d’anciens sourcetypes et parfois des visualisations qui n’étaient plus installées. Certains panneaux mélangeaient aussi trafic web, pare-feu et données de sécurité dans une seule recherche.

Dans mon prompt initial (Vous croyez quoi? que j’allais manuellement migrer 12 XML de 2500 lignes chacun, #2018 style ?) j’ai notamment donné cette consigne à Pwny, mon assistant Codex installé sur le serveur. Je la raccourcis ici, mais pas son intention :

« Use the old dashboards as evidence, interrogate the current Splunk data directly, migrate only what still makes sense, improve it, validate it. »

Pwny a commencé par une vue du reverse proxy. Son ancien index a été migré vers le nouveau, les champs ont été vérifiés sur une heure de données et le panneau supposant un utilisateur authentifié a disparu parce que ce champ n’était tout simplement plus renseigné. Ce premier essai a donné la règle pour tout le reste : sauvegarder, lancer une petite recherche, regarder les champs existants, adapter, déployer, puis rejouer chaque panneau.

Pwny peut interroger Splunk avec le serveur MCP officiel de Splunk, mais avec une identité dédiée et seulement quatre outils de lecture. Mais en 20min mes 12 dashboard historique était migré.

Un dashboard Splunk d’audience commence par définir une page vue

Un accès Nginx ne correspond pas à une page. Lorsque votre navigateur ouvre un article, il demande aussi des feuilles CSS, du JavaScript, des images, des icônes et des polices. Simplement compter toutes les lignes de log pour mesurer le trafic serait aussi honnête qu’un vendeur de voitures qui compterait les roues qu’il vend.

J’ai donc défini une page vue comme une requête GET ayant reçu un statut HTTP entre 200 et 399, sur un chemin qui ne se termine pas par une extension statique connue. Les redirections sont conservées parce qu’elles font partie du parcours vers un contenu ; les CSS, images, scripts, polices et source maps sont exclus.

http_method="GET" status>=200 status<400
| eval path=lower(coalesce(uri_path,""))
| eval is_pageview=if(
    NOT match(path,"\.(css|js|png|jpe?g|gif|svg|ico|woff2?|ttf|map|webmanifest)$"),
    1, 0)

Ce filtre dépend évidemment de votre site. Si vous publiez des documents PDF comme de vraies pages, gardez-les. Si votre framework sert du contenu derrière une extension inhabituelle, adaptez la liste. Le bon test consiste à prendre quelques URLs que vous connaissez, puis à vérifier qu’une visite produit une page vue et non quinze.

Le visiteur le plus fidèle était parfois GPTBot

Mon deuxième problème était plus amusant. Dans des logs serveur, un crawler qui relit méthodiquement tout le blog ressemble comme 2 goutes d’eau à un lecteur absolument passionné (👋 Cédric). Googlebot, Bingbot, GPTBot, ClaudeBot, les lecteurs RSS et les scanners finissent alors dans les mêmes courbes que vous.

La recherche de base classe donc les requêtes en cinq familles : humains, moteurs de recherche, crawlers IA, lecteurs RSS et scanners. Les user agents connus constituent le premier signal. Certains chemins typiques de sonde, comme une recherche de fichier d’environnement ou de page WordPress absente, constituent le second.

| eval ua=lower(coalesce(http_user_agent,""))
| eval traffic_type=case(
    match(ua,"gptbot|claudebot|bytespider|ccbot|perplexitybot"),"AI crawler",
    match(ua,"googlebot|bingbot|duckduckbot|yandex|baiduspider"),"Search crawler",
    match(ua,"feedly|freshrss|netnewswire|tt-rss|inoreader"),"RSS reader",
    match(ua,"curl|wget|python-requests|go-http-client|nmap|nikto|sqlmap|nuclei"),"Scanner",
    match(path,"(^|/)(\.env|wp-login\.php|xmlrpc\.php|phpmyadmin|\.git)(/|$)"),"Scanner",
    match(ua,"mozilla/|opera/"),"Human",
    true(),"Automation")

Un user agent reste une déclaration du client, pas une carte d’identité. Un robot peut écrire Mozilla et se faire passer pour un navigateur. Pour limiter ce cas, ma version reclassifie comme sonde un prétendu humain qui accumule au moins dix erreurs 404 sur cinq chemins différents pendant la période choisie.

Cette heuristique ne prouve rien au niveau individuel. Elle nettoie une tendance. Je sais que c’est facile à contourner (surtout maintenant que je vous le dis…), mais c’est la mesure d’Audience de mon blog, pas un dashboard securité ou celle pour Gucci.com.

Visiteurs et sessions : utiles, mais estimés

Nginx ne fournit aucun identifiant de visiteur. Compter seulement les adresses IP sous-estime les personnes derrière le même NAT ; compter le couple IP et user agent sépare un peu mieux les navigateurs. Dans Splunk, je transforme ce couple en empreinte SHA-256 puis je compte les valeurs distinctes.

| eval visitor_key=sha256(src_ip."|".http_user_agent)
| stats dc(visitor_key) AS visitors

Attention au vocabulaire : ce hash est une pseudonymisation pratique pour le calcul, pas une anonymisation miraculeuse. L’adresse existe toujours dans le journal source et une clé stable peut rester une donnée sensible. C’est pour cela que ni l’adresse, ni l’empreinte, ni le user agent complet ne sont affichés dans la vue (mais bon si vous faites ça au taf, vous allez voir votre DPO avant surtout).

Pour les sessions, j’ai retenu la convention classique de trente minutes d’inactivité. Après avoir trié les pages vues par visiteur et par heure, streamstats récupère la requête précédente. Un écart supérieur à 1 800 secondes est considéré comme une nouvelle session.

| sort 0 visitor_key _time
| streamstats current=f last(_time) AS previous_time BY visitor_key
| eval new_session=if(isnull(previous_time) OR _time-previous_time>1800,1,0)
| streamstats sum(new_session) AS session_number BY visitor_key
| eval session_id=visitor_key.":".session_number
| stats count AS pageviews min(_time) AS session_start max(_time) AS session_end BY session_id
| eval session_duration=session_end-session_start
| eval bounced=if(pageviews=1,1,0)

Je préfère cette méthode à une grosse commande transaction, surtout quand la période s’allonge. Elle permet de calculer les sessions, les pages d’entrée et de sortie, la profondeur, les pages par session et le taux de rebond avec une recherche dont je comprends le « coût » en terme de CPU/RAM.

Il faut en revanche accepter une limite importante. Si vous lisez une seule page pendant dix minutes puis fermez l’onglet, le serveur ne voit aucun événement de départ. La durée observée sera nulle et la session sera classée comme un rebond. Mon taux de rebond est donc une estimation basée sur les requêtes, pas la mesure d’un engagement dans la page.

Le référent de la première page, sinon tout devient « Internal »

Pour l’acquisition, le référent de chaque page ne doit pas être compté de la même manière. Après la première page, les liens internes prennent naturellement le relais. Si je classais chaque requête, le canal Internal finirait par manger une grande partie de Google, Reddit ou Mastodon.

Le dashboard conserve donc le référent, le canal et les paramètres UTM de la première page vue de chaque session. Un référent vide devient Direct ; les moteurs connus deviennent Organic Search ; les réseaux sociaux deviennent Social (Geekeries.org devient Internal) et les autres domaines deviennent Referral. Les lecteurs RSS restent à part, car je veux justement connaître leur poids.

Les query strings complètes ne sont jamais affichées. Seuls les champs UTM nécessaires sont extraits, limités en longueur, décodés puis regroupés. Une campagne n’apparaît qu’à partir de deux sessions. Ce petit seuil évite de transformer un tableau agrégé en loupe sur une visite isolée.

How-to : construire la vue dans Simple XML

Voici le chemin reproductible que je suivrais sur une autre instance. Il suppose que vos accès web sont déjà ingérés et correctement découpés. Si les timestamps ou les champs sont faux, réparez d’abord la source : un dashboard ne peut pas compenser un événement mal parsé.

1. Vérifier les champs réellement disponibles

Commencez sur quinze minutes ou une heure. Remplacez l’index et le sourcetype par les vôtres, puis vérifiez la présence des champs au lieu de recopier mes noms (enfin le CIM de Splunk devrait vous aider là) au hasard.

index=web sourcetype=nginx:access earliest=-1h
| stats count AS events
        count(http_method) AS methods
        count(uri_path) AS paths
        count(status) AS statuses
        count(http_user_agent) AS user_agents
        count(http_referer) AS referers

Il vous faut au minimum l’heure, la méthode, le chemin, le statut et une adresse client. Le user agent sert à classer le trafic ; le référent, la query string, les octets et le temps de réponse enrichissent ensuite l’acquisition, la technologie, la bande passante et la latence.

2. Créer deux base search

La première recherche de base conserve tous les accès utiles et calcule traffic_type, is_pageview, l’appareil, le système, le navigateur et le domaine référent. Les panneaux simples, comme les visiteurs, les pages, les pays ou les crawlers, réutilisent ce résultat.

La seconde recherche reprend uniquement les pages vues humaines et RSS, puis applique la sessionisation. Les panneaux de rebond, profondeur, entrée, sortie et acquisition réutilisent cette synthèse. Deux bases lisibles valent mieux que 26 recherches qui recopient chacune la même expression régulière avec une petite faute différente.

3. Poser le squelette Simple XML

Créez une vue Classic/Simple XML dans votre application Splunk, avec un sélecteur de temps global. Le squelette minimal ressemble à ceci :

<form version="1.1" theme="dark">
  <label>Audience &amp; Web Analytics</label>

  <search id="audience_access">
    <query>index=web sourcetype=nginx:access
    | ... classification et champs calculés ...</query>
    <earliest>$time.earliest$</earliest>
    <latest>$time.latest$</latest>
  </search>

  <fieldset submitButton="false" autoRun="true">
    <input type="time" token="time">
      <default>
        <earliest>-7d@h</earliest>
        <latest>now</latest>
      </default>
    </input>
  </fieldset>

  <row>
    <panel>
      <single>
        <title>Human visitors</title>
        <search base="audience_access">
          <query>| where traffic_type="Human" AND is_pageview=1
          | stats dc(visitor_key) AS Visitors</query>
        </search>
        <option name="drilldown">none</option>
      </single>
    </panel>
  </row>
</form>

La documentation officielle du Simple XML Splunk (oui, on s’en cogne du Dashboard Studio, ca sert à rien, un peu comme leur dernière upgrade du CSS..) détaille les visualisations, les tokens et les options disponibles. Gardez d’abord une seule carte qui fonctionne, puis ajoutez les tendances, les contenus et la technologie. Sinon vous déboguerez vingt panneaux en même temps, activité que je ne souhaite même pas à un crawler IA.

4. Ajouter les panneaux sans ouvrir une fenêtre sur les logs bruts

Mes panneaux ne montrent que des agrégats : nombre de visiteurs, pages vues, sessions, canaux, domaines externes, chemins sans query string, pays et familles de robots. Les cartes géographiques exigent au moins deux visiteurs par pays et les 404 au moins deux occurrences. Tous les drilldowns sont désactivés.

Cette dernière option compte vraiment. Une jolie table agrégée peut être parfaitement raisonnable, puis un clic ouvrir les événements qui contiennent IP, user agent ou paramètres d’URL.

5. Déployer, tester et savoir revenir en arrière

Si vous gérez l’application par fichiers, la vue se place sous local/data/ui/views, sa navigation sous local/data/ui/nav et ses permissions dans les métadonnées de l’application. Avant de remplacer une vue, copiez son XML courant dans un dossier de rollback daté. Notez aussi son propriétaire et ses permissions.

Validez ensuite autre chose que l’import :

  • chaque panneau retourne le résultat attendu sur une période bornée ;
  • le sélecteur de temps agit sur les deux recherches de base ;
  • un résultat vide légitime affiche simplement l’absence de données ;
  • la vue apparaît dans l’application pour le rôle prévu ;
  • aucune adresse, query string ou clé de session ne ressort ;
  • la source déployée correspond à votre copie de travail ;
  • la vue revient après le prochain redémarrage planifié.

Pour annuler, restaurez le XML sauvegardé avec son propriétaire et ses permissions, puis rechargez les vues ou redémarrez Splunk uniquement si votre mode de déploiement l’exige. Pour une nouvelle vue, le rollback consiste à retirer son fichier, son entrée de navigation et sa stanza de métadonnées. Simple, ciblé, et surtout décidé avant la panne.

Ce que donnent les logs de Geekeries.org

Ma version finale comporte 26 panneaux : huit indicateurs d’audience, une tendance et une matrice d’activité, les canaux et campagnes, les pages d’entrée et de sortie, les téléchargements, les 404, la géographie, les appareils, les systèmes, les navigateurs, puis une section dédiée aux crawlers et à leur bande passante.

Les chiffres restent des estimations. Les NAT rapproche plusieurs personnes, des adresses dynamiques peuvent en séparer une seule, les robots mentent, les caches évitent certaines requêtes et aucun log serveur ne mesure vos scroll. Je préfère pourtant ces limites visibles à une fausse précision. Pour mon besoin de blog perso, les tendances, les contenus consultés et le poids de l’automatisation sont déjà très utiles.

Si vous découvrez Splunk dans Docker, mon ancien article sur le monitoring des performances Docker avec Splunk donne le point de départ historique. Depuis, la licorne a manifestement trouvé beaucoup d’autres journaux à mâchouiller.

Mon petit Google Analytics, sans Google Analytics

Je ne remplacerais pas un outil de mesure d’audience complet par ce dashboard, en particulier pour un site marchand qui veut mesurer son taux de conversions au clic près. Pour Geekeries.org, en revanche, il répond à mes questions sans déposer une seul sonde chez les lecteurs. Je vois ce qui intéresse les humains, ce que dévorent les robots et les liens qui cassent, à partir de logs que mon serveur produit déjà de toutes façons.

Le point que je retiens surtout est assez simple : J’ai mis 20min à faire un dashboard de mesure d’Audience avec Codex et le MCP Splunk. Il y a 10 ans, j’ai passé 2 week end à faire le même manuellement, c’était très instructif mais une fois qu’on sait ce qu’on fait, je ne vois plus de raison d’attaquer manuellement ce genre de tâches. A vos prompt et Geekez bien !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. Découvrez comment les données de vos commentaires sont traitées.