¶ciel.observability — Auditoría y trazas
Trazabilidad multi-tenant para Ciel: eventos de auditoría (AuditEvent),
sumideros (AuditSink) y un trazador consciente de herramientas
(ToolAwareTracer) que emite spans a través de un sumidero asíncrono.
¶
ciel.observability
¶
__all__ = ['AuditEvent', 'AuditSink', 'InMemoryAuditSink', 'JsonlAuditSink', 'NullAuditSink', 'TraceSpan', 'ToolAwareTracer', 'assert_tenant_event', 'propagate']
module-attribute
¶
AuditEvent
dataclass
Source code in src/ciel/observability/__init__.py
¶
AuditSink
¶
InMemoryAuditSink
¶
JsonlAuditSink
Bases: AuditSink
JSONL audit sink partitioned by tenant and session.
Each flushed event is written to base_path / tenant_id / session_id
/ {tenant_id}-{session_id}.jsonl. Missing directories are created
automatically and writes are protected by an internal lock to keep
async consumers safe.
Source code in src/ciel/observability/__init__.py
¶
NullAuditSink
¶
ToolAwareTracer
Tool-aware async tracer.
Keeps lightweight session/tenant root spans and emits tool call spans through an async context manager. Every lifecycle event is written to the provided sink so sinks can aggregate cross-tool traces.
Source code in src/ciel/observability/__init__.py
¶
TraceSpan
dataclass
Source code in src/ciel/observability/__init__.py
¶
assert_tenant_event(event: AuditEvent) -> None
¶
propagate(event: AuditEvent, *, tenant_id: Optional[str] = None) -> AuditEvent
Source code in src/ciel/observability/__init__.py
OpenTelemetry audit exporter and tracing bootstrap (lenient).
This module is optional at runtime: if the opentelemetry-api /
opentelemetry-sdk packages are not installed (they live behind the
observability extra) every symbol here still imports cleanly. The
:class:OtlpAuditExporter degrades to a no-op sink and :func:init_tracing
returns None while logging a warning, so callers never need to guard
imports themselves.
When the packages are present, :class:OtlpAuditExporter implements the
:class:~ciel.observability.AuditSink interface and emits an OpenTelemetry
span (plus a span event) for every :class:~ciel.observability.AuditEvent it
receives, preserving multi-tenancy via tenant_id span attributes.
Fase 8 añade helpers de observabilidad centralizada:
:func:init_tracing acepta un otlp_endpoint (exportador OTLP a un
colector) o, por defecto, un InMemorySpanExporter para tests offline;
:func:span_count cuenta los spans exportados (usado por los tests);
:func:current_tracer devuelve el tracer global.
¶
OTEL_AVAILABLE = _OTEL_AVAILABLE
module-attribute
¶
_OTEL_AVAILABLE = True
module-attribute
¶
__all__ = ['OtlpAuditExporter', 'init_tracing', 'OTEL_AVAILABLE', 'current_tracer', 'span_count']
module-attribute
¶
_last_provider = None
module-attribute
¶
logger = logging.getLogger(__name__)
module-attribute
¶
AuditEvent
dataclass
Source code in src/ciel/observability/__init__.py
¶
AuditSink
¶
OtlpAuditExporter
Bases: AuditSink
Audit sink that forwards events to an OpenTelemetry tracer.
Every :meth:write call starts a span named after event.event and
attaches a span event carrying the event payload. Multi-tenancy is
preserved by recording tenant_id (and session_id / agent /
tool_call_id when present) as span attributes.
The sink is safe: a missing opentelemetry install makes :meth:write a
no-op, and any tracing error is swallowed after logging so audit emission
never crashes the caller.
Source code in src/ciel/observability/otel.py
¶
_find_in_memory_exporter(provider) -> Optional['InMemorySpanExporter']
Navega la estructura REAL del TracerProvider del SDK instalado para
hallar el InMemorySpanExporter.
La forma de acceder al span processor varía entre versiones/builds del SDK:
- Algunas versiones exponen el metodo publico
provider.get_active_span_processor()(devuelve unSynchronousMultiSpanProcessor/ConcurrentMultiSpanProcessor), y cada processor hijo tiene el atributospan_exporter. - En opentelemetry-sdk 1.x el provider NO expone ese metodo publico;
el atributo real es
provider._active_span_processor(un multiprocesador) que contiene_span_processors(lista) y cada processor tienespan_exporter.
El helper soporta AMBAS formas de forma defensiva y se detiene ante el
primer InMemorySpanExporter encontrado. Si el exporter no es
in-memory (o no se puede navegar la estructura), devuelve None.
Source code in src/ciel/observability/otel.py
¶
_import_otlp_exporter()
Best-effort import of an OTLP span exporter (lenient).
Tries the gRPC exporter first, then the HTTP/protobuf one. Returns the
exporter class or None if neither is installed.
Source code in src/ciel/observability/otel.py
¶
current_tracer()
Devuelve el tracer de OTel, o None si OTel no está disponible.
Usa el provider real instalado por :func:init_tracing (_last_provider)
en lugar del proxy global, para que las trazas caigan en el exporter que
:func:span_count inspecciona.
Source code in src/ciel/observability/otel.py
¶
init_tracing(*, service_name: str = 'ciel', otlp_endpoint: Optional[str] = None) -> Optional['TracerProvider']
Configure and install a global OpenTelemetry :class:TracerProvider.
¶Parameters
service_name:
Value for the service.name resource attribute.
otlp_endpoint:
If given, spans are exported to this OTLP collector endpoint. If the
OTLP exporter packages are not installed the call degrades to an
in-memory exporter and logs a warning. If None, an in-memory
exporter is used so the gateway is still bootable offline.
¶Returns
TracerProvider | None
The installed provider, or None when opentelemetry is unavailable.
Source code in src/ciel/observability/otel.py
¶
span_count() -> int
Número de spans emitidos por el exporter in-memory (solo tests/diagnóstico).
Si el provider global fue configurado con InMemorySpanExporter (el
caso por defecto de init_tracing sin endpoint), devuelve cuántos
spans se han exportado. Si OTel no está disponible o el exporter no es
in-memory, devuelve -1 (no medible de forma determinista).
Source code in src/ciel/observability/otel.py
Prometheus metrics for Ciel (lenient).
This module is optional at runtime: if prometheus-client is not
installed (it lives behind the observability extra) every symbol here
still imports cleanly and the helpers provided (:func:record_request,
:func:record_tool_call, :func:record_agent_loop) become no-ops that
never raise. A single :func:metrics_handler is exposed so the gateway can
mount a /metrics endpoint that returns valid Prometheus text when the
client is available and a minimal 200 response otherwise.
Multi-tenancy is preserved by recording tenant as a label on every
metric that carries it.
¶
PROM_AVAILABLE = _PROM_AVAILABLE
module-attribute
¶
_PROM_AVAILABLE = True
module-attribute
¶
__all__ = ['PROM_AVAILABLE', 'record_request', 'record_tool_call', 'record_agent_loop', 'metrics_handler', 'generate_latest']
module-attribute
¶
_agent_loops_total = Counter('ciel_agent_loops_total', 'Total Ciel agent loop iterations (optionally by tenant).', ['tenant'])
module-attribute
¶
_requests_total = Counter('ciel_requests_total', 'Total Ciel requests by surface, tenant and status.', ['surface', 'tenant', 'status'])
module-attribute
¶
_tool_calls_total = Counter('ciel_tool_calls_total', 'Total Ciel tool invocations by tenant, tool and status.', ['tenant', 'tool', 'status'])
module-attribute
¶
logger = logging.getLogger(__name__)
module-attribute
¶
metrics_handler(request=None) -> Response
async
Starlette/FastAPI handler exposing /metrics.
Returns Prometheus text exposition when the client is available. When it is not, returns a 200 response with a short notice so the endpoint still resolves without crashing the gateway (offline-safe).
Source code in src/ciel/observability/metrics.py
¶
record_agent_loop(tenant: Optional[str] = None, *, increment: int = 1) -> None
Increment ciel_agent_loops_total.
Safe no-op when prometheus-client is unavailable or an error occurs.
Source code in src/ciel/observability/metrics.py
¶
record_request(surface: str, tenant: Optional[str], status: str, *, increment: int = 1) -> None
Increment ciel_requests_total.
Safe no-op when prometheus-client is unavailable or an error occurs.
Source code in src/ciel/observability/metrics.py
¶
record_tool_call(tool: str, tenant: Optional[str], status: str, *, increment: int = 1) -> None
Increment ciel_tool_calls_total.
Safe no-op when prometheus-client is unavailable or an error occurs.