Logging: Logs werden jetzt im logs/ Unterverzeichnis gespeichert
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
75
LOGGING.md
Normal 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
57
app.py
@@ -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')
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user