SpringBoot整合ELK8.1.x实现日志中心教程

目录

背景

环境准备

环境安装

1.JDK安装

2.安装Elasticsearch

3.安装zookeeper

4.安装Kafka

5.安装logstash

6.安装file beat

解决方案场景

1.日志采集

1.1 应用日志配置

1.1.1 创建logback-spring.xml文件

1.1.2 创建LoggerFactory

1.1.3 trace日志的记录用法

1.1.4 dap-trace日志的记录用法

1.2 应用日志采集

2.日志缓冲

3.日志分析

4.日志存储

5.小总结

6.日志查看


背景

最近公司需要把产品中用到的组件做一下升级,从6.x升级到8.x,本人负责做升级,搞完之后一顿空虚,感觉总少点什么没完成,没错,忘记写点什么了,因此记录下从应用产生日志、filebeat收集日志、发送日志到kafka、logstash完成对日志的结构整理、最终入库es完整链路的方案以及一些配置的说明,希望能帮助到有需求的童鞋们,如果是入门的童鞋,照着本教程一步一步来的话,不出意外的话肯定是能手搓出一套ELK的日志中心的方案出来的;整理不易,望诸君高台贵手,点赞支持。

环境准备

应用:Springboot、Springcloud应用,版本不限

jvm:1.8、17,因为从es8.0.x之后就不支持jdk1.8.x了,本文选的版本是8.1.x,所以需要准备jdk17的环境,但是有些组件还是不能够支持17,因此还需要1.8的环境,具体支持下面会做详细介绍

filebeat、logstash、elasticsearch:8.1.2 

filebeat下载地址:Past Releases of Elastic Stack Software | Elasticicon-default.png?t=N7T8https://www.elastic.co/cn/downloads/past-releases#filebeat

logstash下载地址:Past Releases of Elastic Stack Software | Elasticicon-default.png?t=N7T8https://www.elastic.co/downloads/past-releases#logstash

es下载地址:Past Releases of Elastic Stack Software | Elasticicon-default.png?t=N7T8https://www.elastic.co/downloads/past-releases#elasticsearch

kafka:2.5.1;filebeat8.1.2支持的版本是0.8.2.0 and 2.6.0之间的所有版本,因此选择的版本是2.5.1,也就是2.6.0之前的最后一个release版本,具体支持的详细说明请移步官网介绍:Configure the Kafka output | Filebeat Reference [8.12] | Elastic

下载地址:Apache Kafka

zookeeper:2.5.8,kafka2.5.1对应的zk版本就是2.5.8,如何通过kafka确定zk的对应版本,本人有个方式:去官网下载Kafka的源码:以-src.tgz结尾的就是源码,下载完之后解压,打开解压目录下的/gradle目录,然后打开dependencies.gradle文件,然后搜索zookeeper,你就会看到有个配置如下:"zookeeper":"2.5.8" 这个就是当前kafka版本对应的zk的版本。

zookeeper下载地址:Index of /dist/zookeeper (apache.org)  注意:下载-bin.tar.gz结尾的文件,这个是编译好的介质,不带-bin的是源码,需要自己编译。

sky walking:9.3.0 为什么选择这个版本,因为从9.3.0开始才支持es8.x,之前的版本都只能支持最高到7.x,因此选择这个版本,当然也可以选择更高的版本,本文选择的是9.3.0版本。

下载地址:Downloads | Apache SkyWalking

自此所需要的介质以及对应版本都ok了,接下来就开始安装服务了,先从es开始安装,因为它是最底层环节,别人都需要靠它才能完成最终安装。

环境安装

1.JDK安装

官网下载jdk17,小版本随意,下载完之后上传至服务器,或者直接用wget命令也行,随意

解压完之后,按照如下命令进行全局变量配置

vim /etc/profile
export JAVA_HOME=/usr/lib/jdk17/jdk-17.0.10
export CLASSPATH=.:$JAVA_HOME/lib/
export PATH=.:$JAVA_HOME/bin:$PATH
# 生效
source  /etc/profile

2.安装Elasticsearch

先新建一个用户(出于安全考虑,elasticsearch默认不允许以root账号运行)

# 创建组
groupadd es
# 创建用户
useradd -m -g es es
# 设置密码 如果提示密码过于简单或少于8个字符则可将密码设置的复杂一点
passwd es

新建目录/opt/elk/es,然后上传介质并解压到指定目录 tar -zxvf elasticsearch-8.1.2-linux-x86_64.tar.gz -C /opt/elk/es

解压后目录结构如下:

修改Elasticsearch配置文件 文件路径{安装位置}/config/elasticsearch.yml,8.1.2默认是开启了ssl的,所以如果关闭则需要在配置文件中关闭,具体配置如下:

xpack.security.enabled:  false
xpack.security.transport.ssl.enabled: false
xpack.security.http.ssl.enabled: false# 修改es的ip地址
network.host:{安装机器IP}
node.name: node-1
cluster.initial_master_nodes: ["node-1"]

如果需要开启ssl则只需要配置下面三个配置即可,接下来就是JVM参数的调优,因为es比较吃cpu和内存,如果你的机器够大,建议设置一下jvm的参数,在{安装位置}/config/jvm.options文件中,增加:

-Xms10g
-Xmx10g

启动Elasticsearch

# 进入bin目录
cd  {安装位置}/bin
# 启动Elasticsearch
./elasticsearch -d

验证:访问ip:9200 看到如下返回,则启动成功,到此es初步安装完成,后面等所有环境安装完成之后还会根据业务场景对ELK各组件进行设置调整

3.安装zookeeper

注:2.5.8版本的zk还不支持jdk17,因此zk以及后面的kafka都不能安装在jdk17的环境下,本人选择的是jdk1.8,因此童鞋们注意下。

tar -zxvf apache-zookeeper-3.5.8-bin.tar.gz 解压介质,解压完之后是这样

解压完之后进入到{解压目录}/conf目录 

cd {安装目录}/conf
#修改conf目录下的zoo_sample.cfg 改成zoo.cfg  
mv zoo_sample.cfg zoo.cfg
#修改dataDir配置为自定义目录
vi zoo.cfg 

启动zookeeper,进入zookeeper/bin目录 

我的建议是不要配置环境变量,因为zookeeper本来就是个特定场景下的组件,感觉没什么必要配置全局环境变量,我看很多博主都是直接告诉人家配置全部变量,没必要

# 启动
sh zkServer.sh start
# 查看状态
sh zkServer.sh status
#停止
sh zkServer.sh stop 

执行查看状态,能看到下面信息则表示启动成功

Mode: standalone

4.安装Kafka

Kafka一样,2.5.1同样不支持jdk17(3.1.0开始才支持jdk17),本文选择的是jdk1.8

tar -zxvf kafka_2.12-2.5.1.tgz 解压完之后,进行配置修改

# 编辑{安装位置}/config/server.properties文件
# 修改server.properties文件中以下内容,配置kafka监听端口及ip地址
listeners=PLAINTEXT://{安装机器IP}:9092
advertised.listeners=PLAINTEXT://{安装机器IP}:9092
# 修改zookeeper地址
zookeeper.connect=127.0.0.1:2181/kafka
#修改日志文件路径
log.dirs=/tmp/kafka-logs

启动kafka

#进入kafka/bin目录,执行以下命令
nohup sh kafka-server-start.sh -daemon ../config/server.properties >>/dev/null 2>&1 &

5.安装logstash

注意,8.1.x版本的logstash需jdk17的环节;直接解压介质

tar -zxvf logstash-8.1.2-linux-x86_64.tar.gz 

到此安装已经完成了,哈哈,当然是开玩笑的,如果仅仅是安装,确实已经完成了,但是离我们想要的logstash还差一步:配置,具体配置请参考下面的日志分析章节,这里就不重复介绍了

6.安装file beat

file beat同logstash一样,这里暂时跳过,因为filebeat的配置要紧密结合业务来做对应配置的,所以等后面节合场景来具体说明

解决方案场景

本文所解决的业务场景就是:采集各应用产生的本地日志数据,进行汇总,可以进行应用日志查看,检索,链路分析,大致分为这几个步骤:1.日志采集;2.日志缓冲;3.分析日志;4.日志存储;5.日志查看。

1.日志采集

这章节的重点不是讲如何开发一个spring boot应用,而且从实际场景出发,一个真实的应用所产生的不同类型的日志是如何被file beat采集到的,从而完成日志中心的第一步:日志采集。

1.1 应用日志配置

在实际应用开发中,一个应用所产生的日志可能根据业务需求或者场景会分为很多种,需要记录的日志也随之有很多种,比如:应用程序本身日志-sys.log,应用的跟踪日志-trace.log,应用的sql日志-sql.log等等,这些日志根据类型不同所记录的日志内容肯定也是不一样的,那我们首先要做的第一步就是制定这些日志的格式,以便我们可以根据日志类型在后续logstash里面通过一系列filter生成不通的message和属性,最终生成不同的es索引存储在es中,方便我们做查询,当前这些都是后话,我们先把第一步搞定。

在这里,我们就假设应用需要生成3种类型日志,分别是:

app-sys.log:应用系统日志,记录程序本身的一些日志,包括spring框架、正常代码中自己记录的一些logger、异常等等信息

app-trace.log:应用内部service之间调用关系跟踪日志,记录service内部method的调用、出参入参等,方便查看应用本身的调用逻辑,以便能快速定位问题

app-dap-trace.log:微服务之间的调用日志,记录每个应用之间的调用,以便能查看一个业务场景的整个调用链,能快速知悉业务具体涉及的应用等。

根据这三种类型日志,那么就需要设计对应的日志记录的信息结构,那么我就根据日志的不通性质来定义一些通用的一些属性。当然,在实际开发中,每个人每个公司都有自己的业务,肯定跟着自己的业务来定,我这里只是做一个demo仅供参考。

既然设计已经初步完成了,那么接下来就是怎么实现了;在spring里面通常大家都是用logback来作为日志框架,那么我们就用logback来举例如何生成这三种类型的日志。

1.1.1 创建logback-spring.xml文件

logback文件主要是用来定义每个日志的结构、策略等等,在这里我就不对log back文件做详细的标签介绍了,以下logback-spring.xml 可直接复制即用,只需要修改里面的某些东西即可,具体见如下代码:

<?xml version="1.0" encoding="UTF-8"?><configuration><!-- Spring 自身日志配置 对应APP-SYS --><springProperty scope="context" name="appCode" source="spring.application.name" defaultValue=""/><springProperty scope="context" name="appInstId" source="application.app-inst-id" defaultValue=""/><conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/><conversionRule conversionWord="wex"converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/><conversionRule conversionWord="wEx"converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/><property name="APP_LOG_PATTERN_CONSOLE"value="${APP_LOG_PATTERN_CONSOLE:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %5p [${appCode:-},${appInstId:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B3-ParentSpanId:-}] ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/><property name="APP_LOG_PATTERN_FILE"value="${APP_LOG_PATTERN_FILE:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %5p [${appCode:-},${appInstId:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-B3-ParentSpanId:-}] ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/><property name="APP_LOG_LEVEL" value="${APP_LOG_LEVEL:-INFO}"/><property name="APP_LOG_FILE_MAX_SIZE" value="${APP_LOG_FILE_MAX_SIZE:-100MB}"/><property name="APP_LOG_FILE_MAX_HISTORY" value="${APP_LOG_FILE_MAX_HISTORY:-100}"/><property name="APP_LOG_FILE_TOTAL_SIZE" value="${APP_LOG_FILE_TOTAL_SIZE:-0}"/><property name="APP_LOG_FILE"value="./logs/app-sys.log}"/><!--配置APP-SYS到控制台--><appender name="APP_LOG_APPENDER_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${APP_LOG_PATTERN_CONSOLE}</pattern></encoder></appender><!--配置APP-SYS到文件--><appender name="APP_LOG_APPENDER_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><encoder><pattern>${APP_LOG_PATTERN_FILE}</pattern></encoder><file>${APP_LOG_FILE}</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>${APP_LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern><maxFileSize>${APP_LOG_FILE_MAX_SIZE}</maxFileSize><maxHistory>${APP_LOG_FILE_MAX_HISTORY}</maxHistory><totalSizeCap>${APP_LOG_FILE_TOTAL_SIZE}</totalSizeCap></rollingPolicy></appender><!-- 复制自 spring boot org/springframework/boot/logging/logback/defaults.xml --><logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/><logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/><logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/><logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/><logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/><logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/><logger name="org.hibernate.validator.internal.util.Version" level="WARN"/><!-- APP-TRACE 日志配置 --><property name="APP_TRACE_LOG_PATTERN"value="[${appCode:-},${appInstId:-},%X{X-B3-TraceId:-}] [%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}] %m%n"/><property name="APP_TRACE_LOG_FILE" value="./logs/app-trace.log}"/><property name="APP_TRACE_LOG_LEVEL" value="${APP_TRACE_LOG_LEVEL:-INFO}"/><!--APP-TRACE to file--><appender name="APP_TRACE_LOG_APPENDER_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><encoder><pattern>${APP_TRACE_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder><file>${APP_TRACE_LOG_FILE}</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>${APP_TRACE_LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern><maxFileSize>${APP_TRACE_LOG_FILE_MAX_SIZE:-100MB}</maxFileSize><maxHistory>${APP_TRACE_LOG_FILE_MAX_HISTORY:-100}</maxHistory><totalSizeCap>${APP_TRACE_LOG_FILE_TOTAL_SIZE:-0}</totalSizeCap></rollingPolicy></appender><!--DAP-TRACE 日志配置--><conversionRule conversionWord="dapTraceJsonMsg"converterClass="com.testlog.wx.testspringlog.LogJSONMessageConverter"/><property name="DAP_TRACE_LOG_FILE"value="./logs/app-dap-trace.log}"/><property name="DAP_TRACE_LOG_LEVEL" value="${DAP_TRACE_LOG_LEVEL:-INFO}"/><property name="DAP_TRACE_LOG_FILE_MAX_SIZE" value="${DAP_TRACE_LOG_FILE_MAX_SIZE:-100MB}"/><property name="DAP_TRACE_LOG_FILE_MAX_HISTORY" value="${DAP_TRACE_LOG_FILE_MAX_HISTORY:-100}"/><property name="DAP_TRACE_LOG_FILE_TOTAL_SIZE" value="${DAP_TRACE_LOG_FILE_TOTAL_SIZE:-0}"/><!--DAP-TRACE to console--><appender name="DAP_TRACE_LOG_APPENDER_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%dapTraceJsonMsg</pattern><charset>UTF-8</charset></encoder></appender><!--DAP-TRACE to file--><appender name="DAP_TRACE_LOG_APPENDER_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><encoder><pattern>%dapTraceJsonMsg</pattern><charset>UTF-8</charset></encoder><file>${DAP_TRACE_LOG_FILE}</file><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><fileNamePattern>${DAP_TRACE_LOG_FILE}.%d{yyyy-MM-dd}.%i</fileNamePattern><maxFileSize>${DAP_TRACE_LOG_FILE_MAX_SIZE}</maxFileSize><maxHistory>${DAP_TRACE_LOG_FILE_MAX_HISTORY}</maxHistory><totalSizeCap>${DAP_TRACE_LOG_FILE_TOTAL_SIZE}</totalSizeCap></rollingPolicy></appender><!--日志开关--><root level="${APP_LOG_LEVEL}"><appender-ref ref="APP_LOG_APPENDER_CONSOLE"/><appender-ref ref="APP_LOG_APPENDER_FILE"/></root><logger name="app-trace" additivity="false" level="${APP_TRACE_LOG_LEVEL}"><appender-ref ref="APP_TRACE_LOG_APPENDER_FILE"/></logger><logger name="app-dap-trace" additivity="false" level="${DAP_TRACE_LOG_LEVEL}"><appender-ref ref="DAP_TRACE_LOG_APPENDER_CONSOLE"/><appender-ref ref="DAP_TRACE_LOG_APPENDER_FILE"/></logger></configuration>
1.1.2 创建LoggerFactory

LoggerFactory的作用是为了给程序员更方便的创建不通的logger,我们在这里就封装app-trace和dap-trace logger的创建,app-sys用系统日志即可,无需额外创建,具体代码如下:

package com.testlog.wx.testspringlog;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @Description TODO* @Author wx * @Date 2024/3/28*/
public class DemoTraceLoggerFactory {public static final String TRACE_LOGGER_NAME_PREFIX = "app-trace.";public static final String DAP_TRACE_LOGGER_NAME_PREFIX = "app-dap-trace.";public static Logger getTraceLogger(Class<?> clazz) {return LoggerFactory.getLogger(TRACE_LOGGER_NAME_PREFIX + clazz.getName());}public static Logger getDapTraceLogger(Class<?> clazz) {return LoggerFactory.getLogger(DAP_TRACE_LOGGER_NAME_PREFIX + clazz.getName());}}
1.1.3 trace日志的记录用法

app-trace的作用是用来记录应用内部不同service.method的调用链,因此我们编写一个demo,两个service来做一个演示具体app-trace的记录效果;

首先新建 IDemoService 以及实现类 DemoServiceImpl

IDemoService:

package com.testlog.wx.testspringlog;/*** @Description TODO* @Author wx520* @Date 2024/3/28*/
public interface IDemoService {String sayHi(String name);}

DemoServiceImpl:

package com.testlog.wx.testspringlog;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @Description TODO* @Author wx* @Date 2024/3/28*/
@Service
public class DemoServiceImpl implements IDemoService {@Autowiredprivate IDemoService2 demoService2;@Overridepublic String sayHi(String name) {System.out.println(name);demoService2.test11();return "hello word";}}

再新建IDemoService2以及实现类DemoServiceImpl2

IDemoService2:

package com.testlog.wx.testspringlog;/*** @Description TODO* @Author wx520* @Date 2024/3/28*/
public interface IDemoService2 {void test11();}

DemoServiceImpl2:

package com.testlog.wx.testspringlog;import org.springframework.stereotype.Service;/*** @Description TODO* @Author wx * @Date 2024/3/28*/
@Service
public class DemoServiceImpl2 implements IDemoService2 {@Overridepublic void test11() {System.out.println("test");}}

建完service之后那么就开始写记录trace日志的逻辑,我们用aspect来对@Service做拦截,用@Aroud来进行记录请求在进入method之前和之后的日志信息,具体实现如下:

package com.testlog.wx.testspringlog;import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;import java.util.UUID;/*** @Description TODO* @Author wx* @Date 2024/3/28*/
@Aspect
@Component
public class SpringTraceLoggerMethodInterceptor {private Logger traceLogger = DemoTraceLoggerFactory.getTraceLogger(getClass());@Around("@within(org.springframework.stereotype.Service)")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {Class clazz = joinPoint.getTarget().getClass();String beanClassName = clazz.getName();MethodSignature signature = (MethodSignature) joinPoint.getSignature();String methodName = signature.getName();String message = "Spring Bean " + beanClassName + "#" + methodName;String[] argNames = signature.getParameterNames();Object[] parameters = joinPoint.getArgs();traceLogger.info(entry(message, buildParams(argNames, parameters)));Object result = joinPoint.proceed();traceLogger.info(exit(message, buildResult(result)));return result;}private String buildResult(Object result) {StringBuffer sb = new StringBuffer();sb.append("{");sb.append("result:").append(JsonUtil.toJson(result));sb.append("}");return sb.toString();}private String buildParams(String[] argNames, Object[] parameters) {StringBuffer sb = new StringBuffer();sb.append("{");for (int i = 0; i < argNames.length; i++) {sb.append("" + argNames[i] + ":").append("" + JsonUtil.toJson(parameters[i]) + "");if (i != argNames.length - 1) sb.append(",");}sb.append("}");return sb.toString();}/*** 测试代码 写的不够严谨 不能作为正式开发使用** @param message*/private String entry(String message, String params) {String traceId = MDC.get("X-B3-TraceId");if (StringUtils.isEmpty(traceId)) {MDC.put("X-B3-TraceId", UUID.randomUUID().toString());}return getFormatSysLoggerMessages(message, params, " invoker start.", "Begin");}private String exit(String message, String params) {return getFormatSysLoggerMessages(message, params, " invoker end.", "End");}/*** 测试代码 写的不够严谨 不能作为正式开发使用** @param message* @param params* @param action* @return*/private String getFormatSysLoggerMessages(String message, String params, String action, String loggerType) {StringBuffer sb = new StringBuffer();sb.append("[");sb.append(loggerType).append("]");sb.append("[").append(message).append("]");sb.append("[").append(params).append("]");sb.append("[Spring Bean ").append(message).append(action).append("]");return sb.toString();}}

最后建一个controller用来做远程调用入口

package com.testlog.wx.testspringlog;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import static org.springframework.http.MediaType.ALL_VALUE;/*** @Description TODO* @Author wx * @Date 2024/3/28*/
@RestController
@RequestMapping(value = "/demo", consumes = ALL_VALUE)
public class DemoController {@Autowiredprivate IDemoService demoService;@GetMapping("/sayHi/{name}")public String sayHi(@PathVariable("name") String name) {return demoService.sayHi(name);}}

效果:

开发工具直接启动应用,我们会发现控制台输入的日志信息就跟我们在logback-spring.xml里面配置的app-sys日志格式是一样的,同时,在当前应用的根目录/logs目录下会生成一个app-sys.log的日志文件;

app-sys.log 控制台效果如下:

app-sys.log 文件内容效果如下:

那么我们接下来浏览器直接访问controller接口或者用其他工具都可,访问成功之后会在/logs目录生成app-trace.log日志文件,具体内容如下:

[test-spring-log,,e00cc8a5-fcc0-46d5-b694-eccc8e129bc7] [2024-03-28 14:41:50.636] [Begin][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi][{name:"aa"}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi invoker start.]
[test-spring-log,,e00cc8a5-fcc0-46d5-b694-eccc8e129bc7] [2024-03-28 14:41:50.642] [Begin][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11][{}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11 invoker start.]
[test-spring-log,,e00cc8a5-fcc0-46d5-b694-eccc8e129bc7] [2024-03-28 14:41:50.645] [End][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11][{result:null}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11 invoker end.]
[test-spring-log,,e00cc8a5-fcc0-46d5-b694-eccc8e129bc7] [2024-03-28 14:41:50.645] [End][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi][{result:"hello word"}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi invoker end.]
[test-spring-log,,daa999c2-49d1-40e0-83c6-b33781926185] [2024-03-28 14:51:33.048] [Begin][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi][{name:"aa"}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi invoker start.]
[test-spring-log,,daa999c2-49d1-40e0-83c6-b33781926185] [2024-03-28 14:51:33.055] [Begin][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11][{}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11 invoker start.]
[test-spring-log,,daa999c2-49d1-40e0-83c6-b33781926185] [2024-03-28 14:51:33.059] [End][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11][{result:null}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl2#test11 invoker end.]
[test-spring-log,,daa999c2-49d1-40e0-83c6-b33781926185] [2024-03-28 14:51:33.059] [End][Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi][{result:"hello word"}][Spring Bean Spring Bean com.testlog.wx.testspringlog.DemoServiceImpl#sayHi invoker end.]

你会发现我们日志中一个完整的traceId包含四条日志信息:service1.sayHi begin---》service2.test11 begin ---->service2.tes11 end ---> service1.sayHi end;这就是app-trace日志记录的一个请求的一条完成trace链路。

1.1.4 dap-trace日志的记录用法

dap-trace比较特殊,因为它的作用是用来记录每个微服务实例之间的调用链路的,本来考虑这里加入sky walking一起讲的,但是感觉这样篇幅就收不住了就打住了,后期有时间会整理一套应用节合sky walking以及节合业务属性来做追踪的方案,因此这里就直接使用sleuth来做trace跟踪,

还有一个需要注意的地方就是:前面两种日志都是自定义的常规格式,因此dap-trace日志楼主就想用个特殊格式-json 来做记录,这样也方便大家有这个需求:怎么用logback记录json格式的日志,什么格式这里都有,无需再去查找资料了

pom文件添加sleuth依赖:版本自定 无需跟本文一致

        <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-sleuth</artifactId><version>3.1.7</version></dependency>

既然是记录dap-trace日志,那么肯定是要在请求进来应用后以及响应之前做日志记录,那么楼主推荐大家使用spring的OncePreRequestFilter来做拦截,这个filter的作用和原理我就不说了,望名知意,具体大家可以去官网看文档介绍,代码如下:

package com.testlog.wx.testspringlog;import org.slf4j.Logger;
import org.slf4j.MarkerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerMapping;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;/*** @Description TODO* @Author wx* @Date 2024/3/28*/
@Component
public class DapTraceLoggerFilter extends OncePerRequestFilter implements Ordered {private Logger dapTraceLogger = DemoTraceLoggerFactory.getDapTraceLogger(getClass());@Overridepublic int getOrder() {return HIGHEST_PRECEDENCE + 10;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//这里用来记录dap-tracelong start = System.nanoTime();logRequest(request);filterChain.doFilter(request, response);logResponse(start, response, request);}private void logResponse(long start, HttpServletResponse response, HttpServletRequest request) {Map<String, Object> map = new HashMap<>();map.put("comp", "SPRING_WEB");map.put("r_event", "SS");map.put("hsc", response.getStatus());map.put("api_path", request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));map.put("etime", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start));dapTraceLogger.info(MarkerFactory.getMarker("//@args-json-formatter"), null, map);}private void logRequest(HttpServletRequest request) {Map<String, Object> map = new HashMap<>();map.put("comp", "SPRING_WEB");map.put("r_event", "SR");map.put("hurl", request.getRequestURL().toString());map.put("hm", request.getMethod());map.put("hra", request.getRemoteAddr());
//使用该Marker时, 如果日志参数里有Map类型参数, 则会将Map变成json结构dapTraceLogger.info(MarkerFactory.getMarker("//@args-json-formatter"), null, map);}
}

因为是需要json格式的日志,楼主这里就没有用常规方式去配置,也就是在logback里面配置一大堆属性,这个方式我感觉有点不方便,既然是json,那属性肯定都是动态或者自定义的,方便后期扩展,否则每次新增属性都要去修改logback就很麻烦,不利于迭代和维护,具体代码如下:

package com.testlog.wx.testspringlog;import ch.qos.logback.classic.pattern.ThrowableHandlingConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.slf4j.MDC;
import org.slf4j.MarkerFactory;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;/*** @Description TODO* @Author wx * @Date 2024/3/28*/
public class LogJSONMessageConverter extends ThrowableHandlingConverter {private static final ThreadLocal<SimpleDateFormat> SDF = new ThreadLocal<SimpleDateFormat>() {protected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");}};@Overridepublic String convert(ILoggingEvent le) {Map<String, Object> map = new HashMap<>();String traceId = MDC.get("traceId");String spanId = MDC.get("spanId");String psid = MDC.get("parentId");//skywalking 相关代码暂时去掉 有感兴趣的同学也可以自己打开注释
//        String swTraceId = MDC.get("swTraceId");
//        if (StringUtils.isEmpty(swTraceId) || StringUtils.equals("N/A", swTraceId)) {
//            swTraceId = TraceContext.traceId();
//        }map.put("time", SDF.get().format(new Date(le.getTimeStamp())));map.put("app", LoggerProperties.INSTANCE.getAppCode());map.put("tid", traceId);map.put("sid", spanId);map.put("psid", psid);
//        map.put("sw_tid", swTraceId);if (le.getMarker() == MarkerFactory.getMarker("//@args-json-formatter")) {Object[] array = le.getArgumentArray();if (array != null && array.length > 0) {for (int i = 0, len = array.length; i < len; i++) {Object item = array[i];// 只处理 Map类型 这个根据自己的模型来就是了if (item instanceof Map) {for (Map.Entry<?, ?> entry : ((Map<?, ?>) item).entrySet()) {map.put(String.valueOf(entry.getKey()), entry.getValue());}}}}}return JsonUtil.toJson(map) + System.lineSeparator();}
}

效果:

同样的,访问刚刚的controller接口,会在控制台以及/logs目录生成app-dap-trace.log日志文件,具体效果如下:

{"app":"test-spring-log","comp":"SPRING_WEB","sw_tid":"29dd2d14d50a4be2ac2042b658b838e6.46.17116086928750001","r_event":"SR","hm":"GET","hurl":"http://localhost:8080/demo/sayHi/aa","hra":"0:0:0:0:0:0:0:1","time":"2024-03-28 14:51:32.907","tid":"60c5cfdc3694eb1b","sid":"60c5cfdc3694eb1b"}
{"app":"test-spring-log","comp":"SPRING_WEB","sw_tid":"29dd2d14d50a4be2ac2042b658b838e6.46.17116086928750001","r_event":"SS","etime":183,"hsc":200,"time":"2024-03-28 14:51:33.089","api_path":"/demo/sayHi/{name}","tid":"60c5cfdc3694eb1b","sid":"60c5cfdc3694eb1b"}

同样的,一次请求会记录SR和SS两条日志,全局跟踪链的话就用tid(也就是traceId)来做跟踪。

自此,应用日志配置已经全部完成,接下来就是日志到底如何采集的。

1.2 应用日志采集

日志采集这块就交给file beat了,但是我们有三套日志文件,想要file beat能识别不同格式的日志以便能正确采集到日志信息,就得需要对应配置好file beat,,进入{安装目录}/,新建test-filebeat.yml配置文件进行配置,具体配置如下:

filebeat.inputs:# -----
# app-dap-trace-log
# -----        
- type: filestream    id: app-dap-trace-log-idenabled: truepaths: - /opt/elk/test-logs/app-dap-trace.log*- /opt/elk/test-logs/app-dap-trace.log*- /opt/elk/test-logs/app-dap-trace.log*- /opt/elk/test-logs/app-dap-trace.log*encoding: UTF-8   fields:app-topic-name: app-logapp-log-type: app-dap-trace-log  prospector.scanner.exclude_files: [".gz$"]parsers:    #因为我们的dap-trace日志是json格式的,所以需要用的file beat的ndjson来做解析- ndjson:target: ""add_error_key: trueoverwrite_keys: true# -----
# app-sys-log
# -----
- type: filestreamid: app-sys-log-idenabled: truepaths: - /opt/elk/test-logs/app-sys.log*- /opt/elk/test-logs/app-sys.log*- /opt/elk/test-logs/app-sys.log*encoding: UTF-8   fields:app-topic-name: app-logapp-log-type: app-sys-logprospector.scanner.exclude_files: [".gz$"]parsers:- multiline:type: patternpattern: '^\d{4}\-\d{2}\-\d{2}'  negate: truematch: aftermax_lines: 500# -----
# app-trace-log
# -----
- type: filestreamid: app-log-idenabled: truepaths: - /opt/elk/test-logs/app-trace.log*encoding: UTF-8   fields:app-topic-name: app-logapp-log-type: app-trace-logprospector.scanner.exclude_files: [".gz$"]parsers:- multiline:type: patternpattern: '^\[[^,]+,[^,]+,'negate: truematch: aftermax_lines: 500# -----
# output
# -----
output.kafka:enabled: truehosts: ["localhost:9092"]topic: '%{[fields.app-topic-name]}'max_retries: 3bulk_max_size: 2048timeout: 30broker_timeout: 10channel_buffer_size: 256keep_alive: 60compression: gzipmax_message_bytes: 100000000required_acks: 1client_id: 'app-filebeats'# -----
# 8.x版本logstash会用event做字段,冲突 不想改原本的日志结构 这里做一下rename
# -----  
processors:- rename:fields:- from: "event"to: "r_event"ignore_missing: falsefail_on_error: true  

配置解析:

官方文档: Configure inputs | Filebeat Reference [8.12] | Elastic  版本选择8.1.2

scope属性说明
inputs配置typefilestream老版本为log,新版本统一用filestream
id自定义多个filestream需要用id来区分
paths路径配置当前filestream要采集的日志路径,数组格式,可配置多个
fields字段可自定义添加当前采集的数据的字段属性,比如本文中用添加了app-log-type和app-topic-name用来给后续用
parsersndjson解析器 可多配
target解析json的时候放在哪个属性下,为空就是放在跟目录,老版本是keys_under_root
multiline处理多行数据
typepattern正则匹配多行数据
pattern正则表达式匹配上的数据才会采集
outputkafka输出
topic输出到kafka的topic节合业务定义对应的topic,logstash那边读取对应的topic数据,各行其职
processorsrename修改属性字段名称
fields
from原属性
to目标属性

 在这里为什么要单独列一下processors配置呢,因为开头也说了,楼主因为是升级ELK,但是应用日志模板升级的时候你肯定是不能改的,一改,日志结构就没法兼容了,所以大家也有可能会遇到楼主同样的问题,因为之前楼主公司的trace日志里面有个属性叫:event,是String,记录是输入还是输出类型的,但是8.1.x版本的logstash会把event当作它自己的属性,而且还是个Object类型,一升级,就会导致日志在logstash那边没办法识别,从而存进es的时候就会发现存的索引不对,这个时候file beat的这个rename处理就有大作用了,我可以不用修改应用的日志结构从而兼容升级之前的日志数据,而且file beat还有很多其他的处理器,大家在遇到问题的时候建议多去看看官方文档找找,总有解决办法的。

到此,日志采集环节就已经完成了,接下来就是日志缓冲。

2.日志缓冲

这里加上kafka的主要作用是用来做缓冲,不过这一块看具体业务,有些业务量小的,数据没那么夸张就感觉没必要上,那有些业务非常频繁,数据量很大,如果直接从beat到logstash很大概率会讲logstash压垮,或者是导致logstash非常吃资源,这样就有点得不偿失。

说白了,日志数据并不像其他业务数据一样需要时效性以及100%准确性,只需要在特定的时间完成处理即可,所以kafka就派上用场了。

在这一环节,大家回过头来看上面file beat的配置,大家会发现我们配置了一个自定义的fields:app-topic-name,所以在kafka这里我们也要将这个topic创建出来,具体命令如下:

#进入到kafka安装目录/bin目录下执行命令
./kafka-topics.sh --create --zookeeper 127.0.0.1:2181/kafka --replication-factor 1 --partitions 1 --topic app-log

3.日志分析

现在整个ELK环节从file beat采集--->kafka缓冲--->已经来到了logstash分析整理了,logstash在这里的主要作用就是通过读取kafka推送过来的日志数据,然后分析日志结构,整理成我们和es都想要的格式存进es,进入到{安装目录}/config目录,新建一个test-logstash.conf文件,然后进行配置,具体配置如下:


input {kafka {bootstrap_servers => ["127.0.0.1:9092"]topics_pattern => "app-.*"group_id => "app-logstash"client_id => "app-logstash"auto_offset_reset => "earliest"codec => json {charset => "UTF-8"}consumer_threads => 3decorate_events => true}
}filter {# -----# pre process [fields][app-log-type]# -----if ![fields][app-log-type] and [log][file][path] {grok {match => { "[log][file][path]" => "%{GREEDYDATA}(\\|\/)%{DATA:log-file-name}\."}}mutate {add_field => { "[fields][app-log-type]" => "%{[log-file-name]}-log" } }}# -----# parse log message as json field# -----# app-sys-log is log generated by app8 if [fields][app-log-type] =~ "app-sys-log" {grok {match => { "message" => "%{TIMESTAMP_ISO8601:time}\s+%{LOGLEVEL:level}\s+\[%{DATA:app},%{DATA:inst},%{DATA:tid},%{DATA:sid},%{DATA:psid}\]%{GREEDYDATA:msg}" }}}else if [fields][app-log-type] =~ "app-trace-log" {grok {match => { "message" => "\[%{DATA:app},%{DATA:inst},%{DATA:tid}\]\[%{TIMESTAMP_ISO8601:time}\]%{GREEDYDATA:msg}" }}}# -----# replace logstash @timestamp with log timestamp, and output.elasticsearch create index use this time# -----date {match => [ "time", "yyyy-MM-dd HH:mm:ss.SSS", "ISO8601" ]locale => "cn"timezone => "Asia/Shanghai"}
}output {elasticsearch {hosts => ["127.0.0.1:9200"]index =>  "%{[fields][app-log-type]}-%{+YYYY.MM.dd}"}
}

这里为什么只做了两种日志type的分析,因为dap-trace本身就是json格式的,无需再处理了,而其他两种的日志为什么不也生成json格式的呢,这就涉及到一个开发习惯了,相信绝大数人看日志的话肯定还是习惯于像spring框架产生的那种格式的日志,因为大多数人第一次接触开发,从成功启动第一个应用到看到控制台输出日志,看到的日志就是类似 

2024-03-28 10:21:38.803  INFO [test-spring-log,,,,] 20792 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

这种格式的,这种格式日志也方便大家查看信息定位问题,尤其是堆栈信息,而dap-trace日志不同,它是记录应用之间的调用链的,一般这种日志都是通过第三方工具或者节合业务需求来查看的,所以一般都是记录的json格式数据。

在这里做日志分析的目的就是从日志数据中解析出我们想要的属性,这样存进es的时候方便后续通过某些自定义的一些业务属性进行条件查询,比如,按照日志记录时间查询某时间段的sys日志;比如按照应用appCode来查找不同应用的sys日志,如果你只是一股脑把数据放进es,查是能查,查出来的数据就不一定是你想要的了,就不贴合业务场景了

这里我就不过多介绍logstash配置文件里的那些配置具体作用了,感兴趣的同学可去官方文档查看: Logstash Reference [8.13] | Elastic 

4.日志存储

这一环节有两个方面需要思考:

1.怎么存

2.怎么查

在上一个环节:日志分析章节我们可以看到,我们根据日志的类型去单独解析出具体日子内容,然后解析出来我们需要的属性,然后在根据日志生成的时间+日志类型作为es的索引构成一个完整的日志索引模型,那这个环节我们要做的就是匹配logstash过来的数据,然后按照我们设计好的日志索引模板来存储索引数据,所以我们首先要做的就是:制定好对应的日志类型的索引模板;那么索引模板定好了,所有的数据都会按照我们设计的模型存储到es中,查自然就不在话下了。

针对我们设计的三种日志类型我分别设计了三个对应的日志索引模板,具体如下:

app-sys、app-trace日志索引模板:

{"index_patterns": ["*app-trace-log*","*app-sys-log*"],"priority": 1,"template": {"mappings": {"properties": {"time": {"type": "date","format": "yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"},"app": {"type": "keyword"},"inst": {"type": "keyword"},"tid": {"type": "keyword"},"sid": {"type": "keyword"},"psid": {"type": "keyword"}}}}
}

app-dap-trace日志索引模板:

{"index_patterns": ["*app-dap-trace-log*"],"priority": 3,"template": {"mappings": {"properties": {"time": {"type": "date","format": "yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"},"app": {"type": "keyword"},"inst": {"type": "keyword"},"tid": {"type": "keyword"},"sid": {"type": "keyword"},"psid": {"type": "keyword"},"comp": {"type": "keyword"},"r_event": {"type": "keyword"},"sw_tid": {"type": "keyword"},"api_path": {"type": "keyword"}}}}
}

接下来就是让日志模板在es生效,将上述模板分别保存为app-sys-trace-log-template.json、app-dap-trace-log-template.json文件,然后将文件上传至es安装目录,然后执行如下命令:

curl -H "Content-Type:application/json" -XPUT 'http://ip:9200/_index_template/app-sys-trace-log' -d @app-sys-trace-log-template.json
curl -H "Content-Type:application/json" -XPUT 'http://ip:9200/_index_template/app-dap-trace-log' -d @app-dap-trace-log-template.json

命令解析:

1.  创建索引模板的接口格式是ip:port/_index_template/{模板名称},模板名称可换成你们自定义的名称。

2.  -d @后面的是对应的模板文件名称,我这个命令是在模板所在的目录执行的,如果你是要在其他地方执行,请带上完整路径。

3. 模板里面的priority属性一定要注意:多个模板存在的情况,一定要修改值,不能存在相同priority的模板,这个属性其实就是order的作用,新版本es不允许模板的order存在相同配置。

或者可以直接用postman请求es创建索引模板接口也行,接口还是上面命令里面的接口,body直接复制上面模板内容即可。

创建完模板之后,验证一下我们刚刚创建的索引有没有成功,请求接口:http://ip:9200/_index_template/{模板名称} 查询索引模板的接口,然后查看有没有我们刚刚创建的索引模板,如下所示:

或者查看响应:{"acknowledged":true} 则表示成功

5.小总结

到这里,我们再来捋一下整体流程:

1.首先在开发之前先大家讨论制定好日志类型、日志格式

2.接下来就是定好日志框架,包括日志配置、日志的输入输出等

3.根据前两步对file beat进行配置,能够正确采集到我们应用产生的日志文件内的数据

4.file beat配置好日志特定的topic,然后采集的数据输出到kafka

5.kafka进行日志专属topic的创建

6.logstash通过读取kafka-日志topic中的日志数据对日志进行分析、整理,最后生成es索引所需要的特定格式数据,并输出到es中

7.es制定好日志对应所需要的索引模板,然后创建模板,等待数据从logstash输入。

捋完流程我们再来回过头来将流程跑一遍看看效果如何,日志是否能按照我们所设计的完美进入es中,请按照以下流程来启动各组件(ps:因为各个组件有严格的上下游关系,所以我们一般都是从最底层开始);

1.启动我们的应用,确保有日志输出(随便访问下接口让日志产生)

2.check一下es有没有添加我们的索引模板,check方式请看上面,索引模板ok的话,我们启动es,执行如下命令:

# 进入{es安装目录/bin目录}
nohup sh elasticsearch -d >> ../logs/out.log 2>&1 &

3.启动zookeeper,执行如下命令:

# j进入{安装目录}/bin目录 启动
sh zkServer.sh start
# 查看状态
sh zkServer.sh status
#停止
sh zkServer.sh stop 

4.启动kafka,执行如下命令:

#启动kafka
nohup sh kafka-server-start.sh -daemon ../config/server.properties >>/dev/null 2>&1 &## 初始化topic
./kafka-topics.sh --create --zookeeper 127.0.0.1:2181/kafka --replication-factor 1 --partitions 1 --topic app-log

5.启动logstash,执行如下命令

#进入{安装目录}/bin目录
nohup sh logstash -f ../config/test-logstash.conf >> ../logs/out.log 2>&1 &

6.启动file beat,执行如下命令

#进入{安装目录}
nohup ./filebeat -e -c test-filebeat.yml >/dev/null 2>&1 &

7.验证日志是否正确输出到es

先查看是否有正确按照我们的配置生成索引,请浏览器直接访问接口:

http://ip:9200/_cat/indices?v ,不出意外的话我们就能看到返回的数据有我们定义的索引数据存在。

然后我们查看一下具体某个索引里面的数据,看下结构数据跟我们设定的模板对不对的上,

用远程工具,比如postman访问接口:

post请求:http://ip:9200/{索引名称}/_search?ignore_unavailable=true ,正常情况下会返回如下格式的数据:

{"took": 1,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 5760,"relation": "eq"},"max_score": 1.0,"hits": [{"_index": "app-dap-trace-log-2024.03.02","_id": "J1DEQI4BgHvBZxO0CTeC","_score": 1.0,"_ignored": ["event.original.keyword"],"_source": {"input": {"type": "filestream"},"hra": "127.0.0.1","host": {"name": "node-127-0-0-1"},"hurl": "http://127.0.0.1:14083/api/demo/aa","@version": "1","sw_tid": "","comp": "SPRING_WEB","inst": "127.0.0.1:test-spring-log:14083","app": "test-spring-log","ecs": {"version": "8.0.0"},"r_event": "SR","@timestamp": "2024-03-02T17:57:05.056Z","hm": "POST","log": {"offset": 415082,"file": {"path": "/opt/elk/test-logs/app-dap-trace.log.2024-03-03.0"}},"agent": {"name": "node-127-0-0-1","version": "8.1.2","ephemeral_id": "5ff7d980-129a-4af5-bade-85c656a63fec","id": "d72a0d75-7491-4ebf-9bdd-f461f84dd7bc","type": "filebeat"},"event": {"original": "{\"@timestamp\":\"2024-03-15T06:18:58.222Z\",\"@metadata\":{\"beat\":\"filebeat\",\"type\":\"_doc\",\"version\":\"8.1.2\"}}"},"time": "2024-03-03 01:57:05.056","sid": "2bdf6a3a87116341","tid": "2bdf6a3a87116341","fields": {"app-topic-name": "app-log","app-log-type": "app-dap-trace-log"}}}]}
}

其中,hits.hits._source中的数据就是所存进去的该类型的日志数据以及结构,我们可以看到_source下面的一些key都是我们在模板里面定好的,没有定的就是logstash本身的属性字段,这就是一个我们所需要的一个完整的es索引结构,将来做条件查询的时候这些字段会有大用处。

如果你按照本文来一步一步走下来,得到的数据结构和上面的示例一样,就说明你已经成功了。

6.日志查看

这一块其实有点不大好讲,因为涉及到实际业务场景,每个人所运用的场景都不一样,我就按照我现在做的来给大家讲一讲思路,归根结底,日志中心最后展现给人的就是这个环节:日志查看。用户也是最关心这一点,其他的包括采集、存储什么什么的用户是不关心的,他们只需要一个符合他们的结果就行,其他你们程序员搞定就行(~!~),所以日志查看最核心的就是节合业务场景,那本文所采用的方案就是将业务字段存储进es索引中,节合日志查看系统进行业务场景条件查询日志,这样的话日志和业务就能紧密节合在一起了,如何将业务字段存储进es中,前面已经很详细的描述了,接下来就是如何查询,这里我只是讲一下大致的实现思路细节,给大家提供一个可参考的方向,具体如下:

Rest apis

es已经提供了一套rest api,无论大家是使用java es client还是其他方式,底层都是rest api,所以直接使用rest api,再节合业务场景进行查询es即可,我就列举一些常见的查询示例

根据索引名称条件查询日志信息

POST:http://10.16.16.103:9200/{index_name}/_search 

body:

{"from" : 0, "size" : 300,"query":{"bool":{"filter":[{"range":{"time":{"gte":"2024-04-02 10:31:23.000", "lt":"2024-04-03 10:31:23.000"}}}, {"term": {"app": "appName"}}, {"term": {"inst": "应用实例id"}} //todo 也可以加其他业务字段 只要是你日志里面对应有的]}},"sort": { "time": { "order": "asc" }}
}

这样我们就可以在body里面自定义添加我们业务上需要的查询条件进行search,日志系统也可以自行节合业务场景然后封装自己的rest api进行查询即可,虽然现在es也自己提供了java client,但是我感觉这个适合那种场景:应用需要自行操作es,比如将es当作应用本身的存储来交互;这种的就比较适合使用client来进行操作,但是日志系统并不需要其他的,只需要查询而已,所以为了不强依赖es,我们可以节合业务场景自行封装rest api。

自此,一个完整的springboot整合ELK的日志中心DEMO方案就算是初步完成了,如有更好的方案,欢迎交流,点个赞吧~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/791413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux上管理文件系统

Linux上管理文件系统 机械硬盘 机械硬盘由多块盘片组成&#xff0c;它们都绕着主轴旋转。每块盘片上下方都有读写磁头悬浮在盘片上下方&#xff0c;它们与盘片的距离极小。在每次读写数据时盘片旋转&#xff0c;读写磁头被磁臂控制着不断的移动来读取其中的数据。 所有的盘片…

实操:Dropzone.js实现文件上传

&#x1f3e0;官网 点我前往 &#x1f953;依赖 <script src"https://unpkg.com/dropzone5/dist/min/dropzone.min.js"></script> <link rel"stylesheet" href"https://unpkg.com/dropzone5/dist/min/dropzone.min.css" type&…

手搓链表(java)(不完整)

手搓链表&#xff08;java&#xff09;&#xff08;不完整&#xff09; 文章目录 手搓链表&#xff08;java&#xff09;&#xff08;不完整&#xff09;前言一、代码1.MyLinkedList类&#xff1a;2.测试类&#xff1a; 总结 前言 提示&#xff1a;以下是本篇文章正文内容&…

LeetCode_876(链表的中间结点)

//双指针//时间复杂度O(n) 空间复杂度O(1)public ListNode middleNode(ListNode head) {ListNode slowhead,fast head;while (fast!null && fast.next!null){slow slow.next;fast fast.next.next;}return slow;} 1->2->3->4->5->null 快指针移动两个…

Linux中的shell脚本之流程控制循环遍历

3 条件判断 4 流程控制语句 1&#xff09;if 语句 案例&#xff0c;用户输入用户名和密码&#xff0c;判断用户名是否是admin,密码是否是123,如果正确&#xff0c;则显示登录成功 首先我创建了shell文件&#xff0c;touch getpawer 其中getpawer 是我自己命的名 #!/bin/bas…

Linux-4 gcc和makefile

Linux编译器-gcc/g使用 1.设计样例 c语言&#xff1a;linux中用的stdc99版本--可能会出现其他问题 c&#xff1a;Linux中用的stdc11--使用c11版本 Linux没有文件格式的区分&#xff0c;但是编译器区分 gcc编译器的文件格式是filename.c g编译器的文件格式是filename.cc或者fil…

蓝桥杯练习笔记(十七)

蓝桥杯练习笔记&#xff08;十七&#xff09; 一、 输入样例 7 7 1000001 0100010 0010100 0001AAA 00010A0 00010A0 00010A0蓝桥官网题解&#xff1a; 该题解是用了三个循环分别对三个方向的相同字符的长度进行统计&#xff0c;找出最大长度&#xff0c;最后对找出的最长Y进…

做好产品定位的3个重点

产品定位对于项目而言至关重要&#xff0c;正确的产品定位有助于项目锁定目标市场&#xff0c;精准满足客户需求。通过差异化产品策略&#xff0c;让产品在众多竞品中脱颖而出&#xff0c;形成独特竞争优势&#xff0c;从而有助于产品价值的实现。 因此做好产品定位迫在眉睫&am…

软件杯 深度学习中文汉字识别

文章目录 0 前言1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xf…

【Linux】从零认识文件操作

送给大家一句话&#xff1a; 要相信&#xff0c;所有的不美好都是为了迎接美好&#xff0c;所有的困难都会为努力让道。 —— 简蔓《巧克力色微凉青春》 开始理解基础 IO 吧&#xff01; 1 前言2 知识回顾3 理解文件3.1 进程和文件的关系3.2 文件的系统调用openwrite文件 fd 值…

OpenHarmony实战:小型系统平台驱动移植

在这一步&#xff0c;我们会在源码目录//device/vendor_name/soc_name/drivers目录下创建平台驱动。 建议的目录结构&#xff1a; device ├── vendor_name │ ├── drivers │ │ │ ├── common │ │ │ ├── Kconfig # 厂商驱动内核菜单入口 │ …

七大开源基金会联合制定符合 CRA 法案的共同标准

欧洲议会上个月通过的《欧洲网络弹性法案》(CRA) 制定通用规范和标准 Apache 软件基金会、Blender 基金会、Eclipse 基金会、OpenSSL 软件基金会、PHP 基金会、Python 软件基金会 和 Rust 基金会 这项工作由 Eclipse 基金会牵头&#xff0c;旨在建立基于现有开源最佳实践的安全…

9.图像中值腐蚀膨胀滤波的实现

1 简介 在第七章介绍了基于三种卷积前的图像填充方式&#xff0c;并生成了3X3的图像卷积模板&#xff0c;第八章运用这种卷积模板进行了均值滤波的FPGA实现与MATLAB实现&#xff0c;验证了卷积模板生成的正确性和均值滤波算法的MATLAB算法实现。   由于均值滤波、中值滤波、腐…

leet hot 100-13 最大子数组和

53. 最大子数组和 原题链接思路代码 原题链接 leet hot 100-10 53. 最大子数组和 思路 生成一个数字来记录last 表示前面数字全部之和与0取最大值 如果大于0 就加上如果不大于0 就不管 从当前位置从新开始遍历计算 时间复杂度O(n) 空间复杂度(1) 代码 class Solution {…

JVM剖析

0.前言 Java 是当今世界使用最广泛的技术平台之一。使用 Java 或 JVM 的一些技术包括&#xff1a; Apache spark用于大数据处理&#xff0c;数据分析在JVM上运行;用于数据流的Apache NiFi在内部使用的也是 JVM;现代 Web 和移动应用程序开发中使用的React native使用 的也包含…

HTTPS RSA 握手解析(计算机网络)

传统的 TLS 握手基本都是使用 RSA 算法来实现密钥交换的&#xff0c;在将 TLS 证书部署服务端时&#xff0c;证书文件其实就是服务端的公钥&#xff0c;会在 TLS 握手阶段传递给客户端&#xff0c;而服务端的私钥则一直留在服务端。 在 RSA 密钥协商算法中&#xff0c;客户端会…

佳能打印机E568扫描书和文件方法

官方网站; Canon : Inkjet 手册 : IJ Scan Utility : 启动IJ Scan Utility 打开打印机电源 扫描一个文件&#xff0c;翻页后盖好盖子。再点击扫描。 所有扫描结束之后点退出 点击保存

【无限列车1】SpringCloudAlibaba 与 SpringBoot后端架构的搭建

【无限列车1】SpringCloudAlibaba 与 SpringBoot后端架构的搭建 1、版本说明二、日志相关配置3、AOP 打印日志4、下载开源前端后台管理系统5、添加网关模块6、集成数据库和mp(1) 添加驱动和mp依赖(2) 数据库配置(3) 使用MybatisPlus 7、加密 yaml 文件中的内容(1) 依赖(2) 敏感…

02---webpack基础用法

01 entry打包的入口文件&#xff1a; 单入口entry是一个字符串:适用于单页面项目module.exports {entry:./src/index.js}多入口entry是一个对象module.exports {entry:{index:./src/index.js,app:./src/app.js}} 02 output打包的出口文件&#xff1a; 单入口配置module.ex…

基于SSM的网络视频播放器

目录 背景 技术简介 系统简介 界面预览 背景 互联网的迅猛发展彻底转变了全球各类组织的管理策略。自20世纪90年代起&#xff0c;中国政府和企业便开始探索利用网络系统进行信息管理。然而&#xff0c;早期的网络覆盖不广泛、用户接受度不高、相关法律法规不完善以及技术开…