0
# Output and Logging
1
2
Container output handling and log consumption capabilities for monitoring container behavior, debugging test failures, and processing container output streams in real-time.
3
4
## Capabilities
5
6
### Output Frame
7
8
Representation of container output with support for different output types and content processing.
9
10
```java { .api }
11
/**
12
* Represents a frame of output from a container
13
*/
14
public class OutputFrame {
15
16
/** Output types from container streams */
17
public enum OutputType {
18
/** Standard output stream */
19
STDOUT,
20
/** Standard error stream */
21
STDERR,
22
/** End of output marker */
23
END
24
}
25
26
/** Get output content as UTF-8 string */
27
public String getUtf8String();
28
29
/** Get output content as raw bytes */
30
public byte[] getBytes();
31
32
/** Get the type of output (stdout/stderr/end) */
33
public OutputType getType();
34
35
/** Get timestamp when output was received */
36
public Instant getTimestamp();
37
38
/** Check if this is stdout output */
39
public boolean isStdout();
40
41
/** Check if this is stderr output */
42
public boolean isStderr();
43
44
/** Check if this is end marker */
45
public boolean isEnd();
46
47
@Override
48
public String toString();
49
}
50
```
51
52
### Log Consumers
53
54
Various implementations for consuming and processing container output streams.
55
56
```java { .api }
57
/**
58
* SLF4J integration for container logs
59
*/
60
public class Slf4jLogConsumer implements Consumer<OutputFrame> {
61
62
/** Create with specific logger */
63
public Slf4jLogConsumer(Logger logger);
64
65
/** Create with logger for specific class */
66
public Slf4jLogConsumer(Class<?> clazz);
67
68
/** Create with logger name */
69
public Slf4jLogConsumer(String loggerName);
70
71
/** Configure to separate stdout/stderr to different log levels */
72
public Slf4jLogConsumer withSeparateOutputStreams();
73
74
/** Configure prefix for log messages */
75
public Slf4jLogConsumer withPrefix(String prefix);
76
77
/** Configure to remove ANSI escape sequences */
78
public Slf4jLogConsumer withRemoveAnsiCodes(boolean removeAnsiCodes);
79
80
@Override
81
public void accept(OutputFrame outputFrame);
82
}
83
84
/**
85
* Consumer that collects output into a string buffer
86
*/
87
public class ToStringConsumer implements Consumer<OutputFrame> {
88
89
public ToStringConsumer();
90
91
/** Get all collected output as string */
92
public String toUtf8String();
93
94
/** Get output of specific types only */
95
public String toUtf8String(OutputFrame.OutputType... types);
96
97
/** Clear collected output */
98
public void clear();
99
100
@Override
101
public void accept(OutputFrame outputFrame);
102
}
103
104
/**
105
* Consumer that waits for specific output patterns
106
*/
107
public class WaitingConsumer implements Consumer<OutputFrame> {
108
109
public WaitingConsumer();
110
111
/** Wait for specific predicate condition in output */
112
public void waitUntil(Predicate<OutputFrame> predicate) throws TimeoutException;
113
public void waitUntil(Predicate<OutputFrame> predicate, long timeout, TimeUnit timeUnit) throws TimeoutException;
114
115
/** Wait for output to end */
116
public void waitUntilEnd() throws TimeoutException;
117
public void waitUntilEnd(long timeout, TimeUnit timeUnit) throws TimeoutException;
118
119
/** Get all collected frames */
120
public List<OutputFrame> getFrames();
121
122
/** Get frames of specific types */
123
public List<OutputFrame> getFrames(OutputFrame.OutputType... types);
124
125
/** Convert collected output to string */
126
public String toUtf8String();
127
128
@Override
129
public void accept(OutputFrame outputFrame);
130
}
131
```
132
133
**Usage Examples:**
134
135
```java
136
import org.testcontainers.containers.output.OutputFrame;
137
import org.testcontainers.containers.output.Slf4jLogConsumer;
138
import org.testcontainers.containers.output.ToStringConsumer;
139
import org.testcontainers.containers.output.WaitingConsumer;
140
import org.slf4j.LoggerFactory;
141
142
// SLF4J logging integration
143
Logger logger = LoggerFactory.getLogger("container-logs");
144
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(logger)
145
.withPrefix("APP")
146
.withSeparateOutputStreams()
147
.withRemoveAnsiCodes(true);
148
149
GenericContainer<?> container = new GenericContainer<>("myapp:latest")
150
.withLogConsumer(logConsumer)
151
.withExposedPorts(8080);
152
153
// Collect output to string
154
ToStringConsumer stringConsumer = new ToStringConsumer();
155
container.withLogConsumer(stringConsumer);
156
container.start();
157
158
String allLogs = stringConsumer.toUtf8String();
159
String errorLogs = stringConsumer.toUtf8String(OutputFrame.OutputType.STDERR);
160
161
// Wait for specific output
162
WaitingConsumer waitingConsumer = new WaitingConsumer();
163
container.withLogConsumer(waitingConsumer);
164
container.start();
165
166
// Wait for application to be ready
167
waitingConsumer.waitUntilString("Server started", 30, TimeUnit.SECONDS);
168
```
169
170
### Log Consumer Composition
171
172
Combining multiple log consumers to process output in different ways simultaneously.
173
174
```java { .api }
175
/**
176
* Utility for composing multiple log consumers
177
*/
178
public class ComposingLogConsumer implements Consumer<OutputFrame> {
179
180
public ComposingLogConsumer(Consumer<OutputFrame>... consumers);
181
public ComposingLogConsumer(List<Consumer<OutputFrame>> consumers);
182
183
/** Add additional consumer */
184
public ComposingLogConsumer withConsumer(Consumer<OutputFrame> consumer);
185
186
@Override
187
public void accept(OutputFrame outputFrame);
188
}
189
```
190
191
**Usage Examples:**
192
193
```java
194
// Combine multiple consumers
195
Consumer<OutputFrame> compositeConsumer = new ComposingLogConsumer(
196
new Slf4jLogConsumer(logger),
197
stringConsumer,
198
waitingConsumer
199
);
200
201
container.withLogConsumer(compositeConsumer);
202
```
203
204
### Container Log Access
205
206
Direct access to container logs through container state and methods.
207
208
```java { .api }
209
/**
210
* Container log access methods (from GenericContainer and ContainerState)
211
*/
212
// Get all logs as string
213
public String getLogs();
214
215
// Get logs of specific output types
216
public String getLogs(OutputFrame.OutputType... types);
217
218
// Get logs since specific timestamp
219
public String getLogs(Instant since);
220
221
// Get logs with specific options
222
public String getLogs(LogOptions options);
223
224
/**
225
* Options for log retrieval
226
*/
227
public static class LogOptions {
228
public static LogOptions defaultOptions();
229
230
public LogOptions withTypes(OutputFrame.OutputType... types);
231
public LogOptions withSince(Instant since);
232
public LogOptions withUntil(Instant until);
233
public LogOptions withTail(int lines);
234
public LogOptions withTimestamps(boolean timestamps);
235
}
236
```
237
238
**Usage Examples:**
239
240
```java
241
// Basic log access
242
String allLogs = container.getLogs();
243
String errorLogs = container.getLogs(OutputFrame.OutputType.STDERR);
244
245
// Advanced log access
246
String recentLogs = container.getLogs(
247
LogOptions.defaultOptions()
248
.withSince(Instant.now().minus(Duration.ofMinutes(5)))
249
.withTail(100)
250
.withTimestamps(true)
251
);
252
```
253
254
### Custom Log Consumers
255
256
Creating custom log consumers for specialized output processing.
257
258
```java { .api }
259
/**
260
* Base interface for implementing custom log consumers
261
*/
262
@FunctionalInterface
263
public interface Consumer<T> {
264
void accept(T t);
265
}
266
267
// Example custom consumer implementation
268
public class MetricsLogConsumer implements Consumer<OutputFrame> {
269
270
private final Map<String, AtomicInteger> metricCounts = new ConcurrentHashMap<>();
271
private final Pattern metricPattern = Pattern.compile("METRIC:\\s*(\\w+):(\\d+)");
272
273
@Override
274
public void accept(OutputFrame outputFrame) {
275
if (outputFrame.getType() == OutputFrame.OutputType.STDOUT) {
276
String content = outputFrame.getUtf8String();
277
Matcher matcher = metricPattern.matcher(content);
278
279
while (matcher.find()) {
280
String metricName = matcher.group(1);
281
int metricValue = Integer.parseInt(matcher.group(2));
282
metricCounts.computeIfAbsent(metricName, k -> new AtomicInteger(0))
283
.addAndGet(metricValue);
284
}
285
}
286
}
287
288
public Map<String, Integer> getMetrics() {
289
return metricCounts.entrySet().stream()
290
.collect(Collectors.toMap(
291
Map.Entry::getKey,
292
e -> e.getValue().get()
293
));
294
}
295
}
296
```
297
298
## Advanced Output Processing Patterns
299
300
### Real-Time Log Analysis
301
302
Processing container output in real-time for monitoring and alerting:
303
304
```java
305
@Test
306
public void testRealTimeLogMonitoring() {
307
AtomicBoolean errorDetected = new AtomicBoolean(false);
308
CountDownLatch errorLatch = new CountDownLatch(1);
309
310
Consumer<OutputFrame> errorDetector = outputFrame -> {
311
if (outputFrame.getType() == OutputFrame.OutputType.STDERR) {
312
String content = outputFrame.getUtf8String();
313
if (content.contains("FATAL") || content.contains("ERROR")) {
314
errorDetected.set(true);
315
errorLatch.countDown();
316
}
317
}
318
};
319
320
GenericContainer<?> container = new GenericContainer<>("problematic-app:latest")
321
.withLogConsumer(errorDetector)
322
.withExposedPorts(8080);
323
324
container.start();
325
326
// Wait for error detection or timeout
327
boolean errorFound = errorLatch.await(30, TimeUnit.SECONDS);
328
329
if (errorFound) {
330
fail("Application reported fatal error: " + container.getLogs(OutputFrame.OutputType.STDERR));
331
}
332
}
333
```
334
335
### Structured Log Processing
336
337
Processing structured logs (JSON, key-value pairs) from containers:
338
339
```java
340
public class JsonLogConsumer implements Consumer<OutputFrame> {
341
342
private final ObjectMapper objectMapper = new ObjectMapper();
343
private final List<Map<String, Object>> logEntries = new ArrayList<>();
344
345
@Override
346
public void accept(OutputFrame outputFrame) {
347
if (outputFrame.getType() == OutputFrame.OutputType.STDOUT) {
348
String content = outputFrame.getUtf8String().trim();
349
350
if (content.startsWith("{") && content.endsWith("}")) {
351
try {
352
Map<String, Object> logEntry = objectMapper.readValue(content, Map.class);
353
synchronized (logEntries) {
354
logEntries.add(logEntry);
355
}
356
} catch (JsonProcessingException e) {
357
// Not valid JSON, ignore
358
}
359
}
360
}
361
}
362
363
public List<Map<String, Object>> getLogEntries() {
364
synchronized (logEntries) {
365
return new ArrayList<>(logEntries);
366
}
367
}
368
369
public List<Map<String, Object>> findLogEntries(String level) {
370
return getLogEntries().stream()
371
.filter(entry -> level.equals(entry.get("level")))
372
.collect(Collectors.toList());
373
}
374
}
375
```
376
377
### Performance Monitoring
378
379
Monitoring application performance through log output:
380
381
```java
382
public class PerformanceLogConsumer implements Consumer<OutputFrame> {
383
384
private final Pattern responseTimePattern = Pattern.compile("Response time: (\\d+)ms");
385
private final List<Long> responseTimes = new ArrayList<>();
386
387
@Override
388
public void accept(OutputFrame outputFrame) {
389
String content = outputFrame.getUtf8String();
390
Matcher matcher = responseTimePattern.matcher(content);
391
392
while (matcher.find()) {
393
long responseTime = Long.parseLong(matcher.group(1));
394
synchronized (responseTimes) {
395
responseTimes.add(responseTime);
396
}
397
}
398
}
399
400
public double getAverageResponseTime() {
401
synchronized (responseTimes) {
402
return responseTimes.stream()
403
.mapToLong(Long::longValue)
404
.average()
405
.orElse(0.0);
406
}
407
}
408
409
public long getMaxResponseTime() {
410
synchronized (responseTimes) {
411
return responseTimes.stream()
412
.mapToLong(Long::longValue)
413
.max()
414
.orElse(0L);
415
}
416
}
417
}
418
```
419
420
### Log Filtering and Transformation
421
422
Filtering and transforming log output before processing:
423
424
```java
425
public class FilteringLogConsumer implements Consumer<OutputFrame> {
426
427
private final Consumer<OutputFrame> delegate;
428
private final Predicate<OutputFrame> filter;
429
private final Function<OutputFrame, OutputFrame> transformer;
430
431
public FilteringLogConsumer(Consumer<OutputFrame> delegate,
432
Predicate<OutputFrame> filter,
433
Function<OutputFrame, OutputFrame> transformer) {
434
this.delegate = delegate;
435
this.filter = filter;
436
this.transformer = transformer;
437
}
438
439
@Override
440
public void accept(OutputFrame outputFrame) {
441
if (filter.test(outputFrame)) {
442
OutputFrame transformed = transformer.apply(outputFrame);
443
delegate.accept(transformed);
444
}
445
}
446
447
// Factory methods for common filters
448
public static FilteringLogConsumer onlyErrors(Consumer<OutputFrame> delegate) {
449
return new FilteringLogConsumer(
450
delegate,
451
frame -> frame.getType() == OutputFrame.OutputType.STDERR,
452
Function.identity()
453
);
454
}
455
456
public static FilteringLogConsumer withoutTimestamps(Consumer<OutputFrame> delegate) {
457
return new FilteringLogConsumer(
458
delegate,
459
frame -> true,
460
frame -> {
461
String content = frame.getUtf8String();
462
// Remove timestamp prefix pattern
463
String cleaned = content.replaceFirst("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z\\s+", "");
464
return new OutputFrame(frame.getType(), cleaned.getBytes(StandardCharsets.UTF_8));
465
}
466
);
467
}
468
}
469
```
470
471
### Asynchronous Log Processing
472
473
Processing logs asynchronously to avoid blocking container operations:
474
475
```java
476
public class AsyncLogConsumer implements Consumer<OutputFrame>, AutoCloseable {
477
478
private final Consumer<OutputFrame> delegate;
479
private final ExecutorService executor;
480
private final BlockingQueue<OutputFrame> queue;
481
private final AtomicBoolean running = new AtomicBoolean(true);
482
483
public AsyncLogConsumer(Consumer<OutputFrame> delegate) {
484
this.delegate = delegate;
485
this.executor = Executors.newSingleThreadExecutor();
486
this.queue = new LinkedBlockingQueue<>();
487
488
// Start background processing thread
489
executor.submit(this::processLogs);
490
}
491
492
@Override
493
public void accept(OutputFrame outputFrame) {
494
if (running.get()) {
495
queue.offer(outputFrame);
496
}
497
}
498
499
private void processLogs() {
500
while (running.get() || !queue.isEmpty()) {
501
try {
502
OutputFrame frame = queue.poll(100, TimeUnit.MILLISECONDS);
503
if (frame != null) {
504
delegate.accept(frame);
505
}
506
} catch (InterruptedException e) {
507
Thread.currentThread().interrupt();
508
break;
509
}
510
}
511
}
512
513
@Override
514
public void close() {
515
running.set(false);
516
executor.shutdown();
517
try {
518
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
519
executor.shutdownNow();
520
}
521
} catch (InterruptedException e) {
522
executor.shutdownNow();
523
Thread.currentThread().interrupt();
524
}
525
}
526
}