Logging: Logs werden jetzt im logs/ Unterverzeichnis gespeichert

This commit is contained in:
2025-08-20 08:40:32 +02:00
parent 634806ec44
commit bcc6869d13
3 changed files with 134 additions and 0 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
.venv/ .venv/
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
# Logs
logs/
*.log *.log
.env .env
.DS_Store .DS_Store

75
LOGGING.md Normal file
View File

@@ -0,0 +1,75 @@
# Logging-System
Das Wordle-Helper Logging-System protokolliert Seitenaufrufe und Suchanfragen ohne personenbezogene Daten wie IP-Adressen.
## Log-Datei
- **Verzeichnis:** `logs/`
- **Datei:** `logs/app.log`
- **Format:** UTF-8
- **Rotation:** Keine automatische Rotation (manuell oder über externe Tools)
- **Verzeichnis:** Wird automatisch erstellt, falls es nicht existiert
## Protokollierte Ereignisse
### 1. Seitenaufrufe (PAGE_VIEW)
```
2024-01-01 12:00:00 - INFO - PAGE_VIEW: index | User-Agent: Mozilla/5.0...
2024-01-01 12:00:01 - INFO - PAGE_VIEW: manifest | User-Agent: Mozilla/5.0...
2024-01-01 12:00:02 - INFO - PAGE_VIEW: service_worker | User-Agent: Mozilla/5.0...
```
### 2. Suchanfragen (SEARCH)
```
2024-01-01 12:00:05 - INFO - SEARCH: pos='hallo' includes='' excludes='' sources=['OT'] | User-Agent: Mozilla/5.0...
2024-01-01 12:00:10 - INFO - SEARCH: pos='' includes='aei' excludes='rst' sources=['OT', 'WF'] | User-Agent: Mozilla/5.0...
```
## Log-Format
- **Zeitstempel:** ISO-Format (YYYY-MM-DD HH:MM:SS)
- **Ereignistyp:** PAGE_VIEW oder SEARCH
- **Details:** Spezifische Informationen je nach Ereignistyp
- **User-Agent:** Gekürzt auf 100 Zeichen (ohne IP-Adressen)
## Datenschutz
- **Keine IP-Adressen** werden protokolliert
- **Keine persönlichen Daten** werden gespeichert
- **User-Agent** wird gekürzt und anonymisiert
- **Logs** werden nicht ins Git-Repository übertragen (`.gitignore`)
## Konfiguration
Das Logging ist in `app.py` konfiguriert:
```python
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log', encoding='utf-8'),
logging.StreamHandler()
]
)
```
## Log-Analyse
Die Log-Datei kann mit Standard-Tools analysiert werden:
```bash
# Alle Suchanfragen anzeigen
grep "SEARCH:" logs/app.log
# Seitenaufrufe zählen
grep "PAGE_VIEW:" logs/app.log | wc -l
# Häufigste Suchparameter
grep "SEARCH:" logs/app.log | cut -d'|' -f1
# Logs-Verzeichnis anzeigen
ls -la logs/
```

57
app.py
View File

@@ -1,10 +1,52 @@
from pathlib import Path from pathlib import Path
import json import json
import logging
from datetime import datetime
from typing import Tuple, Dict, List from typing import Tuple, Dict, List
from flask import Flask, render_template, request, send_from_directory from flask import Flask, render_template, request, send_from_directory
app = Flask(__name__) app = Flask(__name__)
# Logging konfigurieren
import os
# Logs-Verzeichnis erstellen, falls es nicht existiert
logs_dir = Path(__file__).parent / "logs"
logs_dir.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(logs_dir / 'app.log', encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def log_page_view(page: str, user_agent: str = None):
"""Protokolliert Seitenaufrufe ohne IP-Adressen"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
user_agent_clean = user_agent[:100] if user_agent else 'Unknown'
logger.info(f"PAGE_VIEW: {page} | User-Agent: {user_agent_clean}")
def log_search_query(search_params: dict, user_agent: str = None):
"""Protokolliert Suchanfragen ohne IP-Adressen"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
user_agent_clean = user_agent[:100] if user_agent else 'Unknown'
# Suchparameter für Logging vorbereiten
pos_str = ''.join(search_params.get('pos', [''] * 5))
includes = search_params.get('includes', '')
excludes = search_params.get('excludes', '')
sources = []
if search_params.get('use_ot'):
sources.append('OT')
if search_params.get('use_wf'):
sources.append('WF')
logger.info(f"SEARCH: pos='{pos_str}' includes='{includes}' excludes='{excludes}' sources={sources} | User-Agent: {user_agent_clean}")
def load_words() -> Tuple[List[str], Dict[str, List[str]]]: def load_words() -> Tuple[List[str], Dict[str, List[str]]]:
data_dir = Path(__file__).parent / "data" data_dir = Path(__file__).parent / "data"
@@ -50,6 +92,9 @@ def filter_words(words: List[str], position_letters: List[str], includes_text: s
@app.route("/", methods=["GET", "POST"]) @app.route("/", methods=["GET", "POST"])
def index(): def index():
# Seitenaufruf protokollieren
log_page_view("index", request.headers.get('User-Agent'))
all_words, sources_map = load_words() all_words, sources_map = load_words()
results_display: List[str] | None = None results_display: List[str] | None = None
pos: List[str] = ["", "", "", "", ""] pos: List[str] = ["", "", "", "", ""]
@@ -70,6 +115,16 @@ def index():
use_ot = request.form.get("use_ot") is not None use_ot = request.form.get("use_ot") is not None
use_wf = request.form.get("use_wf") is not None use_wf = request.form.get("use_wf") is not None
# Suchanfrage protokollieren
search_params = {
'pos': pos,
'includes': includes,
'excludes': excludes,
'use_ot': use_ot,
'use_wf': use_wf
}
log_search_query(search_params, request.headers.get('User-Agent'))
# 1) Buchstaben-/Positionssuche über alle Wörter # 1) Buchstaben-/Positionssuche über alle Wörter
matched = filter_words(all_words, pos, includes, excludes) matched = filter_words(all_words, pos, includes, excludes)
# 2) Quellen-Filter nur auf Ergebnisansicht anwenden # 2) Quellen-Filter nur auf Ergebnisansicht anwenden
@@ -99,12 +154,14 @@ def index():
@app.route('/manifest.webmanifest') @app.route('/manifest.webmanifest')
def manifest_file(): def manifest_file():
log_page_view("manifest", request.headers.get('User-Agent'))
return send_from_directory(Path(__file__).parent / 'static', 'manifest.webmanifest', mimetype='application/manifest+json') return send_from_directory(Path(__file__).parent / 'static', 'manifest.webmanifest', mimetype='application/manifest+json')
@app.route('/sw.js') @app.route('/sw.js')
def service_worker(): def service_worker():
# Service Worker muss auf Top-Level liegen # Service Worker muss auf Top-Level liegen
log_page_view("service_worker", request.headers.get('User-Agent'))
return send_from_directory(Path(__file__).parent / 'static', 'sw.js', mimetype='application/javascript') return send_from_directory(Path(__file__).parent / 'static', 'sw.js', mimetype='application/javascript')