mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Generate console output examples at build time
Closes gh-28208
This commit is contained in:
parent
7c0ccdf78c
commit
6efa77f3f5
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.build.docs;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.Task;
|
||||||
|
import org.gradle.api.file.FileCollection;
|
||||||
|
import org.gradle.api.file.RegularFileProperty;
|
||||||
|
import org.gradle.api.provider.ListProperty;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.tasks.Classpath;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.OutputFile;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
import org.gradle.internal.jvm.Jvm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Task} to run an application for the purpose of capturing its output for
|
||||||
|
* inclusion in the reference documentation.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
*/
|
||||||
|
public class ApplicationRunner extends DefaultTask {
|
||||||
|
|
||||||
|
private final RegularFileProperty output = getProject().getObjects().fileProperty();
|
||||||
|
|
||||||
|
private final ListProperty<String> args = getProject().getObjects().listProperty(String.class);
|
||||||
|
|
||||||
|
private final Property<String> mainClass = getProject().getObjects().property(String.class);
|
||||||
|
|
||||||
|
private final Property<String> expectedLogging = getProject().getObjects().property(String.class);
|
||||||
|
|
||||||
|
private FileCollection classpath;
|
||||||
|
|
||||||
|
@OutputFile
|
||||||
|
public RegularFileProperty getOutput() {
|
||||||
|
return this.output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Classpath
|
||||||
|
public FileCollection getClasspath() {
|
||||||
|
return this.classpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClasspath(FileCollection classpath) {
|
||||||
|
this.classpath = classpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public ListProperty<String> getArgs() {
|
||||||
|
return this.args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public Property<String> getMainClass() {
|
||||||
|
return this.mainClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public Property<String> getExpectedLogging() {
|
||||||
|
return this.expectedLogging;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
void runApplication() throws IOException {
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
File executable = Jvm.current().getExecutable("java");
|
||||||
|
command.add(executable.getAbsolutePath());
|
||||||
|
command.add("-cp");
|
||||||
|
command.add(this.classpath.getFiles().stream().map(File::getAbsolutePath)
|
||||||
|
.collect(Collectors.joining(File.pathSeparator)));
|
||||||
|
command.add(this.mainClass.get());
|
||||||
|
command.addAll(this.args.get());
|
||||||
|
File outputFile = this.output.getAsFile().get();
|
||||||
|
Process process = new ProcessBuilder().redirectOutput(outputFile).redirectError(outputFile).command(command)
|
||||||
|
.start();
|
||||||
|
awaitLogging(process);
|
||||||
|
process.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void awaitLogging(Process process) {
|
||||||
|
long end = System.currentTimeMillis() + 30000;
|
||||||
|
String expectedLogging = this.expectedLogging.get();
|
||||||
|
while (System.currentTimeMillis() < end) {
|
||||||
|
for (String line : outputLines()) {
|
||||||
|
if (line.contains(expectedLogging)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!process.isAlive()) {
|
||||||
|
throw new IllegalStateException("Process exited before '" + expectedLogging + "' was logged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("'" + expectedLogging + "' was not logged within 30 seconds");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> outputLines() {
|
||||||
|
Path outputPath = this.output.get().getAsFile().toPath();
|
||||||
|
try {
|
||||||
|
return Files.readAllLines(outputPath);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new RuntimeException("Failed to read lines of output from '" + outputPath + "'", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -14,6 +14,8 @@ configurations {
|
|||||||
configurationProperties
|
configurationProperties
|
||||||
gradlePluginDocumentation
|
gradlePluginDocumentation
|
||||||
mavenPluginDocumentation
|
mavenPluginDocumentation
|
||||||
|
remoteSpringApplicationExample
|
||||||
|
springApplicationExample
|
||||||
testSlices
|
testSlices
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +163,14 @@ dependencies {
|
|||||||
|
|
||||||
mavenPluginDocumentation(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-maven-plugin", configuration: "documentation"))
|
mavenPluginDocumentation(project(path: ":spring-boot-project:spring-boot-tools:spring-boot-maven-plugin", configuration: "documentation"))
|
||||||
|
|
||||||
|
remoteSpringApplicationExample(platform(project(":spring-boot-project:spring-boot-dependencies")))
|
||||||
|
remoteSpringApplicationExample(project(":spring-boot-project:spring-boot-devtools"))
|
||||||
|
remoteSpringApplicationExample(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-logging"))
|
||||||
|
remoteSpringApplicationExample("org.springframework:spring-web")
|
||||||
|
|
||||||
|
springApplicationExample(platform(project(":spring-boot-project:spring-boot-dependencies")))
|
||||||
|
springApplicationExample(project(path: ":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
|
||||||
|
|
||||||
testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
|
testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
|
||||||
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
||||||
testImplementation("org.assertj:assertj-core")
|
testImplementation("org.assertj:assertj-core")
|
||||||
@ -249,8 +259,35 @@ task documentConfigurationProperties(type: org.springframework.boot.build.contex
|
|||||||
|
|
||||||
task documentDevtoolsPropertyDefaults(type: org.springframework.boot.build.devtools.DocumentDevtoolsPropertyDefaults) {}
|
task documentDevtoolsPropertyDefaults(type: org.springframework.boot.build.devtools.DocumentDevtoolsPropertyDefaults) {}
|
||||||
|
|
||||||
|
task runRemoteSpringApplicationExample(type: org.springframework.boot.build.docs.ApplicationRunner) {
|
||||||
|
classpath = configurations.remoteSpringApplicationExample
|
||||||
|
mainClass = "org.springframework.boot.devtools.RemoteSpringApplication"
|
||||||
|
args = ["https://myapp.example.com", "--spring.devtools.remote.secret=secret", "--spring.devtools.livereload.port=0"]
|
||||||
|
output = file("$buildDir/example-output/remote-spring-application.txt")
|
||||||
|
expectedLogging = "Started RemoteSpringApplication in "
|
||||||
|
}
|
||||||
|
|
||||||
|
task runSpringApplicationExample(type: org.springframework.boot.build.docs.ApplicationRunner) {
|
||||||
|
classpath = configurations.springApplicationExample + sourceSets.main.output
|
||||||
|
mainClass = "org.springframework.boot.docs.features.springapplication.MyApplication"
|
||||||
|
args = ["--server.port=0"]
|
||||||
|
output = file("$buildDir/example-output/spring-application.txt")
|
||||||
|
expectedLogging = "Started MyApplication in "
|
||||||
|
}
|
||||||
|
|
||||||
|
task runLoggingFormatExample(type: org.springframework.boot.build.docs.ApplicationRunner) {
|
||||||
|
classpath = configurations.springApplicationExample + sourceSets.main.output
|
||||||
|
mainClass = "org.springframework.boot.docs.features.springapplication.MyApplication"
|
||||||
|
args = ["--spring.main.banner-mode=off", "--server.port=0"]
|
||||||
|
output = file("$buildDir/example-output/logging-format.txt")
|
||||||
|
expectedLogging = "Started MyApplication in "
|
||||||
|
}
|
||||||
|
|
||||||
tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {
|
tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {
|
||||||
dependsOn dependencyVersions
|
dependsOn dependencyVersions
|
||||||
|
inputs.files(runRemoteSpringApplicationExample).withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
|
inputs.files(runSpringApplicationExample).withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
|
inputs.files(runLoggingFormatExample).withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
asciidoctorj {
|
asciidoctorj {
|
||||||
fatalWarnings = ['^((?!successfully validated).)*$']
|
fatalWarnings = ['^((?!successfully validated).)*$']
|
||||||
}
|
}
|
||||||
@ -280,7 +317,10 @@ tasks.withType(org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask) {
|
|||||||
"spring-kafka-version": versionConstraints["org.springframework.kafka:spring-kafka"],
|
"spring-kafka-version": versionConstraints["org.springframework.kafka:spring-kafka"],
|
||||||
"spring-integration-version": versionConstraints["org.springframework.integration:spring-integration-core"],
|
"spring-integration-version": versionConstraints["org.springframework.integration:spring-integration-core"],
|
||||||
"spring-security-version": securityVersion,
|
"spring-security-version": securityVersion,
|
||||||
"spring-webservices-version": versionConstraints["org.springframework.ws:spring-ws-core"]
|
"spring-webservices-version": versionConstraints["org.springframework.ws:spring-ws-core"],
|
||||||
|
"remote-spring-application-output": runRemoteSpringApplicationExample.outputs.files.singleFile,
|
||||||
|
"spring-application-output": runSpringApplicationExample.outputs.files.singleFile,
|
||||||
|
"logging-format-output": runLoggingFormatExample.outputs.files.singleFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +22,7 @@ The default log output from Spring Boot resembles the following example:
|
|||||||
|
|
||||||
[indent=0]
|
[indent=0]
|
||||||
----
|
----
|
||||||
2019-03-05 10:57:51.112 INFO 45469 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.52
|
include::{logging-format-output}[]
|
||||||
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
|
||||||
2019-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1358 ms
|
|
||||||
2019-03-05 10:57:51.698 INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
|
|
||||||
2019-03-05 10:57:51.702 INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
|
|
||||||
----
|
----
|
||||||
|
|
||||||
The following items are output:
|
The following items are output:
|
||||||
|
@ -12,24 +12,7 @@ When your application starts, you should see something similar to the following
|
|||||||
|
|
||||||
[indent=0,subs="verbatim,attributes"]
|
[indent=0,subs="verbatim,attributes"]
|
||||||
----
|
----
|
||||||
. ____ _ __ _ _
|
include::{spring-application-output}[]
|
||||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
|
||||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
|
||||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
|
||||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
|
||||||
=========|_|==============|___/=/_/_/_/
|
|
||||||
:: Spring Boot :: v{spring-boot-version}
|
|
||||||
|
|
||||||
2021-02-03 10:33:25.224 INFO 17321 --- [ main] o.s.b.d.s.s.SpringApplicationExample : Starting SpringApplicationExample using Java 1.8.0_232 on mycomputer with PID 17321 (/apps/myjar.jar started by pwebb)
|
|
||||||
2021-02-03 10:33:25.226 INFO 17900 --- [ main] o.s.b.d.s.s.SpringApplicationExample : No active profile set, falling back to default profiles: default
|
|
||||||
2021-02-03 10:33:26.046 INFO 17321 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
|
|
||||||
2021-02-03 10:33:26.054 INFO 17900 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
|
|
||||||
2021-02-03 10:33:26.055 INFO 17900 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.41]
|
|
||||||
2021-02-03 10:33:26.097 INFO 17900 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
|
||||||
2021-02-03 10:33:26.097 INFO 17900 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 821 ms
|
|
||||||
2021-02-03 10:33:26.144 INFO 17900 --- [ main] s.tomcat.SampleTomcatApplication : ServletContext initialized
|
|
||||||
2021-02-03 10:33:26.376 INFO 17900 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
|
|
||||||
2021-02-03 10:33:26.384 INFO 17900 --- [ main] o.s.b.d.s.s.SpringApplicationExample : Started SampleTomcatApplication in 1.514 seconds (JVM running for 1.823)
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
|
@ -393,19 +393,7 @@ A running remote client might resemble the following listing:
|
|||||||
|
|
||||||
[indent=0,subs="verbatim,attributes"]
|
[indent=0,subs="verbatim,attributes"]
|
||||||
----
|
----
|
||||||
. ____ _ __ _ _
|
include::{remote-spring-application-output}[]
|
||||||
/\\ / ___'_ __ _ _(_)_ __ __ _ ___ _ \ \ \ \
|
|
||||||
( ( )\___ | '_ | '_| | '_ \/ _` | | _ \___ _ __ ___| |_ ___ \ \ \ \
|
|
||||||
\\/ ___)| |_)| | | | | || (_| []::::::[] / -_) ' \/ _ \ _/ -_) ) ) ) )
|
|
||||||
' |____| .__|_| |_|_| |_\__, | |_|_\___|_|_|_\___/\__\___|/ / / /
|
|
||||||
=========|_|==============|___/===================================/_/_/_/
|
|
||||||
:: Spring Boot Remote :: {spring-boot-version}
|
|
||||||
|
|
||||||
2015-06-10 18:25:06.632 INFO 14938 --- [ main] o.s.b.devtools.RemoteSpringApplication : Starting RemoteSpringApplication on pwmbp with PID 14938 (/Users/pwebb/projects/spring-boot/code/spring-boot-project/spring-boot-devtools/target/classes started by pwebb in /Users/pwebb/projects/spring-boot/code)
|
|
||||||
2015-06-10 18:25:06.671 INFO 14938 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2a17b7b6: startup date [Wed Jun 10 18:25:06 PDT 2015]; root of context hierarchy
|
|
||||||
2015-06-10 18:25:07.043 WARN 14938 --- [ main] o.s.b.d.r.c.RemoteClientConfiguration : The connection to http://localhost:8080 is insecure. You should use a URL starting with 'https://'.
|
|
||||||
2015-06-10 18:25:07.074 INFO 14938 --- [ main] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
|
|
||||||
2015-06-10 18:25:07.130 INFO 14938 --- [ main] o.s.b.devtools.RemoteSpringApplication : Started RemoteSpringApplication in 0.74 seconds (JVM running for 1.105)
|
|
||||||
----
|
----
|
||||||
|
|
||||||
NOTE: Because the remote client is using the same classpath as the real application it can directly read application properties.
|
NOTE: Because the remote client is using the same classpath as the real application it can directly read application properties.
|
||||||
|
Loading…
Reference in New Issue
Block a user