Flume 自定义拦截器 多行读取日志+截断

前言:

Flume百度定义如下:

       Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。

搭建并使用flume不是特别难,而且网上也有技术文章分享,我不再赘述了。本文主要建立在已经搭建并使用flume的情况。

业务场景:

flume读取日志是按行读取,无法进行多行读取,当出现如下日志时将无法读到日志的正确时间与类型信息,所以我们需要有一种可以多行读取日志信息的办法,这里采用自定义拦截器的方法:

2019-08-02 14:34:13.153 [DEBUG][tomcatThreadPool-7][com.xxx.xxx.xxx.xxx.web.CommonHandlerExceptionResolver] (CommonHandlerExceptionResolver.java:134) \n Exception:------------------------------------------------------------------\ncom.xxx.xxx.xx.exceptions.XxxException: \n### Error querying database.  Cause: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (5638500 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.\n### The error may exist in com/xxx/xxx/xxx/basic/mapper/custom/CsWmFrtRateExtMapper.xml\n### The error may involve defaultParameterMap\n### The error occurred while setting parameters\n### SQL: SELECT * FROM ((SELECT cwfr.CS_WM_FRT_RATE_ID AS CS_WM_FRT_RATE_ID, cwfr.ACTIVE_DATE_BEGIN AS ACTIVE_DATE_BEGIN, cwfr.FRT_ITEM_CODE AS FRT_ITEM_CODE, cwfr.FRT_ITEM_NAME AS FRT_ITEM_NAME, cwfr.RP_FLAG AS RP_FLAG, cwfr.FRT_MODE AS FRT_MODE, cwfr.CALCULATION_ITEM AS CALCULATION_ITEM, cwfr.CHARGE_UOM_CODE AS CHARGE_UOM_CODE, cwfr.CHARGE_UOM_NAME AS 

对于一些业务系统的日志可能会比较大,超1M,2M甚至更多,可以根据实际情况只截取前面一部分保留下来即可,为了让功能更具有灵活性,在实现上增加开关属性,默认打开着,不需要时设置关闭。

自定义拦截器实现的属性:过滤正则,截断标识(即开关),总截取最大长度,单个截取最大长度,最后一个事件流。最后一个事件流的作用保留下来与下一批次一起,按正则匹配后才发送出去,因为flume是按批次读取的,默认是100行,而这个配置又与flume运行内存有关系。这个是属于参数调优的话题。

 

特别注意:代码打包后是需要放到flume安装目录下的lib下。放进去后需要重新才会生效。

代码实现如下:

package org.apache.flume.custom;

import com.google.common.collect.Lists;
import org.apache.commons.codec.Charsets;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 自定义拦截器 参考 Author: xiufen.huang Create Data: 2019/8/12 15:46
 */
public class MultInterceptor implements Interceptor {

	// 过滤正则
	private static Pattern regex = null;
	// 截取标志
	private static Boolean cutFlag = true;
	// 总截取最大长度
	private static Integer cutMax = null;
	// 单个截取最大长度
	private static Integer singleCut = null;
	// 最后一个事件流
	private static List lastList = Lists.newArrayList();

	@Override
	public void initialize() {

	}

	@Override
	public Event intercept(Event event) {
//    	System.out.println("----------intercept(Event event)方法执行,处理单个event");  
		return event;
	}

	@Override
	public List intercept(List list) {
//    	System.out.println("进来方法了吗?");

		// 处理结果 event list
		List intercepted = null;

		int addnum = 0;// 记录上一个正确匹配的event在队列中的位置,以便下一event有和它连接的需要

		if (lastList != null && lastList.size() >0){
			// 初始化
			int initCapacity = list.size() + lastList.size();
			intercepted = Lists.newArrayListWithCapacity(initCapacity);
			// 添加
			intercepted.addAll(lastList);

			// 清空
			lastList = Lists.newArrayList();
		}else {
			intercepted = Lists.newArrayListWithCapacity(list.size());
		}

		// 有正则的情况
		for (int i = 0; i < list.size(); i++) {
			Event interceptedEvent  = null;
			Matcher matcher = regex.matcher(new String(list.get(i).getBody(), Charsets.UTF_8));
			if (matcher.find()) {
				interceptedEvent = intercept((Event)list.get(i));
				// 单个的body
				String singleBody = new String(interceptedEvent.getBody(), Charsets.UTF_8);
				int singleBodyLen = singleBody.length();
				System.out.println("正则匹配-原始body---------:" + singleBody);
				if (cutFlag) {
					// 处理最大截取数边界条件--一定要重新一个变量接收
					int lsSingleCut = singleCut > singleBodyLen ? singleBodyLen : singleCut;
					// 截取字符串--新变量
					String singleCutBody = new String(singleBody.substring(0, lsSingleCut));

					System.out.println("单个截取-截取后body=============:" + singleCutBody);
					// 重新赋值body
					interceptedEvent.setBody(singleCutBody.getBytes());
				}

				intercepted.add(interceptedEvent);
				addnum = addnum +1;
//                System.out.println("matcher.find() 下的:addnum:" + addnum);
			} else {
				if (intercepted.size() == 0) {
					// 表示本次没有匹配上
					continue;
				}

 				addnum = addnum >= intercepted.size() ? intercepted.size() - 1 : addnum;


				String body = new String(intercepted.get(addnum).getBody(), Charsets.UTF_8) + "\n"
						+ new String(list.get(i).getBody(), Charsets.UTF_8);

				System.out.println("总截取-原始body---------:" + body);
				int bodyLen = body.length();
				// 截取body-新变量
				String cutBody = body;
				if (cutFlag) {

					// 处理最大截取数边界条件--新变量
					int lsCutMax = cutMax > bodyLen ? bodyLen : cutMax;
					// 截取字符串
					cutBody = new String(body.substring(0, lsCutMax));
					System.out.println("-处理截取-截取后body=============: " + body);
				}

				intercepted.get(addnum).setBody(cutBody.getBytes());
			}
		}

		// 最后一个保存在静态变量,等待下一批次
		if (intercepted != null && intercepted.size() > 0){
			int lastIndex = intercepted.size() -1;
			lastList.add(intercepted.get(lastIndex));
			// 移除最后一个索引
			intercepted.remove(lastIndex);
		}

		return intercepted;
	}

	@Override
	public void close() {
		System.out.println("----------自定义拦截器close方法执行");
	}

	public static class Builder implements Interceptor.Builder {
		@Override
		public Interceptor build() {
			System.out.println("----------build方法执行");
			return new MultInterceptor();
		}

		@Override
		public void configure(Context context) {
			String regexStr = context.getString("regex", null);
			cutFlag = context.getBoolean("cutFlag", true);
			cutMax = context.getInteger("cutMax", 0);
			singleCut = context.getInteger("singleCut", 0);
			System.out.println("参数regexStr:" + regexStr + ",参数cutMax: " + cutMax + ",cutFlag: " + cutFlag
					+ " ,singleCut: " + singleCut);

			// 由于外面传过来的单位是kb,所以这边需要乘以1024
			cutMax = cutMax * 1024;
			System.out.println("总截取最大值:" + cutMax);
			singleCut = singleCut * 1024;
			System.out.println("单个截取最大值:" + singleCut);

			if (null != regexStr) {
				// 转换正则
				regex = Pattern.compile(regexStr);
			}

		}
	}
}

使用说明:

在flume启动配置文件增加以下内容:

#匹配时间并转换为时间戳到header中
a1.sources.tail.interceptors.i2.type=org.apache.flume.custom.MultInterceptor$Builder
#正则表达式,按需求定
a1.sources.tail.interceptors.i2.regex=(((?!0000)[0-9]{4}-((0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-8])|(0[13-9]|1[0-2])-(29|30)|(0[13578]|1[02])-31)|([0-9]{2}(0[48]|[2468][048]|[13579][26])|(0[48]|[2468][048]|[13579][26])00)-02-29))
#开启日志长度截取标志,默认true,开启
a1.sources.tail.interceptors.i2.cutFlag = true
#最大截取字符串长度,整数,尽量控制在2M以内,单位:kb,1M=1024
a1.sources.tail.interceptors.i2.cutMax = 2048
#单个截取字符串长度,整数,尽量控制在1.5M以内,单位:kb,1M=1024
a1.sources.tail.interceptors.i2.singleCut=1024
a1.sources.tail.interceptors.i2.serializers=se1
a1.sources.tail.interceptors.i2.serializers.se1.type=org.apache.flume.interceptor.RegexExtractorInterceptorMillisSerializer
a1.sources.tail.interceptors.i2.serializers.se1.name=timestamp
a1.sources.tail.interceptors.i2.serializers.se1.pattern=yyyy-MM-dd

参考实现:

flume 自定义拦截器实现多行读取日志 

https://blog.csdn.net/nougats/article/details/71188920

 

你可能感兴趣的:(Flume 自定义拦截器 多行读取日志+截断)