Même si vous essayez d'analyser les journaux d'accès tels que Apache et Tomcat, il n'y a pas d'outil étonnamment bon. J'aimerais avoir un outil qui prend en charge divers formats de sortie, ne filtre que les informations nécessaires et le rend facile à comprendre et à visualiser, mais je ne le trouve pas facilement.
Ainsi, dans le monde de l'analyse de données, j'ai essayé d'analyser et de visualiser facilement les journaux d'accès Apache sur Jupyter Notebook en utilisant Pandas et Matplotlib, qui sont standard dans le monde de l'analyse de données.
Tout d'abord, importez le minimum requis Pandas et Matplotlib.
import pandas as pd
from pandas import DataFrame, Series
import matplotlib.pyplot as plt
Réglez l'environnement selon vos préférences.
#Les graphiques, etc. sont dessinés dans Notebook
%matplotlib inline
#Modification de la longueur de chaîne maximale des colonnes DataFrame de la valeur par défaut de 50 à 150
pd.set_option("display.max_colwidth", 150)
Pour savoir comment charger le journal d'accès, j'ai fait référence à cette entrée de blog. Avant de charger le journal d'accès, définissez les fonctions d'analyse de type requises.
from datetime import datetime
import pytz
def parse_str(x):
return x[1:-1]
def parse_datetime(x):
dt = datetime.strptime(x[1:-7], '%d/%b/%Y:%H:%M:%S')
dt_tz = int(x[-6:-3])*60+int(x[-3:-1])
return dt.replace(tzinfo=pytz.FixedOffset(dt_tz))
Ensuite, chargez le journal d'accès avec pd.read_csv ()
. Le format de sortie du journal d'accès que j'avais était légèrement différent, je l'ai donc modifié comme suit. Cette fois, par souci de simplicité, seules quelques colonnes sont extraites.
df = pd.read_csv(
'/var/log/httpd/access_log',
sep=r'\s(?=(?:[^"]*"[^"]*")*[^"]*$)(?![^\[]*\])',
engine='python',
na_values='-',
header=None,
usecols=[0, 4, 6, 7, 8, 9, 10],
names=['ip', 'time', 'response_time', 'request', 'status', 'size', 'user_agent'],
converters={'time': parse_datetime,
'response_time': int,
'request': parse_str,
'status': int,
'size': int,
'user_agent': parse_str})
Après avoir chargé le journal d'accès, analysons-le.
Commencez par vérifier les 5 premières et 5 dernières lignes du fichier.
df
Vous pouvez voir qu'il y a eu 114 004 demandes en environ 1 heure et 20 minutes à partir de 18 h 20 le 24 mars.
Affiche le temps de réponse moyen et maximum.
print('Résultat total du temps de réponse (microsecondes)\n')
print('valeur minimum: {}'.format(str(df['response_time'].min()).rjust(10)))
print('Valeur moyenne: {}'.format(str(round(df['response_time'].mean())).rjust(10)))
print('Médian: {}'.format(str(round(df['response_time'].median())).rjust(10)))
print('Valeur maximum: {}'.format(str(df['response_time'].max()).rjust(10)))
print('\n Pire temps de réponse 15')
df.sort_values('response_time', ascending=False).head(15)
Les 15 pires temps de réponse étaient toutes les demandes adressées au service d'authentification OpenAM, et il a fallu environ 15 secondes pour renvoyer une réponse avec le statut HTTP 406. Vous pouvez voir qu'il y a eu un problème avec l'application qui fonctionne avec OpenAM.
En regardant les 15 pires temps de réponse, size
(taille de la réponse) et ʻuser_agent (agent utilisateur) sont
NaN`. Vérifions le nombre de valeurs manquantes.
df.isnull().sum()
Si «size» est «NaN», c'est une redirection. De plus, environ 30% de ʻuser_agent` sont inconnus. Puisque ce n'est pas seulement le navigateur de l'utilisateur final qui accède à OpenAM, il est probable que beaucoup d'entre eux n'ont pas d'en-tête "User-Agent". Découvrons quels autres agents utilisateurs sont disponibles.
ua_df = DataFrame(df.groupby(['user_agent']).size().index)
ua_df['count'] = df.groupby(['user_agent']).size().values
ua_df
Il y avait 490 types. Il semble que ce soit le résultat car non seulement les utilisateurs finaux, mais aussi de nombreuses applications travaillent ensemble.
Je ne l'ai pas du tout visualisé jusqu'à présent, donc je pense que ce n'était pas intéressant. Montrons-le sous forme de graphique circulaire. Tout d'abord, dessinons le pourcentage du code d'état de la réponse dans un graphique circulaire.
plt.figure(figsize = (5, 5))
labels = ['2xx', '3xx', '4xx', '5xx']
plt.pie(df.groupby([df['status'] // 100]).size(), autopct = '%1.1f%%', labels = labels, shadow = True, startangle = 90)
plt.axis('equal')
df.groupby([df['status'] // 100]).size()
plt.show()
Hmmm, les étiquettes de code d'état à faible pourcentage se chevauchent et ne semblent pas bonnes ...
Alors, définissons une fonction qui regroupe un petit nombre d'éléments (1% ou moins) en «autres». Si vous utilisez la fonction de Matplotlib, vous n'avez peut-être pas besoin d'une telle fonction, mais je ne l'ai pas trouvée, alors j'en ai fait une simple.
#Pour DataFrame
def replace_df_minors_with_others(df_before, column_name):
elm_num = 1
for index, row in df_before.sort_values([column_name], ascending=False).iterrows():
if (row[column_name] / df_before[column_name].sum()) > 0.01:
elm_num = elm_num + 1
df_after = df_before.sort_values([column_name], ascending=False).nlargest(elm_num, columns=column_name)
df_after.loc[len(df_after)] = ['others', df_before.drop(df_after.index)[column_name].sum()]
return df_after
#Pour les dictionnaires
def replace_dict_minors_with_others(dict_before):
dict_after = {}
others = 0
total = sum(dict_before.values())
for key in dict_before.keys():
if (dict_before.get(key) / total) > 0.01:
dict_after[key] = dict_before.get(key)
else:
others = others + dict_before.get(key)
dict_after = {k: v for k, v in sorted(dict_after.items(), reverse=True, key=lambda item: item[1])}
dict_after['others'] = others
return dict_after
Maintenant, utilisons cette fonction pour afficher les types d'agent utilisateur dans un graphique circulaire.
plt.figure(figsize = (15, 10))
ua_df_with_others = replace_df_minors_with_others(ua_df, 'count')
plt.pie(ua_df_with_others['count'], labels = ua_df_with_others['user_agent'], autopct = '%1.1f%%', shadow = True, startangle = 90)
plt.title('User Agent')
plt.show()
Même si vous affichez directement l'en-tête "User-Agent", c'est difficile à comprendre. Qu'est-ce que "Mozilla / 5.0 (Windows NT 6.1; WOW64; Trident / 7.0; rv: 11.0) comme Gecko" ...
Alors, convertissons-le en un affichage facile à comprendre à l'aide d'une bibliothèque appelée Woothee. Installez avec la commande suivante.
$ pip install woothee
Utilisons ceci pour afficher le système d'exploitation client et l'agent utilisateur dans un graphique circulaire.
import woothee
ua_counter = {}
os_counter = {}
for index, row in ua_df.sort_values(['count'], ascending=False).iterrows():
ua = woothee.parse(row['user_agent'])
uaKey = ua.get('name') + ' (' + ua.get('version') + ')'
if not uaKey in ua_counter:
ua_counter[uaKey] = 0
ua_counter[uaKey] = ua_counter[uaKey] + 1
osKey = ua.get('os') + ' (' + ua.get('os_version') + ')'
if not osKey in os_counter:
os_counter[osKey] = 0
os_counter[osKey] = os_counter[osKey] + 1
plt.figure(figsize = (15, 10))
plt.subplot(1,2,1)
plt.title('Client OS')
os_counter_with_others = replace_dict_minors_with_others(os_counter)
plt.pie(os_counter_with_others.values(), labels = os_counter_with_others.keys(), autopct = '%1.1f%%', shadow = True, startangle = 90)
plt.subplot(1,2,2)
plt.title('User Agent')
ua_counter_with_others = replace_dict_minors_with_others(ua_counter)
plt.pie(ua_counter_with_others.values(), labels = ua_counter_with_others.keys(), autopct = '%1.1f%%', shadow = True, startangle = 90)
plt.show()
«INCONNU» a augmenté, mais est-il devenu plus facile à comprendre? Même ainsi, de nombreuses personnes utilisent encore IE sous Windows ...
Examinons ensuite le pourcentage du code d'état (400 ou plus) de la réponse qui a provoqué une erreur.
error_df = df[df['status'] >= 400]
plt.figure(figsize = (10, 10))
labels = ['4xx', '5xx']
plt.pie(error_df.groupby([error_df['status'] // 100]).count().time, labels=labels, counterclock=False, startangle=90)
labels2 = ['400', '401', '403', '404', '406', '500', '501', '502']
plt.pie(error_df.groupby(['status']).count().time, labels=labels2, counterclock=False, startangle=90, radius=0.7)
centre_circle = plt.Circle((0,0),0.4, fc='white')
fig = plt.gcf()
fig.gca().add_artist(centre_circle)
plt.title('Error Status Code')
plt.show()
La caractéristique de ce journal d'accès est qu'il existe de nombreuses réponses d'erreur avec le code d'état 406, qui n'est généralement pas familier.
Maintenant, sortons un graphique différent. Tout d'abord, vérifiez l'état de la charge.
plt.figure(figsize = (15, 5))
access = df['request']
access.index = df['time']
access = access.resample('S').count()
access.index.name = 'Time'
access.plot()
plt.title('Total Access')
plt.ylabel('Access')
plt.show()
Il y a un accès constant, parfois plus de 100 par seconde.
Voyons s'il existe une relation entre la taille de la réponse et l'heure.
plt.figure(figsize = (15, 5))
plt.title('size v.s. response_time')
plt.scatter(df['size']/1000, df['response_time']/1000000, marker='.')
plt.xlabel('Size(KB)')
plt.ylabel('Response Time')
plt.grid()
Bien qu'il n'y ait pas de relation claire, si la taille de la réponse n'est pas 0, on ne peut pas dire que plus la taille est grande, moins cela prend de temps.
Je pense que Pandas et Matplotlib peuvent être utilisés pour analyser les journaux d'accès. Si vous pouvez maîtriser ces bibliothèques, cela sera utile pour le dépannage.
Je suis encore au stade du prototype, mais je suis également engagé sur GitHub.
Recommended Posts