做过服务端开发的同学都清楚日志是多么的重要,你要分析应用当天的 PV/UV,你需要对日志进行统计分析; 你需要排查程序 BUG, 你需要寻找日志中的异常信息等等, 所以, 建立一套合适的日志体系是非常有必要的.
日志体系一般都会遵循这么几个原则 :
根据应用的需要记录对应的信息
用于后期离线统计的日志信息与记录程序运行问题的日志分开存放
选择合适的日志结构和日志记录工具
本文介绍的日志记录环境 :
Spring/Rose Web 框架
SLF4J 日志类
JSON格式的日志
后端开发的时候往往在系统中都存在不只一套日志体系,这篇文章介绍的日志方案用于后期离线统计分析, 对于其他不同的情况需要根据服务的需求而定.
Json格式的信息易于存储和分析,对于规模不是很大的应用服务而言,使用Json格式用于日志记录是个非常不错的选择,由于日志一般都是按行存储,后期根据需要利用普通的Java程序或者Hadoop MapReduce 工具处理都特别的方便;而且Json格式其内部存储类似于map结构,以Key/Value的形式表达信息,基本能够满足实际的需求.
本文介绍的日志记录方法存储的日志信息就类似与下面这样 :
{"Url":"http://localhost:8081/RoseStudy/hello/showHowToRecordLog","Uri":"/RoseStudy/hello/showHowToRecordLog","RemoteIp":"127.0.0.1","HostIp":"127.0.0.1","ActionName":"showHowToRecordLog","Time":1452233120220,"LogSource":1,"JsonResult":{"errorCode":0,"reason":null,"result":"test show how to record log success...","status":"success"}}
可以看到,一行日志包含8个信息(只是测试使用,实际应用中需要根据自己的需求加入不同的类别信息), 分别记录着我们以后统计需要用到的信息.
那么,我们首先需要定义的就是这8个类型信息的常量字符串,以方便后期使用 :
/**
* 日志常量
* Created by zhanghu on 12/24/15.
*/
public class Constants_ {
/**
* 日志中包含的属性字段
* */
public static final String Url = "Url";
public static final String Uri = "Uri";
public static final String RemoteIp = "RemoteIp";
public static final String HostIp = "HostIp";
public static final String ActionName = "ActionName";
public static final String Time = "Time";
public static final String LogSource = "LogSource";
public static final String JsonResult = "JsonResult";
}
服务端在处理任务的时候(Rose中的Action,或者 Servlet中的service)就需要把处理的结果,过程之类的信息记录在日志里.即外部的一个HTTP请求过来,服务端就需要打一/多条日志,就好像这样 :
/**
* url : http://localhost:8081/RoseStudy/hello/showHowToRecordLog
* */
@Get("showHowToRecordLog")
@Post("showHowToRecordLog")
public String showHowToRecordLog(Invocation inv) {
try {
JSonResult jSonResult = JSonResult.newInstance();
jSonResult.errorCode(0L).reason(null).result("test show how to record log success...").status("success");
String logStr = LogGenerator_.getJsonLog(inv.getRequest().getRequestURL().toString(), inv.getRequest().getRequestURI(),
inv.getRequest().getRemoteAddr(), inv.getRequest().getLocalAddr(), "showHowToRecordLog",
LogSource_.ServerSide, jSonResult.toString());
LogOutputer.Instance.outputLogFromServer(logStr);
inv.getResponse().setContentType("application/json;charset=utf-8");
inv.getResponse().setStatus(HttpServletResponse.SC_OK);
inv.addModel("resultJsonString", jSonResult.toString());
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return "resultJson";
}
这里使用的是Rose框架,我们不用过多的关注,在各种框架或者技术中我们只需要关注怎样记录日志就可以了.
所以,我们分析 try catch 中的日志记录过程 :
import net.sf.json.JSONObject;
public class JSonResult {
private long errorCode;
private Object reason;
private Object result;
private Object status;
public static JSonResult newInstance() {
return new JSonResult();
}
public JSonResult() {
errorCode = -1;
}
public long getErrorCode() {
return errorCode;
}
public JSonResult errorCode(long errorCode) {
this.errorCode = errorCode;
return this;
}
public Object getReason() {
return reason;
}
public JSonResult reason(Object reason) {
this.reason = reason;
return this;
}
public Object getStatus() {
return status;
}
public JSonResult status(Object status) {
this.status = status;
return this;
}
public Object getResult() {
return result;
}
public JSonResult result(Object result) {
this.result = result;
return this;
}
@Override
public String toString() {
return toJson().toString();
}
public JSONObject toJson() {
return JSONObject.fromObject(this);
}
}
import net.sf.json.JSONObject;
/**
* 生成日志的服务
* Created by zhanghu on 12/24/15.
*/
public class LogGenerator_ {
/**
* 可解析日志包含这样几个点 :
* - Url 客户端请求的地址
* - Uri 服务器资源的地址
* - RemoteIp 客户端的IP地址
* - HostIp 服务端的IP地址
* - ActionName 请求函数的名字
* - source_ 日志源
* - JsonResult 服务器返回的结果
* */
public static String getJsonLog(String Url, String Uri,
String RemoteIp, String HostIp,
String ActionName,
LogSource_ source_, String JsonResult) {
JSONObject object = new JSONObject();
object.put(Constants_.Url, Url);
object.put(Constants_.Uri, Uri);
object.put(Constants_.RemoteIp, RemoteIp);
object.put(Constants_.HostIp, HostIp);
object.put(Constants_.ActionName, ActionName);
object.put(Constants_.Time, System.currentTimeMillis());
object.put(Constants_.LogSource, LogSource_.getValue(source_));
object.put(Constants_.JsonResult, JsonResult);
return object.toString();
}
}
/**
* 日志源枚举类
* Created by zhanghu on 12/24/15.
*/
public enum LogSource_ {
ServerSide(1, "服务端"),
Unknown(2, "未知");
private int value;
private String description;
LogSource_(int value, String description) {
this.value = value;
this.description = description;
}
public int getValue() {
return value;
}
public String getDescription() {
return description;
}
public static int getValue(LogSource_ source_) {
if (source_ == null) {
return Unknown.getValue();
}
return source_.getValue();
}
public static String getDescription(LogSource_ source_) {
if (source_ == null) {
return Unknown.getDescription();
}
return source_.getDescription();
}
}
/**
* 日志输出类
* Created by zhanghu on 12/24/15.
*/
public class LogOutputer {
public static LogOutputer Instance = new LogOutputer();
private ILogSerializer logSerializer = null;
private LogOutputer() {
this.logSerializer = new LogSerializerImpl();
}
/**
* 这个是真正写日志的接口
* */
public void outputLogFromServer(String jsonObjStr) {
logSerializer.serializerLog(jsonObjStr);
}
}
/**
* 序列化日志接口
* Created by zhanghu on 12/24/15.
*/
public interface ILogSerializer {
void serializerLog(String logStr);
}
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 序列化日志的实现类
* Created by zhanghu on 12/24/15.
*/
public class LogSerializerImpl implements ILogSerializer {
/**
* 这里需要定义两套日志系统 :
* 一类是定义用作统计的日志系统 logger
* 另一类是记录性的日志系统,一般不用做解析 allLogger
* */
private static final Logger logger = LoggerFactory.getLogger("roseLog");
private static final Logger allLogger = LoggerFactory.getLogger(LogSerializerImpl.class);
@Override
public void serializerLog(String logStr) {
try {
JSONObject object = JSONObject.fromObject(logStr);
object.put(Constants_.Time, System.currentTimeMillis());
logger.info(object.toString());
} catch (Exception e) {
allLogger.error("Write Rose Log Error : {}", e.getMessage());
}
}
}
好了, 到这里, 我们已经在我们的系统中构造了一套方便解析的日志系统, 接下来, 埋到我们的应用系统中然后进行统计分析吧 !