SBA监控包括应用的基本信息、logfile(日志在线实时浏览或者download)、JVM信息(实时线程信息、堆信息、非堆信息)、Web(API接口信息、最近100次API调用的信息)、应用中用户登录信息;监控很全面、齐全,但部分监控指标与自己的项目需要还是有点差别的,比如以下两点:
自定义HttpTrace增加入参和出参结果:
在spring-boot-admin中HttpTrace显示的信息包括session、principal、request、response、timeTaken和timestamp,但session、principal对我无用,request是HttpTrace的内部类显示的信息包括:
privatefinalStringmethod;privatefinalURIuri;privatefinalMapString,ListStringheaders;privatefinalStringremoteAddress;
response也是HttpTrace的内部类显示的信息包括:
privatefinalintstatus;privatefinalMapString,ListStringheaders;
唯一缺少的就是请求的请求入参和响应出参,而Headers的信息对该项目完全无用。所以势必要扩展HttpTrace才行,大致的思路是:自定义Filter--装饰模式转换成自定义的request和response对象,内部获取请求和相应内容--HttpExchangeTracer创建HttpTrace对象--InmemoryHttpTraceRepository保存100次请求的HttpTrace对象,供server端使用。由于Filter中使用的部分对象需要先创建所以我们先从需要的零部件开始:
第一步:包装HttpServletRequest获取请求内容:
publicclassRequestWrapperextsHttpServletRequestWrapper{//存放请求的消息体(先缓存一份)privatebyte[]body;//自定义输入流的包装类,将缓存数据再写入到流中privateServletInputStreamWrapperwrapper;privatefinalLoggerlogger=();publicRequestWrapper(HttpServletRequestrequest){super(request);try{//使用Apache的commons-io工具从request中先读取数据body=(());}catch(IOExceptione){("从请求中获取请求参数出现异常:",e);}//将读取出来的内存再写入流中wrapper=newServletInputStreamWrapper(newByteArrayInputStream(body));}//转换成String供外部调用,并替换转义字符publicStringbody(){returnnewString(body).replaceAll("[\n\t\r]","");}//将我们的自定义的流包装类返回,供系统调用读取数据@OverridepublicServletInputStreamgetInputStream()throwsIOException{;}//将我们的自定义的流包装类返回,供系统调用读取数据@OverridepublicBufferedReadergetReader()throwsIOException{returnnewBufferedReader(newInputStreamReader());}//从给定的输入流中读取数据staticfinalclassServletInputStreamWrapperextsServletInputStream{privateInputStreaminputStream;publicServletInputStreamWrapper(InputStreaminputStream){=inputStream;}@OverridepublicbooleanisFinished(){returntrue;}@OverridepublicbooleanisReady(){returnfalse;}@OverridepublicvoidsetReadListener(ReadListenerlistener){}@Overridepublicintread()throwsIOException{();}publicInputStreamgetInputStream(){returninputStream;}publicvoidsetInputStream(InputStreaminputStream){=inputStream;}}}第二步:包装HttpServletResponse类获取响应内容:
publicclassResponseWrapperextsHttpServletResponseWrapper{privateHttpServletResponseresponse;//缓存响应内容的输出流privateByteArrayOutputStreamresult=newByteArrayOutputStream();publicResponseWrapper(HttpServletResponseresponse){super(response);=response;}/***响应的内容供外部调用*针对体积较大的响应内容很容易发生OOM(比如:/actuator/logfile接口),可在调用*该方法的地方就行api过滤*解决方法在第四步*/publicStringbody(){();}@OverridepublicServletOutputStreamgetOutputStream()throwsIOException{returnnewServletOutputStreamWrapper(,);}@OverridepublicPrintWritergetWriter()throwsIOException{returnnewPrintWriter(newOutputStreamWriter(,()));}//自定义输出流用于读出数据staticfinalclassServletOutputStreamWrapperextsServletOutputStream{privateHttpServletResponseresponse;privateByteArrayOutputStreambyteArrayOutputStream;publicServletOutputStreamWrapper(HttpServletResponseresponse,ByteArrayOutputStreambyteArrayOutputStream){=response;=byteArrayOutputStream;}@OverridepublicbooleanisReady(){returntrue;}@OverridepublicvoidsetWriteListener(WriteListenerlistener){}@Overridepublicvoidwrite(intb)throwsIOException{(b);}/***将内容重新刷新到返回的对象中并且避免多次刷新*/@Overridepublicvoidflush()throwsIOException{if(!()){byte[]bytes=();ServletOutputStreamoutputStream=();(bytes);();}}}}第三步:扩展TraceableRequest,该接口中的方法会在创建HttpTraceResponse内部类时引用,自定义实现里面的方法,在端监控页面展示:
publicclassCustomerTraceableResponseimplementsTraceableResponse{//自定义HttpServletResponse包装类不能使用HttpServletResponse对象privateResponseWrapperresponse;privateHttpServletRequestrequest;publicCustomerTraceableResponse(ResponseWrapperresponse,HttpServletRequestrequest){=response;=request;}//返回响应状态@OverridepublicintgetStatus(){();}//扩展Responseheaders添加ResponseBody属性,展示响应内容,但是需要排除/actuator/开头的请求,这里面部分响应内容太大,容易OOM@OverridepublicMapString,ListStringgetHeaders(){if(isActuatorUri()){returnextractHeaders();}else{MapString,ListStringresult=newLinkedHashMap(1);ListStringresponseBody=newArrayList(1);(());("ResponseBody",responseBody);("Content-Type",getContentType());returnresult;}}//是否是需要过滤的请求uriprivatebooleanisActuatorUri(){StringrequestUri=();AntPathMatchermatcher=newAntPathMatcher();("/actuator/**",requestUri);}//server端页面展示的Content-Type以及Length是从Response中获取的privateListStringgetContentType(){ListStringlist=newArrayList(1);(());returnlist;}//针对/actuator/**的请求返回默认的headers内容获privateMapString,ListStringextractHeaders(){MapString,ListStringheaders=newLinkedHashMap();for(Stringname:()){(name,newArrayList((name)));}returnheaders;}}第五步:自定义Filter对Resquest和Response过滤,并创建HttpTrace对象并保存:
publicclassCustomerHttpTraceFilterextsOncePerRequestFilterimplementsOrdered{//该类存储HttpTrace的repository,默认是基于内存的,可扩展该类跟换存储数据的方式privateHttpTraceRepositoryhttpTraceRepository;//该类创建HttpTrace对象,通过SetInclude在配置文件中我们需要展示那些内容的容器(request-headers,response-headers,remote-address,time-taken)对request和response进行筛选privateHttpExchangeTracerhttpExchangeTracer;publicCustomerHttpTraceFilter(HttpTraceRepositoryhttpTraceRepository,HttpExchangeTracerhttpExchangeTracer){=httpTraceRepository;=httpExchangeTracer;}@OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{//校验URI是否有效if(!isRequestValid(request)){(request,response);return;}//将HttpServletRequest包装成我们自己的RequestWrapperwrapper=newRequestWrapper(request);//将HttpServletResponse包装成我们的自己的ResponseWrapperresponseWrapper=newResponseWrapper(response);//创建我们的自己的TraceRequest对象CustomerTraceableRequesttraceableRequest=newCustomerTraceableRequest(wrapper);//创建HttpTrace对象(FilteredTraceableRequest是内部类,通过SetInclude筛选那些信息需要展示就保存那些信息),重点设置HttpTrace#Request对象的各种参数HttpTracehttpTrace=(traceableRequest);try{(wrapper,responseWrapper);}finally{//使用自定义的TraceableResponse保存需要的response信息CustomerTraceableResponsetraceableResponse=newCustomerTraceableResponse(responseWrapper,request);//根据SetInclude设置HttpTrace中session、principal、timeTaken信息以及Response内部类信息(httpTrace,traceableResponse,null,null);//将HttpTrace对象保存在Respository中存储起来(httpTrace);}}privatebooleanisRequestValid(HttpServletRequestrequest){try{newURI(().toString());returntrue;}catch(URISyntaxExceptionex){returnfalse;}}@OverridepublicintgetOrder(){_PRECEDENCE-10;}}第六步:通过@SpringBootApplication(exclude)禁用HttpTraceAutoConfiguration自动配置,自定义自动配置更换Filter过滤器:
@Configuration@ConditionalOnWebApplication@ConditionalOnProperty(prefix="",name="enabled",matchIfMissing=true)@EnableConfigurationProperties()publicclassTraceFilterConfig{//存储HttpTrace信息的对象@Bean@ConditionalOnMissingBean()publicInMemoryHttpTraceRepositorytraceRepository(){returnnewInMemoryHttpTraceRepository();}//创建HttpTrace对象Exchange@Bean@ConditionalOnMissingBeanpublicHttpExchangeTracerhttpExchangeTracer(HttpTracePropertiestraceProperties){returnnewHttpExchangeTracer(());}@ConditionalOnWebApplication(type=)staticclassServletTraceFilterConfiguration{//将我们自定义的Filter已Bean的方式注册,才能生效@Bean@ConditionalOnMissingBeanpublicCustomerHttpTraceFilterhttpTraceFilter(HttpTraceRepositoryrepository,HttpExchangeTracertracer){returnnewCustomerHttpTraceFilter(repository,tracer);}}@ConditionalOnWebApplication(type=)staticclassReactiveTraceFilterConfiguration{@Bean@ConditionalOnMissingBeanpublicHttpTraceWebFilterhttpTraceWebFilter(HttpTraceRepositoryrepository,HttpExchangeTracertracer,HttpTracePropertiestraceProperties){returnnewHttpTraceWebFilter(repository,tracer,());}}}集成Redisson健康状态监控如果有引入spring-boot-starter-redis,SBA默认同过RedisConnectionFactory监控Redis的健康状态,无奈Redisson还没有,自己动手丰衣足食。通过HealthIndicator或ReactiveHealthIndicator使用策略模式实现不同组件的健康监控,后者是使用Rective模式下的。我是通过JavaBean的方式配置Redisson,顺便再添加ReactiveHealthIndicator该指标即可:
@Configuration@EnableConfigurationProperties(value=)publicclassRedissonConfigimplementsReactiveHealthIndicator{//自己的RedissonProperties文件@AutowiredprivateRedissonPropertiesredissonProperties;//暴露redissonClient句柄@Bean@ConditionalOnMissingBeanpublicRedissonClientredisClient(){(config());}//通过Bean的方式配置RedissonConfig相关信息@BeanpublicConfigconfig(){Configconfig=newConfig();()//单实列模式.setAddress(()+":"+()).setPassword(()).setDatabase(()).setConnectionPoolSize(()).setConnectionMinimumIdleSize(()).setIdleConnectionTimeout(()).setSubscriptionConnectionPoolSize(()).setSubscriptionConnectionMinimumIdleSize(()).setTimeout(()).setRetryAttempts(()).setRetryInterval(()).setConnectTimeout(()).setReconnectionTimeout(());(newDefaultCodecProvider()).setEventLoopGroup(newNioEventLoopGroup()).setThreads(().availableProcessors()*2).setNettyThreads(().availableProcessors()*2);returnconfig;}//实现ReactiveHealthIndicator重写health方法@OverridepublicMonoHealthhealth(){returncheckRedissonHealth().onErrorResume((().down(ex).build()));}//我是通过ping的方式判断redis服务器是否up的状态,并增加加Netty和Threads的监控privateMonoHealthcheckRedissonHealth(){=();("address",());//检测健康状态if(().getNodesGroup().pingAll()){();("dataBase",());("redisNodeThreads",().getConfig().getThreads());("nettyThreads",().getConfig().getNettyThreads());}else{();}(());}}在页面上看就是:
Ok!圆满完成!
如有错误,不吝赐教!





