File size: 6,770 Bytes
28c12aa 700e896 28c12aa 700e896 28c12aa d40bd07 28c12aa d40bd07 28c12aa d40bd07 28c12aa d40bd07 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | """Structured logging: colored console renderer + the `step()` stage timer.
Logging standard for this project: every pipeline stage runs inside ``with step("<concern>.<action>") as
metrics:`` β it logs ``step.start`` then ``step.end`` with the elapsed seconds and whatever you attach via
``metrics["key"] = value``. No ``print()`` in the engine; no engine/library names in user-facing strings.
Configuration is read from the environment (so this module has no dependency on the settings layer yet):
``RERANKER_LOG_LEVEL`` (default ``INFO``), ``RERANKER_LOG`` (set ``0`` to silence), ``NO_COLOR`` to drop
ANSI. The stdlib ``import logging`` below is an ABSOLUTE import β Python resolves top-level imports against
``sys.path``, never this same-named sibling β so ``logging.INFO`` references the standard library correctly.
"""
from __future__ import annotations
import contextlib
import logging
import os
import sys
import time
from contextlib import contextmanager
from typing import Any
import structlog
from structlog.typing import EventDict
_log_configured = False
# this module's own functions to skip when locating the real caller (logging plumbing, not user code)
_CALLSITE_SKIP = {"step", "get_logger", "_callsite", "_configure_logging"}
_ANSI = {
"reset": "\033[0m", "bold": "\033[1m", "gray": "\033[90m", "red": "\033[31m",
"green": "\033[32m", "yellow": "\033[33m", "blue": "\033[94m", "magenta": "\033[35m", "cyan": "\033[36m",
}
_LEVEL_COLOR = {"debug": "blue", "info": "green", "warning": "yellow", "error": "red", "critical": "red"}
def _callsite(_logger: Any, _method_name: str, event_dict: EventDict) -> EventDict:
"""Add ``at`` (file:line) + ``func`` to each line, pointing at the code that opened the step."""
frame: Any = sys._getframe()
while frame is not None:
filename = frame.f_code.co_filename
func = frame.f_code.co_name
skip = (
"structlog" in filename
or "contextlib" in filename
or (os.path.basename(filename) == "logging.py" and func in _CALLSITE_SKIP)
)
if not skip:
event_dict["at"] = f"{os.path.basename(filename)}:{frame.f_lineno}"
event_dict["func"] = func
break
frame = frame.f_back
return event_dict
def _make_console_renderer(colors: bool):
"""Build a renderer: ``time [level] [step] [file:line func] event key=value β¦``."""
def paint(text: str, *styles: str) -> str:
if not colors:
return text
prefix = "".join(_ANSI[style] for style in styles)
return f"{prefix}{text}{_ANSI['reset']}"
def render(_logger: Any, _method: str, event_dict: EventDict) -> str:
extra = dict(event_dict)
timestamp = str(extra.pop("timestamp", ""))
level = str(extra.pop("level", "info"))
event = str(extra.pop("event", ""))
name = extra.pop("step", None) or extra.pop("logger", None)
extra.pop("logger", None)
location = f"{extra.pop('at', '')} {extra.pop('func', '')}".strip()
blocks = [paint(timestamp, "gray"), paint(f"[{level:<7}]", "bold", _LEVEL_COLOR.get(level, "green"))]
if name:
blocks.append(paint(f"[{name}]", "cyan"))
if location:
blocks.append(paint(f"[{location}]", "yellow"))
blocks.append(paint(event, "bold", "blue"))
header_line = " ".join(block for block in blocks if block)
pairs = " ".join(
f"{paint(key, 'magenta')}={paint(str(value), 'green')}" for key, value in extra.items()
)
return f"{header_line} {pairs}".rstrip()
return render
def _configure_logging(force: bool = False) -> None:
"""Configure structlog once from the environment (idempotent)."""
global _log_configured
if _log_configured and not force:
return
if os.environ.get("RERANKER_LOG", "1") == "0":
# Silence: filter at CRITICAL so info/warning/error are dropped (the engine never logs CRITICAL).
# NOTE: structlog only accepts standard levels here β CRITICAL+1 raises KeyError.
structlog.configure(
wrapper_class=structlog.make_filtering_bound_logger(logging.CRITICAL),
cache_logger_on_first_use=True,
)
_log_configured = True
return
with contextlib.suppress(Exception):
sys.stdout.reconfigure(encoding="utf-8", line_buffering=True) # type: ignore[union-attr]
colors = "NO_COLOR" not in os.environ
if colors:
with contextlib.suppress(Exception): # wrap stdout so ANSI works on legacy Windows consoles
import colorama
colorama.init()
level = getattr(logging, os.environ.get("RERANKER_LOG_LEVEL", "INFO").upper(), logging.INFO)
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="%H:%M:%S", utc=False),
_callsite,
_make_console_renderer(colors),
],
wrapper_class=structlog.make_filtering_bound_logger(level),
cache_logger_on_first_use=True,
)
_log_configured = True
def get_logger(name: str | None = None):
"""Return a configured structlog logger, with the concern ``name`` bound as ``logger=``."""
_configure_logging()
log = structlog.get_logger(name)
return log.bind(logger=name) if name else log
def _phase_banner(text: str) -> None:
"""A plain full-width divider line bracketing every phase, for grep/scroll tracing in a long log stream
(``grep -A20 "PHASE START: rank.explain"``). Bypasses the structured renderer on purpose β this is a visual
marker, not a parseable event β but still respects ``RERANKER_LOG=0`` silencing."""
if os.environ.get("RERANKER_LOG", "1") == "0":
return
print(f"--------------------------- {text} ---------------------------")
@contextmanager
def step(label: str, **context: Any):
"""Log ``step.start`` / ``step.end`` (+seconds, +metrics), bracketed by PHASE banners. Yields a mutable
metrics dict."""
_phase_banner(f"PHASE START: {label}")
log = get_logger("step").bind(step=label, **context)
log.info("step.start")
metrics: dict[str, Any] = {}
start = time.perf_counter()
try:
yield metrics
except Exception as exc:
log.error("step.fail", seconds=round(time.perf_counter() - start, 3), error=str(exc))
_phase_banner(f"PHASE FAILED: {label} ({round(time.perf_counter() - start, 3)}s)")
raise
else:
log.info("step.end", seconds=round(time.perf_counter() - start, 3), **metrics)
_phase_banner(f"PHASE END: {label} ({round(time.perf_counter() - start, 3)}s)")
|