1"""Request telemetry events for the relay gateway service.
2
3Terminal and intermediate request events are emitted as structured
4structlog events with the request id threaded through. No event ever
5includes request headers, payloads, or upstream details.
6"""
7
8from __future__ import annotations
9
10import time
11
12from lexigram.contracts.ai.relay import (
13 RelayGatewayError,
14 RelayGatewayRequest,
15 RelayGatewayResult,
16 RelayLoss,
17)
18from lexigram.logging import get_logger
19
20__all__ = ["log_conversion_loss", "log_request_completed"]
21
22logger = get_logger(__name__)
23
24
25def log_conversion_loss(
26 request_id: str,
27 converter_id: str,
28 losses: tuple[RelayLoss, ...] | list[RelayLoss],
29) -> None:
30 """Emit the conversion-loss event when a conversion recorded losses.
31
32 Args:
33 request_id: The gateway request identifier.
34 converter_id: The converter that produced the losses.
35 losses: Recorded conversion losses.
36 """
37 if losses:
38 logger.info(
39 "relay_gateway_conversion_loss",
40 request_id=request_id,
41 converter_id=converter_id,
42 loss_codes=tuple(loss.reason for loss in losses),
43 )
44
45
46def log_request_completed(
47 request: RelayGatewayRequest,
48 channel_name: str,
49 outcome: RelayGatewayResult | RelayGatewayError,
50 started: float,
51 *,
52 target: str = "",
53 loss_codes: tuple[str, ...] = (),
54) -> None:
55 """Emit the terminal request-completed event for any outcome.
56
57 Args:
58 request: The original gateway request.
59 channel_name: Selected channel name (or ``""`` when unknown).
60 outcome: The success result or the error that ended the flow.
61 started: Monotonic start time used to compute the duration.
62 target: Target format name (success path only).
63 loss_codes: Conversion loss codes (success path only).
64 """
65 logger.info(
66 "relay_gateway_request_completed",
67 request_id=request.request_id,
68 tenant_id=request.tenant_id,
69 channel=channel_name,
70 source=request.source,
71 target=target,
72 status_code=outcome.status_code,
73 code=outcome.code if isinstance(outcome, RelayGatewayError) else "OK",
74 duration_ms=round((time.monotonic() - started) * 1000, 2),
75 loss_codes=loss_codes,
76 )