项目中有一个新的需求,就是需要解析日志,将日志中的部分数据分析获取出来供系统使用,通俗的讲就是抓取日志中的部分有用的信息,比如下面的apache日志信息,我需要解析每行日志,获取每行日志的IP地址、用户、创建时间、请求方式、地址....如果我们单纯使用java的方式,可能会想到通过文件流读取日志信息,然后逐行解析字符串,但是这种方式太过于复杂,而且效率比较低,在网上查询了相关的资料,决定使用logstash的grok工具,在网上也有相对应的java实现,其实现原理就是自定义正则表达式,通过正则表达式来解析日志,好处就是我们可以将日志中不规则的数据转换为规则的数据,例如map或者json数据,还有就是写一次表达式之后,可以在多处运行,如果有什么不同的地方,只需要修改一下正则表达式即可,是不是感觉很方便呢。
64.242.88.10 - - [07/Mar/2004:16:05:49 -0800] "GET /twiki/bin/edit/Main/Double_bounce_sender?topicparent=Main.ConfigurationVariables HTTP/1.1" 401 12846
64.242.88.10 - - [07/Mar/2004:16:06:51 -0800] "GET /twiki/bin/rdiff/TWiki/NewUserTemplate?rev1=1.3&rev2=1.2 HTTP/1.1" 200 4523
64.242.88.10 - - [07/Mar/2004:16:10:02 -0800] "GET /mailman/listinfo/hsdivision HTTP/1.1" 200 6291
64.242.88.10 - - [07/Mar/2004:16:11:58 -0800] "GET /twiki/bin/view/TWiki/WikiSyntax HTTP/1.1" 200 7352
64.242.88.10 - - [07/Mar/2004:16:20:55 -0800] "GET /twiki/bin/view/Main/DCCAndPostFix HTTP/1.1" 200 5253
网上对grok的定义是:Grok 是 Logstash 最重要的插件。你可以在 grok 里预定义好命名正则表达式,在稍后(grok参数或者其他正则表达式里)引用它。参考博文:http://udn.yyuap.com/doc/logstash-best-practice-cn/filter/grok.html
用java api的方式来集成grok,在github上面已经有了相关的项目工程实现了,我们可以在github中获取这个工程:https://github.com/thekrakken/java-grok,或者直接使用其打包好了的jar包和相关依赖包,可以在我的资源目录中进行下载:http://download.csdn.net/detail/harderxin/9923587
相关依赖包包括:commons-beanutils-1.8.3.jar、commons-lang3-3.1.jar、commons-logging-1.1.1.jar、gson-2.2.2.jar、slf4j-api-1.7.5.jar,版本号可以不与上面列举一样。
如果你是maven工程,可以在pom.xml文件中添加下面的依赖,不过其他依赖包仍然需要自己去引入进来:
io.thekraken
grok
0.1.5
要使用java-grok,首先需要通过定义好的正则表达式文件的路径创建Grok对象,我们可以定义为一个单列模式
package com.harderxin.grok.core;
import io.thekraken.grok.api.Grok;
import io.thekraken.grok.api.exception.GrokException;
public class GrokInstance {
private static Grok grok;
private GrokInstance() {
}
public static Grok getGrokInstance(String grokPatternPath) {
if (grok == null) {
try {
grok = Grok.create(grokPatternPath);
} catch (GrokException e) {
e.printStackTrace();
}
}
return grok;
}
}
获取到Grok对象后,通过传入我们需要解析的日志的表达式名称和要转换的日志消息,来创建Match对象:
public static Match getMatch(String pattern, String message) {
Match match = null;
try {
grok.compile(pattern);
match = grok.match(message);
match.captures();
} catch (GrokException e) {
e.printStackTrace();
match = null;
}
return match;
}
得到Match对象后,我们就可以将数据转换为对应的Map或者Json数据了,我写了一个辅助类:
package com.harderxin.grok.core;
import java.util.Map;
import io.thekraken.grok.api.Grok;
import io.thekraken.grok.api.Match;
import io.thekraken.grok.api.exception.GrokException;
public class GrokUtils {
private static final String GROK_PATTERN_PATH = "conf/agent_patterns";
private static Grok grok = GrokInstance.getGrokInstance(GROK_PATTERN_PATH);
public static Map toMap(String pattern, String message) {
Match match = getMatch(pattern, message);
if (match != null) {
return match.toMap();
}
return null;
}
public static String toJson(String pattern, String message) {
Match match = getMatch(pattern, message);
if (match != null) {
return match.toJson();
}
return null;
}
private static Match getMatch(String pattern, String message) {
Match match = null;
try {
grok.compile(pattern);
match = grok.match(message);
match.captures();
} catch (GrokException e) {
e.printStackTrace();
match = null;
}
return match;
}
}
创建我们的测试类,注意:
上面的每次解析只能解析日志中的单行数据,当要解析一个日志文件的时候,我们需要逐行解析该文件,我们需要解析下面这一行日志:
64.242.88.10 - - [07/Mar/2004:16:45:56 -0800] \"GET /twiki/bin/attach/Main/PostfixCommands HTTP/1.1\" 401 12846,通过程序解析后,我需要得到clientip =64.242.88.10,timestamp = 07/Mar/2004:16:45:56 -0800,verb=GET,httpversion = 1.1等这样格式化的数据,用我们上面的程序就可以做到。
在patterns文件中定义好我们需要解析日志的正则表达式:
XINTEST %{IPORHOST:clientip} %{USER:ident;boolean} %{USER:auth}[%{HTTPDATE:timestamp;date;dd/MMM/yyyy:HH:mm:ss Z}\] \"(?:%{WORD:verb;string} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion;float})?|%{DATA:rawrequest})\" %{NUMBER:response;int} (?:%{NUMBER:bytes;long}|-)
其中的IPORHOST是pattern中已经定义好的正则表达式,如下面所示,clientip是我们为解析后的数据的Key的别名,如果没有别名,默认名称为正则表达式的名称:
IP (?:%{IPV6:UNWANTED}|%{IPV4:UNWANTED})
HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)
IPORHOST (?:%{HOSTNAME:UNWANTED}|%{IP:UNWANTED})
我们的pattern表达式编写好了,名称为XINTEST,下面就可以使用我们的代码进行测试了,在测试的时候,pattern表达式需要使用%{}将名称放进去,这个是规定:
package com.harderxin.grok.test;
import java.util.Map;
import com.harderxin.grok.core.GrokUtils;
public class GrokTest2 {
public static void main(String[] args) {
String pattern = "%{XINTEST}";
String message = "64.242.88.10 - - [07/Mar/2004:16:45:56 -0800] \"GET /twiki/bin/attach/Main/PostfixCommands HTTP/1.1\" 401 12846";
String json = GrokUtils.toJson(pattern, message);
System.out.println(json);
Map map = GrokUtils.toMap(pattern, message);
System.out.println(map.toString());
}
}
{
"HOUR": "16",
"INT": "-0800",
"MINUTE": "45",
"MONTH": "Mar",
"MONTHDAY": "07",
"SECOND": "56",
"TIME": "16:45:56",
"XINTEST": "64.242.88.10 - - [07/Mar/2004:16:45:56 -0800] \"GET /twiki/bin/attach/Main/PostfixCommands HTTP/1.1\" 401 12846",
"YEAR": "2004",
"auth": "-",
"bytes": 12846,
"clientip": "64.242.88.10",
"httpversion": 1.1,
"ident": false,
"request": "/twiki/bin/attach/Main/PostfixCommands",
"response": 401,
"timestamp": "Mar 8, 2004 8:45:56 AM",
"verb": "GET"
}
日志中相关的数据就被我们程序解析出来了!!其实它的原理就是我们自定义正则表达式,然后通过正则表达式去匹配每一行的数据,转换为用户定义的key:value数据!有了这个功能,我们可以解析的日志无论多么复杂多变,只要它里面的数据遵循一定的正则表达式匹配规则,那么就能转换为我们需要的数据!