-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support accessing container logs #502
Comments
#444 somewhat related. I believe the reason why they are not exposed because
Just curios, do you need access logs programmatically or you just want to see them somehow? I know that testcontainers-java pipes stoud/stderr to console which you can control with log levels I believe. I also had the need to see the logs from containers as they happened and it would be nice have it but didn't look at the possibilities myself yet. |
If we add everything that is "handy", our API surface would be so large that the usability would suffer. Please back your feature request with a concrete usecase, thank you! :) |
In "handy", I meant not some narrow case, but a generic log access need, which I don't believe is very exceptional or user specific. My concrete use case - I needed to see container logs while running the tests. Another prospective use cases:
It looks to me that just giving an access to container log streams, could open many possibilities for the users to achieve whatever they want. |
I'd accept a feature PR that explores how we can forward a container's logs to stdout so we can resolve your usecase.
It is always a trade-off. If we expose too many knobs and details, then users need to learn more before they can successfully use the library. |
I have been look at some of the popular implementation such as java, golang and node they all have some way of accessing/seeing logs which I believe valuable. There are several approaches and some of the testcontainers implementations uses multiple of these options;
fn stdout_logs(&self, container_id: &String) -> Box<dyn Read>
I have already working version based on #503 and it look something like this; // CLI - sync version
let cid = container_id.clone();
if log::log_enabled!(target: "testcontainers.containers", log::Level::Debug) {
thread::spawn(move || {
use std::io::{BufRead, BufReader};
let log_stream = docker.stdout_logs(&cid);
let logs = BufReader::new(log_stream.inner);
for line in logs.lines() {
log::debug!(target: "testcontainers.containers", "{}-{}", cid, line.unwrap());
}
});
}
// HTTP - async version
let cid = container_id.clone();
if log::log_enabled!(target: "testcontainers.containers", log::Level::Debug) {
tokio::spawn(async move {
let mut log_stream = docker.stdout_logs(&cid);
while let Some(line) = log_stream
.inner
.next()
.await
.transpose()
.expect("Failed to read next line from container log stream")
{
log::debug!(target: "testcontainers.containers", "{} - {}", cid, line);
}
});
} I think we can also support these two approaches. First approach is good general solution for accessing containers logs. On top of that, second approach more DX centering where you can easily enable/disable container logs. I hope to raise an MR once #503 it's merged which is needed for this to work. |
Thank you for writing this up @endertunc ! Exposing a |
+1, I would like to access container logs, my use case is that I have an error that happens only in CI but not locally. |
What that usecase perhaps be better served by writing all logs to a specified directory? For example, controlled with an environment variable? |
Not sure why you say that actually. You can dump all container logs with:
Pair that with |
So I just ran into this as well, and I'd like to contribute a PR to try and solve this, but want to make sure that we're aligned on the specifics of what might make a good solution! For context, my particular usecase is similar to others in that I have a test that only fails in CI, and I would like to access the logs of the container to figure out what is happening. In my case modifying the test source code is the easiest/fastest method -- and there's no easy way to get at the logs, outside of executing Setting To lay out how I think of the solution space:
To make things more concrete, here's the code I'm trying to debug: // Do the thing
assert_thing_done(&arg, &another_arg, third_arg.as_bytes()).await?; I run that, see that there's a test failure only in CI, and since I'm on a branch, I pop on a // Write new object
if let Err(e) = assert_thing_done(&arg, &another_arg, third_arg.as_bytes()).await {
eprintln!("OUTPUT? {}", ???);
return Err(e);
} Obviously, the test still fails, but as you can see I just want to get a little bit more information, right after the failure (ideally, in the exact same spot). What I want to do is be able to replace that This helps me debug the failure pretty quickly (IMO much more quickly than changing CI setup or test setup code) and figure out if the container is doing something wrong or if the problem is elsewhere. With regards to the previous discussion:
I agree with this solution -- since containers hold on to the client (i.e. a Of course, Arguably The PR I'd submit is pretty small and looks like this: diff --git a/testcontainers/src/core/container.rs b/testcontainers/src/core/container.rs
index 53cbd505..f5a31744 100644
--- a/testcontainers/src/core/container.rs
+++ b/testcontainers/src/core/container.rs
@@ -6,6 +6,8 @@ use bollard_stubs::models::ContainerInspectResponse;
use std::{fmt, marker::PhantomData, net::IpAddr, str::FromStr};
+use super::logs::WaitError;
+
/// Represents a running docker container.
///
/// Containers have a [`custom destructor`][drop_impl] that removes them as soon as they go out of scope:
@@ -226,6 +228,14 @@ where
self.docker_client.start(&self.id);
}
+ pub fn stdout_logs(&self) -> Result<impl Iterator<Item = String>, WaitError> {
+ self.docker_client.stdout_logs(&self.id).to_lines()
+ }
+
+ pub fn stderr_logs(&self) -> Result<impl Iterator<Item = String>, WaitError> {
+ self.docker_client.stderr_logs(&self.id).to_lines()
+ }
+
pub fn rm(&self) {
log::debug!("Deleting docker container {}", self.id);
diff --git a/testcontainers/src/core/logs.rs b/testcontainers/src/core/logs.rs
index 575efdca..c0735cf3 100644
--- a/testcontainers/src/core/logs.rs
+++ b/testcontainers/src/core/logs.rs
@@ -1,7 +1,8 @@
#[cfg(feature = "experimental")]
use futures::{stream::BoxStream, StreamExt};
use std::{
- fmt, io,
+ fmt,
+ io::{self, BufWriter},
io::{BufRead, BufReader, Read},
};
@@ -65,6 +66,15 @@ impl LogStream {
Err(end_of_stream(lines))
}
+
+ /// Get all current lines of output
+ pub fn to_lines(self) -> Result<impl Iterator<Item = String>, WaitError> {
+ BufReader::new(self.inner)
+ .lines()
+ .collect::<Result<Vec<String>, std::io::Error>>()
+ .map_err(WaitError::from)
+ .map(|v| v.into_iter())
+ }
}
fn handle_line(line: String, message: &str, lines: &mut Vec<String>) -> bool { Does this seem like a reasonable start or PR that could close this out? At this point with how the other code has progressed it's merely a convenience function, and performing a copy of existing lines (open to more efficient ways of doing the copy by the way |
Thank you for the detailed comment @t3hmrman ! I am still curious whether the following would also solve the problem:
By default, cargo captures stdout for passing tests. Upon failure, it dumps stdout. I am wondering if it wouldn't be an even better solution if we always forward container logs to stdout? To debug a failure then, you wouldn't even have to make code changes! We can make this conditional on the What do you think? I am also open to adding the |
Absolutely no problem!
Ah this would also certainly work! Let me see if I can think of some pros/cons:
[EDIT] - Oh also, it might make sense to actually output this information under
Yeah this would enable a lack of code changes -- I think it would be the user's job (i.e. code above
Yeah I think both of these are beneficial. Being able to better control how stdout was forwarded would definitely be a good general option. Also I noticed that the Another nice use case might be useful is one that checks a particular container's logs after an operation. Right now, I do I wonder if this would work with the stdout approach... If you could tag the lines coming out of the |
What do you mean by this? In my experience, setting an env var when running tests is easier than changing code and re-compiling it. And what is the point of a code option if it is "best practise" to configure A design goal of this library is to have as few configuration options as possible such that the most important usecases "just work"! |
Well an ENV var is a pretty coarse-grained change that affects the whole test suite, where a code change can be more surgical (i.e. in only the test you need). Modifying the CI environment that tests run in is not always easy to do for people working on codebases. I'd argue that since what you're editing is the code (the testing devloop), the code is the thing is what's in front of you -- editing CI settings is a context-switch. That aside, I'm not really thinking of this as a configuration option -- I don't think it's this library's job to do the I was hoping what this library should be able to do is at least give access to the logs. It's not always the case that I just want the logs printed, I could want to do something else with them, or process them in some other way. |
To wrap this up, I'd suggest the following:
Cargo captures stdout and stderr for successful tests, meaning this isn't noisy by default and only shows the logs when a test fails.
The two are actually entirely separate from each other and can / should implement in separate PRs :) |
Closes #502 Exposes `stdout` and `stderr` methods, which returns: - `tokio::io::AsyncBufRead` impl for async container - `std::io::BufRead` impl for sync container (under `blocking` feature) Also, small alignment is performed: - rename the `ExecResult` `stdout`/`stderr` methods to match the container methods
Closes #502 Exposes `stdout` and `stderr` methods, which returns: - `tokio::io::AsyncBufRead` impl for async container - `std::io::BufRead` impl for sync container (under `blocking` feature) Also, small alignment is performed (related to #617): - rename the `ExecResult` `stdout`/`stderr` methods to match the container methods
Currently it's not possible to access container log streams on the Api (stdout or stderr).
It would be handy for users to catch something or log, without a need of executing docker commands manually.
Perhaps there is a simple possibility to expose them on the Container by using Docker::stdout_logs and Docker::stderr_logs.
The text was updated successfully, but these errors were encountered: