------------本文笔记整理自《Hadoop海量数据处理:技术详解与项目实战》范东来
一、Apache服务器日志信息组成说明
1. group-0 是一条初始的log日志信息;
2. group-1 ~ group-15 是对该条日志正则解析后的结果。
一条原始log日志:
group-0 : 120.196.145.58 - - [11/Dec/2013:10:00:32 +0800] "GET /__utm.gif HTTP/1.1" 200 35 "http://easternmiles.ceair.com/flight/index.html" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36" "BIGipServermu_122.119.122.14=192575354.20480.0000;Webtrends=120.196.145.58.1386724976245806;uuid=5eb501f9-3586-4239-a15c-fe89aa14d624;userId=099;st=1" 1482 352 - easternmiles.ceair.com 794
远端主机(访客IP地址):
group-1 : 120.196.145.58
远端登录名:
group-2 : -
远端用户名:
group-3 : -
服务器接收请求时间:
group-4 : 11/Dec/2013:10:00:32 +0800
请求的第一行(请求方式、请求的URL、请求所用协议):
group-5 : GET /__utm.gif HTTP/1.1
响应码(最后的请求状态):
group-6 : 200
返回的数据流量(以CLF格式显示的除HTTP头以外传送的字节数):
group-7 : 35
上一个访问的页面(访客来源):
group-8 : http://easternmiles.ceair.com/flight/index.html
访客浏览器信息:
group-9 : Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Cookie信息(包含uuid/userId/sessiontime):
group-10 : BIGipServermu_122.119.122.14=192575354.20480.0000;Webtrends=120.196.145.58.1386724976245806;uuid=5eb501f9-3586-4239-a15c-fe89aa14d624;userId=099;st=1
接收的字节数(包括请求头的数据,并且不能为零):
group-11 : 1482
发送的字节数(包括请求头的数据,并且不能为零):
group-12 : 352
%{X-Forwarded-For}i:
group-13 : -
访问主机地址:
group-14 : easternmiles.ceair.com
服务器处理本请求所用时间,以微秒为单位:
group-15 : 794
二、正则解释
参考正则教程链接:Java 正则表达式 | 菜鸟教程
1.用到的正则表达式解释:
"(.+)": 字符串中用一对括号括起来的表示 一个组
"(.+)(.+)":这个就表示 两个组,因为有两对括号
"(.+)":括号中的 . 代表 匹配除 \r\n 之外的任何单个字符
括号中的 + 代表 一次或多次匹配前面的字符或子表达式,也就是多次匹配 .
"\\s":代表字符串中转义"\\",最终转换成正则表达式时,
即为"\s",代表匹配任何空白字符,包括空格、制表符、换页符等,如:\f\n\r\t\v
"\\(":代表字符串中转义"\\",转成正则时,代表 \( 即对"("的转义
这个"\\(\\)"与"()"不同:
前一个代表匹配一对括号,后一个代表匹配一个组
"\\[\\]":代表匹配一对方括号
"[0-9.]":代表匹配任意一位数字或者一个字符,与上一个例子不同
"[0-9.]+\\s":代表匹配任意多位数字或者多个字符,并以空白字符结尾
"[^0-9]":用"^"尖角代表除0-9的任意一个字符
"\\[([^\\[\\]]+)\\]\\s":代表匹配以"["开头,以"]"和空白字符结尾的一串字符
其中字符是除了"["、"]"方括号以外的任意多个字符
2.将需要匹配的日志信息和浏览器信息字符串,及对应的正则匹配字符串,按组(一行一组)拆分如下:
2.1.日志解析:
待解析的一条日志:(Java字符串)
String LOG = "120.196.145.58 "
+ "- "
+ "- "
+ "[11/Dec/2013:10:00:32 +0800] "
+ "\"GET /__utm.gif HTTP/1.1\" "
+ "200 "
+ "35 "
+ "\"http://easternmiles.ceair.com/flight/index.html\" "
+ "\"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36\" "
+ "\"BIGipServermu_122.119.122.14=192575354.20480.0000;Webtrends=120.196.145.58.1386724976245806;uuid=5eb501f9-3586-4239-a15c-fe89aa14d624;userId=099;st=1\" "
+ "1482 "
+ "352 "
+ "- "
+ "easternmiles.ceair.com "
+ "794";
使用的正则表达式:(Java字符串)
String APACHE_LOG_REGEX = "^([0-9.]+)\\s"
+"([\\w.-]+)\\s"
+"([\\w.-]+)\\s"
+"\\[([^\\[\\]]+)\\]\\s"
+"\"((?:[^\"]|\\\")+)\"\\s"
+"(\\d{3})\\s"
+"(\\d+|-)\\s"
+"\"((?:[^\"]|\\\")+)\"\\s"
+"\"((?:[^\"]|\\\")+)\"\\s"
+"\"(.+)\"\\s"
+"(\\d+|-)\\s"
+"(\\d+|-)\\s"
+"(\\d+|-)\\s"
+"(.+)\\s"
+"(\\d+|-)$";
2.2.浏览器解析:
待解析的一条浏览器信息
String userAgentStr = "\"Mozilla/5.0 "
+"(Windows NT 6.1; WOW64) "
+"AppleWebKit/537.36 "
+"(KHTML, like Gecko) "
+"Chrome/31.0.1650.63 "
+"Safari/537.36\" ";
用户浏览器信息匹配正则:(Java字符串)
String USER_AGENT_REGEX = "^(.+)\\s"
+"\\((.+)\\)\\s"
+"(.+)\\s"
+"\\((.+)\\)\\s"
+"(.+)\\s"
+"(.+)$";
三、代码实现
1. 将原书中的日志解析代码从Mapper类中抽离了出来,封装成独立的功能,供Mapper调用。
2. LogParser类设计成了单例模式,以便重复使用此对象,减少由不断创建对象带来的开销。
3. 代码中有测试用例,可直接运行。
4. 其中的IP解析类在上篇博文:利用纯真IP数据库解析IP地址位置信息
package com.etl.utls;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.etl.utls.IpParser;
public class LogParser {
//Apache服务器日志信息的正则表达式
private static final String APACHE_LOG_REGEX =
"^([0-9.]+)\\s([\\w.-]+)\\s([\\w.-]+)\\s\\[([^\\[\\]]+)\\]\\s\"((?:[^"
+ "\"]|\\\")+)\"\\s(\\d{3})\\s(\\d+|-)\\s\"((?:[^\"]|\\\")+)\"\\s\"((?:"
+ "[^\"]|\\\")+)\"\\s\"(.+)\"\\s(\\d+|-)\\s(\\d+|-)\\s(\\d+|-)\\s(.+)\\"
+ "s(\\d+|-)$";
//用户浏览器信息的正则表达式
private static final String USER_AGENT_REGEX = "^(.+)\\s\\((.+)\\)\\s(.+)\\s\\((.+)\\)\\s(.+)\\s(.+)$";
//测试日志一条
public static final String LOG = "120.196.145.58 "
+ "- "
+ "- "
+ "[11/Dec/2013:10:00:32 +0800] "
+ "\"GET /__utm.gif HTTP/1.1\" "
+ "200 "
+ "35 "
+ "\"http://easternmiles.ceair.com/flight/index.html\" "
+ "\"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/31.0.1650.63 Safari/537.36\" "
+ "\"BIGipServermu_122.119.122.14=192575354.20480.0000;Webtrends=120.196.145.58.1386724976245806;"
+ "uuid=5eb501f9-3586-4239-a15c-fe89aa14d624;userId=099;st=1\" "
+ "1482 "
+ "352 "
+ "- "
+ "easternmiles.ceair.com "
+ "794";
public static final String CANNOT_GET = "can not get";
//需要解析的字段
private String ipAddress = null; //ip地址
private String uniqueId = null; //uuid
private String url = null; //访问的url地址
private String sessionId = null; //会话id
private String sessionTimes = null; //服务器响应时间
private String areaAddress = null; //国家
private String localAddress = null; //地区
private String browserType = null; //浏览器类型
private String operationSys = null; //操作系统
private String referUrl = null; //访问的上一个页面url
private String receiveTime = null; //服务器接收请求时间
private String userId = null; //用户id
//保存cookies信息的map
private static Map<String, String> cookies = new HashMap<String, String>();
//实例化的LogParser计数
private static int count = 0;
//LogParser唯一标识
private int index = 0;
private LogParser() {
count++;
index = count;
}
//初始化Log解析器
private void init() {
setIpAddress(CANNOT_GET);
setUniqueId(CANNOT_GET);
setUrl(CANNOT_GET);
setSessionId(CANNOT_GET);
setSessionTimes(CANNOT_GET);
setAreaAddress(CANNOT_GET);
setLocalAddress(CANNOT_GET);
setBrowserType(CANNOT_GET);
setOperationSys(CANNOT_GET);
setReferUrl(CANNOT_GET);
setReceiveTime(CANNOT_GET);
setUserId(CANNOT_GET);
cookies.clear();
}
/*
* 解析一条log日志
*
* @param log 一条日志
*/
public void parse(String log) {
//初始化Log解析器
init();
String ipStr = null;
String receiveTimeStr = null;
String urlStr = null;
String referUrlStr = null;
String userAgentStr = null;
String cookieStr = null;
String hostNameStr = null;
//正则解析日志
Pattern pattern = Pattern.compile(APACHE_LOG_REGEX);
Matcher matcher = pattern.matcher(LOG);
if (matcher.find()) {
// for (int i = 0; i <= matcher.groupCount(); i++) {
// System.out.println("group-" + i + " : " + matcher.group(i));
// }
//根据正则表达式将日志文件断开
ipStr = matcher.group(1); //远端主机(访客IP地址)
receiveTimeStr = matcher.group(4); //服务器接收请求时间
urlStr = matcher.group(5); //请求的第一行(请求方式、请求的URL、请求所用协议)
referUrlStr = matcher.group(8); //上一个访问的页面(访客来源)
userAgentStr = matcher.group(9); //访客浏览器信息
cookieStr = matcher.group(10); //Cookie信息(包含uuid/userId/sessiontime)
hostNameStr = matcher.group(14); //访问主机地址
//保存IP地址
ipAddress = ipStr;
//解析IP地址
IpParser ipParser = new IpParser();
try {
//根据IP地址得出所在区域
areaAddress = ipParser.parse(ipStr).split(" ")[0];
localAddress = ipParser.parse(ipStr).split(" ")[1];
} catch (Exception e) {
e.printStackTrace();
}
//格式化请求接受时间
DateFormat df = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z",Locale.US);
try {
Date date = df.parse(receiveTimeStr);
//将时间转换为字符串
receiveTime = Long.toString(date.getTime());
} catch (ParseException e) {
e.printStackTrace();
}
//将url中的无效字符串丢弃
urlStr = urlStr.substring(5);
//重新拼装成url字符串
url = hostNameStr + urlStr;
//解析用户浏览器信息
pattern = Pattern.compile(USER_AGENT_REGEX);
matcher = pattern.matcher(userAgentStr);
if (matcher.find()) {
// for (int i = 0; i <= matcher.groupCount(); i++) {
// System.out.println("group-" + i + " : " + matcher.group(i));
// }
//获取浏览器类型
browserType = matcher.group(5);
//获取操作系统类型
operationSys = matcher.group(2).split(" ")[0];
}
//保存上一个页面url
referUrl = referUrlStr;
//HashMap保存cookie信息
String[] strs = cookieStr.split(";");
for (int i = 0; i < strs.length; i++) {
String[] kv = strs[i].split("=");
String keyStr = kv[0];
String valStr = kv[1];
cookies.put(keyStr, valStr);
}
//获取uuid信息
uniqueId = cookies.get("uuid");
//获取账号信息
userId = cookies.get("userId");
//如果没有获取成功,说明用户没有登录
if (userId == null) {
userId = "unlog_in";
}
//获取sessionTimes
sessionTimes = cookies.get("st");
//拼装成sessionId
sessionId = uniqueId + "|" + sessionTimes;
}
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getUniqueId() {
return uniqueId;
}
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getSessionTimes() {
return sessionTimes;
}
public void setSessionTimes(String sessionTimes) {
this.sessionTimes = sessionTimes;
}
public String getAreaAddress() {
return areaAddress;
}
public void setAreaAddress(String areaAddress) {
this.areaAddress = areaAddress;
}
public String getLocalAddress() {
return localAddress;
}
public void setLocalAddress(String localAddress) {
this.localAddress = localAddress;
}
public String getBrowserType() {
return browserType;
}
public void setBrowserType(String browserType) {
this.browserType = browserType;
}
public String getOperationSys() {
return operationSys;
}
public void setOperationSys(String operationSys) {
this.operationSys = operationSys;
}
public String getReferUrl() {
return referUrl;
}
public void setReferUrl(String referUrl) {
this.referUrl = referUrl;
}
public String getReceiveTime() {
return receiveTime;
}
public void setReceiveTime(String receiveTime) {
this.receiveTime = receiveTime;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public int getIndex() {
return index;
}
/*
* 测试
*/
public static void main(String[] args) {
//获取日志解析对象
LogParser logParser = new LogParser();
//解析测试用例日志
logParser.parse(LogParser.LOG);
String mapOutKey = logParser.getSessionId() + "&" + logParser.getReceiveTime();
String mapOutValue = logParser.getIpAddress() + "\n" + logParser.getUniqueId() + "\n"
+ logParser.getUrl() + "\n" + logParser.getSessionId() + "\n"
+ logParser.getSessionTimes() + "\n" + logParser.getAreaAddress() + "\n"
+ logParser.getLocalAddress() + "\n" + logParser.getBrowserType() + "\n"
+ logParser.getOperationSys() + "\n" + logParser.getReferUrl() + "\n"
+ logParser.getReceiveTime() + "\n" + logParser.getUserId();
System.out.println("mapOutKey: " + mapOutKey + "\nmapOutValue: " + mapOutValue);
}
}