mappings
描述全部的 URI 路径,以及它们和控制器的映射关系
启动示例项目,访问:http://localhost:8080/actuator/mappings
返回部分信息如下:
{ "/**/favicon.ico": { "bean": "faviconHandlerMapping" }, "{[/hello]}": { "bean": "requestMappingHandlerMapping", "method": "public java.lang.String com.neo.controller.HelloController.index()" }, "{[/error]}": { "bean": "requestMappingHandlerMapping", "method": "public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)" } }
threaddump
/threaddump
接口会生成当前线程活动的快照。这个功能非常好,方便我们在日常定位问题的时候查看线程的情况。 主要展示了线程名、线程ID、线程的状态、是否等待锁资源等信息。
启动示例项目,访问:http://localhost:8080/actuator/threaddump
返回部分信息如下:
[ { "threadName": "http-nio-8088-exec-6", "threadId": 49, "blockedTime": -1, "blockedCount": 0, "waitedTime": -1, "waitedCount": 2, "lockName": "java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@1630a501", "lockOwnerId": -1, "lockOwnerName": null, "inNative": false, "suspended": false, "threadState": "WAITING", "stackTrace": [ { "methodName": "park", "fileName": "Unsafe.java", "lineNumber": -2, "className": "sun.misc.Unsafe", "nativeMethod": true }, ... { "methodName": "run", "fileName": "TaskThread.java", "lineNumber": 61, "className": "org.apache.tomcat.util.threads.TaskThread$WrappingRunnable", "nativeMethod": false } ... ], "lockInfo": { "className": "java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject", "identityHashCode": 372286721 } } ... ]
生产出现问题的时候,可以通过应用的线程快照来检测应用正在执行的任务。
loggers 端点
访问 http://localhost:8080/actuator/loggers
可以查看当前应用的日志级别等信息:
这里面本身并不特别,但是有一个功能却非常有用,比如我们生产环境日志级别一般都是 info,但是现在有一个 bug 通过 info 级别无法排查,那么我们就可以临时修改 log 级别。
比如上图中的 ROOT 节点是 info 级别,那么我们可以通过 postman 等工具来发一个 post 请求修改日志级别。
修改之后就会发现,日志由原来的 info 变成了 debug:
metrics 端点
metrics 是一个非常重要的监控端点,其监控内容覆盖了 JVM 内存、堆、类加载、处理器和 tomcat 容器等一些重要指标:
可以看到这里面包含了非常多的指标,任意访问一个指标就可以查看对应的指标信息:
自定义Endpoint
自定义配置来控制是否开启过滤
actuator: filter: switch: false
自定义监控端点常用注解
自定义一个监控端点主要有如下常用注解:
@Endpoint:定义一个监控端点,同时支持 HTTP 和 JMX 两种方式。
@WebEndpoint:定义一个监控端点,只支持 HTTP 方式。
@JmxEndpoint:定义一个监控端点,只支持 JMX 方式。
以上三个注解作用在类上,表示当前类是一个监控端点,另外还有一些注解会用在方法和参数上:
@ReadOperation:作用在方法上,可用来返回端点展示的信息(通过 Get 方法请求)。
@WriteOperation:作用在方法上,可用来修改端点展示的信息(通过 Post 方法请求)。
@DeleteOperation:作用在方法上,可用来删除对应端点信息(通过 Delete 方法请求)。
@Selector:作用在参数上,用来定位一个端点的具体指标路由。
一般情况下,是没必要自定义Endpoint的,但是也不排除特殊情况,我这里自定义一个Endpoint,用来往request里放一个user对象,这个user是用来做测试的,用于下面突破filter用的(下面再说),这里先说怎么增查这个user。
过程如下:
使用@Endpoint注解相应的类,作为Actuator的一个endpoint。注解要指定id,这个id作为访问路径,比如这里是/actuator/super;
@ReadOperation来注解查询接口,如果要根据路径做查询,要用@Selector注解方法参数;注意这地方是@Selector String arg0,这个arg0不能改变,改成其他的,开放出去的接口还是/{arg0},这就导致你的方法无法正常获取参数值。
@WriteOperation 来注解修改接口,注意请求数据必须是json,而且参数不像controller中那么灵活,不能将实体作为参数,要把实体中相应的属性拿出来做参数。
这里在增加用户时,往request里放一个user对象。
SuperEndPoint :
package com.cff.springbootwork.actuator.endpoint; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @Endpoint(id = "super") public class SuperEndPoint { private Map<String, SuperUser> users = new ConcurrentHashMap<>(); @ReadOperation public Set<String> users() { return users.keySet(); } @ReadOperation public SuperUser usersIdentify(@Selector String arg0) { return users.get(arg0); } @WriteOperation public Set<String> set(String userName, String passwd) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); if (request != null) { SuperUser superUser = new SuperUser(); superUser.setUserName(userName); superUser.setPasswd(passwd); request.getSession().setAttribute("superUser", superUser); users.put(superUser.getUserName(), superUser); } return users.keySet(); } public static class SuperUser { private String userName; private String passwd; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } } }
还要将Endpoint注册为bean
MvcEndPointConfig:
package com.cff.springbootwork.actuator; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.cff.springbootwork.actuator.endpoint.SuperEndPoint; @Configuration @ServletComponentScan public class MvcEndPointConfig { @Bean @ConditionalOnEnabledEndpoint public SuperEndPoint superEndPoint() { return new SuperEndPoint(); } }
使用Filter对访问actuator做限制
上面已经说了,actuator的接口要做保护,我这里就用filter对接口做最简单的保护。
- 对
/actuator/*下
所有路径做过滤,并用actuator.filter.switch
属性对filter做开关; - 如果时
/actuator/super
路径的post操作,放行它,它将会往request中放一个对象;
- 其他
/actuator/*下
路径要判断request中有没有user对象,没有就返回错误提示。
ActuatorPermissionFilter :
package com.cff.springbootwork.actuator.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import com.fasterxml.jackson.databind.ObjectMapper; @WebFilter(urlPatterns = "/actuator/*", filterName = "actuatorPermissionFilter") @Order(1) // 指定过滤器的执行顺序,值越大越靠后执行 public class ActuatorPermissionFilter implements Filter { private String excludePath = "actuator/super"; @Value("${actuator.filter.switch}") Boolean actuatorSwitch; @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if (actuatorSwitch && !(request.getRequestURI().endsWith(excludePath) && request.getMethod().equals(HttpMethod.POST.toString()))) { Object user = request.getSession().getAttribute("superUser"); if (user == null) { // 未登录,返回数据 ObjectMapper mapper = new ObjectMapper(); response.setStatus(HttpStatus.OK.value()); response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); mapper.writeValue(response.getWriter(), "您没有权限访问该接口,请使用自定义的登录接口设置superUser后使用!"); return; } } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }
Spring Boot Monitor做监控页面
额外引入依赖
<dependency> <groupId>cn.pomit</groupId> <artifactId>spring-boot-monitor</artifactId> <version>0.0.1</version> </dependency>
Spring Boot Monitor是一个对Spring boot admin监控工具做修改并适配单机的监控工具,完美继承了Spring boot admin的风格,直接使用actuator的指标进行显示。
Spring Boot Monitor官网:https://www.pomit.cn/SpringBootMonitor
前面maven依赖中,已经说明依赖spring-boot-monitor,这时,无需其他配置.
访问http://127.0.0.1:8080/monitor
, 自动跳转到Spring Boot Monitor的监控页面。
Spring Boot Monitor的监控页面和Spring boot admin的一模一样,前端的功能也一模一样。
可以对Spring boot的各项指标一目了然,还可以进行简单的操作。
当然,如果Spring boot actuator的指标被限制了,它也拿不到相应的指标了,因为它是直接请求actuator接口的。