query_runtime/observability/
jsonl.rs1use std::{error::Error as StdError, fmt, io, io::Write};
4
5use super::{RuntimeObservationRecord, RuntimeTopologyRecord, SealedRuntimeObservationSnapshot};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum RuntimeObservationExportErrorKind {
11 InvalidRecord,
13 DestinationUnavailable,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum RuntimeObservationExportErrorStatus {
20 Permanent,
22 Temporary,
24}
25
26#[derive(Debug)]
27enum RuntimeObservationExportErrorSource {
28 Encode(serde_json::Error),
29 Write(io::Error),
30}
31
32impl fmt::Display for RuntimeObservationExportErrorSource {
33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::Encode(error) => error.fmt(formatter),
36 Self::Write(error) => error.fmt(formatter),
37 }
38 }
39}
40
41impl StdError for RuntimeObservationExportErrorSource {
42 fn source(&self) -> Option<&(dyn StdError + 'static)> {
43 match self {
44 Self::Encode(error) => Some(error),
45 Self::Write(error) => Some(error),
46 }
47 }
48}
49
50#[derive(Debug)]
52pub struct RuntimeObservationExportError {
53 kind: RuntimeObservationExportErrorKind,
54 status: RuntimeObservationExportErrorStatus,
55 record_index: usize,
56 source: RuntimeObservationExportErrorSource,
57}
58
59impl RuntimeObservationExportError {
60 pub const fn kind(&self) -> RuntimeObservationExportErrorKind {
62 self.kind
63 }
64
65 pub const fn status(&self) -> RuntimeObservationExportErrorStatus {
67 self.status
68 }
69
70 pub const fn record_index(&self) -> usize {
72 self.record_index
73 }
74
75 pub const fn is_retryable(&self) -> bool {
77 matches!(self.status, RuntimeObservationExportErrorStatus::Temporary)
78 }
79
80 fn encode(record_index: usize, source: serde_json::Error) -> Self {
81 if let Some(kind) = source.io_error_kind() {
82 let status = match kind {
83 io::ErrorKind::Interrupted | io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => {
84 RuntimeObservationExportErrorStatus::Temporary
85 },
86 _ => RuntimeObservationExportErrorStatus::Permanent,
87 };
88 return Self {
89 kind: RuntimeObservationExportErrorKind::DestinationUnavailable,
90 status,
91 record_index,
92 source: RuntimeObservationExportErrorSource::Encode(source),
93 };
94 }
95 Self {
96 kind: RuntimeObservationExportErrorKind::InvalidRecord,
97 status: RuntimeObservationExportErrorStatus::Permanent,
98 record_index,
99 source: RuntimeObservationExportErrorSource::Encode(source),
100 }
101 }
102
103 fn write(record_index: usize, source: io::Error) -> Self {
104 let status = match source.kind() {
105 io::ErrorKind::Interrupted | io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => {
106 RuntimeObservationExportErrorStatus::Temporary
107 },
108 _ => RuntimeObservationExportErrorStatus::Permanent,
109 };
110 Self {
111 kind: RuntimeObservationExportErrorKind::DestinationUnavailable,
112 status,
113 record_index,
114 source: RuntimeObservationExportErrorSource::Write(source),
115 }
116 }
117}
118
119impl fmt::Display for RuntimeObservationExportError {
120 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(
122 formatter,
123 "runtime observation JSONL export failed: kind={:?}, status={:?}, record_index={}: {}",
124 self.kind, self.status, self.record_index, self.source
125 )
126 }
127}
128
129impl StdError for RuntimeObservationExportError {
130 fn source(&self) -> Option<&(dyn StdError + 'static)> {
131 Some(&self.source)
132 }
133}
134
135pub fn write_runtime_observation_jsonl<W: Write>(
137 snapshot: &SealedRuntimeObservationSnapshot,
138 writer: &mut W,
139) -> Result<(), RuntimeObservationExportError> {
140 let data = snapshot.data();
141 let mut record_index = 0;
142 write_record(writer, record_index, &RuntimeObservationRecord::Manifest(data.manifest.clone()))?;
143 record_index += 1;
144
145 for retained in &data.topology {
146 let record = match &retained.record {
147 RuntimeTopologyRecord::PlanNode(row) => {
148 RuntimeObservationRecord::PlanNode { sequence: retained.sequence, row: row.clone() }
149 },
150 RuntimeTopologyRecord::PlanEdge(row) => {
151 RuntimeObservationRecord::PlanEdge { sequence: retained.sequence, row: row.clone() }
152 },
153 RuntimeTopologyRecord::TaskDefinition(row) => {
154 RuntimeObservationRecord::TaskDefinition { sequence: retained.sequence, row: row.clone() }
155 },
156 RuntimeTopologyRecord::CompiledEdge(row) => {
157 RuntimeObservationRecord::CompiledEdge { sequence: retained.sequence, row: row.clone() }
158 },
159 RuntimeTopologyRecord::BarrierDefinition(row) => {
160 RuntimeObservationRecord::BarrierDefinition { sequence: retained.sequence, row: row.clone() }
161 },
162 RuntimeTopologyRecord::SinkDefinition(row) => {
163 RuntimeObservationRecord::SinkDefinition { sequence: retained.sequence, row: row.clone() }
164 },
165 RuntimeTopologyRecord::JobDefinition(row) => {
166 RuntimeObservationRecord::JobDefinition { sequence: retained.sequence, row: row.clone() }
167 },
168 RuntimeTopologyRecord::WorkerDefinition(row) => {
169 RuntimeObservationRecord::WorkerDefinition { sequence: retained.sequence, row: row.clone() }
170 },
171 RuntimeTopologyRecord::Resource(row) => {
172 RuntimeObservationRecord::Resource { sequence: retained.sequence, row: row.clone() }
173 },
174 };
175 write_record(writer, record_index, &record)?;
176 record_index += 1;
177 }
178 for event in &data.events {
179 write_record(writer, record_index, &RuntimeObservationRecord::TimelineEvent(event.clone()))?;
180 record_index += 1;
181 }
182 write_record(writer, record_index, &RuntimeObservationRecord::Footer(data.footer.clone()))
183}
184
185fn write_record<W: Write>(
186 writer: &mut W,
187 record_index: usize,
188 record: &RuntimeObservationRecord,
189) -> Result<(), RuntimeObservationExportError> {
190 serde_json::to_writer(&mut *writer, record)
194 .map_err(|error| RuntimeObservationExportError::encode(record_index, error))?;
195 writer.write_all(b"\n").map_err(|error| RuntimeObservationExportError::write(record_index, error))
196}