atlas_local/client/
get_logs.rs1use crate::{
2 client::Client,
3 docker::DockerLogContainer,
4 models::{LogOutput, LogsOptions},
5};
6use futures_util::{StreamExt, pin_mut};
7
8#[derive(Debug, thiserror::Error)]
9pub enum GetLogsError {
10 #[error("Failed to get container logs: {0}")]
11 ContainerLogs(String),
12}
13
14impl<D: DockerLogContainer> Client<D> {
15 #[doc = "Example code:\n\n"]
35 #[doc = "```rust,no_run"]
36 #[doc = include_str!("../../examples/get_logs.rs")]
37 #[doc = "```"]
38 pub async fn get_logs(
39 &self,
40 container_id_or_name: &str,
41 options: Option<LogsOptions>,
42 ) -> Result<Vec<LogOutput>, GetLogsError> {
43 let bollard_options = options.map(bollard::query_parameters::LogsOptions::from);
44 let stream = self.docker.logs(container_id_or_name, bollard_options);
45 pin_mut!(stream);
46
47 let mut logs = Vec::new();
48 while let Some(result) = stream.next().await {
49 let log_output = result.map_err(GetLogsError::ContainerLogs)?;
50 logs.push(LogOutput::from(log_output));
51 }
52
53 Ok(logs)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use crate::models::LogsOptions;
61 use futures_util::{Stream, stream};
62 use mockall::mock;
63
64 mock! {
65 Docker {}
66
67 impl DockerLogContainer for Docker {
68 fn logs<'a>(
69 &'a self,
70 container_id: &str,
71 options: Option<bollard::query_parameters::LogsOptions>,
72 ) -> impl Stream<Item = Result<bollard::container::LogOutput, String>>;
73 }
74 }
75
76 #[tokio::test]
77 async fn test_get_logs_success() {
78 let mut mock_docker = MockDocker::new();
80
81 mock_docker
83 .expect_logs()
84 .withf(|container_id, options| container_id == "test-container" && options.is_some())
85 .times(1)
86 .returning(|_, _| {
87 Box::pin(stream::iter(vec![
88 Ok(bollard::container::LogOutput::StdOut {
89 message: "Log line 1\n".into(),
90 }),
91 Ok(bollard::container::LogOutput::StdOut {
92 message: "Log line 2\n".into(),
93 }),
94 ]))
95 });
96
97 let client = Client::new(mock_docker);
98 let options = LogsOptions::builder().stdout(true).stderr(true).build();
99
100 let logs = client
102 .get_logs("test-container", Some(options))
103 .await
104 .expect("get_logs should succeed");
105
106 assert_eq!(logs.len(), 2);
108 assert!(logs[0].is_stdout());
109 assert!(logs[1].is_stdout());
110 }
111
112 #[tokio::test]
113 async fn test_get_logs_error() {
114 let mut mock_docker = MockDocker::new();
116
117 mock_docker
119 .expect_logs()
120 .withf(|container_id, options| {
121 container_id == "nonexistent-container" && options.is_none()
122 })
123 .times(1)
124 .returning(|_, _| Box::pin(stream::iter(vec![Err("No such container".to_string())])));
125
126 let client = Client::new(mock_docker);
127
128 let result = client.get_logs("nonexistent-container", None).await;
130
131 assert!(result.is_err());
133 assert!(matches!(
134 result.as_ref().unwrap_err(),
135 GetLogsError::ContainerLogs(_)
136 ));
137 }
138
139 #[tokio::test]
140 async fn test_get_logs_mixed_stdout_stderr() {
141 let mut mock_docker = MockDocker::new();
143
144 mock_docker
146 .expect_logs()
147 .withf(|container_id, _| container_id == "test-container")
148 .times(1)
149 .returning(|_, _| {
150 Box::pin(stream::iter(vec![
151 Ok(bollard::container::LogOutput::StdOut {
152 message: "stdout line\n".into(),
153 }),
154 Ok(bollard::container::LogOutput::StdErr {
155 message: "stderr line\n".into(),
156 }),
157 Ok(bollard::container::LogOutput::StdOut {
158 message: "another stdout line\n".into(),
159 }),
160 ]))
161 });
162
163 let client = Client::new(mock_docker);
164 let options = LogsOptions::builder().stdout(true).stderr(true).build();
165
166 let logs = client
168 .get_logs("test-container", Some(options))
169 .await
170 .expect("get_logs should succeed");
171
172 assert_eq!(logs.len(), 3);
174
175 assert!(logs[0].is_stdout());
177
178 assert!(logs[1].is_stderr());
180
181 assert!(logs[2].is_stdout());
183 }
184}