Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
74fcf31
Feat: Filtre (#41)
AnthonyGuillauma Apr 5, 2025
45092af
Test: Ajout des tests unitaires pour FiltreLogApache (#42)
AnthonyGuillauma Apr 5, 2025
9599598
Docs: Ajout de la documentation pour FiltreLogApache (#43)
AnthonyGuillauma Apr 5, 2025
f6e5e77
CI: Déplacement workflows vers les branches release (#44)
AnthonyGuillauma Apr 5, 2025
e15728a
Docs: Modification d'une erreur dans l'accueil de la documentation (#45)
AnthonyGuillauma Apr 5, 2025
45ea106
Feat: Ajout de l'exportation de graphique camembert (#46)
AnthonyGuillauma Apr 6, 2025
33474a3
Chore: Ajout des nouvelles dépendances sur requirements.txt (#47)
AnthonyGuillauma Apr 6, 2025
c975734
CI: Amélioration des importations dans les workflows (#48)
AnthonyGuillauma Apr 6, 2025
ddfaea3
Feat: Chemin log absolu dans résultat analyse
AnthonyGuillauma Apr 6, 2025
414640c
Test: Mise à jour d'un test unitaire pour chemin absolu
AnthonyGuillauma Apr 6, 2025
67e7871
Chore: Mise à jour du README.md pour la version 0.2.0 (#50)
AnthonyGuillauma Apr 6, 2025
a049d94
Docs: Mise à jour de la documentation (#51)
AnthonyGuillauma Apr 6, 2025
7fd1364
Chore: Modification d'un lien dans le README (#52)
AnthonyGuillauma Apr 6, 2025
1ed99e5
Fix: Modification de l'emplacement de l'analyse
AnthonyGuillauma Apr 6, 2025
2ae79b0
Test: Modification test unitaire après modification analyse
AnthonyGuillauma Apr 6, 2025
b7a2750
Feat: Amélioration de la lisibilité des camemberts (#54)
AnthonyGuillauma Apr 6, 2025
87633cd
Chore: Ajout du document de réflexion dans le README.md (#55)
AnthonyGuillauma Apr 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/documentation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ jobs:
- name: Installation des dépendances
run: |
python -m pip install --upgrade pip
pip install sphinx
pip install sphinx_rtd_theme --break-system-packages
pip install colorama
pip install -r requirements.txt

# Étape 4 : Générer la documentation
- name: Construction de la documentation (avec Sphinx)
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/qualite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
branches:
- main
- develop
- 'release/**'

jobs:
lint:
Expand All @@ -25,8 +25,7 @@ jobs:
- name: Installation des dépendances
run: |
python -m pip install --upgrade pip
pip install pylint
pip install colorama
pip install -r requirements.txt

# Étape 4 : Lancement de l'analyse
- name: Analyse avec Pylint (note >= 9.0 requise)
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
branches:
- main
- develop
- 'release/**'

# Permissions (lecture uniquement)
permissions:
Expand Down Expand Up @@ -33,6 +33,8 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install colorama
pip install altair
pip install pandas
pip install pytest
pip install pytest-cov
pip install pytest-mock
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

Bienvenue dans le monde de LogBuster, l'outil ultime pour analyser, décortiquer et sauver vos logs Apache des griffes du chaos. Vous avez des logs qui traînent, qui sont indéchiffrables ou tout simplement encombrants ? Pas de panique, LogBuster est là pour les attraper, les analyser et vous offrir des statistiques claires et précises, comme jamais auparavant !

Le document de réflexion est disponible [ici](https://drive.google.com/file/d/1j_F8MizttT8etW3joW23XR5ZI5k-uNqB/view?usp=sharing).

## 📋 Table des matières

- [👻 Fonctionnalités](#-fonctionnalités)
Expand All @@ -28,6 +30,8 @@ Bienvenue dans le monde de LogBuster, l'outil ultime pour analyser, décortiquer

- 📄 Parsing avancé de logs Apache.
- 📉 Extraire des statistiques clés.
- 🥧 Génération de graphiques camemberts.
- 🧽 Filtrer les analyses.
- 🗂️ Ranger les données par catégorie.
- 🧹 Indiquer les erreurs de format avec précision.
- 🚚 Exporter les données en JSON.
Expand All @@ -39,7 +43,7 @@ Bienvenue dans le monde de LogBuster, l'outil ultime pour analyser, décortiquer
git clone https://github.com/AnthonyGuillauma/code_source
cd code_source
python -m venv .venv
source .venv/bin/activate # Activation de l'environnement virtuel sous Bash
source .venv/bin/activate
pip install -r requirements.txt
```

Expand All @@ -48,17 +52,20 @@ pip install -r requirements.txt
git clone https://github.com/AnthonyGuillauma/code_source
cd code_source
python -m venv .venv
.venv\Scripts\activate # Activation de l'environnement virtuel sous Windows
.venv\Scripts\activate
pip install -r requirements.txt
```

## 🛠️ Utilisation de base

```
python app/main.py chemin_log [-s SORTIE]
python app/main.py chemin_log [-s SORTIE] [-i IP] [-c CODE_STATUT_HTTP] [--camembert CAMEMBERT]
```
- `chemin_log` : Le chemin vers le fichier de log Apache à analyser.
- `-s SORTIE` (optionnel) : Le chemin où sauvegarder les résultats de l'analyse. Si non spécifié, les résultats seront sauvegardés dans un fichier `analyse-log-apache.json`.
- `-i IP` (optionnel) : Le filtre à appliquer sur les adresses IP des entrées du fichier de log. Uniquement les entrées avec cette adresse IP seront analysées.
- `-c CODE_STATUT_HTTP` (optionnel) : Le filtre à appliquer sur les code de statut http des entrées du fichier de log. Uniquement les entrées avec ce code de statut http seront analysées.
- `--camembert CAMEMBERT` (optionnel) : Active la génération de graphiques camemberts dans lors de l'analyse pour les statistiques compatibles (plus d'infos [ici](https://anthonyguillauma.github.io/code_source/#o-o-format-de-l-analyse)).

## ⚠️ Précautions

Expand Down
82 changes: 68 additions & 14 deletions app/analyse/analyseur_log_apache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
Module pour l'analyse statistique d'un fichier log Apache.
"""

from os.path import abspath
from collections import Counter
from parse.fichier_log_apache import FichierLogApache
from analyse.filtre_log_apache import FiltreLogApache


class AnalyseurLogApache:
Expand All @@ -17,24 +19,29 @@ class AnalyseurLogApache:
les statistiques des classements (tops).
"""

def __init__(self, fichier_log_apache: FichierLogApache, nombre_par_top: int = 3):
def __init__(self,
fichier_log_apache: FichierLogApache,
filtre: FiltreLogApache,
nombre_par_top: int = 3):
"""
Initialise un nouveau analysateur de fichier log Apache.

Args:
fichier_log_apache (FichierLogApache): Le fichier à analyser.
filtre (FiltreLogApache): Le filtre à appliquer dans l'analyse. Si une entrée ne
passe pas le filtre, elle ne sera pas pris en compte dans l'analyse.
nombre_par_top (int): Le nombre maximal d'éléments à inclure dans
les statistiques des classements (tops). Par défaut, sa valeur est égale à ``3``.

Raises:
TypeError: Si l'argument ``fichier_log_apache`` n'est pas une instance
de :class:`FichierLogApache`
ou si l'argument ``nombre_par_top`` n'est pas un entier.
TypeError: Les paramètres ne sont pas du type attendu.
ValueError: Si l'argument ``nombre_par_top`` est inférieur à ``0``.
"""
# Vérification du type des paramètres
if not isinstance(fichier_log_apache, FichierLogApache):
raise TypeError("La représentation du fichier doit être de type FichierLogApache.")
if not isinstance(filtre, FiltreLogApache):
raise TypeError("Le filtre à appliquer aux entrées doit être de type FiltreLogApache.")
if not isinstance(nombre_par_top, int) or isinstance(nombre_par_top, bool):
raise TypeError("Le nombre par top doit être un entier.")
# Vérification de la valeur du paramètre
Expand All @@ -43,8 +50,22 @@ def __init__(self, fichier_log_apache: FichierLogApache, nombre_par_top: int = 3

# Ajout des données
self.fichier = fichier_log_apache
self.filtre = filtre
self.nombre_par_top = nombre_par_top

def _get_entrees_passent_filtre(self) -> list:
"""
Retourne les entrées qui passent le filtre.

Returns:
list: La liste des entrées qui passent le filtre.
"""
entrees_valides = []
for entree in self.fichier.entrees:
if self.filtre.entree_passe_filtre(entree):
entrees_valides.append(entree)
return entrees_valides

def _get_repartition_elements(self,
liste_elements: list,
nom_elements: str,
Expand Down Expand Up @@ -93,22 +114,29 @@ def get_analyse_complete(self) -> dict:
Retourne l'analyse complète du fichier de log Apache.

L'analyse suit la structure suivante :
- chemin: chemin du fichier
- chemin: chemin absolu du fichier
- total_entrees: voir :meth:`get_total_entrees`
- filtre: filtre appliqué à l'analyse
- statistiques:
- requetes:
- top_urls: voir :meth:`get_top_urls`
- repartition_code_statut_http:
voir :meth:`get_total_par_code_statut_http`
- total_entrees_filtre: voir :meth:`get_total_entrees_filtre`
- requetes:
- top_urls: voir :meth:`get_top_urls`
- reponses:
- repartition_code_statut_http: voir :meth:`get_total_par_code_statut_http`

Returns:
dict: L'analyse sous forme d'un dictionnaire.
"""
return {
"chemin": self.fichier.chemin,
"chemin": abspath(self.fichier.chemin),
"total_entrees": self.get_total_entrees(),
"filtre": self.filtre.get_dict_filtre(),
"statistiques": {
"total_entrees": self.get_total_entrees(),
"total_entrees_filtre": self.get_total_entrees_filtre(),
"requetes": {
"top_urls": self.get_top_urls(),
},
"reponses": {
"repartition_code_statut_http": self.get_total_par_code_statut_http()
}
}
Expand All @@ -123,9 +151,19 @@ def get_total_entrees(self) -> int:
"""
return len(self.fichier.entrees)

def get_total_entrees_filtre(self) -> int:
"""
Retourne le nombre d'entrées qui ont passées le filtre dans le fichier.

Returns:
int: Le nombre total d'entrées.
"""
return len(self._get_entrees_passent_filtre())

def get_top_urls(self) -> list:
"""
Retourne le top :attr:`nombre_par_top` des urls les plus demandées.
Les entrées prisent en compte sont uniquement celles qui ont passées le filtre.

Returns:
list: Une liste de dictionnaires où chaque clé contient :
Expand All @@ -136,14 +174,15 @@ def get_top_urls(self) -> list:
La liste est triée dans l'ordre décroissant du nombre total d'apparitions.
"""
return self._get_repartition_elements(
[entree.requete.url for entree in self.fichier.entrees],
[entree.requete.url for entree in self._get_entrees_passent_filtre()],
"url",
True
)

def get_total_par_code_statut_http(self) -> list:
"""
Retourne la répartition des réponses par code de statut htpp retourné.
Retourne la répartition des réponses par code de statut http retourné.
Les entrées prisent en compte sont uniquement celles qui ont passées le filtre.

Returns:
list: Une liste de dictionnaires où chaque clé contient :
Expand All @@ -154,6 +193,21 @@ def get_total_par_code_statut_http(self) -> list:
La liste est triée dans l'ordre décroissant du nombre total d'apparitions.
"""
return self._get_repartition_elements(
[entree.reponse.code_statut_http for entree in self.fichier.entrees],
[entree.reponse.code_statut_http for entree in self._get_entrees_passent_filtre()],
"code"
)

def get_total_par_code_statut_http_camembert(self) -> list:
"""
Retourne la répartition des réponses par code de statut http retourné sous
un format utilisable par un camembert.
Les entrées prisent en compte sont uniquement celles qui ont passées le filtre.

Returns:
list: Une liste de liste de deux éléments où l'index 0 est le code et l'index 1
son total d'apparition.
"""
return [
[stat["code"], stat["total"]]
for stat in self.get_total_par_code_statut_http()
]
91 changes: 91 additions & 0 deletions app/analyse/filtre_log_apache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
Module pour les filtres lors d'une analyse d'un fichier log Apache.
"""

from typing import Optional
from parse.entree_log_apache import EntreeLogApache


class FiltreLogApache:
"""
Représente le filtre à appliquer lors d'une analyse d'un fichier de log Apache.

Attributes:
adresse_ip (Optional[str]): L'adresse IP que doit avoir une entrée pour
pouvoir passer le filtre. Si sa valeur est ``None``, ce filtre ne sera
pas appliqué.
code_statut_http (Optional[int]): Le code de statut http que doit avoir une entrée
pour pouvoir passer le filtre. Si sa valeur est ``None``, ce filtre ne sera
pas appliqué.
"""

def __init__(self, filtre_adresse_ip: Optional[str], filtre_code_statut_http: Optional[int]):
"""
Initalise le filtre à appliquer lors d'une analyse.

Args:
filtre_adresse_ip (Optional[str]): L'adresse IP que doit avoir une entrée pour
pouvoir passer le filtre. Si sa valeur est ``None``, cette vérification ne sera
pas appliqué.
filtre_code_statut_http (Optional[int]): Le code de statut http que doit
avoir une entrée pour pouvoir passer le filtre. Si sa valeur est ``None``,
cette vérification ne sera pas appliqué.

Raises:
TypeError: Les paramètres ne sont pas du type attendu.
"""
# Vérification des paramètres
if filtre_adresse_ip is not None and not isinstance(filtre_adresse_ip, str):
raise TypeError("L'adresse IP dans un filtre doit être une chaîne de caractère.")
if (filtre_code_statut_http is not None
and not isinstance(filtre_code_statut_http, int)
or isinstance(filtre_code_statut_http, bool)):
raise TypeError("Un code de statut http dans un filtre doit être un entier.")

# Ajout des filtres
self.adresse_ip = filtre_adresse_ip
self.code_statut_http = filtre_code_statut_http

def entree_passe_filtre(self, entree: EntreeLogApache) -> bool:
"""
Indique si l'entrée passée en paramètre passe le filtre.

Args:
entree (EntreeLogApache): L'entrée à vérifier.

Returns:
bool: True si l'entrée passe le filtre, False sinon.

Raises:
TypeError: L'``entrée`` n'est pas de type :class:`EntreeLogApache`
"""
# Vérification du paramètre
if not isinstance(entree, EntreeLogApache):
raise TypeError("L'entrée à vérifier pour le filtre doit être de type EntreeLogApache")

# Vérification que l'entrée passe le filtre
# Application du filtre sur l'adresse IP si activé
if self.adresse_ip is not None:
if self.adresse_ip != entree.client.adresse_ip:
return False
# Application du filtre sur le code de statut http si activé
if self.code_statut_http is not None:
if self.code_statut_http != entree.reponse.code_statut_http:
return False

return True

def get_dict_filtre(self) -> dict:
"""
Retourne le filtre sous forme d'un dictionnaire.
Les clés représentent le champs d'une entrée et leur valeur la valeur
que doit avoir ce champs. Si la valeur d'un filtre est ``None``, cela signifie que
cette vérification n'est pas activé.

Returns:
dict: Les filtres sous forme d'un dictionnaire.
"""
return {
"adresse_ip": self.adresse_ip,
"code_statut_http": self.code_statut_http
}
Loading