您的位置:首页 > 其它

找到合适的方案记录服务端日志

2016-01-08 14:58 288 查看
  做过服务端开发的同学都清楚日志是多么的重要,你要分析应用当天的 PV/UV,你需要对日志进行统计分析; 你需要排查程序 BUG, 你需要寻找日志中的异常信息等等, 所以, 建立一套合适的日志体系是非常有必要的.

  

  日志体系一般都会遵循这么几个原则 :

根据应用的需要记录对应的信息

用于后期离线统计的日志信息与记录程序运行问题的日志分开存放

选择合适的日志结构和日志记录工具

本文介绍的日志记录环境 :

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());
        }
    }
}


  好了, 到这里, 我们已经在我们的系统中构造了一套方便解析的日志系统, 接下来, 埋到我们的应用系统中然后进行统计分析吧 !
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: