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

2023-05-16

  做过服务端开发的同学都清楚日志是多么的重要,你要分析应用当天的 PV/UV,你需要对日志进行统计分析; 你需要排查程序 BUG, 你需要寻找日志中的异常信息等等, 所以, 建立一套合适的日志体系是非常有必要的.
  
  日志体系一般都会遵循这么几个原则 :

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

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

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

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

  • Spring/Rose Web 框架

  • SLF4J 日志类

  • JSON格式的日志

      后端开发的时候往往在系统中都存在不只一套日志体系,这篇文章介绍的日志方案用于后期离线统计分析, 对于其他不同的情况需要根据服务的需求而定.
      Json格式的信息易于存储和分析,对于规模不是很大的应用服务而言,使用Json格式用于日志记录是个非常不错的选择,由于日志一般都是按行存储,后期根据需要利用普通的Java程序或者Hadoop MapReduce 工具处理都特别的方便;而且Json格式其内部存储类似于map结构,以Key/Value的形式表达信息,基本能够满足实际的需求.

1. 日志示例

  本文介绍的日志记录方法存储的日志信息就类似与下面这样 :

{"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";
}

2. 服务端记录日志的过程

  服务端在处理任务的时候(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 中的日志记录过程 :

  • JsonResult
      这个类当然不是JDK中提供的,它是为了我们在给客户端返回结果的时候简化一些步骤而构造的,其内部实现仅仅就是一个 JSONObject, 包含了 errorCode, result 这样的几个 key ,实现代码如下 :
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了,实现代码如下 :
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_
      这个类的作用是区分日志源的, 日志源也是后期统计分析的一个重要的组成部分, 比如,这条日志是来自服务端, 客户端, 还是 未知属性, 我们用一个枚举来实现 :
/**
 * 日志源枚举类
 * 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 这个词可以猜到,这是一个单例的实现, 由于,接口比较简单,不做过多的解释了,直接给出实现代码 :
/**
 * 日志输出类
 * 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());
        }
    }
}

  好了, 到这里, 我们已经在我们的系统中构造了一套方便解析的日志系统, 接下来, 埋到我们的应用系统中然后进行统计分析吧 !

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

找到合适的方案记录服务端日志 的相关文章

随机推荐

  • git 简单操作流程图

  • 关于for循环中的变量int i 如果跳出了这个for循环后,i的值是继续保留还是被释放掉了

    include lt iostream gt using namespace std int main char a 10 定义一个一维数组用来存放字符串 int i j 定义变量 cout lt lt 34 请输入字符 xff1a for
  • 将数组作为参数,调用该函数时候给的是数组地址还是整个数组

    1 在实际的应用中 xff0c 数组经常作为函数参数 xff0c 将数组中的数据传递到另外一个函数中 xff0c 一般来说 xff0c 传递可以采用两种方法 xff1a 1 gt 数组元素作为函数的实参时 xff0c 用法跟普通变量作参数相
  • c++中指针箭头的用法

    1 c 43 43 中指针用箭头来引用类或者结构体的成员 xff0c 箭头操作符 gt 用来引用指针对象 这是是用于类 xff0c 或者是结构体的指针变量用的 如struct Point int x y Point pt 61 new Po
  • string类的各种函数用法

    标准c 43 43 中string类函数介绍 注意不是CString 之所以抛弃char 的字符串而选用C 43 43 标准程序库中的string类 xff0c 是因为他和前者比较起来 xff0c 不必 担心内存是否足够 字符串长度等等 x
  • 中国做图像处理的公司

    xff08 1 xff09 北京北方猎波科技有限公司 xff1a http www northwh com beifangliebo main1 html 红外探测成像产品 xff08 2 xff09 深圳超多维光电子有限公司北京分公司 x
  • 【2015-2016,我在路上】

    前言 xff1a 每天 xff0c 每时 xff0c 每分 xff0c 时光的步伐永远不会停止 xff0c 当我提起笔 xff0c 写下的这一瞬间 xff0c 时间又是一年 xff0c 一年的时光 xff0c 在没逝去时 xff0c 感觉很
  • stm32+djyos下串口缓冲区配置

    就这行简单代码 xff1a write UartFd DataBuf 100 执行时间有时候长 xff0c 有时候短 xff0c 直接影响了后续代码的执行 xff0c why xff1f 进一步了解到 xff0c 用户发送数据 xff0c
  • 远程 sshd提示:Server unexpectedly closed network connection

    root 64 xx vim etc ssh sshd config 修改端口为3330 root 64 xx iptables I INPUT p tcp dport 3330 j ACCEPT 添加防火墙3330端口 允许 root 6
  • 解决git-gui文件数量的上限的问题

    原文链接 xff1a https blog csdn net u014221090 article details 55505228 遇到的情况 对于git的使用 xff0c 有一部分人是使用git bash xff08 命令行 xff09
  • 快速复制论文中的公式

    设备情况 MacOSOffice xff08 WPS不行 xff09 Mathpix Snipping Toolhttps www latex4technics com xff08 需要科技才能访问 0点云 xff09 步骤 xff1a 打
  • k8s官方文档学习

    文章目录 节点1 Addresses xff1a 2 conditions 块描述了所有 Running 节点的状态 xff1a 3 Capacity 块描述节点上的可用资源 xff1a CPU 内存 xff08 memory xff09
  • 一种简单有效的锂电池充电均衡电路

    这个均衡电路用的是三个一模一样的并联稳压电路组成的 xff0c 每个电池上并一个 电路原理图如下 xff1a 每个稳压电源都调节到4 2V 均衡的原理是 xff0c 当电池电压都小于4 2V时 xff0c 并联稳压电路不起作用 xff0c
  • 基于opencv对图片的二进制流进行编解码

    span class token keyword import span cv2 span class token keyword import span numpy span class token keyword as span np
  • STM32与传感器串口通讯问题

    前言 STM32F407ZGT6使用串口通信发送指令给传感器 xff0c 让传感器返回测得的数据 1 过程 大家看我这个程序 xff0c 在main函数前面我定义了一个read instruction数组 xff0c 存放读取传感器的指令
  • 检测到"_ITERATOR_DEBUG_LEVEL"的不匹配项

    最近在项目中遇到了问题 xff0c 编译器提示 检测到 34 ITERATOR DEBUG LEVEL 34 的不匹配项 xff0c 上网查找后发现是编译Release版本用到了DEBUG库的原因 xff0c 其中也提供了在预编译中加入 3
  • C语言实现单链表的逆置

    单链表的逆置是一个非常经典的问题 xff0c 这里利用两个思想进行解决 首先 xff0c 我们需要看下原理图 xff0c 其实两个思想都是一样的 xff0c 都是使后一个的节点的 next 指针指向前一个节点 xff0c 依次递推 xff0
  • UNIX下C语言的图形编程-curses.h函数库

    相信您在网路上一定用过如 tin elm 等工具 这些软体有项共同的特色 即他们能利用上下左右等方向键来控制游标的位置 除此之外 这些程式 的画面也较为美观 对 Programming 有兴趣的朋友一定对此感到好奇 也 许他能在 PC 上用
  • 如何同时启动多个Tomcat服务器

    这篇文章转载自 如何同时启动多个Tomcat服务器 conf子目录中打开server xml文件 xff0c 查找以下三处 xff1a 1 修改http访问端口 xff08 默认为8080端口 xff09 span class hljs t
  • 找到合适的方案记录服务端日志

    做过服务端开发的同学都清楚日志是多么的重要 你要分析应用当天的 PV UV 你需要对日志进行统计分析 你需要排查程序 BUG 你需要寻找日志中的异常信息等等 所以 建立一套合适的日志体系是非常有必要的 日志体系一般都会遵循这么几个原则 根据