Vous avez 4 heures

Je me suis dit que ça pourrait être marrant de vous faire participer aux casse-têtes auxquels je suis confronté. Je lance le truc pour voir si ça vous plaît/amuse, si il n’y a pas de retours je remballerai mon idée ha ha.

On m’a demandé de récupérer les logs du 22/01 à partir de 11h40 sur quelques 130 serveurs. On peut utiliser ansible ou un outil de ce type pour balancer la commande sur tous ces serveurs mais comment récupérer tous les logs (ligne entière) à partir de 11h40 ?

Ci-dessous 50 lignes de logs d’un serveur (j’ai trafiqué et tronqué les infos) qui vous serviront à tester la solution que vous proposez, je vous invite à les copier-coller dans un fichier sur votre poste pour effectuer vos tests. Tous les coups sont permis : Scripts (tous langages : Python, Perl…), shell one-liner, site web qui fournit la solution en 1 clic, corruption de l’auteur (je suis hétérosexuel et j’accepte les virements bancaires), etc.

Vous avez le droit de poser des questions. Je laisse jusqu’à lundi 17h30 puis je fermerai les commentaires, ça laisse du temps pour ceux qui voudraient s’amuser dessus ce week-end. Je fournirai ensuite une solution (la mienne) soit dans les commentaires soit dans un autre article.

En général on a un temps imparti pour effectuer une tâche : Vous avez 4 heures.

Déjà 38 avis pertinents dans Vous avez 4 heures

  • AP41
    Ma solution n’utilise pas ansible mais GNU parallel pour traiter un max de serveurs en simultané. « parallel », c’est le bien. :)

    On admet que…
    1. On peut se connecter par ssh sans mot de passe sur tous les serveurs,
    2. On dispose de la liste des serveurs dans un fichier « listeserveurs »,
    3. Que l’outil GNU parallel est installé localement,
    4. Que l’outil grep est installé sur les serveurs distants *ET* qu’il est suffisamment récent pour gérer des expressions régulières

    Soit le script bash suivant, qui va créer des fichiers « log_ ».

    #!/bin/bash

    logserveur() {
    serveur=$1

    # Sur le serveur, on greppe les logs du 22 janvier
    # - à 11h quarante ou cinquante quelque chose
    # - de 12 à 19h quelque chose
    # - de 20 à 23h quelque chose

    # Le grep est lancé sur le serveur distant pour minimiser ce qu'on fait transiter par le réseau.
    # Le "-C" de ssh s'assure qu'on réduit autant que possible l'utilisation du réseau en compressant.
    # L'utilisateur est omis, pour la lisibilité du truc. On aura paramétré son ~/.ssh/config pour choisir le bon. :)

    if ssh -C $serveur 'grep -E " \[22/Jan/2019:(11:[45]|1[2-9]:|2[0-3]:)" /var/log/logsquinousinteressent' > "logs_$serveur"
    then
    echo "OK $serveur"
    else
    echo "SOUCI $serveur"
    fi
    }

    # Rend la fonction logserveur() visible à GNU parallel
    export -f logserveur

    # Interrogation en parallèle des serveurs dans la le fichier « listeserveurs »,
    # avec un maximum de 16 machines sondées simultanément. On peut ajuster le paramètre
    # ou le virer pour laisser parallel lancer autant de process qu’il le peut…

    parallel --jobs 16 logserveur :::: listeserveurs
    echo "Fini..."

  • Grinn
    Je crois que je ferais un bête grep avec un petite regex :
    egrep '\[22/Jan/2019:(11:[45][0-9]|1[2-9]|2[0-3]):' fichier.de.log
  • AP41
    Rhaa mon indentation et mes guillemets ont été mangés. :) Après la question est de savoir si j’étais hors sujet en allant au delà du simple grep pour détailler le mode opératoire de récupération rapide couplant parallel et ssh. J’ai interprété le « récupérer » de l’énoncé en « extraire sélectivement ET rapatrier ».
  • patrick
    petite version bash (a modifier cat *.log…)

    mini=${1-'22-Jan-2019 08:38'}
    dmini=$(date -d "$mini" "+%s")
    echo "Date mini: $mini == $dmini"
    declare -a line=()
    while IFS='[]' read -ra line ; do
    d="${line[1]}"
    d="${d//[\/]/-}"
    d="${d/[:]/ }"
    if [[ $(date -d "$d" "+%s") > "$dmini" ]]; then
    echo "$d"
    else
    echo "$d < $mini" # pour test
    fi
    done < <(cat *.log )

  • cat logs | while read line; do echo "$line" | php -r '$line = file_get_contents("php://stdin"); $date = new DateTime(explode("]", explode("[", $line)[1])[0]); if ($date->getTimestamp() >= 1548153600) echo $line;'; done

    monpetitserveur.net 42.248.86.23 - - [22/Jan/2019:11:40:57 +0100] "GET
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:11:40:57 +0100] "POST
    monpetitserveur.net 104.48.123.201 - - [22/Jan/2019:12:16:45 +0100] "GET
    monpetitserveur.net 84.12.145.4 - - [22/Jan/2019:12:49:41 +0100] "GET
    monpetitserveur.net 84.12.154.4 - - [22/Jan/2019:12:49:43 +0100] "GET
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:12:49:44 +0100] "POST
    monpetitserveur.net 42.248.86.23 - - [22/Jan/2019:12:50:02 +0100] "GET
    monpetitserveur.net 45.85.135.74 - - [22/Jan/2019:13:15:27 +0100] "GET
    monpetitserveur.net 45.85.135.74 - - [22/Jan/2019:13:15:27 +0100] "GET
    monpetitserveur.net 42.248.86.23 - - [22/Jan/2019:13:46:11 +0100] "GET
    monpetitserveur.net 53.56.8.95 - - [22/Jan/2019:13:48:09 +0100] "POST
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:13:48:09 +0100] "POST
    monpetitserveur.net 190.18.127.56 - - [22/Jan/2019:20:06:48 +0100] "POST
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:20:06:50 +0100] "POST

  • patrick
    en bash (mais les commentaires suivants n’existaient pas et pas compris que la limite était minuit mais il est simple de rajouter un MAX)

    mini=${1-'22-Jan-2019 08:38'}
    dmini=$(date -d "$mini" "+%s")
    echo "Date mini: $mini == $dmini"
    declare -a line=()
    while IFS='[]' read -ra line ; do
    d="${line[1]}"
    d="${d//[\/]/-}"
    d="${d/[:]/ }"
    if [[ $(date -d "$d" "+%s") > "$dmini" ]]; then
    echo "$d"
    else
    echo "$d < $mini" # pour test
    fi
    done < <(cat *.log )

    ps: premier post non passé ?

  • patrick
    ? moi ça marche, juste entrer $1

    ./log.sh "22-Jan-2019 11:40"
    Date mini: 22-Jan-2019 11:40 == 1548153600
    22-Jan-2019 00:54:21 +0100 < 22-Jan-2019 11:40
    ...
    22-Jan-2019 11:22:17 +0100 < 22-Jan-2019 11:40
    22-Jan-2019 11:40:57 +0100
    ...
    22-Jan-2019 20:06:48 +0100
    22-Jan-2019 20:06:50 +0100

  • Fabien
    Ma contribution

    #!/bin/bash

    heuredebut="11"
    heurefin="23"
    date='22/Jan/2019'

    for (( i=$heuredebut; i> /home/fabien/test/ctete/logextract
    done

    C’est pas top mais ca semble marcher :)

  • Fabien ABC
    Petite modif :)

    #!/bin/bash

    heuredebut="11"
    heurefin="23"
    date='22/Jan/2019'

    for (( i=$heuredebut; i> /home/fabien/test/ctete/logextract
    fi
    echo $i
    cat /home/fabien/test/ctete/log | grep $date":"$i >> /home/fabien/test/ctete/logextract
    done

  • patrick
    cette ligne c’est juste une valeur par défaut si pas de $1 – c’est plus une documentation :) 08:38 c’est l’heure
    correctif avec min et max (+ pour logs multi-jours ou plage de quelques heures)

    #usage ./log.sh "22-Jan-2019 11:40" "22-Jan-2019 13:00"
    mini=${1-'22-Jan-2019 08:38'} # valeur par défaut si $1 non entré
    dmini=$(date -d "$mini" "+%s")
    max=${2} # ici maxi c'est date/heure courante
    dmax=$(date -d "$max" "+%s")
    echo "Date mini: $mini == $dmini"
    echo "Date maxi: $max == $dmax"
    declare -a line=()
    while IFS='[]' read -ra line ; do
    d="${line[1]}"
    d="${d//[\/]/-}"
    d="$(date -d "${d/[:]/ }" "+%s")"
    [[ "$d" -ge "$dmini" ]] && [[ "$d" < "$dmax" ]] && printf "%s[%s]%s\n" "${line[0]}" "${line[1]}" "${line[2]}"
    done < <(cat *.log )

  • unixmipfr
    awk -F'/' 'substr($3,6,2)substr($3,9,2)>=1140 {print}' log_file

    monpetitserveur.net 42.248.86.23 - - [22/Jan/2019:11:40:57 +0100] "GET
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:11:40:57 +0100] "POST
    monpetitserveur.net 104.48.123.201 - - [22/Jan/2019:12:16:45 +0100] "GET
    monpetitserveur.net 84.12.145.4 - - [22/Jan/2019:12:49:41 +0100] "GET
    monpetitserveur.net 84.12.154.4 - - [22/Jan/2019:12:49:43 +0100] "GET
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:12:49:44 +0100] "POST
    monpetitserveur.net 42.248.86.23 - - [22/Jan/2019:12:50:02 +0100] "GET
    monpetitserveur.net 45.85.135.74 - - [22/Jan/2019:13:15:27 +0100] "GET
    monpetitserveur.net 45.85.135.74 - - [22/Jan/2019:13:15:27 +0100] "GET
    monpetitserveur.net 42.248.86.23 - - [22/Jan/2019:13:46:11 +0100] "GET
    monpetitserveur.net 53.56.8.95 - - [22/Jan/2019:13:48:09 +0100] "POST
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:13:48:09 +0100] "POST
    monpetitserveur.net 190.18.127.56 - - [22/Jan/2019:20:06:48 +0100] "POST
    monpetitserveur.net 2a00:1450:1:815:124::1 - - [22/Jan/2019:20:06:50 +0100] "POST

  • par habitude (pas testé) je dirais

    perl -lne '
    print if m{
    \[22/Jan/2019
    :(\d{2})
    :(\d{2})
    }x && ( ($1 > 11) || (($1 == 11) && ($2 >= 45 )) )
    ' *.log

  • Tous les systèmes de logs sont circulaires à plus ou moins long terme, sinon RIP l’espace disque. Suffit de bien configurer son système. En l’occurence si on nous demande des logs de janvier en mars, on réponds juste « lol nope, nous c’est 14 jour, avec les backups on peut remonter à un mois au mieux ».

    La commande systemd adaptée : journalctl --since "2019-03-22 11:40:00" -u apache2.service. Évidemment, à adapter, dans mon cas ce sera haproxy.service (j’ai reporté tout mes logs web via HAProxy par simplicité). Et oui, mes logs web vont dans systemd.

  • Linuz
    Hello,
    Autre petite solution avec awk (tant que la date est en 5ème colonne) :
    awk -v from="[22/Jan/2019:11:40:00" -v to="[22/Jan/2019:23:59:59" '$5>=from && $5<=to' test.log
  • yoko
    t’es entrain de troller systemd/journald. Même si les configuration par défaut ne sont pas les même tu peux avoir des comportements proches entre systemd+journald et syslog+logrotate. C’est une évidence qu’il faut que ta gestion de log soit bien configurée pour pouvoir être correctement configurée. J’aime sincèrement beaucoup le scripting et je parse mes logs avec less, grep, rg et sed, mais ça n’empêche pas de se dire qu’avoir une date correctement parsée rend les choses beaucoup plus simple (ajoute 1h à ta date limite et ça invalide 90% des réponses que tu as eu). journald n’est pas la seule solution à sa les solutions des mutualisation des logs (ELK en tête mais il y en a d’autres) auront aussi cet intérêt (en plus d’être clairement plus pertinent si tu a un parc de 130 serveurs). Tout en permettant de gérer la rétention d’un an plus facilement (avoir un serveur avec un gros volume de log alloué c’est plus simple que de devoir prédire les besoins de chaque serveur).
  • Nan mais faut bien configurer journald au même titre que tu configures rsyslog et logrotate, sinon ça n’a aucun sens.

    Nous on ne logue que 14 jours parce que 1 an c’est bien gentil, mais ça prends de la place. 1 an de logs mails, de logs web, etc (surtout quand le site est bourré d’erreurs remplissant les errorlog) pour un site, ça représente déjà quelques dizaines de Gio. Si on les conserves sur l’infra du client (et c’est ce qu’on fait actuellement), ça lui coûterait plus cher en espace disque pour les logs que pour ses données, et ça remplirait nos hyperviseurs bien plus rapidement, ça serait pas rentable, aussi bien techniquement (plus d’espace consommé par chaque VM = moins de VM par hyperviseur) qu’économiquement (pareil, ratio VM vendues / hyperviseurs nécessaires qui diminue).

    Si on les conserve sur une infra dédiée… ben avec quelques centaines de clients, pour un total que plus de 1500 serveurs (dédiés ou virtuels), et jusqu’à quelques centaines de sites par serveur pour les plus gros, ça commence à chiffrer sévère (en stockage, et en coût d’exploitation de l’infra… sans parler de toute la complexité que ça ajoute). 1 an de logs, c’est juste intenable techniquement parlant, et catastrophique économiquement parlant dans notre cas.

    La loi Française a toujours été absurde, maintenant elle est carrément contradictoire à la loi Européenne, et il me semblait avoir lu que l’Europe avait indiqué que la loi Française ne pouvait être appliquée si on veut être dans les règles.

Les commentaires sont fermés.