"""The single :func:`record_event` emit primitive used by every call site.Routes structured :class:`aeat.core.observability._models.RunEvent`records through the standard :mod:`logging` machinery so any handlerattached to the root logger — notably the per-run:class:`aeat.core.observability._sink.JsonlRunSink` — picks them upautomatically while a :func:`aeat.core.observability.run_context` isactive."""from__future__importannotationsimportinspectfrom..loggingimportget_loggerfrom..timeimportnowfrom._contextimportRUN_CONTEXT_VAR,STEP_CONTEXT_VARfrom._errorsimportRunContextMissingErrorfrom._modelsimportRunEvent,RunEventKind,RunEventPayload_logger=get_logger("aeat.core.observability")def_caller_module()->str:"""Return the module name of the first frame outside this file. Falls back to ``"aeat.core.observability"`` if the walk reaches the interpreter without finding a caller (which should never happen in practice). """frame=inspect.currentframe()ifframeisNone:return"aeat.core.observability"candidate=frame.f_backwhilecandidateisnotNone:module_name=candidate.f_globals.get("__name__","")ifisinstance(module_name,str)andmodule_nameandmodule_name!=__name__:returnmodule_namecandidate=candidate.f_backreturn"aeat.core.observability"
[docs]defrecord_event(kind:RunEventKind,*,payload:RunEventPayload,module:str|None=None,)->RunEvent:"""Record a single :class:`RunEvent` against the active run context. Context propagation note: the active ``run_id`` is carried via :class:`contextvars.ContextVar`. These propagate across :func:`asyncio.create_task` and :func:`asyncio.run` automatically (PEP 567), but NOT across plain :class:`threading.Thread` targets nor :func:`asyncio.to_thread` / ``loop.run_in_executor`` workers unless the caller wraps the target with :func:`contextvars.copy_context`. A call to :func:`record_event` from a detached thread therefore raises :exc:`aeat.core.observability.RunContextMissingError`. Callers that need the event recorded in such a thread must either re-enter :func:`aeat.core.observability.run_context` inside the worker or copy the context explicitly. Args: kind: The event kind. payload: A :class:`RunEventPayload` with exactly one variant set. module: Optional explicit module string; defaults to the caller's ``__name__`` resolved by :func:`_caller_module`. Returns: The constructed :class:`RunEvent` (also forwarded to the JSONL sink via the ``run_event`` logging extra). Raises: RunContextMissingError: If no run context is active on the current contextvar. """ctx=RUN_CONTEXT_VAR.get(None)ifctxisNone:raiseRunContextMissingError(f"record_event({kind.value}) called outside an active run_context()",)step_id=STEP_CONTEXT_VAR.get(None)orctx.initial_step_idevent=RunEvent(run_id=ctx.run_id,step_id=step_id,kind=kind,payload=payload,timestamp=now(),module=moduleor_caller_module(),)# INFO level keeps the record flowing through both the JSONL sink# AND any caller that has tightened their own handler levels.# Stderr duplication is suppressed inside# :func:`aeat.core.logging.configure_logging`, where the default stderr# handler carries a filter that excludes records which already# went to the per-run sink (i.e. records with a ``run_event``# extra). Keeping the emission at INFO here means other# subpackages that attach their own INFO-level handlers still see# the observability record._logger.info("run.event %s",kind.value,extra={"run_event":event})returnevent