From 5a82560cf2ffedfeb337127d0940ebfe08bdad45 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 11 Aug 2026 05:05:07 +0000 Subject: [PATCH] Format preview worker logs like the main process, tagged with worker pid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker stderr is now inherited by the parent, so its log lines land in the server log — but they arrived with the default logging format and a noisy "preview-worker config=..." line at every spawn. - Extract the emoji level-prefix formatting from cista.sanic_logging into cista.util.logformat, which has no Sanic dependency (the worker must not import Sanic: import-time prints could corrupt the stdout protocol). - Worker configures its stderr handler with the same emoji prefixes plus a worker[pid] tag, and the config-loaded info message is removed. --- cista/preview_worker.py | 20 +++++++++++++++++--- cista/sanic_logging.py | 34 +++------------------------------- cista/util/logformat.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 34 deletions(-) create mode 100644 cista/util/logformat.py diff --git a/cista/preview_worker.py b/cista/preview_worker.py index c4f66e7..75d5d68 100644 --- a/cista/preview_worker.py +++ b/cista/preview_worker.py @@ -17,6 +17,7 @@ import gc import io import logging import mimetypes +import os import shlex import struct import subprocess @@ -33,9 +34,19 @@ import pyvips from blake3 import blake3 from cista import config +from cista.util.logformat import format_level_prefix logger = logging.getLogger(__name__) + +class _WorkerLogFormatter(logging.Formatter): + """Emoji level prefix like the main process, tagged with the worker pid.""" + + def format(self, record: logging.LogRecord) -> str: + prefix = format_level_prefix(record.levelno) + return f"{prefix}worker[{os.getpid()}]: {record.getMessage()}" + + AVIF_FAST_EFFORT = 0 DOC_PREVIEW_SUFFIXES = {".pdf", ".xps", ".epub", ".mobi"} @@ -554,11 +565,14 @@ def _run_loop() -> None: def main() -> None: - # Configure all log output to stderr before any imports that may emit logs. - logging.basicConfig(stream=sys.stderr, level=logging.INFO) + # Configure all log output to stderr before any imports that may emit + # logs. stderr is inherited by the parent, so this lands in the server + # log, formatted like the main process and tagged with the worker pid. + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter(_WorkerLogFormatter()) + logging.basicConfig(level=logging.INFO, handlers=[handler]) try: config.load_config() - logger.info("preview-worker config=%s", config.conffile) except Exception: logger.exception("preview-worker failed to load config at startup") if len(sys.argv) > 1: diff --git a/cista/sanic_logging.py b/cista/sanic_logging.py index bdcbad8..e4ccaa3 100644 --- a/cista/sanic_logging.py +++ b/cista/sanic_logging.py @@ -3,11 +3,13 @@ import logging import os import sys -import unicodedata from ipaddress import IPv6Address from sanic.log import LOGGING_CONFIG_DEFAULTS +from cista.util.logformat import EmojiFormatter as _EmojiFormatter +from cista.util.logformat import display_width as _display_width + logger = logging.getLogger("cista.access") @@ -132,14 +134,6 @@ def format_duration_ms(duration_ms: float) -> str: return f"{hours}h{minutes}m" -def _display_width(text: str) -> int: - return sum( - 1 + (unicodedata.east_asian_width(c) in "FW") - for c in text - if unicodedata.category(c) != "Mn" - ) - - def _format_left(label: str) -> str: return label[:19].ljust(19) @@ -279,28 +273,6 @@ def configure_access_logging() -> None: logger.propagate = False -_LEVEL_EMOJI = { - logging.DEBUG: "🔍", - logging.INFO: "â„šī¸", # noqa: RUF001 - logging.WARNING: "âš ī¸", - logging.ERROR: "🛑", - logging.CRITICAL: "🛑", -} - - -def _format_level_prefix(levelno: int) -> str: - emoji = _LEVEL_EMOJI.get(levelno, "â–Ēī¸") - prefix = f"{emoji} " - return prefix + (" " * max(0, 3 - _display_width(prefix))) - - -class _EmojiFormatter(logging.Formatter): - """Compact formatter: emoji + message, no timestamp/level text/logger name.""" - - def format(self, record: logging.LogRecord) -> str: - return _format_level_prefix(record.levelno) + record.getMessage() - - def configure_main_logging() -> None: """Replace Sanic's verbose 'Main yyyy-mm-dd INFO:' prefix with emoji-only format. diff --git a/cista/util/logformat.py b/cista/util/logformat.py new file mode 100644 index 0000000..4bd6860 --- /dev/null +++ b/cista/util/logformat.py @@ -0,0 +1,37 @@ +"""Shared log formatting helpers with no Sanic dependency. + +Used by the main process (cista.sanic_logging) and by the preview worker +subprocess, which must not import Sanic. +""" + +import logging +import unicodedata + +LEVEL_EMOJI = { + logging.DEBUG: "🔍", + logging.INFO: "â„šī¸", # noqa: RUF001 + logging.WARNING: "âš ī¸", + logging.ERROR: "🛑", + logging.CRITICAL: "🛑", +} + + +def display_width(text: str) -> int: + return sum( + 1 + (unicodedata.east_asian_width(c) in "FW") + for c in text + if unicodedata.category(c) != "Mn" + ) + + +def format_level_prefix(levelno: int) -> str: + emoji = LEVEL_EMOJI.get(levelno, "â–Ēī¸") + prefix = f"{emoji} " + return prefix + (" " * max(0, 3 - display_width(prefix))) + + +class EmojiFormatter(logging.Formatter): + """Compact formatter: emoji + message, no timestamp/level text/logger name.""" + + def format(self, record: logging.LogRecord) -> str: + return format_level_prefix(record.levelno) + record.getMessage()