| """ |
| Structured logging with execution context. |
| |
| Every execution (a job, a provider invocation, a retry attempt) is |
| given an `ExecutionContext` that binds the following fields into every |
| log line produced inside that context: |
| |
| execution_id — UUID for the top-level job |
| provider_id — name of the provider being invoked (or "-") |
| retry_count — how many retries have occurred for this attempt |
| status — "started" | "success" | "failed" | "retried" |
| |
| Implementation: loguru's `contextualize()` binds dict keys into the |
| `extra` dict of every record created within the context. We layer a |
| small wrapper on top so callers don't have to remember the keys. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import sys |
| import uuid |
| from contextlib import contextmanager |
| from contextvars import ContextVar |
| from typing import Any, Iterator, Optional |
|
|
| from loguru import logger |
|
|
| from config.settings import Settings |
|
|
|
|
| |
| _execution_id: ContextVar[str] = ContextVar("execution_id", default="-") |
| _provider_id: ContextVar[str] = ContextVar("provider_id", default="-") |
| _retry_count: ContextVar[int] = ContextVar("retry_count", default=0) |
| _status: ContextVar[str] = ContextVar("status", default="started") |
|
|
|
|
| def setup_logging(settings: Settings) -> None: |
| """Configure loguru sinks. Call once at app startup.""" |
| logger.remove() |
|
|
| if settings.log_json: |
| fmt = ( |
| '{{"timestamp":"{time:YYYY-MM-DDTHH:mm:ss.SSSZ}",' |
| '"level":"{level}",' |
| '"execution_id":"{extra[execution_id]}",' |
| '"provider_id":"{extra[provider_id]}",' |
| '"retry_count":{extra[retry_count]},' |
| '"status":"{extra[status]}",' |
| '"message":"{message}"}}' |
| ) |
| else: |
| fmt = ( |
| "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | " |
| "eid={extra[execution_id]} | pid={extra[provider_id]} | " |
| "retry={extra[retry_count]} | status={extra[status]} | " |
| "{message}" |
| ) |
|
|
| logger.configure( |
| extra={ |
| "execution_id": "-", |
| "provider_id": "-", |
| "retry_count": 0, |
| "status": "started", |
| } |
| ) |
| logger.add(sys.stderr, format=fmt, level=settings.log_level, backtrace=True, diagnose=False) |
|
|
|
|
| def new_execution_id() -> str: |
| """Generate a fresh execution id.""" |
| return uuid.uuid4().hex[:12] |
|
|
|
|
| @contextmanager |
| def execution_context( |
| execution_id: Optional[str] = None, |
| provider_id: str = "-", |
| retry_count: int = 0, |
| status: str = "started", |
| ) -> Iterator[dict]: |
| """Bind execution context to every log line produced inside the block. |
| |
| Usage: |
| with execution_context(provider_id="haar") as ctx: |
| ctx["status"] = "running" |
| logger.info("detecting faces") |
| ctx["status"] = "success" |
| """ |
| eid = execution_id or new_execution_id() |
| ctx = { |
| "execution_id": eid, |
| "provider_id": provider_id, |
| "retry_count": retry_count, |
| "status": status, |
| } |
| eid_token = _execution_id.set(eid) |
| pid_token = _provider_id.set(provider_id) |
| rc_token = _retry_count.set(retry_count) |
| st_token = _status.set(status) |
| with logger.contextualize(**ctx): |
| try: |
| yield ctx |
| finally: |
| _execution_id.reset(eid_token) |
| _provider_id.reset(pid_token) |
| _retry_count.reset(rc_token) |
| _status.reset(st_token) |
|
|
|
|
| def current_execution_id() -> str: |
| return _execution_id.get() |
|
|
|
|
| def current_provider_id() -> str: |
| return _provider_id.get() |
|
|
|
|
| __all__ = [ |
| "setup_logging", |
| "new_execution_id", |
| "execution_context", |
| "current_execution_id", |
| "current_provider_id", |
| "logger", |
| ] |
|
|