记录Logstash从入门到上线

本文记录了使用ELK Stack(Elasticsearch、Logstash、Kibana)处理日志的过程,从Logback日志到Logstash过滤再到Elasticsearch存储。介绍了Logstash的输入、过滤、输出模块,通过Logstash的filter模块将非结构化的日志转换为结构化数据,包括日期解析、正则匹配、字段转换等操作,以满足日志分析需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记录Logstash从入门到上线

背景

现有一需求,需要做埋点数据,记录每一次请求时间、耗时等数据。根据设计要以记日志的形式使用ELK记录并分析埋点数据。由于之前并没有接触过ELK,因此本次将学习到上线的思路记录下来。

V0

思路

ELK是啥,Logstash是啥,完全不清楚。稍微百度了一下可以简单理解为ES作为仓库,Logstash作为中转和过滤数据的服务,Kibina作为数据分析工具。

目前项目里使用Logback+slf4j作为日志工具。记录日志的方式为

log.info("xxxxx");

xxxx为日志自定义内容,输出到控制台或者日志文件中的不仅只有xxxx,还有线程号,时间等详细信息,由logback配置而成。

详细信息+自定义内容=全部内容

首先需要对Logstash有一个全面了解,以下两篇好文可能会帮助到你

https://blog.csdn.net/hushukang/article/details/84423184

https://www.cnblogs.com/FengGeBlog/p/10305318.html

经过以上两篇文章的学习,可以知道Logstash有输入模块(input)、过滤模块(filter)、输出模块(output)

对于我而言,我的目的很明确:将日志从springboot经过logstash发送到es。

因此

  • 输入模块

    logback发送数据到logstash

  • 过滤模块

    logstash:把我的日志的全部内容(string)过滤成结构化数据(json)

  • 输出模块

    指向es地址

由于过滤模块一时半会玩不明白,因此简单点,先把数据试下如何从Logback发送到Logstash再存入ES。

配置Logstash
input{
    tcp {
        mode => "server"
        #发布服务的端口
        port => 4560
        codec => json_lines
    }
}
output{
	stdout {
        codec=>rubydebug
    }
    #es的地址和索引(数据库)
	elasticsearch{
			hosts => ["esip:port"]
			index => "track"
	}
}
配置Logback
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
     <!--logstash地址,ip:port -->
     <destination>${logstash.server-addr}</destination>
     <!-- encoder必须配置,有多种可选 -->
     <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
         <!--在生成的json中会加这个字段-->
         <!--<customFields>{"appname":"${spring.application.name}"}</customFields>-->
     </encoder>
 	<!--logback根据这里的条件过滤,发送匹配的日志到logstash-->
     <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
         <level>${log.level}</level>
     </filter>
</appender>

参考链接:https://blog.csdn.net/DPnice/article/details/94650402

我的配置:
<springProperty scope="context" name="LOGSTASH_ADDR" source="logging.logstash.address" defaultValue=""/>

<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    <destination>${LOGSTASH_ADDR}</destination>
    <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
    </encoder>
</appender>
<root level="info">
    ...
    <appender-ref ref="LOGSTASH"/>
</root>
小结

以上代码将V0完成,并没有实现我的需求,但是ELk如何运作可以基本上可以理清楚。

原以为logback仅会将全部日志信息(包含时间、线程号、日志信息的string)全部发送到logback,正愁如何将信息拆分出来。最后惊喜地发现原来logback已经将不同的信息作为不同的字段,以json的形式发送到logstash,这样一来我们就可以只解析自定义的日志内容,不用处理其他数据。

{
        "opTime" => 1628849263688,
          "host" => "1.1.1.1",
        "userId" => "214edd60-3b31-4835-96b8-a37c3d344dd5",
       "appName" => "elkDemoApp",
          "port" => "3636",
      "costTime" => 1381,
    "@timestamp" => 2021-08-13T10:07:45.069Z,
      "@version" => "1",
        "opType" => "008_01_00",
      "opStatus" => 1,
        "caseNo" => "zt1111"
}

V1

思路

现在我们已经可以在logstash中获取我们自定义的内容:一个名为message的string。即log.info("xxxxx");中的xxxxx

现在需要在logstash中将这个string转成多个普通字段,这样才能作为字段存入es。

之前只是略读上面两篇文章,这次需要详细学习Logstash的过滤模块(filter).

…学习中…

通过一下午的学习,大概了解到filter可以做的事

插件名称说明
date日期解析
grok正则匹配解析
dissect分隔符解析
mutate对字段作处理,比如重命名、删除、替换
json按照json解析字段内容到指定字段中
geoip增加地理位置数据
ruby利用ruby代码来动态修改Logstash Event

初步想法:日志中按照顺序记录字段,如log.info("张三,16,10000,武汉"),然后在logstash中用分隔符分成多个字段

filter{
	#截取header,只取日志中埋点的日志
	mutate{
		split => ["message",","]
		add_field => ["name","%{[message][0]}"]
		add_field => ["age","%{[message][1]}"]
		...
	}
}

这样的话很明显的两个问题,1是字段在logstash配置文件中写死,不好修改。2是顺序固定,如果字段为空不好调换位置。

V2

因此继续学习,看有没有其他方案,于是发现下面一段代码

从日志message中获取字段和值

https://www.cnblogs.com/yulinlewis/p/10854424.html

filter {
	#从日志中解析字段
	ruby {
		code => "
			array1 = event.get('msgtemp').split(';')
			array1.each do |temp1|
				if temp1.nil? then
					next
				end
				array2 = temp1.split('=')
				key = array2[0]
				value = array2[1]
				if key.nil? then
					next
				end
				event.set(key, value)
			end
		"
	}
}

这段配置可以以kv的方式,记录字段,很好的避免了上面V1中的两个问题。

logger.info("name={};age={};salary={};add={}","张三",16,10000,"武汉");

Field[] declaredFields = TrackInfo.class.getDeclaredFields();
for (Field declaredField : declaredFields) {
    //带有Transient注解的不写入日志
    if(declaredField.getAnnotation(javax.persistence.Transient.class)!=null){
        continue;
    }
    declaredField.setAccessible(true);
    Object value = declaredField.get(info);
    if(value != null && !"".equals(value)){
        logBuilder.append(declaredField.getName()).append("=").append(value.toString());
        logBuilder.append(";");
    }
}

V3

以上代码已经基本实现了我的需求,不过还是不太完美,需要再做一点点优化

问题

1、字段发送到logstash后,都变成了字符串,不能以数字形势发送到es,对统计可能有影响

2、所有的日志都会通过Logback发送到Logstash,再发送到es,比较浪费资源

解决方式:

1、埋点相关日志以track:开头,Logback中添加配置,只发送包含track字符串的日志到Logstash。

2、Logstash中添加配置,只发送track:开头的日志到ES。

3、Logstash的filter中添加配置,将指定类型的字段转换成数字类型,便于统计

Logback:

<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    <destination>${LOGSTASH_ADDR}</destination>
    <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
    </encoder>
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
        <evaluator>
            <expression>message.contains("track")</expression>
        </evaluator>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
</appender>

后端:

数字类型的字段如age,在记录日志时再跟上后缀@int,代表需要在Logstash中转换成数字类型

log.info("name=张三;age=16@int")

StringBuilder logBuilder = new StringBuilder("track:");
Field[] declaredFields = TrackInfo.class.getDeclaredFields();
for (Field declaredField : declaredFields) {
    //带有Transient注解的不写入日志
    if(declaredField.getAnnotation(javax.persistence.Transient.class)!=null){
        continue;
    }
    declaredField.setAccessible(true);
    Object value = declaredField.get(info);
    if(value != null && !"".equals(value)){
        logBuilder.append(declaredField.getName()).append("=").append(value.toString());
        if(value instanceof Number){
            logBuilder.append("@int");
        }
        logBuilder.append(";");
    }
}
logBuilder.deleteCharAt(logBuilder.lastIndexOf(";"));

//log.info("track:name={};age={}@int;addr={}","张三",16,"武汉");
log.info(logBuilder.toString());

Logstash:

filter{
	#截取header,只取日志中埋点的日志
	mutate{
		split => ["message",":"]
		add_field => ["header","%{[message][0]}"]
		add_field => ["msgtemp","%{[message][1]}"]
	}
	
	if [header] != "track" {
		drop {}
	}
	
	#从日志中解析字段  key=value;key=value@type  如果不带type,则直接设置value;如果带type则类型转换(暂时支持整型)。
	ruby {
		code => "
			array1 = event.get('msgtemp').split(';')
			array1.each do |temp1|
				if temp1.nil? then
					next
				end
				array2 = temp1.split('=')
				key = array2[0]
				valueandtype = array2[1]
				if key.nil? then
					next
				end
								
				array3 = valueandtype.split('@')

				value=array3[0]
				type=array3[1]
				if type==='int' then
					event.set(key, Integer(value))
				else
					event.set(key, value)
				end
				
			end
		"
	}
	
	#删除多余字段
	mutate{
		remove_field => ["header","appname","logger_name","level","msgtemp","message","ROLLINGFILE_MAXHISTORY","ROLLINGFILE_TOTALSIZECAP","ROLLINGFILE_MAXFILESIZE","level_value","thread_name","LOGSTASH_ADDR"]  
    }
}

最终实现了一个可复用性较高的方法。虽然说方法有点笨,就是截取字符串实现的。没有用到Logstash各种其他高大上的功能,但还是在短时间内满足本次需求。后续可能还会有其他bug或者需要优化的地方,继续完善。

默认pattern

https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/ecs-v1/java

logstash中过滤日志

https://www.oschina.net/question/2895820_2233507

logback中过滤日志

https://www.cnblogs.com/yongze103/archive/2012/05/05/2484753.html

程序的后台路径是/mxzadmin/,后台登陆账号和密码均为:mxiaozheng 本程序郑陆伟(www.zhengluwei.net)个人版权所有,不得以任何方式恶意散播,谢谢合作,违者必究。 如有疑问请联系作者QQ:512711380,站长导航QQ交流群:129293051 2.3版本程序演示地址:http://dh.mxiaozheng.cn/ 【V2.3】更新日志 更新于2013-7-27 1、增加百度联盟等联盟广告位,优化了首页的UI部分; 2、通过配置文件可以修改静态页面的生成路径,实现自定义路径功能,操作更加方面; 3、修复了部分虚拟主机点击后台登录按钮无反应的Bug; 4、修复了部分浏览器点击后台登录按钮无反应的Bug; 5、搜索框全面改版,只保留百度搜索。优化了用户体验,简化了操作。 【V2.1 】更新说明 1、修复了后台密码长度和管理员账号长度的相关错误; 2、在后台增加了用户可以自行修改设置弹窗信息的功能; 3、修复了数据库输入字符串不能为空的错误(其实这个错误是可以通过设置清空数据库实现的); 4、强化了SQL注入的防护。 关于本程序的环境配置和基本开发信息: .Net 2.0+Access数据库,MSSQL版本可以定制开发; 简单的采用了三层结构开发;全静态页面,有利于网站优化; 后台管理更加强大和方面,可以随意更换主站网址。 如果是虚拟空间使用本程序,请务必保证空间支持.net 2.0或以上版本,以免程序不能正常使用。 浏览器兼容:IE6-9,Firefox,Chrome内核的所有浏览器,Opera浏览器 技术特点:采用ASP.NET简单的三层架构开发,全站前台实现纯静态页面,利于网站整体优化。 功能描述:本代码是一个站长网址导航和搜索功能,用户可以在后台任意添加自己需要的链接。 注意事项:如果是虚拟空间使用本程序,请务必保证空间支持.net,以免程序不能正常使用。 另外,内置有标准的robots.txt文件,如果不明白,请勿随意修改,以免影响贵站的百度收录和排名。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值