Skip to content

Commit cacf0bc

Browse files
barrycomminsbsideup
authored andcommitted
Added withLogConsumer() method to DockerComposeContainer (#629)
* Added withLogConsumer() method to DockerComposeContainer Fixes #605 * Added try-finally block to DockerComposeLogConsumerTest
1 parent 2cb1429 commit cacf0bc

File tree

3 files changed

+62
-3
lines changed

3 files changed

+62
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file.
1919
- Added `WaitStrategyTarget` interface which is the target of the new `WaitStrategy` ([\#600](https://github.com/testcontainers/testcontainers-java/pull/600))
2020
- *Breaking:* Removed hard-coded `wnameless` Oracle database image name. Users should instead place a file on the classpath named `testcontainers.properties` containing `oracle.container.image=IMAGE`, where IMAGE is a suitable image name and tag/SHA hash. For information, the approach recommended by Oracle for creating an Oracle XE docker image is described [here](https://blogs.oracle.com/oraclewebcentersuite/implement-oracle-database-xe-as-docker-containers).
2121
- Added `DockerHealthcheckWaitStrategy` that is based on Docker's built-in [healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck) ([\#618](https://github.com/testcontainers/testcontainers-java/pull/618)).
22+
- Added `withLogConsumer(String serviceName, Consumer<OutputFrame> consumer)` method to `DockerComposeContainer` ([\#605](https://github.com/testcontainers/testcontainers-java/issues/605))
2223

2324
## [1.6.0] - 2018-01-28
2425

core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.slf4j.LoggerFactory;
1616
import org.slf4j.profiler.Profiler;
1717
import org.testcontainers.DockerClientFactory;
18+
import org.testcontainers.containers.output.OutputFrame;
1819
import org.testcontainers.containers.output.Slf4jLogConsumer;
1920
import org.testcontainers.containers.startupcheck.IndefiniteWaitOneShotStartupCheckStrategy;
2021
import org.testcontainers.containers.wait.strategy.Wait;
@@ -31,7 +32,9 @@
3132
import java.nio.file.Paths;
3233
import java.time.Duration;
3334
import java.util.AbstractMap.SimpleEntry;
35+
import java.util.ArrayList;
3436
import java.util.Arrays;
37+
import java.util.Collections;
3538
import java.util.HashMap;
3639
import java.util.HashSet;
3740
import java.util.List;
@@ -40,6 +43,7 @@
4043
import java.util.concurrent.ConcurrentHashMap;
4144
import java.util.concurrent.TimeUnit;
4245
import java.util.concurrent.atomic.AtomicInteger;
46+
import java.util.function.Consumer;
4347

4448
import static com.google.common.base.Preconditions.checkArgument;
4549
import static com.google.common.base.Preconditions.checkNotNull;
@@ -72,6 +76,7 @@ public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> e
7276
private final Map<String, ComposeServiceWaitStrategyTarget> serviceInstanceMap = new ConcurrentHashMap<>();
7377
private final Map<String, WaitAllStrategy> waitStrategyMap = new ConcurrentHashMap<>();
7478
private final SocatContainer ambassadorContainer = new SocatContainer();
79+
private final Map<String, List<Consumer<OutputFrame>>> logConsumers = new ConcurrentHashMap<>();
7580

7681
private static final Object MUTEX = new Object();
7782

@@ -148,11 +153,13 @@ private void createServiceInstance(Container container) {
148153
final ComposeServiceWaitStrategyTarget containerInstance = new ComposeServiceWaitStrategyTarget(container,
149154
ambassadorContainer, ambassadorPortMappings.getOrDefault(serviceName, new HashMap<>()));
150155

156+
String containerId = containerInstance.getContainerId();
151157
if (tailChildContainers) {
152-
LogUtils.followOutput(DockerClientFactory.instance().client(), containerInstance.getContainerId(),
153-
new Slf4jLogConsumer(logger()).withPrefix(container.getNames()[0]));
158+
followLogs(containerId, new Slf4jLogConsumer(logger()).withPrefix(container.getNames()[0]));
154159
}
155-
serviceInstanceMap.putIfAbsent(serviceName, containerInstance);
160+
//follow logs using registered consumers for this service
161+
logConsumers.getOrDefault(serviceName, Collections.emptyList()).forEach(consumer -> followLogs(containerId, consumer));
162+
serviceInstanceMap.putIfAbsent(serviceName, containerInstance);
156163
}
157164

158165
private void waitUntilServiceStarted(String serviceName, ComposeServiceWaitStrategyTarget serviceInstance) {
@@ -401,6 +408,27 @@ public SELF withTailChildContainers(boolean tailChildContainers) {
401408
return self();
402409
}
403410

411+
/**
412+
* Attach an output consumer at container startup, enabling stdout and stderr to be followed, waited on, etc.
413+
* <p>
414+
* More than one consumer may be registered.
415+
*
416+
* @param serviceName the name of the service as set in the docker-compose.yml file
417+
* @param consumer consumer that output frames should be sent to
418+
* @return this instance, for chaining
419+
*/
420+
public SELF withLogConsumer(String serviceName, Consumer<OutputFrame> consumer) {
421+
String serviceInstanceName = getServiceInstanceName(serviceName);
422+
final List<Consumer<OutputFrame>> consumers = this.logConsumers.getOrDefault(serviceInstanceName, new ArrayList<>());
423+
consumers.add(consumer);
424+
this.logConsumers.putIfAbsent(serviceInstanceName, consumers);
425+
return self();
426+
}
427+
428+
private void followLogs(String containerId, Consumer<OutputFrame> consumer) {
429+
LogUtils.followOutput(DockerClientFactory.instance().client(), containerId, consumer);
430+
}
431+
404432
private SELF self() {
405433
return (SELF) this;
406434
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.testcontainers.junit;
2+
3+
import org.junit.Test;
4+
import org.junit.runner.Description;
5+
import org.testcontainers.containers.DockerComposeContainer;
6+
import org.testcontainers.containers.output.WaitingConsumer;
7+
8+
import java.io.File;
9+
import java.util.concurrent.TimeUnit;
10+
import java.util.concurrent.TimeoutException;
11+
12+
import static org.testcontainers.containers.output.OutputFrame.OutputType.STDOUT;
13+
14+
public class DockerComposeLogConsumerTest {
15+
16+
@Test
17+
public void testLogConsumer() throws TimeoutException {
18+
WaitingConsumer logConsumer = new WaitingConsumer();
19+
DockerComposeContainer environment = new DockerComposeContainer(new File("src/test/resources/v2-compose-test.yml"))
20+
.withExposedService("redis_1", 6379)
21+
.withLogConsumer("redis_1", logConsumer);
22+
23+
try {
24+
environment.starting(Description.EMPTY);
25+
logConsumer.waitUntil(frame -> frame.getType() == STDOUT && frame.getUtf8String().contains("Ready to accept connections"), 5, TimeUnit.SECONDS);
26+
} finally {
27+
environment.finished(Description.EMPTY);
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)