Source code for pynenc.trigger.log_messages
"""
Stable log message vocabulary for the trigger system.
Centralizes the message names, entity-ref formatting, and participant
summaries that :class:`pynenc.trigger.base_trigger.BaseTrigger` emits during
event/condition/run lifecycle. Keeping the contract in one place lets the
Pynmon log-parser, renderer, docs, and tests share the same source of truth.
Message names use a ``trigger.<topic>.<verb>`` prefix so log filters can
slice the trigger lifecycle by topic without parsing message text.
Entity refs follow the ``kind:value`` convention already used by
``pynenc.util.log`` and ``pynmon.util.log_parser``. New kinds added here:
- ``event:{event_id}``
- ``trigger:{trigger_id}``
- ``trigger-run:{trigger_run_id}``
- ``condition:{condition_id}``
- ``valid-condition:{valid_condition_id}``
- ``source-invocation:{invocation_id}``
- ``triggered-invocation:{invocation_id}``
- ``atomic-service-run:{atomic_service_run_id}``
- ``cron:{iso_timestamp}``
List forms (``events:[...]`` etc.) follow the same convention.
"""
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pynenc.trigger.conditions import ConditionContext
# --------------------------------------------------------------------------- #
# Stable message names
# --------------------------------------------------------------------------- #
[docs]
class TriggerLogMsg:
"""Stable lifecycle message names emitted by the trigger component."""
EVENT_EMITTED = "trigger.event.emitted"
CONDITION_MATCHED = "trigger.condition.matched"
CONDITION_REGISTERED = "trigger.condition.registered"
RUN_CLAIMED = "trigger.run.claimed"
RUN_SKIPPED = "trigger.run.skipped"
RUN_EXECUTED = "trigger.run.executed"
RUN_STORED = "trigger.run.stored"
CRON_CLAIMED = "trigger.cron.claimed"
CRON_SKIPPED = "trigger.cron.skipped"
# --------------------------------------------------------------------------- #
# Entity ref kinds (mirror pynmon.util.log_parser)
# --------------------------------------------------------------------------- #
#: New singular ref kinds introduced for trigger observability.
TRIGGER_ENTITY_REF_KINDS: tuple[str, ...] = (
"event",
"trigger",
"trigger-run",
"condition",
"valid-condition",
"source-invocation",
"triggered-invocation",
"atomic-service-run",
"cron",
)
#: New plural list-form ref kinds.
TRIGGER_ENTITY_LIST_KINDS: tuple[str, ...] = (
"events",
"triggers",
"trigger-runs",
"conditions",
"valid-conditions",
"source-invocations",
"triggered-invocations",
)
# --------------------------------------------------------------------------- #
# Formatting helpers
# --------------------------------------------------------------------------- #
[docs]
def ref(kind: str, value: str | None) -> str:
"""Format a single ``kind:value`` entity ref, returning '' for empty values.
:param kind: Entity kind (e.g. ``"event"``, ``"trigger-run"``).
:param value: Entity id; when falsy the helper returns the empty string so
callers can drop the token from the message with a simple ``filter``.
"""
if not value:
return ""
return f"{kind}:{value}"
[docs]
def ref_list(kind_plural: str, values: list[str]) -> str:
"""Format a list-form entity ref like ``events:[id1,id2]``.
Returns '' when ``values`` is empty so callers can omit the token cleanly.
"""
if not values:
return ""
return f"{kind_plural}:[{','.join(values)}]"
[docs]
def context_source_ref(context: "ConditionContext") -> str:
"""Return the appropriate entity ref for the source of a condition context.
Maps each :class:`ConditionContext` subclass to the most informative ref:
- ``EventContext`` -> ``event:{event_id}``
- ``StatusContext`` / ``ResultContext`` / ``ExceptionContext`` ->
``source-invocation:{invocation_id}``
- ``CronContext`` -> ``"cron:{iso_timestamp}"`` (no entity id; the
timestamp is the only meaningful source identifier).
Returns the empty string when no source can be identified, so callers may
join the result into a message without an extra branch.
"""
from pynenc.trigger.conditions import (
CronContext,
EventContext,
StatusContext,
)
if isinstance(context, EventContext):
return ref("event", context.event_id)
# ExceptionContext and ResultContext are StatusContext subclasses; the
# source-invocation ref is the same for all three.
if isinstance(context, StatusContext):
return ref("source-invocation", str(context.invocation_id))
if isinstance(context, CronContext):
return f"cron:{context.timestamp.isoformat()}"
return ""
[docs]
def join_tokens(*tokens: str) -> str:
"""Join non-empty tokens with single spaces.
Convenience wrapper so callers can compose log messages from a list of
optional refs without manual ``filter`` boilerplate.
"""
return " ".join(t for t in tokens if t)