> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwairy.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Fichiers de logs Apache

> Envoyez à Analytics des crawlers les requêtes de crawlers d’IA d’un serveur Apache : log JSON dédié, envoi des fichiers fermés par lots et vérification.

Utilisez ce guide lorsque votre site tourne sur un serveur Apache administré par vous ou par votre hébergeur, et qu’aucun connecteur de CDN ou de plateforme ne s’applique. Apache écrit les requêtes des crawlers d’IA dans un fichier de log dédié, au format attendu par Qwairy. Un script planifié envoie ensuite les fichiers fermés au [collecteur HTTP générique](https://docs.qwairy.co/fr/documentation/measure/crawler-analytics/connectors/generic-http.md).

Le fichier dédié s’ajoute à vos logs d’accès existants, qui continuent de fonctionner si vous suivez l’étape de l’hôte virtuel. Qwairy ne reçoit que la méthode, le chemin, le nom d’hôte, le User-Agent, l’heure et le statut des requêtes de crawlers d’IA.

> [!WARNING]
>
> Si un CDN ou un cache de pages complètes répond à certaines requêtes avant Apache, ces requêtes n’arrivent jamais dans les logs d’Apache. Collectez plutôt au niveau de cette couche, ou acceptez des comptes partiels.

## Avant de commencer

Si vous n’administrez pas le serveur, transmettez cette liste à la personne qui le gère. Il lui faut :

* Apache 2.4 avec `mod_rewrite`, `mod_setenvif` et `mod_log_config` activés, et le programme `rotatelogs` installé avec Apache ;
* l’autorisation de modifier la configuration d’Apache et de le recharger ;
* un shell Linux avec `sh`, `curl`, `find`, `split` et `sha256sum`, et un moyen de lancer une commande toutes les cinq minutes, comme cron ;
* un accès HTTPS sortant depuis le serveur vers `https://www.qwairy.co` ;
* le `ServerName` de l’hôte virtuel, qui doit être le nom d’hôte configuré pour la marque Qwairy, ou son équivalent apex ou `www` ;
* une clé créée dans Qwairy, comme décrit ci-dessous.

N’activez qu’une seule source de livraison Analytics des crawlers pour la marque.

## Créer et protéger la clé

1. Dans **Measure > Analytics des crawlers > Paramètres**, sélectionnez **API HTTP générique**.
2. Sélectionnez **Créer une clé**, puis saisissez un nom descriptif et le fuseau horaire IANA utilisé pour les analytics quotidiennes.
3. Copiez le secret lorsqu’il apparaît. Qwairy n’en affiche ensuite que le préfixe.
4. Sur le serveur, enregistrez-le dans un fichier lisible uniquement par l’utilisateur qui exécute le script :

```bash theme={null}
sudo install -d -m 700 /etc/qwairy
printf 'export QWAIRY_CRAWLER_KEY=%s\n' 'YOUR_CRAWLER_KEY' | sudo tee /etc/qwairy/crawler.env > /dev/null
sudo chmod 600 /etc/qwairy/crawler.env
```

Remplacez `YOUR_CRAWLER_KEY` par le secret. Ne le commitez pas dans le gestionnaire de code source et ne l’écrivez pas dans des logs.

## Écrire un fichier de log dédié

La configuration ci-dessous écrit un événement JSON par ligne, uniquement pour les requêtes `GET` et `HEAD` des crawlers d’IA connus. Un nouveau fichier démarre toutes les cinq minutes : le script d’envoi ne lit donc que des fichiers qu’Apache a fermés.

**1. Créez le répertoire de logs**

```bash theme={null}
sudo install -d -m 750 /var/log/apache2/qwairy
```

Utilisez le répertoire de logs de votre distribution, par exemple `/var/log/httpd` sur les systèmes dérivés de Red Hat, et gardez le même chemin à chaque étape.

**2. Définissez le filtre de crawlers et le format de log**

Ajoutez ces lignes à la configuration principale du serveur, en dehors de tout bloc `<VirtualHost>` :

```apache theme={null}
SetEnvIfNoCase User-Agent "(?:^|[^a-z0-9_-])(?:Claude-SearchBot|MistralAI-Index|OAI-SearchBot|PerplexityBot|ChatGPT-User|Google-GeminiNotebook|Google-NotebookLM|Google-Agent|MistralAI-User|Perplexity-User|Claude-User|Google-CloudVertexBot|ClaudeBot|GPTBot|GrokBot|meta-webindexer|Amzn-User|Meta-ExternalFetcher|AI2Bot|Ai2Bot-Dolma|Amazonbot|Bytespider|CCBot|Meta-ExternalAgent|DuckAssistBot|KimiBot|Kimi-SearchBot|YouBot|Kimi-User|Diffbot|Kangaroo Bot|omgili|omgilibot|PanguBot|Timpibot|Webzio-Extended)(?=$|[^a-z0-9_-])" qwairy_ai_crawler

LogFormat "{\"request_method\":\"%m\",\"request_path\":\"%<{QWAIRY_PATH}e\",\"hostname\":\"%v\",\"user_agent\":\"%{User-Agent}i\",\"timestamp\":\"%{%Y-%m-%dT%H:%M:%S%z}t\",\"status_code\":%>s}" qwairy_ndjson
```

La liste des User-Agents correspond aux recettes générées dans Qwairy. Qwairy réapplique son registre maintenu à la réception : une liste plus ancienne ne manque que les identités ajoutées depuis.

**3. Journalisez les requêtes des crawlers d’IA dans l’hôte virtuel**

Ajoutez ces lignes dans le `<VirtualHost>` qui sert le site en HTTPS, avant toute autre règle de réécriture.

Si cet hôte virtuel n’a pas encore de ligne `CustomLog`, il écrit dans le log d’accès principal du serveur. Un `CustomLog` ajouté dans un hôte virtuel interrompt cet héritage : copiez d’abord la ligne `CustomLog` principale dans l’hôte virtuel pour conserver votre log d’accès habituel.

```apache theme={null}
RewriteEngine On
RewriteCond %{THE_REQUEST} "^[A-Z]+ (/[^?# ]*)"
RewriteRule ^ - [E=QWAIRY_PATH:%1]
CustomLog "|/usr/bin/rotatelogs -l /var/log/apache2/qwairy/qwairy-ai.%Y%m%d%H%M%S.ndjson 300" qwairy_ndjson "expr=-n reqenv('qwairy_ai_crawler') && %{REQUEST_METHOD} in {'GET','HEAD'} && %{THE_REQUEST} =~ /^[ -~]+$/ && %{HTTP:User-Agent} =~ /^[ -~]{1,512}$/"
```

Lancez `command -v rotatelogs` et utilisez le chemin renvoyé. Si un autre hôte virtuel sert aussi des pages du même site, ajoutez-y les mêmes lignes avec un autre préfixe de fichier.

**4. Vérifiez et rechargez Apache**

```bash theme={null}
sudo apachectl configtest
sudo apachectl graceful
```

Chaque choix de cette configuration évite une erreur connue :

| Choix | Raison |
| - | - |
| `%<{QWAIRY_PATH}e`, lu dans la ligne de requête | Enregistre le chemin demandé par le crawler, sans chaîne de requête, avant qu’Apache ne fasse correspondre `/` à un fichier d’index. Il conserve aussi les caractères encodés comme `%C3%A9`, ce qui garde chaque ligne en JSON valide. |
| Conditions ASCII imprimable sur `THE_REQUEST` et `User-Agent`, avec un User-Agent de 512 caractères au plus | Apache écrit les autres octets en séquences `\x`, qui ne sont pas du JSON valide. Ces requêtes ne sont pas journalisées et ne peuvent donc pas bloquer un lot. Les User-Agents connus des crawlers d’IA sont bien plus courts que 512 caractères. |
| `%v` | Enregistre le `ServerName` de l’hôte virtuel plutôt que l’en-tête `Host` envoyé par le client. |
| `%{%Y-%m-%dT%H:%M:%S%z}t` | Enregistre l’heure locale avec son décalage, par exemple `2026-10-30T10:15:30+0100`. Le point de terminaison accepte cette forme. Le format de date par défaut d’Apache est rejeté. |
| `%>s` | Enregistre le statut final renvoyé au crawler. |
| `rotatelogs` avec `300` | Ferme le fichier toutes les cinq minutes, ce qui permet d’envoyer les fichiers sans suivre de position de lecture. |

Une ligne ressemble à ceci :

```json theme={null}
{"request_method":"GET","request_path":"/guides/getting-started","hostname":"www.example.com","user_agent":"Mozilla/5.0 (compatible; GPTBot/1.3; +https://openai.com/gptbot)","timestamp":"2026-10-30T10:15:30+0100","status_code":200}
```

## Envoyer les fichiers fermés

Enregistrez ce script sous `/usr/local/bin/qwairy-send-logs`, puis lancez `sudo chmod 700 /usr/local/bin/qwairy-send-logs` :

```sh theme={null}
#!/bin/sh
# Send closed Qwairy log files to Crawler Analytics. Run every 5 minutes.
set -eu

LOG_DIR="${QWAIRY_LOG_DIR:-/var/log/apache2/qwairy}"
ENDPOINT="${QWAIRY_ENDPOINT:-https://www.qwairy.co/api/v1/logs/ingest}"
MAX_DELAY="${QWAIRY_MAX_DELAY:-240}"
: "${QWAIRY_CRAWLER_KEY:?Set QWAIRY_CRAWLER_KEY}"

mkdir -p "$LOG_DIR/failed"

# 1. Run one instance at a time. A lock older than an hour was left by a crash.
LOCK="$LOG_DIR/.lock"
find "$LOG_DIR" -maxdepth 1 -type d -name .lock -mmin +60 -exec rmdir {} \;
mkdir "$LOCK" 2>/dev/null || exit 0
trap 'rmdir "$LOCK"' EXIT

# 2. Wait a random delay, so that servers do not all send at the same minute.
if [ "$MAX_DELAY" -gt 0 ]; then
  sleep "$(( $(od -An -N2 -tu2 /dev/urandom | tr -d ' ') % MAX_DELAY ))"
fi

# 3. Group all closed files (unchanged for more than 5 minutes) into batches of
#    at most 1,000 lines and 1,900,000 bytes. The staging directory lets an
#    interrupted run resume with the same batches, so no line is sent twice.
STAGE="$LOG_DIR/.staging"
if [ -d "$STAGE" ] && { [ ! -d "$STAGE/src" ] || [ -z "$(ls -A "$STAGE/src")" ]; }; then
  rm -rf "$STAGE"
fi
if [ ! -d "$STAGE" ]; then
  closed=$(find "$LOG_DIR" -maxdepth 1 -type f -name '*.ndjson' -mmin +5)
  if [ -n "$closed" ]; then
    mkdir "$STAGE"
    date +%Y%m%d%H%M%S > "$STAGE/name"
    mkdir "$STAGE/src"
    printf '%s\n' "$closed" | while read -r file; do mv "$file" "$STAGE/src/"; done
  fi
fi
if [ -d "$STAGE/src" ] && [ ! -e "$STAGE/done" ]; then
  rm -rf "$STAGE/out"
  mkdir "$STAGE/out"
  awk -v prefix="$STAGE/out/qwairy-$(cat "$STAGE/name").ndjson.part." '
    { size = length($0) + 1 }
    n == 0 || lines >= 1000 || bytes + size > 1900000 {
      if (out != "") close(out)
      n++; lines = 0; bytes = 0; out = sprintf("%s%05d", prefix, n)
    }
    { print > out; lines++; bytes += size }
  ' "$STAGE"/src/*
  touch "$STAGE/done"
fi
if [ -e "$STAGE/done" ]; then
  find "$STAGE/out" -type f -exec mv {} "$LOG_DIR/" \;
  rm -rf "$STAGE"
fi

# 4. Send each batch with a stable idempotency key, so a retry is never counted twice.
failed=0
for part in "$LOG_DIR"/*.ndjson.part.*; do
  [ -e "$part" ] || continue
  key=$( { basename "$part"; cat "$part"; } | sha256sum | cut -d ' ' -f 1 )
  status=$(curl --silent --output /dev/null --write-out '%{http_code}' --max-time 30 \
    --header 'Content-Type: application/json' \
    --header "X-API-Key: $QWAIRY_CRAWLER_KEY" \
    --header "Idempotency-Key: $key" \
    --data-binary "@$part" "$ENDPOINT") || status=000
  case "$status" in
    200) rm "$part" ;;
    400 | 409 | 413 | 415) mv "$part" "$LOG_DIR/failed/"; failed=1 ;;
    *) echo "Qwairy returned $status for $part; retrying on the next run" >&2; exit 1 ;;
  esac
done

if [ "$failed" -ne 0 ]; then
  echo "Some batches were rejected and moved to $LOG_DIR/failed" >&2
  exit 1
fi
```

Le script ne s’exécute qu’une fois à la fois et attend d’abord un délai aléatoire de quatre minutes au plus, pour que les serveurs n’envoient pas tous à la même minute. Il regroupe ensuite tous les fichiers fermés en lots d’au plus 1 000 lignes et 1 900 000 octets : un site peu visité par les crawlers n’envoie donc qu’une requête par passage. Chaque lot part avec une `Idempotency-Key` dérivée de son contenu, et un répertoire d’étape permet à un passage interrompu de reprendre avec les mêmes lots : aucune ligne n’est comptée deux fois. Il traite les réponses ainsi :

| Réponse | Action |
| - | - |
| `200` | Supprime le lot. |
| `400`, `409`, `413` ou `415` | Déplace le lot dans `failed/` pour examen, continue, puis se termine en erreur à la fin du passage. Un `409` signifie que le lot a déjà été livré avec un autre contenu : sa livraison précédente est comptée. |
| `401`, `403`, `429`, `5xx` ou aucune réponse | S’arrête, conserve le lot et réessaie au passage suivant. Le script se termine en erreur pour que cron puisse le signaler. |

Planifiez le script toutes les cinq minutes, par exemple dans `/etc/cron.d/qwairy` :

```text theme={null}
*/5 * * * * root . /etc/qwairy/crawler.env && /usr/local/bin/qwairy-send-logs
```

Si le répertoire de logs diffère de `/var/log/apache2/qwairy`, ajoutez `QWAIRY_LOG_DIR=/votre/chemin` avant le script dans la ligne cron.

Pour lancer le script à la main sans délai aléatoire, définissez `QWAIRY_MAX_DELAY=0` :

```bash theme={null}
sudo sh -c '. /etc/qwairy/crawler.env && QWAIRY_MAX_DELAY=0 /usr/local/bin/qwairy-send-logs'
```

## Vérifier la livraison

1. Demandez une page en tant que crawler d’IA, en remplaçant le nom d’hôte :

   ```bash theme={null}
   curl --user-agent "Mozilla/5.0 (compatible; GPTBot/1.3; +https://openai.com/gptbot)" \
     --output /dev/null --write-out "%{http_code}\n" \
     "https://www.example.com/"
   ```

2. Vérifiez qu’une ligne apparaît dans le fichier le plus récent de `/var/log/apache2/qwairy`, avec `"request_path":"/"`, votre `ServerName` et une heure avec décalage.

3. Attendez au moins six minutes, puis lancez le script une fois à la main avec la commande ci-dessus. Il n’affiche rien lorsque tous les lots ont été acceptés, et `failed/` reste vide.

4. Dans Qwairy, attendez que le connecteur passe de l’état en attente à connecté, puis ouvrez le jour analytique en cours et cherchez la page.

## Limites et comportement

* Les événements arrivent dans Qwairy environ 6 à 20 minutes après la requête : jusqu’à cinq minutes avant la fermeture du fichier, six minutes avant que le script le considère comme fermé, jusqu’à cinq minutes avant le passage suivant de cron, et jusqu’à quatre minutes de délai aléatoire.
* Les événements sont acceptés jusqu’à 72 heures après la fin de leur jour analytique. Si l’envoi s’arrête plus longtemps, les événements plus anciens sortent de la [fenêtre d’arrivée tardive](https://docs.qwairy.co/fr/documentation/measure/crawler-analytics/connectors/generic-http.md#limites-et-périmètre-de-livraison) et sont rejetés. Les logs d’accès historiques ne peuvent pas être importés.
* Seules les requêtes `GET` et `HEAD` des crawlers d’IA listés, avec une ligne de requête en ASCII imprimable et un User-Agent en ASCII imprimable de 512 caractères au plus, sont écrites. Le trafic humain et les autres requêtes ne quittent jamais le serveur.
* Une seule ligne invalide fait renvoyer `400` à tout le lot. Les autres lignes de ce lot ne sont pas comptées tant que vous ne l’avez pas corrigé.
* Les requêtes servies par un CDN ou un cache avant Apache ne sont pas observées.

## Dépannage

| Symptôme | Cause probable | Correction |
| - | - | - |
| Aucun fichier dans le répertoire de logs | Un module est désactivé, le répertoire manque, les lignes sont dans un autre hôte virtuel, ou le User-Agent n’est pas listé | Vérifiez `apachectl -M`, le répertoire et l’hôte virtuel qui a répondu à la requête de test. |
| `"request_path":"-"` dans les lignes | Les lignes de réécriture manquent dans cet hôte virtuel, ou `RewriteEngine` est désactivé | Ajoutez les trois lignes de réécriture dans le même `<VirtualHost>` que `CustomLog`. |
| Fichiers dans `failed/`, et le script se termine en erreur | Une ligne n’est pas du JSON valide, ou le lot a été rejeté | Pour un `400`, corrigez ou supprimez la ligne invalide, puis replacez le fichier dans le répertoire de logs. Pour un `409`, une livraison précédente de ce lot est déjà comptée : supprimez le lot, sans le renommer pour le renvoyer. |
| Le script s’arrête aussitôt sans rien envoyer | Un autre passage détient le verrou `.lock` | Aucune action. Un verrou laissé par un passage interrompu est levé au bout d’une heure. |
| Le script s’arrête sur `401` ou `403` | Clé erronée, inactive ou d’un autre connecteur | Vérifiez `/etc/qwairy/crawler.env` et le statut de la clé dans Qwairy. |
| Le script s’arrête sur `429` ou `5xx` | Limite de débit ou indisponibilité temporaire | Aucune action. Le passage suivant renvoie le même lot. |
| Lots acceptés mais `processed` vaut `0` | Le `ServerName` diffère du nom d’hôte de la marque | Définissez `ServerName` sur le nom d’hôte de la marque ou sa forme apex ou `www`. |

## Revenir en arrière

1. Retirez de l’hôte virtuel les lignes `CustomLog` et de réécriture de Qwairy, conservez votre `CustomLog` habituel, puis lancez `sudo apachectl graceful`.
2. Attendez au moins six minutes pour que le dernier fichier soit considéré comme fermé, puis lancez le script une dernière fois à la main avec la commande ci-dessus.
3. Vérifiez que le répertoire de logs ne contient plus qu’un répertoire `failed/` vide. Traitez tout lot restant avant de continuer.
4. Supprimez `/etc/cron.d/qwairy`, `/usr/local/bin/qwairy-send-logs`, `/etc/qwairy/crawler.env` et le répertoire de logs.
5. Révoquez la clé dans Qwairy.

Arrêter la livraison crée un trou de couverture. Vérifiez les alertes de continuité de livraison avant de comparer des périodes qui l’encadrent.

Directives Apache vérifiées dans la documentation de [mod\_log\_config](https://httpd.apache.org/docs/2.4/mod/mod_log_config.html), [mod\_rewrite](https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html), [mod\_setenvif](https://httpd.apache.org/docs/2.4/mod/mod_setenvif.html) et [rotatelogs](https://httpd.apache.org/docs/2.4/programs/rotatelogs.html) le 30 octobre 2026, avec Apache 2.4.

## Pages associées

* [Collecteur HTTP générique](https://docs.qwairy.co/fr/documentation/measure/crawler-analytics/connectors/generic-http.md)
* [Analytics des crawlers](https://docs.qwairy.co/fr/documentation/measure/crawler-analytics.md)
* [WordPress et PHP](https://docs.qwairy.co/fr/documentation/measure/crawler-analytics/connectors/wordpress.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.