Skip to content

Commit dfdd36b

Browse files
authored
Merge pull request #112 from Synicix/logging_patch
Added logging to PodResult
2 parents 8180f3d + 44b4245 commit dfdd36b

File tree

8 files changed

+126
-2
lines changed

8 files changed

+126
-2
lines changed

src/core/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ impl fmt::Debug for OrcaError {
122122
match &self.kind {
123123
Kind::AgentCommunicationFailure { backtrace, .. }
124124
| Kind::FailedToStartPod { backtrace, .. }
125+
| Kind::FailedToExtractRunInfo { backtrace, .. }
125126
| Kind::IncompletePacket { backtrace, .. }
126127
| Kind::InvalidPath { backtrace, .. }
127128
| Kind::InvalidIndex { backtrace, .. }

src/uniffi/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ pub(crate) enum Kind {
3131
source: Box<dyn Error + Send + Sync>,
3232
backtrace: Option<Backtrace>,
3333
},
34+
#[snafu(display(
35+
"Failed to extract run info from the container image file: {container_name}."
36+
))]
37+
FailedToExtractRunInfo {
38+
container_name: String,
39+
backtrace: Option<Backtrace>,
40+
},
3441
#[snafu(display(
3542
"Missing expected output file or dir with key {packet_key} at path {path:?} for pod job (hash: {pod_job_hash})."
3643
))]

src/uniffi/model/pod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ pub struct PodResult {
204204
pub created: u64,
205205
/// Time in epoch when terminated in seconds.
206206
pub terminated: u64,
207+
/// Logs about stdout and stderr, where stderr is append at the end
208+
pub logs: String,
207209
}
208210

209211
impl PodResult {
@@ -220,6 +222,7 @@ impl PodResult {
220222
created: u64,
221223
terminated: u64,
222224
namespace_lookup: &HashMap<String, PathBuf>,
225+
logs: String,
223226
) -> Result<Self> {
224227
let output_packet = pod_job
225228
.pod
@@ -276,6 +279,7 @@ impl PodResult {
276279
status,
277280
created,
278281
terminated,
282+
logs,
279283
};
280284
Ok(Self {
281285
hash: hash_buffer(to_yaml(&pod_result_no_hash)?),

src/uniffi/orchestrator/docker.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use crate::{
1212
use async_trait;
1313
use bollard::{
1414
Docker,
15-
container::{RemoveContainerOptions, StartContainerOptions, WaitContainerOptions},
15+
container::{
16+
LogOutput, LogsOptions, RemoveContainerOptions, StartContainerOptions, WaitContainerOptions,
17+
},
1618
errors::Error::DockerContainerWaitError,
1719
image::{CreateImageOptions, ImportImageOptions},
1820
};
@@ -70,6 +72,9 @@ impl Orchestrator for LocalDockerOrchestrator {
7072
) -> Result<PodResult> {
7173
ASYNC_RUNTIME.block_on(self.get_result(pod_run, namespace_lookup))
7274
}
75+
fn get_logs_blocking(&self, pod_run: &PodRun) -> Result<String> {
76+
ASYNC_RUNTIME.block_on(self.get_logs(pod_run))
77+
}
7378
#[expect(
7479
clippy::try_err,
7580
reason = r#"
@@ -263,8 +268,65 @@ impl Orchestrator for LocalDockerOrchestrator {
263268
),
264269
})?,
265270
namespace_lookup,
271+
self.get_logs(pod_run).await?,
266272
)
267273
}
274+
275+
async fn get_logs(&self, pod_run: &PodRun) -> Result<String> {
276+
let mut std_out = Vec::new();
277+
let mut std_err = Vec::new();
278+
279+
self.api
280+
.logs::<String>(
281+
&pod_run.assigned_name,
282+
Some(LogsOptions {
283+
stdout: true,
284+
stderr: true,
285+
..Default::default()
286+
}),
287+
)
288+
.try_collect::<Vec<_>>()
289+
.await?
290+
.iter()
291+
.for_each(|log_output| match log_output {
292+
LogOutput::StdOut { message } => {
293+
std_out.extend(message.to_vec());
294+
}
295+
LogOutput::StdErr { message } => {
296+
std_err.extend(message.to_vec());
297+
}
298+
LogOutput::StdIn { .. } | LogOutput::Console { .. } => {
299+
// Ignore stdin logs, as they are not relevant for our use case
300+
}
301+
});
302+
303+
let mut logs = String::from_utf8_lossy(&std_out).to_string();
304+
if !std_err.is_empty() {
305+
logs.push_str("\nSTDERR:\n");
306+
logs.push_str(&String::from_utf8_lossy(&std_err));
307+
}
308+
309+
// Check for errors in the docker state, if exist, attach it to logs
310+
// This is for when the container exits immediately due to a bad command or similar
311+
let error = self
312+
.api
313+
.inspect_container(&pod_run.assigned_name, None)
314+
.await?
315+
.state
316+
.context(selector::FailedToExtractRunInfo {
317+
container_name: &pod_run.assigned_name,
318+
})?
319+
.error
320+
.context(selector::FailedToExtractRunInfo {
321+
container_name: &pod_run.assigned_name,
322+
})?;
323+
324+
if !error.is_empty() {
325+
logs.push_str(&error);
326+
}
327+
328+
Ok(logs)
329+
}
268330
}
269331

270332
#[uniffi::export]

src/uniffi/orchestrator/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ pub trait Orchestrator: Send + Sync + fmt::Debug {
121121
pod_run: &PodRun,
122122
namespace_lookup: &HashMap<String, PathBuf>,
123123
) -> Result<PodResult>;
124+
/// Get the logs for a specific pod run.
125+
/// # Errors
126+
/// Will return `Err` if there is an issue getting logs.
127+
fn get_logs_blocking(&self, pod_run: &PodRun) -> Result<String>;
124128
/// How to asynchronously start containers with an alternate image.
125129
///
126130
/// # Errors
@@ -170,6 +174,9 @@ pub trait Orchestrator: Send + Sync + fmt::Debug {
170174
pod_run: &PodRun,
171175
namespace_lookup: &HashMap<String, PathBuf>,
172176
) -> Result<PodResult>;
177+
178+
/// Get the logs for a specific pod run.
179+
async fn get_logs(&self, pod_run: &PodRun) -> Result<String>;
173180
}
174181
/// Orchestration execution agent daemon and client.
175182
pub mod agent;

tests/fixture/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ pub fn pod_result_style(
157157
1_737_922_307,
158158
1_737_925_907,
159159
namespace_lookup,
160+
"Example logs".to_owned(),
160161
)
161162
}
162163

tests/model.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ fn pod_job_to_yaml() -> Result<()> {
104104
fn hash_pod_result() -> Result<()> {
105105
pretty_assert_eq!(
106106
pod_result_style(&NAMESPACE_LOOKUP_READ_ONLY)?.hash,
107-
"e752d86d4fc5435bfa4564ba951851530f5cf2228586c2f488c2ca9e7bcc7ed1",
107+
"0bc0f17230cf78dbf3054c6887f42abad8b48382efee2ffe1adc79f0ae02fd1a",
108108
"Hash didn't match."
109109
);
110110
Ok(())
@@ -134,6 +134,7 @@ fn pod_result_to_yaml() -> Result<()> {
134134
status: Completed
135135
created: 1737922307
136136
terminated: 1737925907
137+
logs: Example logs
137138
"},
138139
"YAML serialization didn't match."
139140
);

tests/orchestrator.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,44 @@ async fn verify_pod_result_not_running() -> Result<()> {
295295
);
296296
Ok(())
297297
}
298+
299+
#[test]
300+
fn logs() -> Result<()> {
301+
execute_wrapper(|orchestrator, namespace_lookup| {
302+
let pod_job = pod_job_custom(
303+
pod_custom(
304+
"alpine:3.14",
305+
vec!["bin/sh".into(), "-c".into(), "echo \"hi\"".into()],
306+
HashMap::new(),
307+
)?,
308+
HashMap::new(),
309+
namespace_lookup,
310+
)?;
311+
312+
let pod_run = orchestrator.start_blocking(&pod_job, namespace_lookup)?;
313+
let pod_result = orchestrator.get_result_blocking(&pod_run, namespace_lookup)?;
314+
315+
assert_eq!(
316+
pod_result.status,
317+
PodStatus::Completed,
318+
"Pod status is not completed"
319+
);
320+
321+
assert_eq!(orchestrator.get_logs_blocking(&pod_run)?, "hi\n");
322+
assert_eq!(
323+
orchestrator
324+
.get_result_blocking(&pod_run, namespace_lookup)?
325+
.logs,
326+
"hi\n"
327+
);
328+
329+
orchestrator.delete_blocking(&pod_run)?;
330+
331+
assert!(
332+
!orchestrator.list_blocking()?.contains(&pod_run),
333+
"Unexpected container remains."
334+
);
335+
336+
Ok(())
337+
})
338+
}

0 commit comments

Comments
 (0)