commit
3c013322a2
|
@ -20,12 +20,15 @@ import java.io.File;
|
|||
import java.io.FileOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import groovy.text.Template;
|
||||
import groovy.text.TemplateEngine;
|
||||
|
@ -70,6 +73,9 @@ public class EndpointDocumentation {
|
|||
|
||||
static final File LOG_FILE = new File("target/logs/spring.log");
|
||||
|
||||
private static final Set<String> SKIPPED = Collections.<String>unmodifiableSet(
|
||||
new HashSet<String>(Arrays.asList("/docs", "/logfile", "/heapdump")));
|
||||
|
||||
@Autowired
|
||||
private MvcEndpoints mvcEndpoints;
|
||||
|
||||
|
@ -103,28 +109,27 @@ public class EndpointDocumentation {
|
|||
|
||||
@Test
|
||||
public void endpoints() throws Exception {
|
||||
|
||||
final File docs = new File("src/main/asciidoc");
|
||||
|
||||
final Map<String, Object> model = new LinkedHashMap<String, Object>();
|
||||
final List<EndpointDoc> endpoints = new ArrayList<EndpointDoc>();
|
||||
model.put("endpoints", endpoints);
|
||||
for (MvcEndpoint endpoint : getEndpoints()) {
|
||||
final String endpointPath = StringUtils.hasText(endpoint.getPath())
|
||||
? endpoint.getPath() : "/";
|
||||
|
||||
if (!endpointPath.equals("/docs") && !endpointPath.equals("/logfile")) {
|
||||
final String endpointPath = (StringUtils.hasText(endpoint.getPath())
|
||||
? endpoint.getPath() : "/");
|
||||
if (!SKIPPED.contains(endpointPath)) {
|
||||
String output = endpointPath.substring(1);
|
||||
output = output.length() > 0 ? output : "./";
|
||||
this.mockMvc.perform(get(endpointPath).accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk()).andDo(document(output))
|
||||
.andDo(new ResultHandler() {
|
||||
|
||||
@Override
|
||||
public void handle(MvcResult mvcResult) throws Exception {
|
||||
EndpointDoc endpoint = new EndpointDoc(docs,
|
||||
endpointPath);
|
||||
endpoints.add(endpoint);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
|
|||
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.LogFileMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
|
||||
|
@ -132,6 +133,13 @@ public class EndpointWebMvcManagementContextConfiguration {
|
|||
return new EnvironmentMvcEndpoint(delegate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnEnabledEndpoint("heapdump")
|
||||
public HeapdumpMvcEndpoint heapdumpMvcEndpoint() {
|
||||
return new HeapdumpMvcEndpoint();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
@ConditionalOnEnabledEndpoint("health")
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://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.actuate.endpoint.mvc;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointProperties;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link MvcEndpoint} implementations without a backing
|
||||
* {@link Endpoint}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Lari Hotari
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public abstract class AbstractMvcEndpoint extends WebMvcConfigurerAdapter
|
||||
implements MvcEndpoint, EnvironmentAware {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Endpoint URL path.
|
||||
*/
|
||||
@NotNull
|
||||
@Pattern(regexp = "/.*|^$", message = "Path must start with /")
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* Enable the endpoint.
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* Mark if the endpoint exposes sensitive information.
|
||||
*/
|
||||
private Boolean sensitive;
|
||||
|
||||
private final boolean sensitiveDefault;
|
||||
|
||||
public AbstractMvcEndpoint(String path, boolean sensitive) {
|
||||
this.path = path;
|
||||
this.sensitiveDefault = sensitive;
|
||||
}
|
||||
|
||||
public AbstractMvcEndpoint(String path, boolean sensitive, boolean enabled) {
|
||||
this.path = path;
|
||||
this.sensitiveDefault = sensitive;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
protected final Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return EndpointProperties.isEnabled(this.environment, this.enabled);
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return EndpointProperties.isSensitive(this.environment, this.sensitive,
|
||||
this.sensitiveDefault);
|
||||
}
|
||||
|
||||
public void setSensitive(Boolean sensitive) {
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Class<? extends Endpoint> getEndpointType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,15 +16,10 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint.mvc;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* {@link MvcEndpoint} to expose actuator documentation.
|
||||
|
@ -33,95 +28,42 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
|
|||
* @since 1.3.0
|
||||
*/
|
||||
@ConfigurationProperties("endpoints.docs")
|
||||
public class DocsMvcEndpoint extends WebMvcConfigurerAdapter
|
||||
implements MvcEndpoint, EnvironmentAware {
|
||||
public class DocsMvcEndpoint extends AbstractMvcEndpoint {
|
||||
|
||||
private static final String DOCS_LOCATION = "classpath:/META-INF/resources/spring-boot-actuator/docs/";
|
||||
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Endpoint URL path.
|
||||
*/
|
||||
private String path = "/docs";
|
||||
|
||||
/**
|
||||
* Enable the endpoint.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Mark if the endpoint exposes sensitive information.
|
||||
*/
|
||||
private Boolean sensitive;
|
||||
|
||||
private final ManagementServletContext managementServletContext;
|
||||
|
||||
private Curies curies = new Curies();
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public Curies getCuries() {
|
||||
return this.curies;
|
||||
}
|
||||
|
||||
public DocsMvcEndpoint(ManagementServletContext managementServletContext) {
|
||||
super("/docs", false);
|
||||
this.managementServletContext = managementServletContext;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
|
||||
public String browse() {
|
||||
return "forward:" + this.managementServletContext.getContextPath() + this.path
|
||||
return "forward:" + this.managementServletContext.getContextPath() + getPath()
|
||||
+ "/index.html";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "", produces = MediaType.TEXT_HTML_VALUE)
|
||||
public String redirect() {
|
||||
return "redirect:" + this.managementServletContext.getContextPath() + this.path
|
||||
return "redirect:" + this.managementServletContext.getContextPath() + getPath()
|
||||
+ "/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler(
|
||||
this.managementServletContext.getContextPath() + this.path + "/**")
|
||||
this.managementServletContext.getContextPath() + getPath() + "/**")
|
||||
.addResourceLocations(DOCS_LOCATION);
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return EndpointProperties.isSensitive(this.environment, this.sensitive, false);
|
||||
}
|
||||
|
||||
public void setSensitive(Boolean sensitive) {
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Endpoint<?>> getEndpointType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties of the default CurieProvider (used for adding docs links). If enabled,
|
||||
* all unqualified rels will pick up a prefix and a curie template pointing to the
|
||||
|
|
|
@ -16,20 +16,12 @@
|
|||
|
||||
package org.springframework.boot.actuate.endpoint.mvc;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* {@link MvcEndpoint} to expose HAL-formatted JSON.
|
||||
|
@ -40,41 +32,16 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
|
|||
* @since 1.3.0
|
||||
*/
|
||||
@ConfigurationProperties("endpoints.actuator")
|
||||
public class HalJsonMvcEndpoint extends WebMvcConfigurerAdapter
|
||||
implements MvcEndpoint, EnvironmentAware {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Endpoint URL path.
|
||||
*/
|
||||
@NotNull
|
||||
@Pattern(regexp = "^$|/.*", message = "Path must be empty or start with /")
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* Enable the endpoint.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Mark if the endpoint exposes sensitive information.
|
||||
*/
|
||||
private Boolean sensitive;
|
||||
|
||||
public class HalJsonMvcEndpoint extends AbstractMvcEndpoint {
|
||||
private final ManagementServletContext managementServletContext;
|
||||
|
||||
public HalJsonMvcEndpoint(ManagementServletContext managementServletContext) {
|
||||
super(getDefaultPath(managementServletContext), false);
|
||||
this.managementServletContext = managementServletContext;
|
||||
this.path = getDefaultPath(managementServletContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
private String getDefaultPath(ManagementServletContext managementServletContext) {
|
||||
private static String getDefaultPath(
|
||||
ManagementServletContext managementServletContext) {
|
||||
if (StringUtils.hasText(managementServletContext.getContextPath())) {
|
||||
return "";
|
||||
}
|
||||
|
@ -87,39 +54,7 @@ public class HalJsonMvcEndpoint extends WebMvcConfigurerAdapter
|
|||
return new ResourceSupport();
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return EndpointProperties.isSensitive(this.environment, this.sensitive, false);
|
||||
}
|
||||
|
||||
public void setSensitive(Boolean sensitive) {
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Endpoint<?>> getEndpointType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected final ManagementServletContext getManagementServletContext() {
|
||||
return this.managementServletContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://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.actuate.endpoint.mvc;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.PlatformManagedObject;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.UsesJava7;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* {@link MvcEndpoint} to expose heap dumps.
|
||||
*
|
||||
* @author Lari Hotari
|
||||
* @author Phillip Webb
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@ConfigurationProperties("endpoints.heapdump")
|
||||
@HypermediaDisabled
|
||||
public class HeapdumpMvcEndpoint extends AbstractMvcEndpoint implements MvcEndpoint {
|
||||
|
||||
private final long timeout;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
private HeapDumper heapDumper;
|
||||
|
||||
public HeapdumpMvcEndpoint() {
|
||||
this(TimeUnit.SECONDS.toMillis(10));
|
||||
}
|
||||
|
||||
protected HeapdumpMvcEndpoint(long timeout) {
|
||||
super("/heapdump", true);
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
public void invoke(@RequestParam(defaultValue = "true") boolean live,
|
||||
HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException, ServletException {
|
||||
if (!isEnabled()) {
|
||||
response.setStatus(HttpStatus.NOT_FOUND.value());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (this.lock.tryLock(this.timeout, TimeUnit.MILLISECONDS)) {
|
||||
try {
|
||||
dumpHeap(live, request, response);
|
||||
return;
|
||||
}
|
||||
finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
// Ignore
|
||||
}
|
||||
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
|
||||
}
|
||||
|
||||
private void dumpHeap(boolean live, HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws IOException, ServletException, InterruptedException {
|
||||
if (this.heapDumper == null) {
|
||||
this.heapDumper = createHeapDumper();
|
||||
}
|
||||
File file = createTempFile(live);
|
||||
try {
|
||||
this.heapDumper.dumpHeap(file, live);
|
||||
handle(file, request, response);
|
||||
}
|
||||
finally {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private File createTempFile(boolean live) throws IOException {
|
||||
String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm").format(new Date());
|
||||
File file = File.createTempFile("heapdump" + date + (live ? "-live" : ""),
|
||||
".hprof");
|
||||
file.delete();
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method used to create the {@link HeapDumper}.
|
||||
* @return the heap dumper to use
|
||||
* @throws HeapDumperUnavailableException if the heap dumper cannot be created
|
||||
*/
|
||||
protected HeapDumper createHeapDumper() throws HeapDumperUnavailableException {
|
||||
return new HotSpotDiagnosticMXBeanHeapDumper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the heap dump file and respond. By default this method will return the
|
||||
* response as a GZip stream.
|
||||
* @param heapDumpFile the generated dump file
|
||||
* @param request the HTTP request
|
||||
* @param response the HTTP response
|
||||
* @throws ServletException on servlet error
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
protected void handle(File heapDumpFile, HttpServletRequest request,
|
||||
HttpServletResponse response) throws ServletException, IOException {
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment; filename=\"" + (heapDumpFile.getName() + ".gz") + "\"");
|
||||
try {
|
||||
InputStream in = new FileInputStream(heapDumpFile);
|
||||
try {
|
||||
GZIPOutputStream out = new GZIPOutputStream(response.getOutputStream());
|
||||
StreamUtils.copy(in, out);
|
||||
out.finish();
|
||||
}
|
||||
catch (NullPointerException ex) {
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
in.close();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy interface used to dump the heap to a file.
|
||||
*/
|
||||
protected interface HeapDumper {
|
||||
|
||||
/**
|
||||
* Dump the current heap to the specified file.
|
||||
* @param file the file to dump the heap to
|
||||
* @param live if only <em>live</em> objects (i.e. objects that are reachable from
|
||||
* others) should be dumped
|
||||
* @throws IOException on IO error
|
||||
* @throws InterruptedException on thread interruption
|
||||
*/
|
||||
void dumpHeap(File file, boolean live) throws IOException, InterruptedException;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HeapDumper} that uses {@code com.sun.management.HotSpotDiagnosticMXBean}
|
||||
* available on Oracle and OpenJDK to dump the heap to a file.
|
||||
*/
|
||||
@UsesJava7
|
||||
protected static class HotSpotDiagnosticMXBeanHeapDumper implements HeapDumper {
|
||||
|
||||
private Object diagnosticMXBean;
|
||||
|
||||
private Method dumpHeapMethod;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected HotSpotDiagnosticMXBeanHeapDumper() {
|
||||
try {
|
||||
Class<?> diagnosticMXBeanClass = ClassUtils.resolveClassName(
|
||||
"com.sun.management.HotSpotDiagnosticMXBean", null);
|
||||
this.diagnosticMXBean = ManagementFactory.getPlatformMXBean(
|
||||
(Class<PlatformManagedObject>) diagnosticMXBeanClass);
|
||||
this.dumpHeapMethod = ReflectionUtils.findMethod(diagnosticMXBeanClass,
|
||||
"dumpHeap", String.class, Boolean.TYPE);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new HeapDumperUnavailableException(
|
||||
"Unable to locate HotSpotDiagnosticMXBean", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpHeap(File file, boolean live) {
|
||||
ReflectionUtils.invokeMethod(this.dumpHeapMethod, this.diagnosticMXBean,
|
||||
file.getAbsolutePath(), live);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception to be thrown if the {@link HeapDumper} cannot be created.
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
protected static class HeapDumperUnavailableException extends RuntimeException {
|
||||
|
||||
public HeapDumperUnavailableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,20 +22,14 @@ import javax.servlet.ServletContext;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import org.jolokia.http.AgentServlet;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.ServletContextAware;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
@ -50,31 +44,12 @@ import org.springframework.web.util.UrlPathHelper;
|
|||
*/
|
||||
@ConfigurationProperties(prefix = "endpoints.jolokia", ignoreUnknownFields = false)
|
||||
@HypermediaDisabled
|
||||
public class JolokiaMvcEndpoint implements MvcEndpoint, InitializingBean,
|
||||
ApplicationContextAware, ServletContextAware, EnvironmentAware {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Endpoint URL path.
|
||||
*/
|
||||
@NotNull
|
||||
@Pattern(regexp = "/.*", message = "Path must start with /")
|
||||
private String path = "/jolokia";
|
||||
|
||||
/**
|
||||
* Enable the endpoint.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Mark if the endpoint exposes sensitive information.
|
||||
*/
|
||||
private Boolean sensitive;
|
||||
|
||||
public class JolokiaMvcEndpoint extends AbstractMvcEndpoint
|
||||
implements InitializingBean, ApplicationContextAware, ServletContextAware {
|
||||
private final ServletWrappingController controller = new ServletWrappingController();
|
||||
|
||||
public JolokiaMvcEndpoint() {
|
||||
super("/jolokia", true);
|
||||
this.controller.setServletClass(AgentServlet.class);
|
||||
this.controller.setServletName("jolokia");
|
||||
}
|
||||
|
@ -99,43 +74,6 @@ public class JolokiaMvcEndpoint implements MvcEndpoint, InitializingBean,
|
|||
this.controller.setApplicationContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return EndpointProperties.isSensitive(this.environment, this.sensitive, true);
|
||||
}
|
||||
|
||||
public void setSensitive(Boolean sensitive) {
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Class<? extends Endpoint> getEndpointType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping("/**")
|
||||
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
|
|
@ -22,18 +22,12 @@ import java.io.IOException;
|
|||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.logging.LogFile;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -52,64 +46,18 @@ import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
|
|||
* @since 1.3.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "endpoints.logfile")
|
||||
public class LogFileMvcEndpoint implements MvcEndpoint, EnvironmentAware {
|
||||
public class LogFileMvcEndpoint extends AbstractMvcEndpoint {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(LogFileMvcEndpoint.class);
|
||||
|
||||
/**
|
||||
* Endpoint URL path.
|
||||
*/
|
||||
@NotNull
|
||||
@Pattern(regexp = "/.*", message = "Path must start with /")
|
||||
private String path = "/logfile";
|
||||
|
||||
/**
|
||||
* Enable the endpoint.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Mark if the endpoint exposes sensitive information.
|
||||
*/
|
||||
private Boolean sensitive;
|
||||
|
||||
/**
|
||||
* External Logfile to be accessed. Can be used if the logfile is written by output
|
||||
* redirect and not by the logging-system itself.
|
||||
*/
|
||||
private File externalFile;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSensitive() {
|
||||
return EndpointProperties.isSensitive(this.environment, this.sensitive, true);
|
||||
}
|
||||
|
||||
public void setSensitive(Boolean sensitive) {
|
||||
this.sensitive = sensitive;
|
||||
public LogFileMvcEndpoint() {
|
||||
super("/logfile", true);
|
||||
}
|
||||
|
||||
public File getExternalFile() {
|
||||
|
@ -120,12 +68,6 @@ public class LogFileMvcEndpoint implements MvcEndpoint, EnvironmentAware {
|
|||
this.externalFile = externalFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Class<? extends Endpoint> getEndpointType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequestMapping(method = { RequestMethod.GET, RequestMethod.HEAD })
|
||||
public void invoke(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
@ -147,7 +89,7 @@ public class LogFileMvcEndpoint implements MvcEndpoint, EnvironmentAware {
|
|||
if (this.externalFile != null) {
|
||||
return new FileSystemResource(this.externalFile);
|
||||
}
|
||||
LogFile logFile = LogFile.get(this.environment);
|
||||
LogFile logFile = LogFile.get(getEnvironment());
|
||||
if (logFile == null) {
|
||||
logger.debug("Missing 'logging.file' or 'logging.path' properties");
|
||||
return null;
|
||||
|
|
|
@ -429,8 +429,8 @@ public class EndpointWebMvcAutoConfigurationTests {
|
|||
this.applicationContext.register(RootConfig.class, BaseConfiguration.class,
|
||||
ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class);
|
||||
this.applicationContext.refresh();
|
||||
// /health, /metrics, /env, /actuator (/shutdown is disabled by default)
|
||||
assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).hasSize(4);
|
||||
// /health, /metrics, /env, /actuator, /heapdump (/shutdown is disabled by default)
|
||||
assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class)).hasSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -83,7 +83,7 @@ public class HalBrowserMvcEndpointDisabledIntegrationTests {
|
|||
public void endpointsDoNotHaveLinks() throws Exception {
|
||||
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
|
||||
String path = endpoint.getPath();
|
||||
if ("/actuator".equals(path)) {
|
||||
if ("/actuator".equals(path) || endpoint instanceof HeapdumpMvcEndpoint) {
|
||||
continue;
|
||||
}
|
||||
path = path.length() > 0 ? path : "/";
|
||||
|
|
|
@ -122,7 +122,7 @@ public class HalBrowserMvcEndpointVanillaIntegrationTests {
|
|||
@Test
|
||||
public void endpointsEachHaveSelf() throws Exception {
|
||||
Set<String> collections = new HashSet<String>(
|
||||
Arrays.asList("/trace", "/beans", "/dump"));
|
||||
Arrays.asList("/trace", "/beans", "/dump", "/heapdump"));
|
||||
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
|
||||
String path = endpoint.getPath();
|
||||
if (collections.contains(path)) {
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright 2012-2016 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
|
||||
*
|
||||
* http://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.actuate.endpoint.mvc;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.fusesource.hawtbuf.ByteArrayInputStream;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link HeapdumpMvcEndpoint}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class HeapdumpMvcEndpointTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
private TestHeapdumpMvcEndpoint endpoint;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.context.getBean(HeapdumpMvcEndpoint.class).setEnabled(true);
|
||||
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
|
||||
}
|
||||
|
||||
@After
|
||||
public void reset() {
|
||||
this.endpoint.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWhenDisabledShouldReturnNotFoundStatus() throws Exception {
|
||||
this.endpoint.setEnabled(false);
|
||||
this.mvc.perform(get("/heapdump")).andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWhenNotAvailableShouldReturnServiceUnavailableStatus()
|
||||
throws Exception {
|
||||
this.endpoint.setAvailable(false);
|
||||
this.mvc.perform(get("/heapdump")).andExpect(status().isServiceUnavailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWhenLockedShouldReturnTooManyRequestsStatus() throws Exception {
|
||||
this.endpoint.setLocked(true);
|
||||
this.mvc.perform(get("/heapdump")).andExpect(status().isTooManyRequests());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeShouldReturnGzipContent() throws Exception {
|
||||
MvcResult result = this.mvc.perform(get("/heapdump")).andExpect(status().isOk())
|
||||
.andReturn();
|
||||
byte[] bytes = result.getResponse().getContentAsByteArray();
|
||||
GZIPInputStream stream = new GZIPInputStream(new ByteArrayInputStream(bytes));
|
||||
byte[] uncompressed = FileCopyUtils.copyToByteArray(stream);
|
||||
assertThat(uncompressed).isEqualTo("HEAPDUMP".getBytes());
|
||||
}
|
||||
|
||||
@Import({ JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
EndpointWebMvcAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ManagementServerPropertiesAutoConfiguration.class })
|
||||
@Configuration
|
||||
public static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public HeapdumpMvcEndpoint endpoint() {
|
||||
return new TestHeapdumpMvcEndpoint();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestHeapdumpMvcEndpoint extends HeapdumpMvcEndpoint {
|
||||
|
||||
private boolean available;
|
||||
|
||||
private boolean locked;
|
||||
|
||||
private String heapDump;
|
||||
|
||||
TestHeapdumpMvcEndpoint() {
|
||||
super(TimeUnit.SECONDS.toMillis(1));
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.available = true;
|
||||
this.locked = false;
|
||||
this.heapDump = "HEAPDUMP";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HeapDumper createHeapDumper() {
|
||||
return new HeapDumper() {
|
||||
|
||||
@Override
|
||||
public void dumpHeap(File file, boolean live)
|
||||
throws IOException, InterruptedException {
|
||||
if (!TestHeapdumpMvcEndpoint.this.available) {
|
||||
throw new HeapDumperUnavailableException("Not available", null);
|
||||
}
|
||||
if (TestHeapdumpMvcEndpoint.this.locked) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
FileCopyUtils.copy(TestHeapdumpMvcEndpoint.this.heapDump.getBytes(),
|
||||
file);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public void setAvailable(boolean available) {
|
||||
this.available = available;
|
||||
}
|
||||
|
||||
public void setLocked(boolean locked) {
|
||||
this.locked = locked;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -62,7 +62,7 @@ The way that endpoints are exposed will depend on the type of technology that yo
|
|||
Most applications choose HTTP monitoring, where the ID of the endpoint is mapped
|
||||
to a URL. For example, by default, the `health` endpoint will be mapped to `/health`.
|
||||
|
||||
The following endpoints are available:
|
||||
The following technology agnostic endpoints are available:
|
||||
|
||||
[cols="2,5,1"]
|
||||
|===
|
||||
|
@ -86,11 +86,6 @@ HATEOAS to be on the classpath.
|
|||
|Displays a collated list of all `@ConfigurationProperties`.
|
||||
|true
|
||||
|
||||
|`docs`
|
||||
|Displays documentation, including example requests and responses, for the Actuator's
|
||||
endpoints. Requires `spring-boot-actuator-docs` to be on the classpath.
|
||||
|false
|
||||
|
||||
|`dump`
|
||||
|Performs a thread dump.
|
||||
|true
|
||||
|
@ -104,8 +99,9 @@ endpoints. Requires `spring-boot-actuator-docs` to be on the classpath.
|
|||
|true
|
||||
|
||||
|`health`
|
||||
|Shows application health information (when the application is secure, a simple '`status`' when accessed over an
|
||||
unauthenticated connection or full message details when authenticated).
|
||||
|Shows application health information (when the application is secure, a simple '`status`'
|
||||
when accessed over an unauthenticated connection or full message details when
|
||||
authenticated).
|
||||
|false
|
||||
|
||||
|`info`
|
||||
|
@ -116,12 +112,6 @@ unauthenticated connection or full message details when authenticated).
|
|||
|Shows any Liquibase database migrations that have been applied.
|
||||
|true
|
||||
|
||||
|`logfile`
|
||||
|Returns the contents of the logfile (if `logging.file` or `logging.path` properties have
|
||||
been set). Only available via MVC. Supports the use of the HTTP `Range` header to retrieve
|
||||
part of the log file's content.
|
||||
|true
|
||||
|
||||
|`metrics`
|
||||
|Shows '`metrics`' information for the current application.
|
||||
|true
|
||||
|
@ -139,6 +129,32 @@ part of the log file's content.
|
|||
|true
|
||||
|===
|
||||
|
||||
If you are using Spring MVC, the following additional endpoints can also be used:
|
||||
|
||||
[cols="2,5,1"]
|
||||
|===
|
||||
| ID | Description | Sensitive Default
|
||||
|
||||
|`docs`
|
||||
|Displays documentation, including example requests and responses, for the Actuator's
|
||||
endpoints. Requires `spring-boot-actuator-docs` to be on the classpath.
|
||||
|false
|
||||
|
||||
|`heapdump`
|
||||
|Returns a GZip compressed `hprof` heap dump file.
|
||||
|true
|
||||
|
||||
|`jolokia`
|
||||
|Exposes JMX beans over HTTP (when Jolokia is on the classpath).
|
||||
|true
|
||||
|
||||
|`logfile`
|
||||
|Returns the contents of the logfile (if `logging.file` or `logging.path` properties have
|
||||
been set). Supports the use of the HTTP `Range` header to retrieve part of the log file's
|
||||
content.
|
||||
|true
|
||||
|===
|
||||
|
||||
NOTE: Depending on how an endpoint is exposed, the `sensitive` property may be used as
|
||||
a security hint. For example, sensitive endpoints will require a username/password when
|
||||
they are accessed over HTTP (or simply disabled if web security is not enabled).
|
||||
|
|
Loading…
Reference in New Issue