homelab-brain/redax-wp/src/logger.py
root 064ae085b5 redax-wp: Sprint 1+2 — vollständiger Stack
Infrastruktur:
- CT 113 auf pve-hetzner erstellt (Docker, Tailscale)
- Forgejo-Repo redax-wp angelegt

Code (Sprint 2):
- docker-compose.yml: wordpress + db + redax-web
- .env.example mit allen Variablen
- database.py: articles, feeds, feed_items, prompts, settings
- wordpress.py: WP REST API Client (create/update post, media upload, Yoast SEO)
- rss_fetcher.py: Feed-Import, Blacklist, Teaser-Modus, KI-Rewrite
- app.py: Flask Dashboard, Scheduler (publish/rss/briefing), alle API-Routen
- templates: base, login, index (Zwei-Spalten-Editor), feeds, history, prompts, settings, hilfe
- README.md + .gitignore

Made-with: Cursor
2026-02-27 07:52:31 +07:00

100 lines
2.8 KiB
Python

"""
Strukturiertes Logging für FünfVorAcht.
Schreibt JSON-Lines nach /logs/fuenfvoracht.log
"""
import json
import logging
import os
from datetime import datetime
LOG_PATH = os.environ.get('LOG_PATH', '/logs/fuenfvoracht.log')
_file_handler = None
def _get_file_handler():
global _file_handler
if _file_handler is None:
os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True)
_file_handler = logging.FileHandler(LOG_PATH, encoding='utf-8')
_file_handler.setLevel(logging.DEBUG)
return _file_handler
def _write(level: str, event: str, **kwargs):
entry = {
'ts': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
'level': level,
'event': event,
**kwargs,
}
line = json.dumps(entry, ensure_ascii=False)
try:
handler = _get_file_handler()
record = logging.LogRecord(
name='fuenfvoracht', level=getattr(logging, level),
pathname='', lineno=0, msg=line, args=(), exc_info=None
)
handler.emit(record)
except Exception:
pass
# Auch in stdout damit docker logs es zeigt
print(line, flush=True)
def info(event: str, **kwargs):
_write('INFO', event, **kwargs)
def warning(event: str, **kwargs):
_write('WARNING', event, **kwargs)
def error(event: str, **kwargs):
_write('ERROR', event, **kwargs)
# Kurzformen für häufige Events
def article_generated(date: str, source: str, version: int, tag: str):
info('article_generated', date=date, source=source[:120], version=version, tag=tag)
def article_saved(date: str, post_time: str):
info('article_saved', date=date, post_time=post_time)
def article_scheduled(date: str, post_time: str, notify_at: str):
info('article_scheduled', date=date, post_time=post_time, notify_at=notify_at)
def article_sent_to_bot(date: str, post_time: str, chat_ids: list):
info('article_sent_to_bot', date=date, post_time=post_time, chat_ids=chat_ids)
def article_approved(date: str, post_time: str, by_chat_id: int):
info('article_approved', date=date, post_time=post_time, by_chat_id=by_chat_id)
def article_posted(date: str, post_time: str, channel_id: str, message_id: int):
info('article_posted', date=date, post_time=post_time,
channel_id=channel_id, message_id=message_id)
def article_skipped(date: str, post_time: str):
info('article_skipped', date=date, post_time=post_time)
def posting_failed(date: str, post_time: str, reason: str):
error('posting_failed', date=date, post_time=post_time, reason=reason[:300])
def reviewer_added(chat_id: int, name: str):
info('reviewer_added', chat_id=chat_id, name=name)
def reviewer_removed(chat_id: int):
info('reviewer_removed', chat_id=chat_id)
def slot_conflict(date: str, post_time: str):
warning('slot_conflict', date=date, post_time=post_time)