对于web应用, 笔者通常会在请求开始和结束的时候打印一行日志, 并记录接口的处理时间, 也就是说通常所说的接口响应时间. 这样当生产环境项目出现异常时, 可以通过接口的响应时间和次数初步推测有可能产生问题的接口, 从而更快定位和解决问题。

1. 日志格式

笔者习惯于使用logback 输出日志, logback 定义日志格式比较灵活, 笔者定义的格式为:

  • 接口开始: s e s s i o n I d ] [ requestId}-接口:[$url]-ended, 耗时:254ms
2019-04-01 13:38:20.591[INFO][http-127.0.0.1-8080-1][org.zongf.test.ApiAccessLogUtil][8BD0EF4739EB58C634D1853B2F7BAF96][67617bd63bb4437295eb6a49601d4b0f]-接口:[/article/1001]-start
2019-04-01 13:38:20.591[INFO][http-127.0.0.1-8080-1][org.zongf.test.ApiAccessLogUtil][8BD0EF4739EB58C634D1853B2F7BAF96][67617bd63bb4437295eb6a49601d4b0f]-接口:[/article/1001]-这是其它日志, 模拟不匹配
2019-04-01 13:38:22.367[INFO][http-127.0.0.1-8080-1][org.zongf.test.ApiAccessLogUtil][8BD0EF4739EB58C634D1853B2F7BAF96][67617bd63bb4437295eb6a49601d4b0f]-接口:[/article/1001]-ended, 访问时间:254ms

2. 解析日志

2.1 建立日志模型

将单行日志抽象为java 类, 这样解析之后便于后续做排序, 统计等操作。

public class LogLine {
    // 日志输出时间
    private String time;
    // 日志级别
    private String level;
    // 日志线程名称
    private String thread;
    // 日志类名称
    private String clz;
    // 请求会话 id
    private String sid;
    // 请求id
    private String reqid;
    // 接口地址
    private String uri;
    // 日志类型: start-开始日志 ended,-结束日志
    private String tag;
    // 请求耗时
    private int cost;
	// 省略setter/getter 方法

2.2 日志解析工具类

笔者编写一个解析单行日志的工具类, 用于将单行日志转换为java 模型.

public class LogLineParserUtil {
    /* 正则表达式模式说明:
       匹配时间: ([^\[\]]*)
       匹配日志级别,线程名称等被中括号包裹的字符串: \[([^\[\]]*)\]
       精确匹配字符串start或ended: (start|ended,)
       匹配剩余字符:(.*)
    // 日志匹配模式表达式. 用括号表示捕获匹配内容, 在后面可以时候用matcher.group(idx)获取捕获的字符串内容
    private static final String pattenExp = "([^\\[\\]]*)\\[([^\\[\\]]*)\\]\\[([^\\[\\]]*)\\]\\[([^\\[\\]]*)\\]\\[([^\\[\\]]*)\\]\\[([^\\[\\]]*)\\]-接口:\\[([^\\[\\]]*)\\]\\-(start|ended,)(.*)";
    private static Pattern pattern ;
    static {
        pattern = Pattern.compile(pattenExp);
     * @Description: 解析单行日志
     * @param line 日志
     * @return: LogLine
     * @author: zongf
     * @time: 2019-04-02 11:08:37
    public static LogLine parserLine(String line) {
        // 匹配单行日志
        Matcher matcher = pattern.matcher(line);
        boolean isFind = matcher.find();
        if (isFind) {
            // 匹配成功, 则创建日志模型
            LogLine logLine = new LogLine();
            // 赋值每个字段
            logLine.setTime(matcher.group(1));
            logLine.setLevel(matcher.group(2));
            logLine.setThread(matcher.group(3));
            logLine.setClz(matcher.group(4));
            logLine.setSid(matcher.group(5));
            logLine.setReqid(matcher.group(6));
            logLine.setUri(matcher.group(7));
            logLine.setTag(matcher.group(8));
            // 如果单行日志类型为ended, 则处理耗时字段
            if ("ended,".equals(logLine.getTag())) {
                String cost = StringUtils.substringBetween(matcher.group(9),":", "ms");
                logLine.setCost(Integer.parseInt(cost));
            return logLine;
        //匹配失败, 返回空
        return null;

2.3 测试用例

笔者这里只测试将单行日志解析为java 对象, 对于读取文件, 日志统计排序就不介绍了, 都是java 的基本功.

    @Test
    public void test_parseLog(){
        String[] logs = new String[]{
                "2019-04-01 13:38:20.591[INFO][http-127.0.0.1-8080-1][org.zongf.test.ApiAccessLogUtil][8BD0EF4739EB58C634D1853B2F7BAF96][67617bd63bb4437295eb6a49601d4b0f]-接口:[/article/1001]-start",
                "2019-04-01 13:38:20.591[INFO][http-127.0.0.1-8080-1][org.zongf.test.ApiAccessLogUtil][8BD0EF4739EB58C634D1853B2F7BAF96][67617bd63bb4437295eb6a49601d4b0f]-接口:[/article/1001]-这是其它日志, 模拟不匹配",
                "2019-04-01 13:38:22.367[INFO][http-127.0.0.1-8080-1][org.zongf.test.ApiAccessLogUtil][8BD0EF4739EB58C634D1853B2F7BAF96][67617bd63bb4437295eb6a49601d4b0f]-接口:[/article/1001]-ended, 访问时间:254ms"
        for (String log : logs) {
            LogLine logLine = LogLineParserUtil.parserLine(log);
            System.out.println(logLine);

2.4 解析结果

笔者自定义toString方法, 只输出几个字段用于测试。 从输出结果来看, 由于第二行日志和笔者自定义的正则匹配模式不匹配, 所以并未做正确解析.

{time='2019-04-01 13:38:20.591', cost=0, uri='/article/1001', sid='8BD0EF4739EB58C634D1853B2F7BAF96', reqid='67617bd63bb4437295eb6a49601d4b0f'}
{time='2019-04-01 13:38:22.367', cost=254, uri='/article/1001', sid='8BD0EF4739EB58C634D1853B2F7BAF96', reqid='67617bd63bb4437295eb6a49601d4b0f'}
                                    项目中有一个新的需求,就是需要解析日志,将日志中的部分数据分析获取出来供系统使用,通俗的讲就是抓取日志中的部分有用的信息,比如下面的apache日志信息,我需要解析每行日志,获取每行日志的IP地址、用户、创建时间、请求方式、地址....如果我们单纯使用java的方式,可能会想到通过文件流读取日志信息,然后逐行解析字符串,但是这种方式太过于复杂,而且效率比较低,在网上查询了相关的资料,决定使用log...
  其原因可能有两个,一个是找不到依赖的某些dll,另一个是有些依赖库存在版本冲突。在我这里是由于Qt的版本冲突造成的。因为之前用的是qt5.9.9,而现在使用的是qt5.14.0。解决办法就是,检查一下环境变量里面的依赖库dll路径,看是否混用了,或者看一下使用的dll版本是否正确。
......
类路径下寻找
寻找顺序为:logback-test.xml->logback.groovy->logback.xml->Configer的实现类->使用默认的BasicConfigurator
BasicConfigurator的等同xml配置
<configuration>
    <appender...
 * {phoneNumber=(+358)0393289092389},
 * then:
 *       (Group #1)          (Group #2)          (Group #3)          (Group #4)          (Group #5)
 *       {                   phoneNumber=        (+358)0393289092389     
                                    参考官网直达车logback(一)理论概述
logback(二)springboot配置日志文件格式、logback-spring配置文件详解
logback(三)mybatis-plus结合logback将sql语句输出到日志文件首先看一下,我们启动springboot项目默认会出现如下格式
当我们输出一行日志时,会打印如下日志
这个时候发现“哈哈哈”不知道是那个方法、哪行,我们就可以按照自己想要的格式来修改日志输出格式下面这个配置是可以直接使用的
logback-spring.xml文件,放到resou
logback支持类似于占位符的变量替换功能,即如果输出的msg里面带有{}符号且括号中间不带其他字符,那么logback在构造LoggingEvent的时候,会用MessageFormat类来格式化msg,将{}替换成具体的参数值。
示例如下:
logger.info("{},it's
 OK.","Hi");
则输出结果如下:
Hi,it's OK.