Skip to content

Commit 1257d50

Browse files
committed
Improve logging in DockerApi
This commit introduces a new constructor in `DockerApi` that accepts `DockerLogger` as a parameter. The `DockerLogger` is a pretty simple callback interface used to provide DockerApi output logging. See gh-43460 Signed-off-by: Dmytro Nosan <[email protected]>
1 parent 48e3de0 commit 1257d50

File tree

8 files changed

+264
-10
lines changed

8 files changed

+264
-10
lines changed

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.function.Consumer;
2222

2323
import org.springframework.boot.buildpack.platform.docker.DockerApi;
24+
import org.springframework.boot.buildpack.platform.docker.DockerLogger;
2425
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
2526
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
2627
import org.springframework.boot.buildpack.platform.docker.TotalProgressPushListener;
@@ -89,6 +90,19 @@ public Builder(BuildLog log, DockerConfiguration dockerConfiguration) {
8990
dockerConfiguration);
9091
}
9192

93+
/**
94+
* Create a new builder instance.
95+
* @param buildLog a logger used to record {@link Builder} output
96+
* @param dockerLogger a logger used to record {@link DockerApi} output
97+
* @param dockerConfiguration the docker configuration
98+
* @since 3.5.0
99+
*/
100+
public Builder(BuildLog buildLog, DockerLogger dockerLogger, DockerConfiguration dockerConfiguration) {
101+
this(buildLog,
102+
new DockerApi((dockerConfiguration != null) ? dockerConfiguration.getHost() : null, dockerLogger),
103+
dockerConfiguration);
104+
}
105+
92106
Builder(BuildLog log, DockerApi docker, DockerConfiguration dockerConfiguration) {
93107
Assert.notNull(log, "'log' must not be null");
94108
this.log = log;

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public class DockerApi {
8787
* Create a new {@link DockerApi} instance.
8888
*/
8989
public DockerApi() {
90-
this(HttpTransport.create(null));
90+
this(HttpTransport.create(null), DockerLogger.toSystemOut());
9191
}
9292

9393
/**
@@ -96,21 +96,34 @@ public DockerApi() {
9696
* @since 2.4.0
9797
*/
9898
public DockerApi(DockerHostConfiguration dockerHost) {
99-
this(HttpTransport.create(dockerHost));
99+
this(HttpTransport.create(dockerHost), DockerLogger.toSystemOut());
100+
}
101+
102+
/**
103+
* Create a new {@link DockerApi} instance.
104+
* @param dockerHost the Docker daemon host information
105+
* @param logger a logger used to record output
106+
* @since 3.5.0
107+
*/
108+
public DockerApi(DockerHostConfiguration dockerHost, DockerLogger logger) {
109+
this(HttpTransport.create(dockerHost), logger);
100110
}
101111

102112
/**
103113
* Create a new {@link DockerApi} instance backed by a specific {@link HttpTransport}
104114
* implementation.
105115
* @param http the http implementation
116+
* @param logger a logger used to record output
106117
*/
107-
DockerApi(HttpTransport http) {
118+
DockerApi(HttpTransport http, DockerLogger logger) {
119+
Assert.notNull(http, "'http' must not be null");
120+
Assert.notNull(logger, "'logger' must not be null");
108121
this.http = http;
109122
this.jsonStream = new JsonStream(SharedObjectMapper.get());
110123
this.image = new ImageApi();
111124
this.container = new ContainerApi();
112125
this.volume = new VolumeApi();
113-
this.system = new SystemApi();
126+
this.system = new SystemApi(logger);
114127
}
115128

116129
private HttpTransport http() {
@@ -485,7 +498,10 @@ public void delete(VolumeName name, boolean force) throws IOException {
485498
*/
486499
class SystemApi {
487500

488-
SystemApi() {
501+
private final DockerLogger logger;
502+
503+
SystemApi(DockerLogger logger) {
504+
this.logger = logger;
489505
}
490506

491507
/**
@@ -502,6 +518,7 @@ ApiVersion getApiVersion() {
502518
}
503519
}
504520
catch (Exception ex) {
521+
this.logger.log("Warning: Failed to determine Docker API version: " + ex.getMessage());
505522
// fall through to return default value
506523
}
507524
return UNKNOWN_API_VERSION;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.buildpack.platform.docker;
18+
19+
import java.io.PrintStream;
20+
21+
/**
22+
* Callback interface used to provide {@link DockerApi} output logging.
23+
*
24+
* @author Dmytro Nosan
25+
* @since 3.5.0
26+
* @see #toSystemOut()
27+
*/
28+
public interface DockerLogger {
29+
30+
/**
31+
* Logs a given message.
32+
* @param message the message to log
33+
*/
34+
void log(String message);
35+
36+
/**
37+
* Factory method that returns a {@link DockerLogger} the outputs to
38+
* {@link System#out}.
39+
* @return {@link DockerLogger} instance that logs to system out
40+
*/
41+
static DockerLogger toSystemOut() {
42+
return to(System.out);
43+
}
44+
45+
/**
46+
* Factory method that returns a {@link DockerLogger} the outputs to a given
47+
* {@link PrintStream}.
48+
* @param out the print stream used to output the log
49+
* @return {@link DockerLogger} instance that logs to the given print stream
50+
*/
51+
static DockerLogger to(PrintStream out) {
52+
return new PrintStreamDockerLogger(out);
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.buildpack.platform.docker;
18+
19+
import java.io.PrintStream;
20+
21+
import org.springframework.util.Assert;
22+
23+
/**
24+
* {@link DockerLogger} implementation that prints output to a {@link PrintStream}.
25+
*
26+
* @author Dmytro Nosan
27+
*/
28+
class PrintStreamDockerLogger implements DockerLogger {
29+
30+
private final PrintStream stream;
31+
32+
PrintStreamDockerLogger(PrintStream stream) {
33+
Assert.notNull(stream, "'stream' must not be null");
34+
this.stream = stream;
35+
}
36+
37+
@Override
38+
public void log(String message) {
39+
this.stream.println(message);
40+
}
41+
42+
}

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/DockerApiTests.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
import org.springframework.boot.buildpack.platform.io.IOConsumer;
6060
import org.springframework.boot.buildpack.platform.io.Owner;
6161
import org.springframework.boot.buildpack.platform.io.TarArchive;
62+
import org.springframework.boot.testsupport.system.CapturedOutput;
63+
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
6264
import org.springframework.util.LinkedMultiValueMap;
6365
import org.springframework.util.MultiValueMap;
6466

@@ -82,7 +84,7 @@
8284
* @author Rafael Ceccone
8385
* @author Moritz Halbritter
8486
*/
85-
@ExtendWith(MockitoExtension.class)
87+
@ExtendWith({ MockitoExtension.class, OutputCaptureExtension.class })
8688
class DockerApiTests {
8789

8890
private static final String API_URL = "/v" + DockerApi.API_VERSION;
@@ -108,7 +110,7 @@ class DockerApiTests {
108110

109111
@BeforeEach
110112
void setup() {
111-
this.dockerApi = new DockerApi(this.http);
113+
this.dockerApi = new DockerApi(this.http, DockerLogger.toSystemOut());
112114
}
113115

114116
private HttpTransport http() {
@@ -732,9 +734,10 @@ void getApiVersionWithNoVersionHeaderReturnsUnknownVersion() throws Exception {
732734
}
733735

734736
@Test
735-
void getApiVersionWithExceptionReturnsUnknownVersion() throws Exception {
737+
void getApiVersionWithExceptionReturnsUnknownVersion(CapturedOutput output) throws Exception {
736738
given(http().head(eq(new URI(PING_URL)))).willThrow(new IOException("simulated error"));
737739
assertThat(this.api.getApiVersion()).isEqualTo(DockerApi.UNKNOWN_API_VERSION);
740+
assertThat(output).contains("Warning: Failed to determine Docker API version: simulated error");
738741
}
739742

740743
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.buildpack.platform.docker;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import org.springframework.boot.testsupport.system.CapturedOutput;
23+
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/**
28+
* Tests for {@link DockerLogger}.
29+
*
30+
* @author Dmytro nosan
31+
*/
32+
@ExtendWith(OutputCaptureExtension.class)
33+
class DockerLoggerTests {
34+
35+
@Test
36+
void toSystemOutPrintsToSystemOut(CapturedOutput output) {
37+
DockerLogger logger = DockerLogger.toSystemOut();
38+
logger.log("Hello world");
39+
assertThat(output.getErr()).isEmpty();
40+
assertThat(output.getOut()).contains("Hello world");
41+
}
42+
43+
@Test
44+
void toPrintsToOutput(CapturedOutput output) {
45+
DockerLogger logger = DockerLogger.to(System.err);
46+
logger.log("Hello world");
47+
assertThat(output.getOut()).isEmpty();
48+
assertThat(output.getErr()).contains("Hello world");
49+
}
50+
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.buildpack.platform.docker;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.PrintStream;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
/**
27+
* Tests for {@link PrintStreamDockerLogger}.
28+
*
29+
* @author Dmytro Nosan
30+
*/
31+
class PrintStreamDockerLoggerTests {
32+
33+
@Test
34+
void printsExpectedOutput() {
35+
TestPrintStream stream = new TestPrintStream();
36+
PrintStreamDockerLogger logger = new PrintStreamDockerLogger(stream);
37+
logger.log("Some message");
38+
logger.log("Some message1");
39+
assertThat(stream.toString()).isEqualTo(String.format("Some message%nSome message1%n"));
40+
}
41+
42+
static class TestPrintStream extends PrintStream {
43+
44+
TestPrintStream() {
45+
super(new ByteArrayOutputStream());
46+
}
47+
48+
@Override
49+
public String toString() {
50+
return this.out.toString();
51+
}
52+
53+
}
54+
55+
}

spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@
4040
import org.springframework.boot.buildpack.platform.build.Builder;
4141
import org.springframework.boot.buildpack.platform.build.Creator;
4242
import org.springframework.boot.buildpack.platform.build.PullPolicy;
43+
import org.springframework.boot.buildpack.platform.docker.DockerLogger;
4344
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
4445
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
4546
import org.springframework.boot.buildpack.platform.io.Owner;
@@ -265,7 +266,8 @@ private void buildImage() throws MojoExecutionException {
265266
DockerConfiguration dockerConfiguration = (this.docker != null)
266267
? this.docker.asDockerConfiguration(request.isPublish())
267268
: new Docker().asDockerConfiguration(request.isPublish());
268-
Builder builder = new Builder(new MojoBuildLog(this::getLog), dockerConfiguration);
269+
Builder builder = new Builder(new MojoBuildLog(this::getLog), new MojoDockerLogger(this::getLog),
270+
dockerConfiguration);
269271
builder.build(request);
270272
}
271273
catch (IOException ex) {
@@ -364,6 +366,21 @@ private BuildRequest customizeCreator(BuildRequest request) {
364366
return request;
365367
}
366368

369+
private static class MojoDockerLogger implements DockerLogger {
370+
371+
private final Supplier<Log> log;
372+
373+
MojoDockerLogger(Supplier<Log> log) {
374+
this.log = log;
375+
}
376+
377+
@Override
378+
public void log(String message) {
379+
this.log.get().info(message);
380+
}
381+
382+
}
383+
367384
/**
368385
* {@link BuildLog} backed by Mojo logging.
369386
*/

0 commit comments

Comments
 (0)