docker_wrapper/command/
logs.rs1use super::{CommandExecutor, CommandOutput, DockerCommand};
6use crate::error::Result;
7use crate::stream::{OutputLine, StreamResult, StreamableCommand};
8use async_trait::async_trait;
9use tokio::process::Command as TokioCommand;
10use tokio::sync::mpsc;
11
12#[derive(Debug, Clone)]
14pub struct LogsCommand {
15 container: String,
17 follow: bool,
19 timestamps: bool,
21 tail: Option<String>,
23 since: Option<String>,
25 until: Option<String>,
27 details: bool,
29 pub executor: CommandExecutor,
31}
32
33impl LogsCommand {
34 #[must_use]
36 pub fn new(container: impl Into<String>) -> Self {
37 Self {
38 container: container.into(),
39 follow: false,
40 timestamps: false,
41 tail: None,
42 since: None,
43 until: None,
44 details: false,
45 executor: CommandExecutor::new(),
46 }
47 }
48
49 #[must_use]
51 pub fn follow(mut self) -> Self {
52 self.follow = true;
53 self
54 }
55
56 #[must_use]
58 pub fn timestamps(mut self) -> Self {
59 self.timestamps = true;
60 self
61 }
62
63 #[must_use]
65 pub fn tail(mut self, lines: impl Into<String>) -> Self {
66 self.tail = Some(lines.into());
67 self
68 }
69
70 #[must_use]
72 pub fn all(mut self) -> Self {
73 self.tail = Some("all".to_string());
74 self
75 }
76
77 #[must_use]
79 pub fn since(mut self, timestamp: impl Into<String>) -> Self {
80 self.since = Some(timestamp.into());
81 self
82 }
83
84 #[must_use]
86 pub fn until(mut self, timestamp: impl Into<String>) -> Self {
87 self.until = Some(timestamp.into());
88 self
89 }
90
91 #[must_use]
93 pub fn details(mut self) -> Self {
94 self.details = true;
95 self
96 }
97
98 pub async fn run(&self) -> Result<CommandOutput> {
106 self.execute().await
107 }
108}
109
110#[async_trait]
111impl DockerCommand for LogsCommand {
112 type Output = CommandOutput;
113
114 fn get_executor(&self) -> &CommandExecutor {
115 &self.executor
116 }
117
118 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
119 &mut self.executor
120 }
121
122 fn build_command_args(&self) -> Vec<String> {
123 let mut args = vec!["logs".to_string()];
124
125 if self.follow {
126 args.push("--follow".to_string());
127 }
128
129 if self.timestamps {
130 args.push("--timestamps".to_string());
131 }
132
133 if let Some(ref tail) = self.tail {
134 args.push("--tail".to_string());
135 args.push(tail.clone());
136 }
137
138 if let Some(ref since) = self.since {
139 args.push("--since".to_string());
140 args.push(since.clone());
141 }
142
143 if let Some(ref until) = self.until {
144 args.push("--until".to_string());
145 args.push(until.clone());
146 }
147
148 if self.details {
149 args.push("--details".to_string());
150 }
151
152 args.push(self.container.clone());
154
155 args.extend(self.executor.raw_args.clone());
157
158 args
159 }
160
161 async fn execute(&self) -> Result<Self::Output> {
162 let args = self.build_command_args();
163 self.execute_command(args).await
164 }
165}
166
167#[async_trait]
169impl StreamableCommand for LogsCommand {
170 async fn stream<F>(&self, handler: F) -> Result<StreamResult>
171 where
172 F: FnMut(OutputLine) + Send + 'static,
173 {
174 let mut cmd = TokioCommand::new("docker");
175 cmd.arg("logs");
176
177 for arg in self.build_command_args() {
178 cmd.arg(arg);
179 }
180
181 crate::stream::stream_command(cmd, handler).await
182 }
183
184 async fn stream_channel(&self) -> Result<(mpsc::Receiver<OutputLine>, StreamResult)> {
185 let mut cmd = TokioCommand::new("docker");
186 cmd.arg("logs");
187
188 for arg in self.build_command_args() {
189 cmd.arg(arg);
190 }
191
192 crate::stream::stream_command_channel(cmd).await
193 }
194}
195
196impl LogsCommand {
197 pub async fn stream<F>(&self, handler: F) -> Result<StreamResult>
222 where
223 F: FnMut(OutputLine) + Send + 'static,
224 {
225 <Self as StreamableCommand>::stream(self, handler).await
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn test_logs_basic() {
235 let cmd = LogsCommand::new("test-container");
236 let args = cmd.build_command_args();
237 assert_eq!(args, vec!["logs", "test-container"]);
238 }
239
240 #[test]
241 fn test_logs_follow() {
242 let cmd = LogsCommand::new("test-container").follow();
243 let args = cmd.build_command_args();
244 assert_eq!(args, vec!["logs", "--follow", "test-container"]);
245 }
246
247 #[test]
248 fn test_logs_with_tail() {
249 let cmd = LogsCommand::new("test-container").tail("100");
250 let args = cmd.build_command_args();
251 assert_eq!(args, vec!["logs", "--tail", "100", "test-container"]);
252 }
253
254 #[test]
255 fn test_logs_with_timestamps() {
256 let cmd = LogsCommand::new("test-container").timestamps();
257 let args = cmd.build_command_args();
258 assert_eq!(args, vec!["logs", "--timestamps", "test-container"]);
259 }
260
261 #[test]
262 fn test_logs_with_since() {
263 let cmd = LogsCommand::new("test-container").since("10m");
264 let args = cmd.build_command_args();
265 assert_eq!(args, vec!["logs", "--since", "10m", "test-container"]);
266 }
267
268 #[test]
269 fn test_logs_all_options() {
270 let cmd = LogsCommand::new("test-container")
271 .follow()
272 .timestamps()
273 .tail("50")
274 .since("1h")
275 .details();
276 let args = cmd.build_command_args();
277 assert_eq!(
278 args,
279 vec![
280 "logs",
281 "--follow",
282 "--timestamps",
283 "--tail",
284 "50",
285 "--since",
286 "1h",
287 "--details",
288 "test-container"
289 ]
290 );
291 }
292}