找到合适的方案记录服务端日志
2016-01-08 14:58
288 查看
做过服务端开发的同学都清楚日志是多么的重要,你要分析应用当天的 PV/UV,你需要对日志进行统计分析; 你需要排查程序 BUG, 你需要寻找日志中的异常信息等等, 所以, 建立一套合适的日志体系是非常有必要的.
日志体系一般都会遵循这么几个原则 :
根据应用的需要记录对应的信息
用于后期离线统计的日志信息与记录程序运行问题的日志分开存放
选择合适的日志结构和日志记录工具
本文介绍的日志记录环境 :
Spring/Rose Web 框架
SLF4J 日志类
JSON格式的日志
后端开发的时候往往在系统中都存在不只一套日志体系,这篇文章介绍的日志方案用于后期离线统计分析, 对于其他不同的情况需要根据服务的需求而定.
Json格式的信息易于存储和分析,对于规模不是很大的应用服务而言,使用Json格式用于日志记录是个非常不错的选择,由于日志一般都是按行存储,后期根据需要利用普通的Java程序或者Hadoop MapReduce 工具处理都特别的方便;而且Json格式其内部存储类似于map结构,以Key/Value的形式表达信息,基本能够满足实际的需求.
可以看到,一行日志包含8个信息(只是测试使用,实际应用中需要根据自己的需求加入不同的类别信息), 分别记录着我们以后统计需要用到的信息.
那么,我们首先需要定义的就是这8个类型信息的常量字符串,以方便后期使用 :
这里使用的是Rose框架,我们不用过多的关注,在各种框架或者技术中我们只需要关注怎样记录日志就可以了.
所以,我们分析 try catch 中的日志记录过程 :
JsonResult
这个类当然不是JDK中提供的,它是为了我们在给客户端返回结果的时候简化一些步骤而构造的,其内部实现仅仅就是一个 JSONObject, 包含了 errorCode, result 这样的几个 key ,实现代码如下 :
LogGenerator_.getJsonLog(…)
根据名字我们可以看出我们用到这个接口获取一条日志信息, 而这个日志信息我们可以猜出它就是一个JSONObject, 其中包含了上面 Constants_ 类中列出的那8个日志类别, 那么, 我们只需要把这些传入接口的信息 put 到JSONObject 中就OK了,实现代码如下 :
LogSource_
这个类的作用是区分日志源的, 日志源也是后期统计分析的一个重要的组成部分, 比如,这条日志是来自服务端, 客户端, 还是 未知属性, 我们用一个枚举来实现 :
LogOutputer.Instance.outputLogFromServer(logStr)
这个接口用于序列化日志到某一个存储位置, 从 Instance 这个词可以猜到,这是一个单例的实现, 由于,接口比较简单,不做过多的解释了,直接给出实现代码 :
好了, 到这里, 我们已经在我们的系统中构造了一套方便解析的日志系统, 接下来, 埋到我们的应用系统中然后进行统计分析吧 !
日志体系一般都会遵循这么几个原则 :
根据应用的需要记录对应的信息
用于后期离线统计的日志信息与记录程序运行问题的日志分开存放
选择合适的日志结构和日志记录工具
本文介绍的日志记录环境 :
Spring/Rose Web 框架
SLF4J 日志类
JSON格式的日志
后端开发的时候往往在系统中都存在不只一套日志体系,这篇文章介绍的日志方案用于后期离线统计分析, 对于其他不同的情况需要根据服务的需求而定.
Json格式的信息易于存储和分析,对于规模不是很大的应用服务而言,使用Json格式用于日志记录是个非常不错的选择,由于日志一般都是按行存储,后期根据需要利用普通的Java程序或者Hadoop MapReduce 工具处理都特别的方便;而且Json格式其内部存储类似于map结构,以Key/Value的形式表达信息,基本能够满足实际的需求.
1. 日志示例
本文介绍的日志记录方法存储的日志信息就类似与下面这样 :[code]{"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个类型信息的常量字符串,以方便后期使用 :
[code]/** * 日志常量 * 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"; }
2. 服务端记录日志的过程
服务端在处理任务的时候(Rose中的Action,或者 Servlet中的service)就需要把处理的结果,过程之类的信息记录在日志里.即外部的一个HTTP请求过来,服务端就需要打一/多条日志,就好像这样 :[code]/** * 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 中的日志记录过程 :
JsonResult
这个类当然不是JDK中提供的,它是为了我们在给客户端返回结果的时候简化一些步骤而构造的,其内部实现仅仅就是一个 JSONObject, 包含了 errorCode, result 这样的几个 key ,实现代码如下 :
[code]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); } }
LogGenerator_.getJsonLog(…)
根据名字我们可以看出我们用到这个接口获取一条日志信息, 而这个日志信息我们可以猜出它就是一个JSONObject, 其中包含了上面 Constants_ 类中列出的那8个日志类别, 那么, 我们只需要把这些传入接口的信息 put 到JSONObject 中就OK了,实现代码如下 :
[code]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(); } }
LogSource_
这个类的作用是区分日志源的, 日志源也是后期统计分析的一个重要的组成部分, 比如,这条日志是来自服务端, 客户端, 还是 未知属性, 我们用一个枚举来实现 :
[code]/** * 日志源枚举类 * 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(); } }
LogOutputer.Instance.outputLogFromServer(logStr)
这个接口用于序列化日志到某一个存储位置, 从 Instance 这个词可以猜到,这是一个单例的实现, 由于,接口比较简单,不做过多的解释了,直接给出实现代码 :
[code]/** * 日志输出类 * 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); } }
[code]/** * 序列化日志接口 * Created by zhanghu on 12/24/15. */ public interface ILogSerializer { void serializerLog(String logStr); }
[code]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()); } } }
好了, 到这里, 我们已经在我们的系统中构造了一套方便解析的日志系统, 接下来, 埋到我们的应用系统中然后进行统计分析吧 !
相关文章推荐
- 自下而上归并排序 数组实现
- MongoDB基本命令
- Nginx -- Gzip 压缩功能作用
- 并查集(涂色问题) HDOJ 4056 Draw a Mess
- Struts2内建校验器(基于校验框架的文件校验)
- 在UIView页面执行pushViewController操作
- 华为机试——字符串到数字的转换
- Hadoop经典案例Spark实现(四)——平均成绩
- Java -- 内存机制
- 编译安装php-5.4.44
- SQL2008全部数据导出导入两种方法【转】
- Discuz安装前安全规范
- 开启慢查询
- AndroidStudio 在setid时,有红线标示解决办法
- android 混淆代码异常总结
- unity3d 自定义鼠标样式纹理
- Android控件使用—AutoCompleteTextView自动补全实现搜索功能
- 纹理(openGL)
- 字符串初始化
- 字符串长度