Skip to main content

Observer

Struct Observer 

Source
pub struct Observer { /* private fields */ }
Expand description

Process-level structured logger and trace correlator.

Cloning this value is cheap. Emission uses try_send; output and JSON serialization happen on the dedicated writer thread.

Implementations§

Source§

impl Observer

Source

pub fn record_admission_capacity( &self, output: Option<&EmergencyDiagnosticHandle>, context: AdmissionEventContext<'_>, facts: &AdmissionCapacityFacts, construction: AdmissionConstruction, ) -> AdmissionCapacitySubmission

Emit original four-dimensional facts without synthetic Call/Event/root. Metrics use the existing fixed dimensions even when output is absent.

Examples found in repository?
examples/admission_capacity.rs (line 16)
8pub fn consume_original(
9    original: ProfuseGwAdmissionObservationReceipt,
10    observer: &Observer,
11    output: Option<&EmergencyDiagnosticHandle>,
12    context: AdmissionEventContext<'_>,
13    construction: AdmissionConstruction,
14) -> AdmissionCapacitySubmission {
15    let facts = AdmissionCapacityFacts::from_receipt(&original);
16    observer.record_admission_capacity(output, context, &facts, construction)
17}
Source§

impl Observer

Source

pub fn record_execution_pressure( &self, output: Option<&EmergencyDiagnosticHandle>, facts: ExecutionPressureFacts, ) -> DiagnosticSubmission

Source§

impl Observer

Source

pub fn record_standard_rpc_shutdown( &self, output: Option<&EmergencyDiagnosticHandle>, joined: bool, rpc: &RpcStageSnapshot, ) -> DiagnosticSubmission

Original same-lock ledger sample after application driver join and physical pool reclaim. Values are not manufactured from join success.

Source§

impl Observer

Source

pub fn record_ingress_fifo( &self, output: Option<&EmergencyDiagnosticHandle>, request_id: Option<ContextIdentity>, queue_sequence: u64, transition: &'static str, reason: &'static str, ) -> DiagnosticSubmission

Source§

impl Observer

Source

pub fn record_ingress_fifo_attempt( &self, output: Option<&EmergencyDiagnosticHandle>, request_id: Option<ContextIdentity>, queue_sequence: u64, decision_attempt: u64, input_peak: usize, outcome: &'static str, ) -> DiagnosticSubmission

Attempt identity only. Resource amounts remain on the original same-lock capacity receipt; input_peak is the already recognized input demand.

Source§

impl Observer

Source

pub fn record_resource_quiescent( &self, output: Option<&EmergencyDiagnosticHandle>, context: Option<RequestContextSnapshot>, completion_sequence: u64, resource: &ProcessSnapshot, rpc: &RpcStageSnapshot, ) -> DiagnosticSubmission

A post-join sample, not an admission decision or an atomic combined ledger receipt.

Source§

impl Observer

Source

pub fn start_external_call( &self, application: impl Into<ApplicationId>, module: impl Into<ModuleId>, service: impl Into<ServiceId>, operation: impl Into<OperationId>, inbound_trace_id: Option<&str>, ) -> (ActiveCall, InboundTrace)

Starts an external request using the legacy compatibility policy.

Invalid supplied identifiers are replaced for compatibility with 0.3.0 callers. Production entry points must use Observer::start_external_call_checked so only a missing identifier can cause generation.

Source

pub fn start_external_call_checked( &self, application: impl Into<ApplicationId>, module: impl Into<ModuleId>, service: impl Into<ServiceId>, operation: impl Into<OperationId>, inbound_trace_id: Option<&str>, ) -> Result<(ActiveCall, InboundTrace), TraceIdError>

Starts a production entry call. A supplied opaque protocol identifier is inherited byte-for-byte after bounded validation; only an absent identifier may cause generation.

Source

pub fn start_external_call_with_rpc( &self, application: impl Into<ApplicationId>, module: impl Into<ModuleId>, service: impl Into<ServiceId>, operation: impl Into<OperationId>, inbound_trace_id: Option<&str>, rpc: RpcCorrelationId, ) -> Result<(ActiveCall, InboundTrace), TraceIdError>

Entry supplied protocol RPC, already validated by its typed carrier. No child RPC is created for local stages or database transactions.

Examples found in repository?
examples/outbound_child_diagnostic.rs (lines 15-22)
12fn main() {
13    let observer = Observer::with_writer(ObserverConfig::default(), std::io::sink()).unwrap();
14    let (incoming, _) = observer
15        .start_external_call_with_rpc(
16            "shop",
17            "ingress",
18            "orders",
19            "query",
20            Some("gateway-opaque"),
21            RpcCorrelationId::new("0.3").unwrap(),
22        )
23        .unwrap();
24    let ingress_event = EventContext::new(
25        RequestIdentity::new("request-7").unwrap(),
26        RouteIdentity::new("/incoming").unwrap(),
27        1,
28    )
29    .unwrap();
30    let parent = RequestDiagnosticScope::output_unavailable(incoming.context(), &ingress_event)
31        .with_task(DiagnosticTaskId::from_runtime_id("42").unwrap())
32        .unwrap_or_else(|_| panic!("task"))
33        .with_zone(DiagnosticZone::from_validated_ingress("zone-a").unwrap());
34
35    // programming.rs generates the wire child; Boundary uses its trace/rpc plus
36    // request_id/function. The adapter below does NOT generate these identities.
37    let wire_trace = "gateway-opaque";
38    let wire_rpc = "0.3.1";
39    let request_id = "request-7";
40    let function = "remote.query";
41    let (root, _) = observer
42        .start_external_call_with_rpc(
43            "saddle",
44            "zone-a",
45            "profusecontract",
46            "invoke",
47            Some(wire_trace),
48            RpcCorrelationId::new(wire_rpc).unwrap(),
49        )
50        .unwrap();
51    let event = EventContext::new(
52        RequestIdentity::new(request_id).unwrap(),
53        RouteIdentity::new(function).unwrap(),
54        1,
55    )
56    .unwrap();
57    let child = parent
58        .derive_outbound_child(root.context(), &event)
59        .unwrap();
60    let failure = child.fail(
61        std::io::ErrorKind::ConnectionRefused,
62        DiagnosticCategory::UnexpectedError,
63        BoundedDiagnosticCause::new(
64            DiagnosticStage::RequestDecode,
65            DiagnosticCode::new("outbound.connect_failed").unwrap(),
66        ),
67    );
68    let id = failure.source_diagnostic().id();
69    let (retained, delivery) =
70        failure.finish_boundary_retained(None, &DiagnosticOutcomeAxes::default());
71    assert_eq!(retained.source_diagnostic().id(), id);
72    assert_eq!(*retained.error(), std::io::ErrorKind::ConnectionRefused);
73    assert_eq!(
74        delivery.source_submission(),
75        DiagnosticSubmission::OutputUnavailable
76    );
77    assert_eq!(
78        delivery.boundary_submission(),
79        DiagnosticSubmission::OutputUnavailable
80    );
81    println!("GO: Boundary-shaped child Call/Event consumed; absent output retains same failure");
82}
Source

pub fn start_child_call( &self, parent: &CallContext, kind: CallKind, module: impl Into<ModuleId>, service: impl Into<ServiceId>, operation: impl Into<OperationId>, ) -> ActiveCall

Starts a Service, DB or transaction child call on the parent’s trace.

Source

pub fn record_domain_event(&self, context: &CallContext, event: DomainEvent)

Records an intentional domain event without accepting arbitrary objects.

Source§

impl Observer

Source

pub fn record_transaction_terminal( observation: TransactionTerminalObservation, outcome: TransactionOutcome, )

DB owning driver calls once before returning its result/physical receipt. Uses the carrier’s observer/context, with no caller-supplied scope or sink. Queue loss preserves the DB result; a missing log remains NOT_PROVEN.

ⓘ
use saddle_observability::{Observer, TransactionOutcome, TransactionTerminalObservation};
fn replay(observation: TransactionTerminalObservation) {
    Observer::record_transaction_terminal(observation, TransactionOutcome::Committed);
    Observer::record_transaction_terminal(observation, TransactionOutcome::Rejected);
}
Source

pub fn record_lifecycle_timeout( &self, application: &str, stage: LifecycleTimeoutStage, elapsed_ms: u64, )

Source

pub fn start_stage( &self, parent: &CallContext, stage: Stage, event_context: EventContext, ) -> ActiveStage

Source

pub fn record_capacity( &self, context: &CallContext, event_context: &EventContext, value: CapacityObservation, )

Source

pub fn observe_database_disposition(&self, disposition: DatabaseDisposition)

Update the existing fixed-label counter without allocating or emitting a second log. The DB owning terminal path calls this exactly once with its actual physical disposition, independently of operation outcome.

Source

pub fn record_database_disposition( &self, context: &CallContext, event_context: &EventContext, disposition: DatabaseDisposition, )

Source

pub fn record_resource_finalization( &self, context: &CallContext, event_context: &EventContext, disposition: DatabaseDisposition, elapsed_ms: u64, )

Source

pub fn record_outbound( &self, context: &CallContext, event_context: &EventContext, observation: OutboundObservation, )

Source

pub fn record_lifecycle( &self, context: &CallContext, event_context: &EventContext, state: LifecycleState, health: Health, )

Source

pub fn record_logger_health( &self, context: &CallContext, event_context: &EventContext, dropped: u64, failure: Option<OutputStage>, )

Source§

impl Observer

Source

pub fn record_diagnostic( &self, diagnostic: &Diagnostic, emergency: &EmergencyDiagnosticHandle, context: Option<(&CallContext, &EventContext)>, ) -> DiagnosticSubmission

Mirrors one safe diagnostic into independent emergency output and the existing logger. The result acknowledges only emergency queue admission.

Source§

impl Observer

Source

pub fn with_writer( config: ObserverConfig, writer: impl Write + Send + 'static, ) -> Result<Self, InitError>

Creates an observer with a framework-owned writer.

Application startup should normally use init. This constructor is useful for embedding and deterministic tests.

Examples found in repository?
examples/admission_capacity.rs (line 57)
55fn main() {
56    let process = example_profile();
57    let observer = Observer::with_writer(ObserverConfig::default(), std::io::sink()).unwrap();
58    let app = ContextLabel::checked("app").unwrap();
59    let (permit, original) = match process.verified_profile().try_admit_observed() {
60        ProfuseGwLightweightObservedAdmissionOutcome::Ready(permit, original) => (permit, original),
61        _ => panic!("example first admission must succeed"),
62    };
63    let submission = consume_original(
64        original,
65        &observer,
66        None,
67        AdmissionEventContext::Unrooted {
68            application: &app,
69            lifecycle: RequestViewPhase::Admitted,
70        },
71        AdmissionConstruction::Failed(AdmissionConstructionFailure::Storage),
72    );
73    assert_eq!(submission.cpu, DiagnosticSubmission::OutputUnavailable);
74    assert_eq!(submission.memory, DiagnosticSubmission::OutputUnavailable);
75    assert_eq!(submission.database, DiagnosticSubmission::OutputUnavailable);
76    assert_eq!(
77        submission.profuse_contract,
78        DiagnosticSubmission::OutputUnavailable
79    );
80    // The output did not release or remake admission: original permit remains.
81    permit.into_execution().cancel();
82    process.finish().unwrap();
83    for (name, layout) in ["facts", "borrowed_context", "record", "submission"]
84        .into_iter()
85        .zip(admission_capacity_layouts())
86    {
87        println!(
88            "AE layout {name}: bytes={} align={}",
89            layout.size(),
90            layout.align()
91        );
92    }
93    let existing = root_diagnostic::request_logging_layouts();
94    println!(
95        "AE existing frame={} packet={} slots={} confirmation_owner={}",
96        existing.source_frame.size(),
97        existing.emergency_packet.size(),
98        existing.emergency_slots,
99        existing.confirmation_owner.size()
100    );
101    println!("AE consumer example GO; AF/Service production wiring NOT_RUN");
102}
More examples
Hide additional examples
examples/outbound_child_diagnostic.rs (line 13)
12fn main() {
13    let observer = Observer::with_writer(ObserverConfig::default(), std::io::sink()).unwrap();
14    let (incoming, _) = observer
15        .start_external_call_with_rpc(
16            "shop",
17            "ingress",
18            "orders",
19            "query",
20            Some("gateway-opaque"),
21            RpcCorrelationId::new("0.3").unwrap(),
22        )
23        .unwrap();
24    let ingress_event = EventContext::new(
25        RequestIdentity::new("request-7").unwrap(),
26        RouteIdentity::new("/incoming").unwrap(),
27        1,
28    )
29    .unwrap();
30    let parent = RequestDiagnosticScope::output_unavailable(incoming.context(), &ingress_event)
31        .with_task(DiagnosticTaskId::from_runtime_id("42").unwrap())
32        .unwrap_or_else(|_| panic!("task"))
33        .with_zone(DiagnosticZone::from_validated_ingress("zone-a").unwrap());
34
35    // programming.rs generates the wire child; Boundary uses its trace/rpc plus
36    // request_id/function. The adapter below does NOT generate these identities.
37    let wire_trace = "gateway-opaque";
38    let wire_rpc = "0.3.1";
39    let request_id = "request-7";
40    let function = "remote.query";
41    let (root, _) = observer
42        .start_external_call_with_rpc(
43            "saddle",
44            "zone-a",
45            "profusecontract",
46            "invoke",
47            Some(wire_trace),
48            RpcCorrelationId::new(wire_rpc).unwrap(),
49        )
50        .unwrap();
51    let event = EventContext::new(
52        RequestIdentity::new(request_id).unwrap(),
53        RouteIdentity::new(function).unwrap(),
54        1,
55    )
56    .unwrap();
57    let child = parent
58        .derive_outbound_child(root.context(), &event)
59        .unwrap();
60    let failure = child.fail(
61        std::io::ErrorKind::ConnectionRefused,
62        DiagnosticCategory::UnexpectedError,
63        BoundedDiagnosticCause::new(
64            DiagnosticStage::RequestDecode,
65            DiagnosticCode::new("outbound.connect_failed").unwrap(),
66        ),
67    );
68    let id = failure.source_diagnostic().id();
69    let (retained, delivery) =
70        failure.finish_boundary_retained(None, &DiagnosticOutcomeAxes::default());
71    assert_eq!(retained.source_diagnostic().id(), id);
72    assert_eq!(*retained.error(), std::io::ErrorKind::ConnectionRefused);
73    assert_eq!(
74        delivery.source_submission(),
75        DiagnosticSubmission::OutputUnavailable
76    );
77    assert_eq!(
78        delivery.boundary_submission(),
79        DiagnosticSubmission::OutputUnavailable
80    );
81    println!("GO: Boundary-shaped child Call/Event consumed; absent output retains same failure");
82}
Source

pub fn metrics_snapshot(&self) -> MetricsSnapshot

Returns a fixed-size, allocation-free copy of the current process metrics.

Source

pub fn flush(&self) -> FlushFuture ⓘ

Flushes records accepted before the lifecycle coordinator reaches the writer. The returned future never blocks the async executor thread.

Source

pub fn shutdown(&self) -> FlushFuture ⓘ

Stops admission, drains accepted records, flushes output and joins the writer without blocking the async executor thread.

Source

pub fn dropped_events(&self) -> u64

Trait Implementations§

Source§

impl Clone for Observer

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl ComponentLifecycle for Observer

Source§

fn name(&self) -> &'static str

Source§

fn start(&self) -> LifecycleFuture<'_>

Source§

fn shutdown(&self) -> LifecycleFuture<'_>

Source§

fn shutdown_with_primary( &self, _primary: Option<DiagnosticOccurrence>, ) -> Pin<Box<dyn Future<Output = Result<(), SaddleError>> + Send + '_>>

The cleanup owner may correlate its original source with an existing primary before recording. Components without their own source writer retain the ordinary shutdown behavior.
Source§

impl RecordedComponentLifecycle for Observer

The formal component boundary keeps the actual FlushError until the controlled source file has acknowledged it. The Core lifecycle impl above remains a legacy compatibility surface for callers migrating to this one.

Source§

fn name(&self) -> &'static str

Source§

fn start<'a>( &'a self, application: &'a ContextLabel, output: &'a SourceOutput, ) -> RecordedLifecycleFuture<'a>

Source§

fn shutdown<'a>( &'a self, application: &'a ContextLabel, output: &'a SourceOutput, ) -> RecordedLifecycleFuture<'a>

Source§

fn shutdown_with_primary<'a>( &'a self, application: &'a ContextLabel, output: &'a SourceOutput, primary: Option<DiagnosticOccurrence>, ) -> RecordedLifecycleFuture<'a>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.