当前位置: 首页 > news >正文

devOps系列(八)efk+prometheus+grafana日志监控和告警

前言

作者目前打算分享一期关于devOps系列的文章,希望对热爱学习和探索的你有所帮助。

文章主要记录一些简洁、高效的运维部署指令,旨在 记录和能够快速地构建系统。就像运维文档或者手册一样,方便进行系统的重建、改造和优化。每篇文章独立出来,可以单独作为其中一项组件的部署和使用。

本章为 devOps系列(八)efk+prometheus+grafana日志监控和告警

大纲

devOps系列介绍

devOps系列(一)docker搭建

devOps系列(二)gitlab搭建

devOps系列(三)nexus-harbor搭建

devOps系列(四)jenkins搭建

devOps系列(五)efk系统搭建

devOps系列(六)grafana+prometheus搭建

devOps系列(七)grafana+prometheus监控告警

devOps系列(八)efk+prometheus+grafana日志监控和告警

正文

日志收集

目前我们已经搭建好了efk日志系统,接下来就是把日志数据采集进来。

目前java程序的采集,可以在框架侧写一个基于logback日志收集starter依赖框架,便于日志收集的安装和管理。

可以自建一个starter依赖工程项目,也可以直接植入项目工程。

注:本文着重介绍核心原理,可能无法直接使用

需要引入的依赖

  <dependency><groupId>com.sndyuk</groupId><artifactId>logback-more-appenders</artifactId></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></dependency><dependency><groupId>org.komamitsu</groupId><artifactId>fluency-core</artifactId></dependency><dependency><groupId>org.komamitsu</groupId><artifactId>fluency-fluentd</artifactId></dependency>

核心logback配置文件 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false"><!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--><springProperty name="profile" source="spring.profiles.active"/><springProperty name="applicationName" source="spring.application.name"/><!--   默认地址 --><springProperty name="fluentdAddr" source="framework.logback.fluentd-addr" defaultValue="fluentd.jafir.top"/><property name="LOG_HOME" value="/${applicationName}/logs"/><!-- 控制台输出 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!-- info及其以上日志 --><appender name="LOCAL_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>INFO</level></filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_HOME}/info_log.%d{yyyy-MM-dd}.log</FileNamePattern><!--日志文件保留天数--><MaxHistory>30</MaxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><!-- 设置编码格式,以防中文乱码 --><charset class="java.nio.charset.Charset">UTF-8</charset></encoder><!--日志文件最大的大小--><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><MaxFileSize>10MB</MaxFileSize></triggeringPolicy></appender><!-- 错误日志 --><appender name="LOCAL_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_HOME}/error_log.%d{yyyy-MM-dd}.log</FileNamePattern><!--日志文件保留天数--><MaxHistory>30</MaxHistory></rollingPolicy><encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern><!-- 设置编码格式,以防中文乱码 --><charset class="java.nio.charset.Charset">UTF-8</charset></encoder><!--日志文件最大的大小--><triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"><MaxFileSize>10MB</MaxFileSize></triggeringPolicy></appender><!-- Fluency --><appender name="FLUENCY_SYNC" class="ch.qos.logback.more.appenders.FluencyLogbackAppender"><!-- Tag for Fluentd. Farther information: http://docs.fluentd.org/articles/config-file --><!-- 微服务名 --><tag>${applicationName}</tag><!-- [Optional] Label for Fluentd. Farther information: http://docs.fluentd.org/articles/config-file --><!-- Host name/address and port number which Fluentd placed --><remoteHost>${fluentdAddr}</remoteHost><port>24224</port><!-- [Optional] Multiple name/addresses and port numbers which Fluentd placed<remoteServers><remoteServer><host>primary</host><port>24224</port></remoteServer><remoteServer><host>secondary</host><port>24224</port></remoteServer></remoteServers>--><!-- [Optional] Additional fields(Pairs of key: value) --><!-- 环境 --><additionalField><key>env</key><value>${profile}</value></additionalField><!-- [Optional] Configurations to customize Fluency's behavior: https://github.com/komamitsu/fluency#usage  --><ackResponseMode>false</ackResponseMode><!-- <fileBackupDir>/tmp</fileBackupDir> --><bufferChunkInitialSize>33554432</bufferChunkInitialSize><bufferChunkRetentionSize>268435456</bufferChunkRetentionSize><maxBufferSize>1073741824</maxBufferSize><bufferChunkRetentionTimeMillis>1000</bufferChunkRetentionTimeMillis><connectionTimeoutMilli>5000</connectionTimeoutMilli><readTimeoutMilli>5000</readTimeoutMilli><waitUntilBufferFlushed>30</waitUntilBufferFlushed><waitUntilFlusherTerminated>40</waitUntilFlusherTerminated><flushAttemptIntervalMillis>200</flushAttemptIntervalMillis><senderMaxRetryCount>12</senderMaxRetryCount><!-- [Optional] Enable/Disable use of EventTime to get sub second resolution of log event date-time --><useEventTime>true</useEventTime><sslEnabled>false</sslEnabled><!-- [Optional] Enable/Disable use the of JVM Heap for buffering --><jvmHeapBufferMode>false</jvmHeapBufferMode><!-- [Optional] If true, Map Marker is expanded instead of nesting in the marker name --><flattenMapMarker>false</flattenMapMarker><!--  [Optional] default "marker" --><markerPrefix></markerPrefix><!-- [Optional] Message encoder if you want to customize message --><encoder><pattern><![CDATA[%-5level %logger{50}#%line %message]]></pattern></encoder><!-- [Optional] Message field key name. Default: "message" --><messageFieldKeyName>msg</messageFieldKeyName></appender><!-- Fluency --><appender name="FLUENCY_SYNC_ACCESS" class="ch.qos.logback.more.appenders.FluencyLogbackAppender"><!-- Tag for Fluentd. Farther information: http://docs.fluentd.org/articles/config-file --><!-- 微服务名 --><tag>access-${applicationName}</tag><!-- [Optional] Label for Fluentd. Farther information: http://docs.fluentd.org/articles/config-file --><!-- Host name/address and port number which Fluentd placed --><remoteHost>${fluentdAddr}</remoteHost><port>24224</port><!-- [Optional] Multiple name/addresses and port numbers which Fluentd placed<remoteServers><remoteServer><host>primary</host><port>24224</port></remoteServer><remoteServer><host>secondary</host><port>24224</port></remoteServer></remoteServers>--><!-- [Optional] Additional fields(Pairs of key: value) --><!-- 环境 --><additionalField><key>env</key><value>${profile}</value></additionalField><!-- [Optional] Configurations to customize Fluency's behavior: https://github.com/komamitsu/fluency#usage  --><ackResponseMode>false</ackResponseMode><!-- <fileBackupDir>/tmp</fileBackupDir> --><bufferChunkInitialSize>33554432</bufferChunkInitialSize><bufferChunkRetentionSize>268435456</bufferChunkRetentionSize><maxBufferSize>1073741824</maxBufferSize><bufferChunkRetentionTimeMillis>1000</bufferChunkRetentionTimeMillis><connectionTimeoutMilli>5000</connectionTimeoutMilli><readTimeoutMilli>5000</readTimeoutMilli><waitUntilBufferFlushed>30</waitUntilBufferFlushed><waitUntilFlusherTerminated>40</waitUntilFlusherTerminated><flushAttemptIntervalMillis>200</flushAttemptIntervalMillis><senderMaxRetryCount>12</senderMaxRetryCount><!-- [Optional] Enable/Disable use of EventTime to get sub second resolution of log event date-time --><useEventTime>true</useEventTime><sslEnabled>false</sslEnabled><!-- [Optional] Enable/Disable use the of JVM Heap for buffering --><jvmHeapBufferMode>false</jvmHeapBufferMode><!-- [Optional] If true, Map Marker is expanded instead of nesting in the marker name --><flattenMapMarker>false</flattenMapMarker><!--  [Optional] default "marker" --><markerPrefix></markerPrefix><!-- [Optional] Message encoder if you want to customize message --><encoder><pattern>%message%n</pattern></encoder><!-- [Optional] Message field key name. Default: "message" --><messageFieldKeyName>msg</messageFieldKeyName></appender><appender name="FLUENCY" class="ch.qos.logback.classic.AsyncAppender"><!-- Max queue size of logs which is waiting to be sent (When it reach to the max size, the log will be disappeared). --><queueSize>999</queueSize><!-- Never block when the queue becomes full. --><neverBlock>true</neverBlock><!-- The default maximum queue flush time allowed during appender stop.If the worker takes longer than this time it will exit, discarding any remaining items in the queue.10000 millis--><maxFlushTime>1000</maxFlushTime><appender-ref ref="FLUENCY_SYNC"/></appender><appender name="FLUENCY_ACCESS" class="ch.qos.logback.classic.AsyncAppender"><!-- Max queue size of logs which is waiting to be sent (When it reach to the max size, the log will be disappeared). --><queueSize>999</queueSize><!-- Never block when the queue becomes full. --><neverBlock>true</neverBlock><!-- The default maximum queue flush time allowed during appender stop.If the worker takes longer than this time it will exit, discarding any remaining items in the queue.10000 millis--><maxFlushTime>1000</maxFlushTime><appender-ref ref="FLUENCY_SYNC_ACCESS"/></appender><springProfile name="local"><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="STDOUT"/><appender-ref ref="FLUENCY"/><!--            <appender-ref ref="LOCAL_ALL"/>--><!--            <appender-ref ref="LOCAL_ERROR"/>--><!--            <appender-ref ref="FLUENCY"/>--></root><logger name="com.jafir.logback.aop.WebLogAspect" level="INFO" additivity="false"><appender-ref ref="STDOUT"/><!--            <appender-ref ref="FLUENCY_ACCESS"/>--></logger></springProfile><springProfile name="dev,test,preprod"><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="STDOUT"/><!--            <appender-ref ref="LOCAL_ALL"/>--><!--            <appender-ref ref="LOCAL_ERROR"/>--><appender-ref ref="FLUENCY"/></root><logger name="com.jafir.logback.aop.WebLogAspect" level="INFO" additivity="false"><appender-ref ref="STDOUT"/><appender-ref ref="FLUENCY_ACCESS"/></logger></springProfile><springProfile name="prod"><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="STDOUT"/>
<!--            <appender-ref ref="LOCAL_ALL"/>-->
<!--            <appender-ref ref="LOCAL_ERROR"/>--><appender-ref ref="FLUENCY"/></root><logger name="com.jafir.logback.aop.WebLogAspect" level="INFO" additivity="false"><appender-ref ref="STDOUT"/><appender-ref ref="FLUENCY_ACCESS"/></logger></springProfile><!-- 关闭某个日志打印 --><logger name="org.komamitsu.fluency.Fluency" level="OFF" /><logger name="org.komamitsu.fluency.fluentd.ingester.sender.RetryableSender" level="OFF" /><logger name="org.komamitsu.fluency.fluentd.ingester.sender.NetworkSender" level="OFF" /></configuration>

springBoot的AutoConfiguration类

package com.jafir.logback;import com.jafir.logback.aop.WebLogAspect;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;@Configuration
@Import(WebLogAspect.class)
@EnableConfigurationProperties(LogbackProperties.class)
@ConditionalOnProperty(prefix = "framework.logback", value = "enabled", havingValue = "true", matchIfMissing = true)
@ComponentScan(value = "com.jafir.logback")
public class LogbackAutoConfiguration {
}
package com.jafir.logback;import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.List;@ConfigurationProperties("framework.logback")
public class LogbackProperties {private Boolean enabled = false;private String fluentdAddr = "fluentd.jaifr.top";private List<String> excludeUrl;public List<String> getExcludeUrl() {return excludeUrl;}public void setExcludeUrl(List<String> excludeUrl) {this.excludeUrl = excludeUrl;}public Boolean getEnabled() {return enabled;}public void setEnabled(Boolean enabled) {this.enabled = enabled;}public String getFluentdAddr() {return fluentdAddr;}public void setFluentdAddr(String fluentdAddr) {this.fluentdAddr = fluentdAddr;}
}

可以通过yml配置文件来进行装配控制

framework.logback.enabled 控制是否开启日志收集

framework.logback.fluentdAddr 设置fluentd的地址

framework.logback.excludeUrl 设置过滤不进行收集的地址

核心servlet拦截器类

package com.jafir.logback.aop;import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpStatus;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.jafir.logback.LogResponseBody;
import com.jafir.logback.LogbackProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ContentCachingResponseWrapper;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;@Aspect
@Slf4j
public class WebLogAspect {private final boolean NEED_RESPONSE_BODY = true;private final LogbackProperties logbackProperties;private final ObjectMapper objectMapper;private static final List<String> DEFAULT_EXCLUDE_URL = new ArrayList<>();static {DEFAULT_EXCLUDE_URL.add("/actuator/prometheus");DEFAULT_EXCLUDE_URL.add("/health/detect");}public WebLogAspect(ObjectMapper objectMapper, LogbackProperties logbackProperties) {this.objectMapper = objectMapper;this.logbackProperties = logbackProperties;if (CollUtil.isNotEmpty(logbackProperties.getExcludeUrl())) {logbackProperties.getExcludeUrl().addAll(DEFAULT_EXCLUDE_URL);} else {logbackProperties.setExcludeUrl(DEFAULT_EXCLUDE_URL);}}@Around("execution(public void javax.servlet.http.HttpServlet.service(..)))")public Object webLog(ProceedingJoinPoint joinPoint) throws Throwable {Object[] args = joinPoint.getArgs();DelegateHttpRequest servletRequest = new DelegateHttpRequest((HttpServletRequest) args[0]);HttpServletResponse servletResponse = (HttpServletResponse) args[1];ContentCachingResponseWrapper responseWrapper = null;if (NEED_RESPONSE_BODY) {responseWrapper = new ContentCachingResponseWrapper(servletResponse);}// 过滤不需要拦截的请求if (doNotIntercept(servletRequest)) {return joinPoint.proceed();}WebLog webLog = new WebLog();webLog.setTimestamp(Instant.now());WebLog.Request request = new WebLog.Request();InputStream servletRequestStream = servletRequest.getInputStream();int size;byte[] buffer = new byte[1024];ByteArrayOutputStream tmpRequestStream = new ByteArrayOutputStream();while ((size = servletRequestStream.read(buffer)) != -1) {tmpRequestStream.write(buffer, 0, size);}request.setBody(tmpRequestStream.toString());Map<String, List<String>> requestHeaders = new HashMap<>();Enumeration<String> servletRequestHeaders = servletRequest.getHeaderNames();while (servletRequestHeaders.hasMoreElements()) {String header = servletRequestHeaders.nextElement();Enumeration<String> values = servletRequest.getHeaders(header);List<String> list = new ArrayList<>();while (values.hasMoreElements()) {String value = values.nextElement();list.add(value);}requestHeaders.put(header, list);}request.setHeaders(requestHeaders);request.setMethod(servletRequest.getMethod());Object rawUrl = servletRequest.getAttribute("raw-api-uri");if (rawUrl instanceof String) {request.setRequestUri((String) rawUrl);} else {request.setRequestUri(servletRequest.getRequestURI());}Map<String, List<String>> parameters = new HashMap<>();for (Map.Entry<String, String[]> entry : servletRequest.getParameterMap().entrySet()) {List<String> list = new ArrayList<>(Arrays.asList(entry.getValue()));parameters.put(entry.getKey(), list);}request.setParameters(parameters);Object attributeStart = servletRequest.getAttribute("raw-api-start");long start;if (attributeStart instanceof Long) {start = (long) attributeStart;} else {start = System.nanoTime();}Object value;try {if (NEED_RESPONSE_BODY) {value = joinPoint.proceed(new Object[]{servletRequest, responseWrapper});} else {value = joinPoint.proceed(new Object[]{servletRequest, servletResponse});}} catch (Throwable e) {((HttpServletRequest) args[0]).setAttribute("raw-api-uri", servletRequest.getRequestURI());((HttpServletRequest) args[0]).setAttribute("raw-api-start", start);throw e;}@SuppressWarnings("unchecked")Map<String, String> pathMap = (Map<String, String>) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);if (pathMap != null && !pathMap.isEmpty()) {//写入path数据request.setPathParameters(pathMap);}long timeTaken = (System.nanoTime() - start) / 1_000_000;WebLog.Response response = new WebLog.Response();int status = servletResponse.getStatus();if (NEED_RESPONSE_BODY) {boolean isSuccess = true;// 成功的接口不用记录 responseBodyif (HttpStatus.HTTP_OK != status) {isSuccess = false;}String responseBodyStr;try {responseBodyStr = IOUtils.toString(responseWrapper.getContentInputStream(), StandardCharsets.UTF_8.displayName());} catch (Exception e) {responseBodyStr = "";isSuccess = false;log.error("接口: {} ,IOUtils.toString 出现异常", servletRequest.getRequestURI());}// 失败的记录一下bodyif (!isSuccess) {response.setResponseBody(responseBodyStr);}try {responseWrapper.copyBodyToResponse();} catch (Exception e) {log.error("接口: {} ,copyBodyToResponse 出现异常", servletRequest.getRequestURI());}}response.setStatus(status);Map<String, List<String>> responseHeaders = new HashMap<>();Collection<String> servletResponseHeaders = servletResponse.getHeaderNames();for (String headerName : servletResponseHeaders) {Collection<String> values = servletResponse.getHeaders(headerName);List<String> list = new ArrayList<>(values);responseHeaders.put(headerName, list);}response.setHeaders(responseHeaders);String bestUri = String.valueOf(servletRequest.getRequest().getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));//兼容处理    抛异常情况下该值null 用requestUri兼容if(bestUri!=null && !bestUri.isEmpty() && !"null".equals(bestUri)) {request.setUri(bestUri);}else {request.setUri(request.getRequestUri());}webLog.setTimeTaken(timeTaken);webLog.setRequest(request);webLog.setResponse(response);log.info(objectMapper.writeValueAsString(webLog));return value;}private boolean doNotIntercept(DelegateHttpRequest servletRequest) {// 放行文件类型if (servletRequest.getContentType() != null && servletRequest.getContentType().contains("multipart")) {return true;}// 如果是 post的 x-www-form 类型 也就是 xx=xx&xx=xx&xx 这种格式的 (一般很少有这样使用的)if ((servletRequest.getContentType() != null&& servletRequest.getContentType().contains("application/x-www-form-urlencoded")&& "post".equalsIgnoreCase(servletRequest.getMethod()))) {return true;}// 放行不做拦截的urifor (String uri : logbackProperties.getExcludeUrl()) {if (uri.equals(servletRequest.getRequestURI())) {return true;}}return false;}
}

日志bean

package com.jafir.logback.aop;import lombok.Data;import java.time.Instant;
import java.util.List;
import java.util.Map;@Data
public class WebLog {private Instant timestamp;private Long timeTaken;private Request request;private Response response;@Datapublic static class Request {private String method;private String uri;private String requestUri;private Map<String, List<String>> headers;private Map<String, List<String>> parameters;private String body;private Map<String, String> pathParameters;}@Datapublic static class Response {/*** http 的 status*/private Integer status;/*** WebResponseBody 的 code*/private Integer bodyCode;private Map<String, List<String>> headers;private String responseBody;}
}

request的代理类(主要目的是保留读取到的流数据。流只能读取一次)

package com.jafir.logback.aop;import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;public class DelegateHttpRequest extends HttpServletRequestWrapper {private byte[] bytes;private final byte[] buffer = new byte[4096];public DelegateHttpRequest(HttpServletRequest request) {super(request);}@Overridepublic ServletInputStream getInputStream() throws IOException {if (bytes == null) {ServletInputStream inputStream = super.getInputStream();ByteArrayOutputStream outputStream = new ByteArrayOutputStream();int size = 0;while ((size = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, size);}outputStream.close();bytes = outputStream.toByteArray();}return new DelegateServletInputStream(new ByteArrayInputStream(bytes));}
}
package com.jafir.logback.aop;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.InputStream;public class DelegateServletInputStream extends ServletInputStream {private final InputStream inputStream;public DelegateServletInputStream(InputStream inputStream){this.inputStream = inputStream;}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return true;}@Overridepublic void setReadListener(ReadListener listener) { }@Overridepublic int read() throws IOException {return inputStream.read();}
}

以上核心内容其实就是 一个拦截器 进行拦截接口,然后按照一定的结构打印日志,然后logback再利用appender 写入到fluentd中,完成日志的收集。

fluent的日志收集大致如下:

fluency

收集的日志结构中比较重要的字段有:

timetaken: 接口耗时

request:请求

response:返回结果(错误时包含body信息)

status: 表示http的状态 正常都是200

bodyCode: 表示websponse结构里面的code值(如果你的返回结构是 在http之上 又封装了一层 msg code body的话 这里的bodyCode就是 返回的结果里面的code,一般我们会对其进行业务异常和系统异常的code区分。比如 200是正常,500为系统异常,其他是业务异常等)

目前比较重要的是logback-spring.xml中,有几个点。

env : 用于区分环境,logback-spring.xml是支持 profile 获取的

FLUENCY_SYNC: 普通的日志收集,就是整个应用程序的日志。如果是应用日志 则索引名为 $applicationName-年月日

FLUENCY_SYNC_ACCESS: 访问日志的收集,也就是接口的请求日志 通过webaspect拦截器写的日志。如果是访问日志 则索引名为access-$applicationName–年月日

这样的话可以在es中区分开应用日志和访问日志,FLUENCY_SYNC和FLUENCY_SYNC_ACCESS 也能够分开进行收集。

如: 普通的日志就用 FLUENCY_SYNC ,只有 WebLogAspect 下面的拦截器日志,用FLUENCY_SYNC_ACCESS收集

 <springProfile name="prod"><!-- 日志输出级别 --><root level="INFO"><appender-ref ref="STDOUT"/>
<!--            <appender-ref ref="LOCAL_ALL"/>-->
<!--            <appender-ref ref="LOCAL_ERROR"/>--><appender-ref ref="FLUENCY"/></root><logger name="com.jafir.logback.aop.WebLogAspect" level="INFO" additivity="false"><appender-ref ref="STDOUT"/><appender-ref ref="FLUENCY_ACCESS"/></logger></springProfile>

如上则完成了 efk的日志收集,最终在kibana中可以通过新建pattern来查阅筛选日志信息。

日志监控和告警

对于服务的接口已经按照不同的索引存在于了es中,我们也可以用grafana来进行展示和监控。

grafana添加es datasource

img

img

添加监控表

img

错误统计

错误数query条件: 利用status 或者 bodeCode

img

env:"test" AND @log_name:"access-xxxx" AND   !response.status:"200"

意为:测试环境下的xxx服务,返回结果不等于200的数量

接口响应统计

接口响应query条件: 利用timeTaken

img

env:"test" AND @log_name:"access-jisu-http-web"
注意:

grafan的监控表 query不能使用变量,只能写死,所以可能会写多个环境 多个服务 多张表

添加告警

img

告警理论上可以使用grafana自身集成的alertmanager 但是尝试之后发现并不好用 所以我们还是使用 前面prometheus监控搭建得 alertmanager 和 prometheus-alert结合使用

img

这里就添加对应地址即可

img

img

其他可以默认 然后就好了

原理介绍

es数据源-》grafana (监控数据表 触发告警条件 发送告警) -》 alertmanager (配置路由到指定webhook) -》 prometheus-alert (根据不同模板组装数据)-》企业微信

alertmanager和prometheus-alert配置调整

prometheus-alert地址

http://192.168.20.2:8080/

img

找到grafana-wx 然后设置模板

{{range $k, $v := .alerts}}{{if eq $v.status "resolved"}}## [Prometheus恢复]()
###### 告警类型: {{$v.labels.alertname}}
###### 告警状态: {{ $v.status }}
###### 告警详情: {{$v.annotations.__value_string__}}
###### 故障时间:{{GetCSTtime $v.startsAt}}
###### 恢复时间:{{GetCSTtime $v.endsAt}}
{{else}}
## [Prometheus告警]()
###### 告警类型: {{$v.labels.alertname}}
###### 告警状态: {{ $v.status }}
###### 告警详情: {{$v.annotations.__value_string__}}
###### 故障时间:{{GetCSTtime $v.startsAt}}
{{end}}{{end}}

也可以进行测试 (测试内容可以从prometheus-alert日志中寻找)

{"receiver":"web\\.hook\\.grafanaalert","status":"resolved","alerts":[{"status":"resolved","labels":{"__alert_rule_namespace_uid__":"IrqNMj34z","__alert_rule_uid__":"lBw5-C3Vz","alertname":"DatasourceNoData","datasource_uid":"bP2dUr3Vz","ref_id":"A","rulename":"api-server错误"},"annotations":{"__dashboardUid__":"CBAou9qVz","__panelId__":"10"},"startsAt":"2023-08-03T00:00:12.197Z","endsAt":"2023-08-03T01:12:05.562Z","generatorURL":"http://localhost:3000/alerting/lBw5-C3Vz/edit","fingerprint":"f265175a34e6cc2e"}],"groupLabels":{"alertname":"DatasourceNoData"},"commonLabels":{"__alert_rule_namespace_uid__":"IrqNMj34z","__alert_rule_uid__":"lBw5-C3Vz","alertname":"DatasourceNoData","datasource_uid":"bP2dUr3Vz","ref_id":"A","rulename":"api-server错误"},"commonAnnotations":{"__dashboardUid__":"CBAou9qVz","__panelId__":"10"},"externalURL":"http://alertmanager:9093","version":"4","groupKey":"{}/{__alert_rule_namespace_uid__=\"IrqNMj34z\"}:{alertname=\"DatasourceNoData\"}","truncatedAlerts":0}

img

prometheus告警模板

img

{{range $k, $v := .alerts}}{{if eq $v.status "resolved"}}
## [Prometheus恢复]()
###### 告警类型: {{$v.labels.alertname}}
###### 故障主机: {{$v.labels.instance}}
###### 环境类型:{{$v.labels.job}}
###### 告警详情: {{$v.annotations.description}}
###### 故障时间:{{GetCSTtime $v.startsAt}}
###### 恢复时间:{{GetCSTtime $v.endsAt}}{{else}}
## [Prometheus告警]()
###### 告警类型: {{$v.labels.alertname}}
###### 故障主机: {{$v.labels.instance}}
###### 环境类型:{{$v.labels.job}}
###### 告警详情: {{$v.annotations.description}}
###### 故障时间:{{GetCSTtime $v.startsAt}}{{end}}
{{end}}
alertmanager配置
global:resolve_timeout: 15s
route:group_by: ['alertname','instance']group_wait: 10sgroup_interval: 10srepeat_interval: 2mreceiver: 'web.hook.prometheusalert'routes:- receiver: 'web.hook.grafanaalert'  # 路由到名为 "web.hook.grafanaalert" 的接收器match:__alert_rule_namespace_uid__: 'IrqNMj34z'  # 匹配 alertname 为 "grafana" 的告警
receivers:
- name: 'web.hook.prometheusalert'webhook_configs:- url: 'http://prometheus-alert:8080/prometheusalert?type=wx&tpl=prometheus-wx&wxurl=你的企业微信webhook'
- name: 'web.hook.grafanaalert'webhook_configs:- url: 'http://prometheus-alert:8080/prometheusalert?type=wx&tpl=grafana-wx&wxurl=你的企业微信webhook'

配置含义:

10s 检测一下 2m 再重复提示

默认情况下都认为是prometheus的告警,走prometheusalert发送到对应prometheus-wx的模板

如果是数据包含 __alert_rule_namespace_uid__: 'IrqNMj34z' 则认为是grafana的告警 走grafanaalert发送到对应grafana-wx的模板

以上配置可以自适应调整,如果有发短信 或者 打电话告警的,也可以利用prometheusAlert全家桶的方式接入进来。

测验

配置好了之后 就可以在grafana进行告警测试了

img

相关文章:

devOps系列(八)efk+prometheus+grafana日志监控和告警

前言 作者目前打算分享一期关于devOps系列的文章&#xff0c;希望对热爱学习和探索的你有所帮助。 文章主要记录一些简洁、高效的运维部署指令&#xff0c;旨在 记录和能够快速地构建系统。就像运维文档或者手册一样&#xff0c;方便进行系统的重建、改造和优化。每篇文章独立…...

考研英语单词29

Day 29 unify v.统一&#xff0c;使成一体【union n.结合&#xff0c;联合&#xff0c;工会&#xff0c;团结 unity n.团结&#xff0c;统一&#xff0c;协调】 offend v.冒犯&#xff0c;使不愉快【offender n.冒犯者 offensive a.冒犯的&#xff0c;无礼的】 d…...

spring-security 过滤器

spring-security过滤器 版本信息过滤器配置过滤器配置相关类图过滤器加载过程创建 HttpSecurity Bean 对象创建过滤器 过滤器作用ExceptionTranslationFilter 自定义过滤器 本章介绍 spring-security 过滤器配置类 HttpSecurity&#xff0c;过滤器加载过程&#xff0c;自定义过…...

掌握这7种软件设计原则,让你的代码更优雅

掌握这7种软件设计原则&#xff0c;让你的代码更优雅 在软件开发过程中&#xff0c;设计原则是非常重要的指导方针&#xff0c;它们可以帮助我们创建出更加清晰、可维护和可扩展的软件系统。本文将介绍7种常见的软件设计原则&#xff0c;并解释它们如何提升代码质量。 1. 单…...

Flutter自定义tabbar任意样式

场景描述 最近在使用遇到几组需要自定义的tabbar或者类似组件&#xff0c;在百度查询资料中通常&#xff0c;需要自定义 TabIndicator extends Decoration 比如上图中的带圆角的指示器这样实现 就很麻烦&#xff0c; 搜出来的相关也是在此之处上自己画&#xff0c;主要再遇…...

Java设计模式【策略模式】

一、前言 1.1 背景 针对某种业务可能存在多种实现方式&#xff0c;传统方式是通过传统if…else…或者switch代码判断&#xff1b; 弊端&#xff1a; 代码可读性差扩展性差难以维护 1.2 简介 策略模式是一种行为型模式&#xff0c;它将对象和行为分开&#xff0c;将行为定…...

(13)Hive调优——动态分区导致的小文件问题

前言 动态分区指的是&#xff1a;分区的字段值是基于查询结果自动推断出来的&#xff0c;核心语法就是insertselect。 具体内容指路文章&#xff1a; https://blog.csdn.net/SHWAITME/article/details/136111924?spm1001.2014.3001.5501文章浏览阅读483次&#xff0c;点赞15次…...

【linux】使用g++调试内存泄露:AddressSanitizer

1、简介 AddressSanitizer(又名 ASan)是 C/C++ 的内存错误检测器。它可以用来检测: 释放后使用(悬空指针) 堆缓冲区溢出 堆栈缓冲区溢出 全局缓冲区溢出 在作用域之后使用 初始化顺序错误 内存泄漏这个工具非常快,只将被检测的程序速度减慢约2倍,而Valgrind将会是程序…...

第三百五十七回

文章目录 1. 概念介绍2. 使用方法2.1 List2.2 Map2.3 Set 3. 示例代码4. 内容总结 我们在上一章回中介绍了"convert包"相关的内容&#xff0c;本章回中将介绍collection.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的内容是col…...

新版Java面试专题视频教程——框架篇

新版Java面试专题视频教程——框架篇 框架篇 01-框架篇介绍02-Spring-单例bean是线程安全的吗03-Spring-AOP相关面试题04-Spring-事务失效的场景05-Spring-bean的生命周期5.1 BeanDefinition 06-Spring-bean的循环依赖(循环引用)6.1 一般对象的循环依…...

网络爬虫实战 | 上传以及下载处理后的文件

详细代码在文尾 以实现爬虫一个简单的(SimFIR (doctrp.top))网址为例,需要遵循几个步骤: 1. 分析网页结构 首先,需要分析该网页的结构,了解图片是如何存储和组织的。这通常涉及查看网页的HTML源代码,可能还包括CSS和JavaScript文件。检查图片URL的模式,看看是否有规律…...

Linux--shell编程中有关while循环的详细内容

文章关于while循环的内容目录 一、while循环 ​​​​​​​​​​​​​​二、无限循环 ​​​​​​​​​​​​​​三、case语句 ​​​​​​​四、跳出循环 ​​​​​​​​​​​​​​五、break ​​​​​​​六、continue​​​​​​​ ​​​​​​​一、w…...

回归测试与重新测试

软件开发是一个充满挑战的旅程&#xff0c;在这条道路上始终伴随着错误和不确定性的挑战。然而&#xff0c;真正将卓越软件与其他软件区分开来的是管理和解决这些挑战的效率&#xff0c;这就是结构良好的测试计划变得至关重要的地方&#xff0c;该计划的核心在于两个基本实践&a…...

java 版本企业招标投标管理系统源码+多个行业+tbms+及时准确+全程电子化

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…...

详解动态内存管理!

目录 ​编辑 1.为什么要用动态内存分配 2.malloc和free 2.1 malloc 2.2 free 3.calloc和realloc 3.1 calloc 3.2 realloc 4.常见的动态内存的错误 4.1 对NULL的解引用操作 4.2 对动态内存开辟空间的越界访问 4.3 对非动态内存开辟空间用free释放 4.4 使用free释放动…...

iocp简单例子

下方代码中&#xff0c;没有写注释的地方&#xff0c;说明与icop网络无关也就是它们都不重要&#xff0c;重要的位置全部都有注释&#xff0c;复制下方代码就可以运行看效果 iocp带网络的例子&#xff1a; 客户端&#xff1a; 客户端只有一个main&#xff0c;只有socket相关函…...

HAL STM32 HW I2C DMA + SSD1306/SH1106驱动示例

HAL STM32 HW I2C DMA SSD1306/SH1106驱动示例 &#x1f4cd;硬件I2C DMA驱动参考&#xff1a;https://blog.csdn.net/weixin_45065888/article/details/118225993 &#x1f516;本工程基于STM32F103VCT6&#xff0c;驱动程序独立&#xff0c;可以移植到任意STM32型号上使用。…...

grafana配置钉钉告警模版(一)

1、配置钉钉告警模版 创建钉钉告警模版&#xff0c;然后在创建钉钉告警时调用模版。 定义发送内容具体代码 my_text_alert_list 是模版名称后面再配置钉钉告警时需要调用。 {{/* 定义消息体片段 */}} {{ define "my_text_alert_list" }}{{ range . }}告警名称&…...

佳能2580的下载手册

凡是和电子产品有关的产品其内部都开始不断地进行内卷&#xff0c;在不断地内卷背后&#xff0c;意味着科技更新和换代&#xff0c;自己也入手了一台佳能2580的打印机&#xff0c;一台相对比较老式的打印机&#xff0c;以此不断地自己想要进行打印的需要。 下载的基础步骤&…...

YOLO-World:实时开放词汇目标检测

paper&#xff1a;https://arxiv.org/pdf/2401.17270.pdf Github&#xff1a;GitHub - AILab-CVC/YOLO-World: Real-Time Open-Vocabulary Object Detection online demo&#xff1a;https://huggingface.co/spaces/stevengrove/YOLO-World 目录 0. 摘要 1. 引言 2. 相关工…...

Unity中关于群组的一些组件

前言 在游戏开发环境中&#xff0c;UI组件是构建玩家交互界面的基础。以下是一些常见UI组件的详细解释和它们适用的场景&#xff0c;方便我们更好地理解和使用这些工具。 1. Graphic Raycaster Graphic Raycaster组件是游戏UI交互的核心。在Unity等游戏引擎中&#xff0c;当玩…...

面向对象详解,面向对象的三大特征:封装、继承、多态

文章目录 一、面向对象与面向过程1、什么是面向过程&#xff1f;2、什么是面向对象&#xff1f; 二、类与对象1. 初识对象2. 类的成员方法2.1 类的定义和使用2.2 成员方法 3. 类和对象4. 魔法方法1. _ _ inint _ _ 构造方法2. _ _ str _ _ 字符串方法3. _ _ lt _ _ 小于符号比较…...

【阿里云服务器的一些使用坑】都是无知的泪水呀

发生了什么&#xff1f; 我想学习一下关于Java的MySQL、Nginx 相关的知识。然后就用首次优惠注册的阿里云&#xff0c;都没有搞清楚实例&#xff0c;镜像&#xff0c;带宽&#xff0c;磁盘。然后。因为一不小心——我想去换一个Ubuntu的镜像而不是CentOS。就把实例给释放啊。之…...

Docker的常用命令||Docker是个流行的容器化平台,它允许你打包、分发和运行应用程序。

Docker是一个流行的容器化平台&#xff0c;它允许你打包、分发和运行应用程序。以下是一些常用的Docker命令及其示例用法&#xff1a; 1. **docker run**: 用于运行一个新的容器实例。 docker run <image_name> 例如&#xff0c;运行一个Nginx容器&#xff1a; docker ru…...

汽车电子论文学习--电动汽车电机驱动系统动力学特性分析

关键重点&#xff1a; 1. 汽车的低速转矩存在最大限制&#xff0c;受附着力限制&#xff0c;因路面不同而变化。 2. 起步加速至规定转速的时间可以计算得到&#xff1a; 3. 电机额定功率的计算方式&#xff1a; 可以采取最高设计车速90%或120km/h匀速行驶的功率作为电机额定功…...

c++的一些陌生用法记录

c的一些陌生用法记录 1. 完美转发std::forward<decltype(PH1)>(PH1)static的用法 1. 完美转发std::forward<decltype(PH1)>(PH1) static的用法 static函数与普通函数的区别&#xff1a; 用static修饰的函数&#xff0c;本限定在本源码文件中&#xff0c;不能被本源…...

Vue | (三)使用Vue脚手架(中)| 尚硅谷Vue2.0+Vue3.0全套教程

文章目录 &#x1f4da;Todo-list 案例&#x1f407;组件化编码流程&#xff08;通用&#xff09;&#x1f407;实现静态组件&#x1f407;展示动态数据&#x1f407;交互⭐️添加一个todo⭐️todo勾选实现⭐️删除功能实现⭐️底部统计功能实现⭐️底部全选功能实现⭐️底部一…...

TenorFlow多层感知机识别手写体

文章目录 数据准备建立模型建立输入层 x建立隐藏层h1建立隐藏层h2建立输出层 定义训练方式建立训练数据label真实值 placeholder定义loss function选择optimizer 定义评估模型的准确率计算每一项数据是否正确预测将计算预测正确结果&#xff0c;加总平均 开始训练画出误差执行结…...

Java基础(二十六):Java8 Stream流及Optional类

Java基础系列文章 Java基础(一)&#xff1a;语言概述 Java基础(二)&#xff1a;原码、反码、补码及进制之间的运算 Java基础(三)&#xff1a;数据类型与进制 Java基础(四)&#xff1a;逻辑运算符和位运算符 Java基础(五)&#xff1a;流程控制语句 Java基础(六)&#xff1…...

qt - 19种精美软件样式

qt - 19种精美软件样式 一、效果演示二、核心程序三、下载链接 一、效果演示 二、核心程序 #include "mainwindow.h"#include <QtAdvancedStylesheet.h> #include <QmlStyleUrlInterceptor.h>#include "ui_mainwindow.h" #include <QDir&g…...