[xxl-job source code articles 03] xxl-job log system source code interpretation

guide

xxl-job not only has its own rpc, registration center, but also its own log system. It can be said that although the sparrow is small, it has all internal organs.

This chapter will analyze the log system of xxl-job from the source code level. When we use xxl-job to record logs, we only need to use it during task execution XxlJobHelper.log(). The method is as simple as log4j/slf4j. We can see the real-time log of the task running through the console.

XxlJobHelper.log("hello world");

image-20220413192926220

This is too advanced, and the log can be accurate to the task level, and you can also view it in real time on the console, scheduling method, and all the information that class information should have. So how is it realized? Next, I will take you to analyze xxl -The implementation of the job.

Source code analysis

This time, let's be more straightforward and reason the logic of the log system in a positive direction, and locate the log method

com.xxl.job.core.context.XxlJobHelper#log(java.lang.String, java.lang.Object...)

public static boolean log(String appendLogPattern, Object ... appendLogArguments) {
    
    
    // 使用slf4j解析器格式化日志内容
    FormattingTuple ft = MessageFormatter.arrayFormat(appendLogPattern, appendLogArguments);
    String appendLog = ft.getMessage();
    // 获得栈帧信息
    StackTraceElement callInfo = new Throwable().getStackTrace()[1];
    return logDetail(callInfo, appendLog);
}

pay attention to this paragraphStackTraceElement callInfo = new Throwable().getStackTrace()[1];

This is the method to obtain the call stack frame, index 0 is the current stack frame, 1 is the call stack frame, and so on, here is the index 1, that is to say, the stack frame information that calls the method is obtained, which can be obtained StackTraceElementby Call class name, method name, line number and other information

continue intocom.xxl.job.core.context.XxlJobHelper#logDetail

private static boolean logDetail(StackTraceElement callInfo, String appendLog) {
    
    
    // 获得当前上下文对象
    XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();
    if (xxlJobContext == null) {
    
    
        return false;
    }
    // 拼接格式化日志信息
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append(DateUtil.formatDateTime(new Date())).append(" ")
            .append("["+ callInfo.getClassName() + "#" + callInfo.getMethodName() +"]").append("-")
            .append("["+ callInfo.getLineNumber() +"]").append("-")
            .append("["+ Thread.currentThread().getName() +"]").append(" ")
            .append(appendLog!=null?appendLog:"");
    String formatAppendLog = stringBuffer.toString();
    // 获得日志文件路径
    String logFileName = xxlJobContext.getJobLogFileName();
    if (logFileName!=null && logFileName.trim().length()>0) {
    
    
        // 流的形式将日志写入本地文件
        XxlJobFileAppender.appendLog(logFileName, formatAppendLog);
        return true;
    } else {
    
    
        logger.info(">>>>>>>>>>> {}", formatAppendLog);
        return false;
    }
}

Here is a key piece of code

XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();

This is the context object for obtaining the current task, and it will be initialized when it is scheduled. I have mentioned it in the previous article [ [xxl-job source code article 01] xxl-job source code interpretation of the magical time wheel trigger process interpretation ], and I will not expand here

We can see that xxl-job writes the logs to the local file and does not push them to the server. The logs here are not in the push mode, but in the mode. When we open the task log in the background, the server 拉模式will Go to the client to pull the log.

Locate to the interfacecom.xxl.job.admin.controller.JobLogController#logDetailCat

@RequestMapping("/logDetailCat")
@ResponseBody
public ReturnT<LogResult> logDetailCat(String executorAddress, long triggerTime, long logId, int fromLineNum){
    
    
   try {
    
    
      // 获得执行机的remote实例
      ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(executorAddress);
      // 调用客户端netty http接口拉取日志
      ReturnT<LogResult> logResult = executorBiz.log(new LogParam(triggerTime, logId, fromLineNum));
      // 判断日志是否结束
           if (logResult.getContent()!=null && logResult.getContent().getFromLineNum() > logResult.getContent().getToLineNum()) {
    
    
               XxlJobLog jobLog = xxlJobLogDao.load(logId);
               if (jobLog.getHandleCode() > 0) {
    
    
                   logResult.getContent().setEnd(true);
               }
           }
      return logResult;
   } catch (Exception e) {
    
    
      logger.error(e.getMessage(), e);
      return new ReturnT<LogResult>(ReturnT.FAIL_CODE, e.getMessage());
   }
}

When the execution status is incomplete, the xxl-job log console will call this interface cyclically until the task is completed.

Next, let's take a look at the code related to the client reading log filescom.xxl.job.core.biz.impl.ExecutorBizImpl#log

@Override
public ReturnT<LogResult> log(LogParam logParam) {
    
    
    // log filename: logPath/yyyy-MM-dd/9999.log
    // 获得日志文件名
    String logFileName = XxlJobFileAppender.makeLogFileName(new Date(logParam.getLogDateTim()), logParam.getLogId());
    // 读取流信息
    LogResult logResult = XxlJobFileAppender.readLog(logFileName, logParam.getFromLineNum());
    return new ReturnT<LogResult>(logResult);
}

com.xxl.job.core.log.XxlJobFileAppender#readLog

public static LogResult readLog(String logFileName, int fromLineNum){
    
    
   if (logFileName==null || logFileName.trim().length()==0) {
    
    
           return new LogResult(fromLineNum, 0, "readLog fail, logFile not found", true);
   }
   File logFile = new File(logFileName);
   if (!logFile.exists()) {
    
    
           return new LogResult(fromLineNum, 0, "readLog fail, logFile not exists", true);
   }
   // read file
   StringBuffer logContentBuffer = new StringBuffer();
   int toLineNum = 0;
   LineNumberReader reader = null;
   try {
    
    
      reader = new LineNumberReader(new InputStreamReader(new FileInputStream(logFile), "utf-8"));
      String line = null;
      while ((line = reader.readLine())!=null) {
    
    
         toLineNum = reader.getLineNumber();       // [from, to], start as 1
         if (toLineNum >= fromLineNum) {
    
    
            logContentBuffer.append(line).append("\n");
         }
      }
   } catch (IOException e) {
    
    
      logger.error(e.getMessage(), e);
   } finally {
    
    
      if (reader != null) {
    
    
         try {
    
    
            reader.close();
         } catch (IOException e) {
    
    
            logger.error(e.getMessage(), e);
         }
      }
   }
   LogResult logResult = new LogResult(fromLineNum, toLineNum, logContentBuffer.toString(), false);
   return logResult;
}

The xxl-job client will save the log file to the disk. When the log console is opened, the server will request the client in a loop, and the client will read the log with the specified number of lines in the form of io stream and return it to the server.

Link

[xxl-job source code articles 01] xxl-job source code interpretation of the magical time wheel trigger process interpretation

[xxl-job source code articles 02] Application of self-developed RPC netty by the registration center?

Guess you like

Origin blog.csdn.net/qq_21046665/article/details/124166873