HTML5 / CSS 2.x/3 / PHP / MySQL ...

Traçage des erreurs dans un fichier log

En matière d'erreurs, on distinguera:

En traçant les erreurs dans un fichier de log, vous historisez ces erreurs et vous les consultez tranquillement pour y remédier si c'est possible.

Notre modèle MVC simplifié

Il s'appuye sur la fonction include qui est normalement sécurisée:

/**
 * sécurisation de l'inclusion dynamique d'un template
 */
function includePageDuSite($page) {
    if (file_exists($page)) {
      return $page;
    }
    die("Ce Fichier n'existe pas sur le Serveur");
}

Voir: un modèle MVC simplifié pour vous remémorer la sécurisation de cette fonction include.

Générer un fichier de traçage

Dans notre fonction includePageDuSite nous provoquons un échec de traitement si le template demandé n'existe pas physiquement sur le disque cote serveur.

L'idée est de remplacer cette ligne:

    ...
   die("Ce Fichier n'existe pas sur le Serveur");
   ...

par un traitement qui va enregistrer l'erreur dans un fichier de log.

Pour enregistrer des données dans un fichier, nous avons à disposition la fonction php fputs:

    fputs($myFile,$txt_log);

qui écrit le contenu de $txt_log dans le fichier pointé par $myFile.

On aura au préalable ouvert ledit fichier:

    // enregistrement du journal d'activité
    preg_match("`^(.*\/)([^\/]+)$`",$_SERVER['SCRIPT_FILENAME'], $matches);
    $chemin_script = $matches[1];
    $fichierCible = $chemin_script.$fichier;
    $myFile=fopen($fichierCible,'a+');

Pour le nom de fichier, on va le définir:

  tracage.log

si on veut définir un fichier unique dont la taille va croître indéfiniment.

    $fichier = "tracage_".date('Ymd').".log";

va définir un fichier par jour qui aura un nom de la forme:

  tracage_20140210.log

Les chiffres correspondent à l'horodatage au format YMD (Année Mois Jour).

On ouvre ce fichier de traçage avec l'option a+ qui place le pointeur d'écriture en fin de fichier. Tout nouveau texte sera rajouté en fin de fichier.

Traçage de notre erreur

Le texte à écrire dans le fichier de traçage est libre. mais autant l'adapter pour permettre de comprendre ce qui s'est passé:

    // Si on peut déterminer l'adresse IP
    $adresse_ip = Null;
    if(isset($_SERVER['REMOTE_ADDR'])) {
        $adresse_ip = '"'.$_SERVER['REMOTE_ADDR'].'"';
    }
    $txt_log=$adresse_ip.';'.date('d/m/Y H:i:s').';'.$texte."\n";

La variable $txt_log contient toute la ligne de texte à insérer dans le fichier de traçage. Cette ligne commence par l'adresse IP, suivi de la date de l'erreur au format AAAA-MM-JJ HH:MM:SS et s'achève par le texte de l'erreur dans la variable $texte.

Mettons tout ça dans une fonction:

function tracage($texte) {
    // Si on peut déterminer l'adresse IP
    $adresse_ip = Null;
    if(isset($_SERVER['REMOTE_ADDR'])) {
        $adresse_ip = '"'.$_SERVER['REMOTE_ADDR'].'"';
    }
    $txt_log=$adresse_ip.';'.date('d/m/Y H:i:s').';'.$texte."\n";
    // écriture dans un fichier de traçage
    $fichier = "tracage_".date('Ymd').".log";
    preg_match("`^(.*\/)([^\/]+)$`",$_SERVER['SCRIPT_FILENAME'], $matches);
    $chemin_script = $matches[1];
    $fichierCible = $chemin_script.$fichier;
    $myFile=fopen($fichierCible,'a+');
    fputs($myFile,$txt_log);
    fclose($myFile);
}

Ne reste plus qu'à intégrer tout ça à notre fonction includePageDuSite:

/**
 * sécurisation de l'inclusion dynamique d'un template
 */
function includePageDuSite($page) {
    if (file_exists($page)) {
      return $page;
    }
    tracage("erreur template: ".$page);
    return('planDuSite.phtml');
}

A partir de maintenant, notre modèle MVC simplifié ne va plus générer d'erreur de chargement d'un template. L'erreur est enregistrée dans un fichier de traçage, puis on renvoie l'utilisateur sur une page de gestion de l'erreur. ici on envoie l'utilisateur vers le plan du site.

Exemples de traçages

"81.52.143.25";06/06/2009 00:41:05;erreur template: templates/accueil.php.phtml

Ici l'utilisateur a rajouté .php au lien qui s'est affiché dans sa barre d'URL.

"82.251.89.93";11/06/2009 19:00:10;erreur template: templates/http://217.218.225.2:2082/index.html?.phtml

Ici nous avons typiquement une tentative d'injection de code.

"91.213.126.71";06/11/2009 22:08:38;erreur template: templates/../../../../../../
    ../../../../../../../../../../../../../../../../etc/passwd.phtml

Ici l'utilisateur - un robot de hacking - a tenté de récupérer le contenu du fichier passwd.html

.

Traçage des requêtes SQL

Maintenant nous disposons d'un moyen de tracer les erreurs de fonctionnement du site. Autant y aller à fond et intercepter les erreurs SQL. Ici un script très classique exécutant une requête SQL:

    $resultat = mysql_query($sql)
    or die ("Requête invalide: <b>$sql</b>" . mysql_error());

Ce code arrête le script s'il se produit une erreur SQL. Si ce n'est pas une erreur permanente ni critique, il est souhaitable de ne pas arrêter tout le script juste pour une erreur locale.

Exemple: vous avez un site avec une belle page d'accueil. Sur cette page figurent des actualités, les derniers messages du forum, les dernières annonces... Une seule erreur SQL et plus de page d'accueil.

En modifiant le code qui exécute la requête SQL on peut tracer l'erreur SQL et ne pas bloquer le script de toute une page web:

    $resultat = mysql_query($sql)
    or  traçage("Requête invalide: $sql " . mysql_error());

Le script ne s'interrompt plus et nous conservons une trace de l'erreur:

"82.246.141.136";08/11/2009 08:27:31;(/index.php - detailFicheLieu)
    Requête invalide: 
    SELECT * FROM nat_lieux WHERE num_fiche=448 (commune de Butgenbach le tolre) 
    You have an error in your SQL syntax; check the manual that corresponds to 
    your MySQL server version for the right syntax to use near '(commune de 
    Butgenbach le tolre)' at line 1

Ici un utilisateur a entré des données en GET en complément d'un id numérique.

Voici le script complet pour exécuter notre code SQL:

/**
 * Exécute les requêtes sql qui sont injectées
 * @param   $sql      string chaîne contenant la requête SQL à exécuter
 * @return  array | null 
 */
function execute($sql) {
    // $_dbh variable pointant sur la base copurante
    if (empty($_dbh)) {
        connect(); // fonction de connection à la base de données
    }
    return = mysql_query($sql)
        or tracage("Requête invalide: $sql " . mysql_error());
    } 
}

Si vous utilisez un Framework, ce genre de fonction est déjà installé dans l'outil.

Tous les articles sur ce thème