- CT 112 auf pve-hetzner: Bot + Dashboard produktiv - Strukturiertes JSON-Logging (logger.py, /logs/fuenfvoracht.log) - 15-Min-Zeitslots: UNIQUE(date, post_time), DB-Migration, Konflikterkennung - Einplan-Flow: scheduled-Status, notify_at, automatische Bot-Benachrichtigung - Board-API: Umplanen (reschedule) + Löschen per Article-ID - Morgen-Briefing täglich 10:00 MEZ: Tagesplan + Ausblick 3 Tage - Fehler-Alarm: detaillierte Meldung an alle Redakteure bei Posting-Fehler - Reviewer-Verwaltung: DB-Tabelle, API add/remove, Willkommensnachricht - Zweiter Redakteur (1329146910) parallel eingebunden - Markenzeichen automatisch unter jeden Beitrag (Duplikat-Schutz) - Tailwind CSS self-hosted im Docker-Image (kein CDN, schnelle Ladezeiten) - TELEGRAM_CHANNEL_ID gesetzt (-1001285446620) - Hilfe-Seite /hilfe: 6 Aufgabenbereiche mit Klickpfaden - STATE.md aktualisiert und vollständig dokumentiert Made-with: Cursor
100 lines
2.8 KiB
Python
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)
|