Avez-vous des scripts awk et grep utiles pour analyser les journaux Apache? [fermé]


70

Je peux utiliser des analyseurs de journaux, mais il me faut souvent analyser les journaux Web récents pour voir ce qui se passe actuellement.

Je fais parfois des choses comme trouver le top 10 des ips qui demandent un certain fichier

cat foo.log | grep request_to_file_foo | awk '{print $1}' |  sort -n | uniq -c | sort -rn | head

Qu'est-ce que vous avez dans votre boîte à outils?


1
J'avais en fait cette belle et belle regex que j'avais écrite à la main pour analyser tous mes journaux personnalisés Apache dans des champs individuels pour les soumettre à une base de données. Je me dis que je ne l'ai plus. C'était un one-liner; vous a renvoyé une variable pour chaque élément du journal - alors je l'inscrivais dans MySQL. Si je le trouve, je le posterai ici.
Kyle Hodgson

Réponses:


54

Vous pouvez faire à peu près n'importe quoi avec les fichiers journaux Apache avec awk seul. Les fichiers journaux Apache sont essentiellement séparés par des espaces, vous pouvez prétendre que les guillemets n'existent pas et accéder à toutes les informations qui vous intéressent par le numéro de colonne. Le seul cas où cela se produit est que si vous avez le format de journal combiné et que vous êtes intéressé par les agents utilisateurs, vous devez alors utiliser des guillemets (") comme séparateur et exécuter une commande awk distincte. Vous trouverez ci-dessous les adresses IP de chaque utilisateur qui demande la page d'index trié par nombre d'occurrences:

awk -F'[ "]+' '$7 == "/" { ipcount[$1]++ }
    END { for (i in ipcount) {
        printf "%15s - %d\n", i, ipcount[i] } }' logfile.log

7 $ est l'URL demandée. Vous pouvez ajouter les conditions souhaitées au début. Remplacez le '$ 7 == "/" par les informations de votre choix.

Si vous remplacez le $ 1 dans (ipcount [$ 1] ++), vous pouvez regrouper les résultats selon d'autres critères. Utiliser $ 7 montrerait quelles pages ont été consultées et à quelle fréquence. Bien sûr, vous voudriez alors changer la condition au début. Ce qui suit montrerait quelles pages ont été consultées par un utilisateur à partir d'une adresse IP spécifique:

awk -F'[ "]+' '$1 == "1.2.3.4" { pagecount[$7]++ }
    END { for (i in pagecount) {
        printf "%15s - %d\n", i, pagecount[i] } }' logfile.log

Vous pouvez également diriger la sortie par le biais du tri pour obtenir les résultats dans l'ordre, soit dans le cadre de la commande shell, soit également dans le script awk lui-même:

awk -F'[ "]+' '$7 == "/" { ipcount[$1]++ }
    END { for (i in ipcount) {
        printf "%15s - %d\n", i, ipcount[i] | sort } }' logfile.log

Ce dernier serait utile si vous décidiez d'étendre le script awk pour imprimer d'autres informations. Tout dépend de ce que vous voulez savoir. Ceux-ci devraient servir de point de départ pour tout ce qui vous intéresse.


Yah, il semble toujours étrange de voir de longs pipelines fous chat / grep / awk. Une fois que vous êtes dans awk, c'est généralement suffisant. Les trois premières clauses de l'article d'origine pourraient être trivialement écrites comme "awk '/ request_to_file_foo / {print $ 1}' foo.log". awk peut prendre un fichier en entrée et utiliser regex pour savoir quelles lignes prendre en compte.
Zac Thompson

Élégant et simple. Bien.
Olivier Dulac

Attention, les espaces semblent autorisés dans le champ "authuser" (3ème), cela casse tout, et je pense personnellement que cela devrait être interdit, pour nous permettre de le faire ;-)
Mandark

23

Une chose que je n'ai jamais vue quelqu'un d'autre faire, pour des raisons que je ne peux pas imaginer, est de changer le format du fichier journal Apache en une version plus facilement analysable avec les informations qui comptent vraiment pour vous.

Par exemple, nous n'utilisons jamais l'authentification de base HTTP. Il n'est donc pas nécessaire de consigner ces champs. Je suis intéressé par le temps que chaque demande met à traiter, nous allons donc l'ajouter. Pour un projet, nous voulons également savoir (sur notre équilibreur de charge) si l'un des serveurs traite les demandes plus lentement que les autres, nous enregistrons donc le nom. du serveur que nous mandatons en retour.

Voici un extrait de la configuration apache d'un serveur:

# We don't want to log bots, they're our friends
BrowserMatch Pingdom.com robot

# Custom log format, for testing
#
#         date          proto   ipaddr  status  time    req     referer         user-agent
LogFormat "%{%F %T}t    %p      %a      %>s     %D      %r      %{Referer}i     %{User-agent}i" standard
CustomLog /var/log/apache2/access.log standard env=!robot

Ce que vous ne pouvez pas vraiment dire, c’est qu’entre chaque champ se trouve un caractère de tabulation littéral (\ t). Cela signifie que si je veux faire une analyse en Python, peut-être montrer des statuts non-200 par exemple, je peux le faire:

for line in file("access.log"):
  line = line.split("\t")
  if line[3] != "200":
    print line

Ou si je voulais faire "qui est hotlinking images?" ce serait

if line[6] in ("","-") and "/images" in line[5]:

Pour les comptes IP dans un journal d'accès, l'exemple précédent:

grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" logfile | sort -n | uniq -c | sort -n

devient quelque chose comme ceci:

cut -f 3 log | uniq -c | sort -n

Plus facile à lire et à comprendre, et beaucoup moins onéreuse en calcul (sans regex), ce qui, sur des journaux de 9 Go, fait une énorme différence de temps. Lorsque cela devient vraiment intéressant, c'est si vous voulez faire la même chose pour User-agents. Si vos journaux sont délimités par des espaces, vous devez effectuer une recherche d’expression régulière ou une recherche manuelle à la chaîne. Avec ce format, c'est simple:

cut -f 8 log | uniq -c | sort -n

Exactement le même que ci-dessus. En fait, tout résumé que vous voulez faire est essentiellement identique.

Pourquoi est-ce que je dépenserais le processeur de mon système en awk et si grep, quand couper ferait exactement ce que je voulais d'un ordre de grandeur plus rapide?


2
En réalité, vos exemples pour le nouveau format sont encore trop compliqués - les comptes IP deviennent cut -f 3 log | uniq -c | sort -ndes agents utilisateurs cut -f 8 log | uniq -c | sort -n.
Creshal

Vous avez raison, c'est plus simple. J'ai mis à jour les exemples pour refléter cela.
Dan Udey

"cat file | grep string" est inutile, pourquoi pas "grep string file"?
c4f4t0r

2
Je n'ai aucune excuse et ai mis à jour l'exemple en conséquence.
Dan Udey

15

Oubliez awk et grep. Consultez asql . Pourquoi écrire des scripts illisibles lorsque vous pouvez utiliser SQL comme syntaxe pour interroger le fichier journal. Par exemple.

asql v0.6 - type 'help' for help.
asql> load /home/skx/hg/engaging/logs/access.log
Loading: /home/skx/hg/engaging/logs/access.log
sasql> select COUNT(id) FROM logs
46
asql> alias hits SELECT COUNT(id) FROM logs
ALIAS hits SELECT COUNT(id) FROM logs
asql> alias ips SELECT DISTINCT(source) FROM logs;
ALIAS ips SELECT DISTINCT(source) FROM logs;
asql> hits
46
asql> alias
ALIAS hits SELECT COUNT(id) FROM logs
ALIAS ips SELECT DISTINCT(source) FROM logs;

Intéressant, mais vous pourriez rencontrer des problèmes si vos journaux sont particulièrement volumineux, je pense. De plus, dans quelle mesure est-il adapté aux formats de journaux personnalisés?
Vagnerr

Je l'essaie pour le moment, le temps de chargement est très lent (au moins dans la version 0.9). Charger un journal de 200Mb prend plus de cinq minutes ..
aseques

Je dois dire qu'après le temps de chargement (cela a pris environ 15 minutes), la synstaxe de ce programme est excellente, vous pouvez trier, compter et grouper par. Vraiment sympa.
aseques

Apache HTTPD a une méthode avec laquelle vous pouvez effectivement envoyer les journaux à une base de données. Oui, les écritures peuvent prendre beaucoup de temps, mais un proxy fileté peut faire la bonne chose en sandwich au milieu. Quoi qu'il en soit, cela accélérera considérablement l'interrogation des journaux dans une syntaxe similaire à SQL. Pas de chargement impliqué aussi - le serveur de base de données est perpétuellement "ON".
près de rora

6

Voici un script permettant de rechercher les principales URL, principaux référents et principaux agents utilisateurs parmi les N entrées de journal récentes.

#!/bin/bash
# Usage
# ls-httpd type count
# Eg: 
# ls-httpd url 1000
# will find top URLs in the last 1000 access log entries
# ls-httpd ip 1000
# will find top IPs in the last 1000 access log entries
# ls-httpd agent 1000
# will find top user agents in the last 1000 access log entries

type=$1
length=$2

if [ "$3" == "" ]; then
  log_file="/var/log/httpd/example.com-access_log"
else
  log_file="$3"
fi

if [ "$type" = "ip" ]; then
  tail -n $length $log_file | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | sort -n | uniq -c | sort -n
elif [ "$type" = "agent" ]; then
  tail -n $length $log_file | awk -F\" '{print $6}'| sort -n | uniq -c | sort -n
elif [ "$type" = "url" ]; then
  tail -n $length $log_file | awk -F\" '{print $2}'| sort -n | uniq -c | sort -n
fi

La source


4

pour les comptes IP dans un journal d'accès:

cat log | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | sort -n | uniq -c | sort -n

C'est un peu moche, mais ça marche. J'utilise aussi ce qui suit avec netstat (pour voir les connexions actives):

netstat -an | awk '{print $5}' | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" | egrep -v "(`for i in \`ip addr | grep inet |grep eth0 | cut -d/ -f1 | awk '{print $2}'\`;do echo -n "$i|"| sed 's/\./\\\./g;';done`127\.|0\.0\.0)" | sort -n | uniq -c | sort -n

Ils sont certains de mes "one liners" préférés :)


3

Construire une liste de questions courantes constituerait un excellent index pour répondre à cette question. Mes questions communes sont:

  • pourquoi le hitrate a-t-il changé?
  • pourquoi le temps de réponse global augmente-t-il? '

Je remarque de tels changements en surveillant les pages d'état du serveur (via mod_status) pour connaître le temps de réponse et le temps de réponse approximatif pour les demandes actives et récemment complétées (sachant pertinemment que je manque une énorme pile de données, mais que les échantillons sont assez bons).

J'utilise la directive LogFormat suivante (le% T est vraiment utile)

LogFormat "%h %l %u %t \"%r\" %>s %b 
    \"%{Referer}i\" \"%{User-Agent}i\" %T" custom

Je cherche la cause à effet et ce qui s'est passé en premier ... généralement sur des sous-ensembles spécifiques de modèles dans mes journaux, j'ai donc besoin de connaître les informations suivantes pour tout modèle / expression régulière donné:

  • hitcounts par intervalle (minute ou heure) pour un modèle donné (adresse ip ou chaîne cgi ou paramètres, etc.)
  • histogrammes du temps de réponse approximatif (en utilisant le paramètre% T)

J'utilise généralement perl, car il devient finalement assez complexe pour en valoir la peine.


Un exemple non perl serait un taux de réussite rapide par minute pour les codes de statut autres que 200:

tail -9000 access_log | grep -v '" 200 ' | cut -d: -f2,3 | uniq -c

Oui, je triche avec ce grep, en supposant qu'un quote-espace-200-espace ne correspond qu'à des codes de statut http ... peut utiliser awk ou perl pour isoler le champ, mais gardez à l'esprit qu'il peut être inexact.


Un exemple plus complexe en perl pourrait consister à visualiser un changement de taux de hit pour un motif.

Le script ci-dessous a beaucoup à mâcher, en particulier si vous ne connaissez pas perl.

  • lit stdin pour que vous puissiez utiliser des portions de vos journaux, utiliser tail (surtout avec tail -f), avec ou sans greps et autre filtrage ...
  • trompe l'extraction de timestamp d'époque avec le hack d'une regex et l'utilisation de Date :: Manip
  • vous pouvez le modifier seulement légèrement pour extraire le temps de réponse ou d'autres données arbitraires

le code suit:

#!/usr/bin/perl
# script to show changes in hitrates for any regex pattern
# results displayed with arbitrary intervals
# and ascii indication of frequency
# gaps are also displayed properly
use Date::Manip;
use POSIX qw(strftime);
$pattern=shift || ".";
$ival=shift || 60;
$tick=shift || 10;
$minb=undef;
while (<>){
    next unless /$pattern/;
    $stamp="$1 $2" if m[(../.../....):(..:..:..)];
    $epoch = UnixDate(ParseDate($stamp),"%s");
    $bucket= int($epoch/$ival)*$ival;
    $minb=$bucket if $bucket<$minb || !defined($minb);
    $maxb=$bucket if $bucket>$maxb;
    $count{$bucket}++;
}
# loop thru the min/max range to expose any gaps
for($t=$minb;$t<=$maxb;$t+=$ival){
    printf "%s %s %4d %s\n",
            $t,
            strftime("%m/%d/%Y %H:%M:%S",localtime($t)),
            $count{$t}+0,
            substr("x"x100,0,$count{$t}/$tick
    );
}

Si vous souhaitez simplement traiter des métriques standard, passez à la caisse.

  • 'mergelog' pour rassembler tous vos journaux (si vous avez plusieurs apaches derrière un équilibreur de charge) et
  • Webalizer (ou awstats ou un autre analyseur commun).

3

Ici, mon exemple 'sed', il lit le format par défaut des journaux Apache et le convertit en un format plus pratique pour le traitement automatique. La ligne entière est définie comme une expression régulière, les variables sont enregistrées et écrites en sortie avec '#' comme séparateur.

La notation simplifiée de l'entrée est la suivante:% s% s% s [% s] "% s"% s% s "% s" "% s"

Exemple de ligne de saisie: xx.xx.xx.xx - - [29 / Mar / 2011: 12: 33: 02 +0200] "GET /index.html HTTP / 1.0" 200 9443 "-" "Mozilla / 4.0"

Exemple de ligne de sortie: xx.xx.xx.xx # - # - # 29 / mars / 2011: 12: 33: 02 + 0200 # GET /index.html HTTP / 1.0 # 200 # 9443 # - # Mozilla / 4.0

cat access.log | \ 
  sed 's/^\(.*\) \(.*\) \(.*\) \[\(.*\)\] \"\(.*\)\" \(.*\) \(.*\) \"\(.*\)\" \"\(.*\)\"$/\1#\2#\3#\4#\5#\6#\7#\8#\9/g'

Ressentez le pouvoir des expressions régulières :-)


Cela a rendu le traitement avec AWK un jeu d'enfant. Je cherchais un moyen rapide d’installer un déliminateur commun et c’est tout.
Citricguy

J'ai senti la puissance de la regex et je voulais juste transmettre mon propre tweak, qui supprime le "HTML / 1.1" et sépare le protocole (d'une manière probablement non conforme aux normes) dans son propre domaine. Enjoy: `` `cat access.log | sed 's /^(.*) (. *) (. *) [(. *)] \ "([[: alpha:]] +) (. *) HTTP \ / 1 \ .1 \" ( . *) (. *) \ "(. *) \" \ "(. *) \" $ / \ 1 # \ 2 # \ 3 # \ 4 # \ 5 # \ 6 # \ 7 # \ 8 # \ 9 # \ 10 / g '``
Josh Rumbut

2

J'utilise beaucoup awk en traînant ou en catantant le fichier. Chaque soir, je me livre un rapport Web pour chaque serveur. En fonction de votre fichier journal et de votre LogFormat, vous devrez éditer quelques uns des liners pour travailler pour vous ...

Voici un exemple simple:

Si je souhaite modifier les journaux sur mon serveur pour seulement les codes d'état 404/500, je procéderais comme suit:

# $6 is the status code in my log file

tail -f ${APACHE_LOG} |  awk  '$8 ~ /(404|500)/ {print $6}'

<snip>

echo ""
#echo  "Hits by source IP:"
echo "======================================================================"

awk '{print $2}' "$1" | grep -ivE "(127.0.0.1|192.168.100.)" | sort | uniq -c | sort -rn | head -25

echo ""
echo ""
#echo "The 25 most popular pages:"
echo "======================================================================"

awk '{print $6}' "$1" | grep -ivE '(mod_status|favico|crossdomain|alive.txt)' | grep -ivE '(.gif|.jpg|.png)' | \
 sed 's/\/$//g' | sort | \
 uniq -c | sort -rn | head -25

echo ""    
echo ""
echo "The 25 most popular pages (no js or css):"
echo "======================================================================"

awk '{print $6}' "$1" | grep -ivE '(mod_status|favico|crossdomain|alive.txt)' | grep -ivE '(.gif|.jpg|.png|.js|.css)' | \
 sed 's/\/$//g' | sort | \
   uniq -c | sort -rn | head -25

   echo ""


#echo "The 25 most common referrer URLs:"
echo "======================================================================"

awk '{print $11}' "$1" | \
 grep -vE "(^"-"$|/www.$host|/$host)" | \
 sort | uniq -c | sort -rn | head -25

echo ""

#echo "Longest running requests"
echo "======================================================================"

awk  '{print $10,$6}' "$1" | grep -ivE '(.gif|.jpg|.png|.css|.js)'  | awk '{secs=0.000001*$1;req=$2;printf("%.2f minutes req time for %s\n", secs / 60,req )}' | sort -rn | head -50

exit 0

</ snip>


2

Qui relie vos images à chaud:

awk -F\" '($2 ~ /\.(jpg|gif)/ && $4 !~ /^http:\/\/www\.mydomain\.com/){print $4}' access_log | sort | uniq -c | sort

1

Ce que j'ai tendance à faire le plus souvent est de lire des sections d'un journal en fonction du temps. J'ai donc écrit le script suivant en utilisant sed pour extraire la période qui m'intéresse. Cela fonctionne pour tous les fichiers du journal que je viens. across et peut également gérer les journaux archivés.

#! / bin / bash
#Ce script doit renvoyer un ensemble de lignes entre 2 valeurs, le but principal étant de rechercher un fichier journal entre 2 fois
Utilisation #Script: logship.sh "start" "stop" fichier

#Si le fichier contient des "/" dans la plage de dates, les deux lignes suivantes ajoutent le caractère d'échappement afin que la recherche puisse être effectuée pour ces caractères.
start = $ (echo "$ 1" | sed 's / \ // \\\ // g')
stop = $ (echo "$ 2" | sed 's / \ // \\\ // g')

zipped = $ (echo "$ 3" | grep -c "gz $") # indique si le fichier est compressé ou non

if ["$ zipped" == "1"]; alors #Si le fichier est compressé, passez-le par zcat avant sed
        zcat 3 $ | sed -n "/ $ start /, / $ stop / p";
autre
        sed -n "/ $ start /, / $ stop / p" $ 3; #si ce n'est pas compressé, lancez sed
Fi

1

Bien que ce ne soit pas sed ou awk, il y a deux choses que j'ai trouvées utiles pour gérer les fichiers journaux Apache et icecast.

AWStats a un script très utile appelé logresolvemerge.pl qui combine plusieurs fichiers journaux compressés ou non compressés, supprime les dupes et les trie par horodatage. Il peut également effectuer des recherches DNS et être configuré pour exécuter des processus multithread. C'est particulièrement utile lorsque vous utilisez awstats, car awstats ne peut pas ajouter de lignes de journal avec des horodatages plus anciens que la base de données actuelle. Vous devez donc tous les ajouter dans l'ordre, mais c'est très simple, car vous n'avez qu'à tout jeter à logresolvemerge.pl et que tout s'affiche correctement.

sed et awk gèrent mal les dates car ils les traitent généralement comme des ficelles. awk a quelques fonctions de date et d’heure, mais elles n’ont pas beaucoup à faire. Par exemple, l'extraction d'une plage de lignes entre deux horodatages est difficile si ces horodatages exacts ne se produisent pas dans le fichier (même si les valeurs entre eux le sont) - l'exemple de Chris a exactement ce problème. Pour remédier à cela, j'ai écrit un script PHP qui indique les plages d'horodatage des fichiers journaux et peut également extraire un bloc par plage d'horodatage, en utilisant n'importe quel format de date ou d'heure (le format d'horodatage du fichier journal n'a pas besoin de l'être).

Pour conserver ce sujet, voici quelques astuces utiles: Obtenez le nombre total d'octets servis à partir du journal apache ou icecast:

cat access.log | awk '{ sum += $10 } END { print sum }'

Obtenez le nombre total de secondes connectées à partir d'un journal icecast:

cat access.log | awk '{ sum += $13 } END { print sum }'

+1 pour un simple journal de sommation d'octets avec awk
rymo

0

La récupération de ce vieux fil, après avoir abandonné sur asql pour les grands fichiers journaux, regardé une solution againg, également serverfault, je l' ai trouvé au sujet WTOP ici il est un outil opensource, qui est capable de faire la surveillance en direct ou les journaux processus et obtenir des statistiques (top N), très flexible et puissant, la place officielle est ici

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.