package com.yirendai.infra.cicada.repository; import com.yirendai.infra.cicada.config.CicadaCollectorProps; import com.yirendai.infra.cicada.constants.DateTimeFormats; import com.yirendai.infra.cicada.util.BufferedFile; import com.yirendai.infra.cicada.util.progress.Progress; import com.yirendai.infra.cicada.util.progress.ProgressRecorder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.filefilter.PrefixFileFilter; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; /** * trace日志读取工具. * @author Zecheng */ @Slf4j @Component public class LogReader { private static final String FILENAME_PREFIX = "record.log"; private static final int LINE_COLUMN_SIZE = 2; private static final int LINES_LIMIT = 10000; private volatile boolean isInterrupt = false; @Autowired private CicadaCollectorProps props; @Autowired private ProgressRecorder recorder; public void finish() { isInterrupt = true; } // 从rootDir目录下的文件中tail数据,操作必须串行 public synchronized List<String> read() { final List<String> results = new LinkedList<String>(); final DateTime curTime = DateTime.now(); Progress progress = recorder.get(); // 获取所有自上次读取以来到当前时间为止,合法(未读取或未读取完)的文件列表 final List<String> validFilePaths = getAllValidFiles(progress, curTime); boolean isCurDateLog = false; final String curDateStr = curTime.toString(DateTimeFormats.FULL_DATE_ENGLISH); for (int i = 0; i < validFilePaths.size(); ++i) { // 判断是否为当天的日志 final String logDateStr = StringUtils.substringAfterLast(validFilePaths.get(i), "."); if (logDateStr.equals(curDateStr)) { isCurDateLog = true; } results.addAll(getLines(validFilePaths.get(i), progress, isCurDateLog)); progress = null; } return results; } private List<String> getLines(final String path, final Progress progress, final boolean isCurDateLog) { // 打开文件并定位到上次读取的位置(如果有) BufferedFile file = BufferedFile.open(path); try { if (progress != null) { file.seek(progress.getOffset()); } } catch (IOException ex) { log.error("failed seek file {} to offset {}, error {}", path, progress.getOffset(), ex); } final List<String> lines = new LinkedList<String>(); // 如果读取的日志文件不是当天的文件, // 从文件的当前偏移把后面所有的日志读取出来 if (!isCurDateLog) { try { for (final String line : file.getAllLines()) { final String[] arr = line.split("\t"); if (arr.length != LINE_COLUMN_SIZE) { continue; } lines.add(arr[1]); } // 记录进度 final Progress newProgress = new Progress(path, file.tell()); recorder.set(newProgress); } catch (IOException ex) { log.error("failed get all lines from file {}, error {}", path, ex); } finally { file.close(); } return lines; } // 如果是文件尾,等待 1000ms 再继续读 try { if (file.feof()) { TimeUnit.MILLISECONDS.sleep(1000); } } catch (IOException ex) { log.error("failed judge if reached EOF, file {}, error {}", path, ex); } catch (InterruptedException ex) { log.error("failed sleep interrupted, error{}", ex); } String line = null; long curOffset = 0; int count = 0; try { while (true) { // 如果设置了isInterrupt标识,表示进程接收到SIGTERM信号 // 应该停止读操作,丢弃已经读取的数据,进行清理工作 if (isInterrupt) { break; } line = file.getLine(); if (line == null) { curOffset = file.tell(); // 如果当前文件指针并非指向文件尾,重新打开文件, // seek到上次的位置,继续读取 if (curOffset < file.getFileSize()) { file.close(); file = BufferedFile.open(path); file.seek(curOffset); continue; } else { // 否则,记录进度,退出循环 final Progress newProgress = new Progress(path, curOffset); recorder.set(newProgress); break; } } final String[] arr = line.split("\t"); if (arr.length != LINE_COLUMN_SIZE) { continue; } lines.add(arr[1]); // 退出条件 ++count; if (count >= LINES_LIMIT) { // 记录读取进度 curOffset = file.tell(); final Progress newProgress = new Progress(path, curOffset); recorder.set(newProgress); break; } } } catch (IOException ex) { log.error("failed get lines from file {}, error {}", path, ex); } finally { file.close(); } return lines; } private List<String> getAllValidFiles(final Progress progress, final DateTime endTime) { final List<String> validPaths = new ArrayList<String>(); // 有效期范围内的文件列表 // 如果目录不存在,返回空列表 final File dir = new File(props.getTraceLogRootDir()); if (!dir.exists()) { return validPaths; } // 扫描目录下的所有文件 final String[] fileNames = dir.list(new PrefixFileFilter(FILENAME_PREFIX)); // 按日期排序 Arrays.sort(fileNames); if (progress == null) { // 返回所有endTime之前的文件 for (final String fileName : fileNames) { final String dateStr = StringUtils.substringAfterLast(fileName, "."); final DateTime logDate = DateTime.parse(dateStr); if (logDate.isBefore(endTime)) { validPaths.add(props.getTraceLogRootDir() + fileName); } } } else { final String curFilePath = progress.getFileName(); validPaths.add(curFilePath); final String beginTimeStr = StringUtils.substringAfterLast(curFilePath, "."); final DateTime beginTime = new DateTime(beginTimeStr); // 返回所有上次记录到当前时间范围之内的文件 for (final String fileName : fileNames) { final String dateStr = StringUtils.substringAfterLast(fileName, "."); final DateTime logDate = DateTime.parse(dateStr); if (logDate.isAfter(beginTime) && logDate.isBefore(endTime)) { validPaths.add(props.getTraceLogRootDir() + fileName); } } } return validPaths; } }