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
impl Observer
Sourcepub fn record_admission_capacity(
&self,
output: Option<&EmergencyDiagnosticHandle>,
context: AdmissionEventContext<'_>,
facts: &AdmissionCapacityFacts,
construction: AdmissionConstruction,
) -> AdmissionCapacitySubmission
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.
Source§impl Observer
impl Observer
Sourcepub 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)
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.
Sourcepub 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>
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.
Sourcepub 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>
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?
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}Sourcepub fn start_child_call(
&self,
parent: &CallContext,
kind: CallKind,
module: impl Into<ModuleId>,
service: impl Into<ServiceId>,
operation: impl Into<OperationId>,
) -> ActiveCall
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.
Sourcepub fn record_domain_event(&self, context: &CallContext, event: DomainEvent)
pub fn record_domain_event(&self, context: &CallContext, event: DomainEvent)
Records an intentional domain event without accepting arbitrary objects.
Source§impl Observer
impl Observer
Sourcepub fn record_transaction_terminal(
observation: TransactionTerminalObservation,
outcome: TransactionOutcome,
)
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);
}pub fn record_lifecycle_timeout( &self, application: &str, stage: LifecycleTimeoutStage, elapsed_ms: u64, )
pub fn start_stage( &self, parent: &CallContext, stage: Stage, event_context: EventContext, ) -> ActiveStage
pub fn record_capacity( &self, context: &CallContext, event_context: &EventContext, value: CapacityObservation, )
Sourcepub fn observe_database_disposition(&self, disposition: DatabaseDisposition)
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.
pub fn record_database_disposition( &self, context: &CallContext, event_context: &EventContext, disposition: DatabaseDisposition, )
pub fn record_resource_finalization( &self, context: &CallContext, event_context: &EventContext, disposition: DatabaseDisposition, elapsed_ms: u64, )
pub fn record_outbound( &self, context: &CallContext, event_context: &EventContext, observation: OutboundObservation, )
pub fn record_lifecycle( &self, context: &CallContext, event_context: &EventContext, state: LifecycleState, health: Health, )
pub fn record_logger_health( &self, context: &CallContext, event_context: &EventContext, dropped: u64, failure: Option<OutputStage>, )
Source§impl Observer
impl Observer
Sourcepub fn record_diagnostic(
&self,
diagnostic: &Diagnostic,
emergency: &EmergencyDiagnosticHandle,
context: Option<(&CallContext, &EventContext)>,
) -> DiagnosticSubmission
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
impl Observer
Sourcepub fn with_writer(
config: ObserverConfig,
writer: impl Write + Send + 'static,
) -> Result<Self, InitError>
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?
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={}",
96 existing.source_frame.size(),
97 existing.emergency_packet.size(),
98 existing.emergency_slots
99 );
100 println!("AE consumer example GO; AF/Service production wiring NOT_RUN");
101}More examples
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}62fn main() {
63 // Storage contract exercise only. R0/A/R must reserve the exact layouts before
64 // this constructor is enabled in a real admitted or rejection path.
65 let mut publisher = RequestRootPublisher::create(
66 ContextLabel::checked("saddle").unwrap(),
67 ContextFact::NotEstablished,
68 )
69 .unwrap();
70 let root = publisher.reference();
71 let early = root.view(
72 RequestLocalFacts::new(RequestViewPhase::Reading).with_task(ContextFact::Present(17)),
73 );
74 let read_error = std::io::Error::from(std::io::ErrorKind::UnexpectedEof);
75 let early_failure = RootDiagnosticScope::new(&early, None).source_error(
76 &read_error,
77 DiagnosticStage::RequestDecode,
78 RootRequestEvent::Ingress,
79 RootOutcomeFacts::default(),
80 );
81 let event = EventContext::new(
82 RequestIdentity::new("request-a").unwrap(),
83 RouteIdentity::new("example.live").unwrap(),
84 1,
85 )
86 .unwrap();
87 assert!(
88 publisher
89 .publish(
90 request_identity_group(
91 &call("0", 1, "entry", "app", "example.live"),
92 &event,
93 ContextFact::Present(ContextLabel::checked("local").unwrap())
94 )
95 .unwrap()
96 )
97 .is_ok()
98 );
99 let active = early
100 .refresh(&root)
101 .unwrap()
102 .with_phase(RequestViewPhase::Handler);
103 // Formal Boundary style: real child Call and event originate from its actual
104 // ProfuseContract request. Obs validates rather than inventing a child RPC.
105 let child_event = EventContext::new(
106 RequestIdentity::new("request-a").unwrap(),
107 RouteIdentity::new("receipt.invoke").unwrap(),
108 1,
109 )
110 .unwrap();
111 let child = request_child_view(
112 &active,
113 &call("0.1", 2, "local", "profusecontract", "invoke"),
114 &child_event,
115 )
116 .unwrap();
117 assert!(active.same_request(&child));
118 let (_, issuer) = DbPhysicalDispositionIssuer::issue().into_startup_and_request_issuer();
119 let (request, execution) = issuer.issue_request().unwrap();
120 // Real D passes &sqlx::Error here before driver_ref/business mapping. This
121 // standalone example uses the standard Error contract, with no DB dependency.
122 let raw_error = std::io::Error::new(
123 std::io::ErrorKind::PermissionDenied,
124 "original driver description",
125 );
126 let db_failure = database_source(&active, &request, &execution, None, &raw_error);
127 drop(raw_error);
128 let supervised = RootSupervisionReturn::<()>::failed(
129 db_failure.map_classification(DiagnosticCode::new("handler.failed").unwrap()),
130 );
131 let failure = supervised.consume().err().unwrap();
132 // Public projections contain no source body or root. Real Runtime must also
133 // finish the original physical pair, then drop all views before settlement.
134 let public = failure
135 .finish(&active, None, RootOutcomeFacts::default())
136 .ok()
137 .unwrap();
138 let early_public = early_failure
139 .finish(&active, None, RootOutcomeFacts::default())
140 .ok()
141 .unwrap();
142 let observer = Observer::with_writer(ObserverConfig::default(), std::io::sink()).unwrap();
143 let completed = RootDiagnosticScope::new(&active, None)
144 .start_stage(&observer, RootRequestEvent::Response)
145 .finish_nonfailure(RootOutcomeFacts {
146 axes: DiagnosticOutcomeAxes {
147 operation: OperationOutcome::Succeeded,
148 ..Default::default()
149 },
150 ..Default::default()
151 })
152 .ok()
153 .unwrap();
154 assert_eq!(completed, DiagnosticSubmission::OutputUnavailable);
155 assert_eq!(
156 RootDiagnosticScope::new(&active, None).ordinary(
157 &observer,
158 RootRequestEvent::Handler,
159 RootOutcomeFacts::default()
160 ),
161 DiagnosticSubmission::Enqueued
162 );
163 drop((publisher, root, early, active, child));
164 assert_eq!(
165 public.source_submission(),
166 DiagnosticSubmission::OutputUnavailable
167 );
168 assert_eq!(
169 early_public.terminal_submission(),
170 DiagnosticSubmission::OutputUnavailable
171 );
172 for (name, (payload, shared)) in ["root", "view", "child"]
173 .into_iter()
174 .zip(saddle_core::request_context::request_context_layouts())
175 {
176 println!(
177 "layout {name}: payload={} align={} arc_allocation={} arc_align={}",
178 payload.size(),
179 payload.align(),
180 shared.size(),
181 shared.align()
182 );
183 }
184 println!(
185 "layout receipt={} public={} root_ref={} view_ref={} identity_input={} bounded_detail={} borrowed_stage={}",
186 size_of::<RootRequestFailure>(),
187 size_of::<PublicRequestFailure>(),
188 size_of::<RequestRootRef>(),
189 size_of::<RequestExecutionView>(),
190 size_of::<RequestIdentityGroup>(),
191 size_of::<BoundedDiagnostic>(),
192 size_of::<RootActiveStage<'static>>()
193 );
194 println!("C consumer contract GO; production reservation/lifecycle NOT_RUN");
195 let layout = saddle_observability::root_diagnostic::request_logging_layouts();
196 let (stream, cycle, payload) = saddle_observability::root_diagnostic::original_capture_layout();
197 println!(
198 "layout original_stream={} align={} cycle_detector={} align={} payload_limit={} total_content_limit=none",
199 stream.size(),
200 stream.align(),
201 cycle.size(),
202 cycle.align(),
203 payload
204 );
205 println!(
206 "layout source_frame={} source_record={} emergency_packet={} emergency_slots={} ordinary_command={} ordinary_payload_max={}",
207 layout.source_frame.size(),
208 layout.source_record.size(),
209 layout.emergency_packet.size(),
210 layout.emergency_slots,
211 layout.ordinary_command.size(),
212 layout.ordinary_encoded_bytes_max
213 );
214}Sourcepub fn metrics_snapshot(&self) -> MetricsSnapshot
pub fn metrics_snapshot(&self) -> MetricsSnapshot
Returns a fixed-size, allocation-free copy of the current process metrics.
Sourcepub fn flush(&self) -> FlushFuture ⓘ
pub fn flush(&self) -> FlushFuture ⓘ
Flushes records accepted before the lifecycle coordinator reaches the writer. The returned future never blocks the async executor thread.
Sourcepub fn shutdown(&self) -> FlushFuture ⓘ
pub fn shutdown(&self) -> FlushFuture ⓘ
Stops admission, drains accepted records, flushes output and joins the writer without blocking the async executor thread.