使用Spring跟踪应用异常(2)—委托模式

上一篇博文中,我们讨论了需要确定应用是否能在生产环境中正常运行,一种方法是通过检查日志文件中的异常并分析它们。不过,日志文件会占用大量的硬盘空间,而且人工检查它们是非常枯燥和不切实际的。我也指出了有几种自动化监控日志文件的方法,譬如,一个基于spring的工具能每天梳理日志,并在发现异常时发出邮件通知。

上次只讲到FileLocator类,它通过遍历一个目录及其下的子目录来找到日志文件,并将它们交给类FileValidator处理。类FileValidator会对文件进行一些检查。首先,检查文件是否足够新。因为有些应用是定期运行的,没有必要对找到的所有文件都进行检查,我们只需要检查当前这次运行过程中新建或修改的日志文件即可。

基于委托模式的异常跟踪,设计思想是通过整合同一个接口的不同实现,构建一个新的聚合对象来负责文件检查。

在上面的类图中,将类RegexValidator和FileAgeValidator的实例注入FileValidator,FileValidator就会代理这些类的验证工作。

下面先来看看Validator接口

public interface Validator {

  /** The validation method */
  public <T> boolean validate(T obj);

}

上面的代码展示了Validator接口的简单,它只有一个泛型方法validate(T obj),这增加了接口的灵活性和可重用性。当类实现了这个接口时,它们可以根据自己的需要改变方法的输入参数类型,就像下面这样:

public class RegexValidator implements Validator {

  private static final Logger logger = LoggerFactory.getLogger(RegexValidator.class);

  private final Pattern pattern;

  public RegexValidator(String regex) {
    pattern = Pattern.compile(regex);
    logger.info("loaded regex: {}", regex);
  }

  @Override
  public <T> boolean validate(T string) {

    boolean retVal = false;
    Matcher matcher = pattern.matcher((String) string);
    retVal = matcher.matches();
    if (retVal && logger.isDebugEnabled()) {
      logger.debug("Found error line: {}", string);
    }

    return retVal;
  }
}

类RegexValidator有一个单参数构造函数,参数为一个正则表达式字符串,构造函数会将该字符串转换为一个模式实例。在方法validate(T String)中,模式实例用于创建Matcher对象,以验证输入参数是否与该对象匹配。如果匹配,validate(T string)方法返回true。

@Service
public class FileAgeValidator implements Validator {

  @Value("${max.days}")
  private int maxDays;

  /**
   * Validate the age of the file.
   * 
   * @see com.captaindebug.errortrack.Validator#validate(java.lang.Object)
   */
  @Override
  public <T> boolean validate(T obj) {

    File file = (File) obj;
    Calendar fileDate = getFileDate(file);

    Calendar ageLimit = getFileAgeLimit();

    boolean retVal = false;
    if (fileDate.after(ageLimit)) {
      retVal = true;
    }

    return retVal;
  }

  private Calendar getFileAgeLimit() {

    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.DAY_OF_MONTH, -1 * maxDays);
    return cal;
  }

  private Calendar getFileDate(File file) {

    long fileDate = file.lastModified();
    Calendar when = Calendar.getInstance();
    when.setTimeInMillis(fileDate);
    return when;
  }

}

上面的类FileAgeValidator是Validator接口的第二个实现,在该类中需要注意的是max.days属性,它被注入到FileAgeValidator类的用@Value标记的maxDays中。这个变量决定了文件距今的最大未修改天数,如果文件上次修改时间早于它,validate(T obj)将返回false。

在FileAgeValidator类中,validate(T obj)的参数obj会被转换为一个File对象,然后将该File对象的上次修改时间转换为一个Calendar实例fileDate。再将maxDasy变量转换为第二个Calendar实例ageLimit,如果fileDate表示的时间晚于ageLimit表示的时间,validate(T obj)方法就返回true。

validator包中的最后一个类是FileValidator,我们会将其他所有类的验证工作委托给该类。这些类包括一个FileAgeValidator和两个RegexValidator。

@Service
public class FileValidator implements Validator {

  private static final Logger logger = LoggerFactory.getLogger(FileValidator.class);

  @Value("${following.lines}")
  private Integer extraLineCount;

  @Autowired
  @Qualifier("scan-for")
  private RegexValidator scanForValidator;

  @Autowired(required = false)
  @Qualifier("exclude")
  private RegexValidator excludeValidator;

  @Autowired
  private FileAgeValidator fileAgeValidator;

  @Autowired
  private Results results;

  @Override
  public <T> boolean validate(T obj) {

    boolean retVal = false;
    File file = (File) obj;
    if (fileAgeValidator.validate(file)) {
      results.addFile(file.getPath());
      checkFile(file);
      retVal = true;
    }
    return retVal;
  }

  private void checkFile(File file) {

    try {
      BufferedReader in = createBufferedReader(file);
      readLines(in, file);
      in.close();
    } catch (Exception e) {
      logger.error("Error whilst processing file: " + file.getPath() + " Message: " + e.getMessage(), e);
    }
  }

  @VisibleForTesting
  BufferedReader createBufferedReader(File file) throws FileNotFoundException {
    BufferedReader in = new BufferedReader(new FileReader(file));
    return in;
  }

  private void readLines(BufferedReader in, File file) throws IOException {
    int lineNumber = 0;
    String line;
    do {
      line = in.readLine();
      if (isNotNull(line)) {
        processLine(line, file.getPath(), ++lineNumber, in);
      }
    } while (isNotNull(line));
  }

  private boolean isNotNull(Object obj) {
    return obj != null;
  }

  private int processLine(String line, String filePath, int lineNumber, BufferedReader in) throws IOException {

    if (canValidateLine(line) && scanForValidator.validate(line)) {
      List<String> lines = new ArrayList<String>();
      lines.add(line);
      addExtraDetailLines(in, lines);
      results.addResult(filePath, lineNumber, lines);
      lineNumber += extraLineCount;
    }

    return lineNumber;
  }

  private boolean canValidateLine(String line) {
    boolean retVal = true;
    if (isNotNull(excludeValidator)) {
      retVal = !excludeValidator.validate(line);
    }
    return retVal;
  }

  private void addExtraDetailLines(BufferedReader in, List<String> lines) throws IOException {

    for (int i = 0; i < extraLineCount; i++) {
      String line = in.readLine();
      if (isNotNull(line)) {
        lines.add(line);
      } else {
        break;
      }
    }
  }

}

FileValidator类的validate(T obj)以一个File作为输入参数,它首先验证文件在指定时间内是否修改过。如果是,就通知Report类发现一个新的需要检查的文件。然后通过一个BufferedReader来顺序检查文件中的每一行,检查过程中需要忽略一些不关心的异常。如果某一行没通过excludeValidator验证(即不是需要忽略的行),就用scanForValidator来检查该行。当这行包含一个错误时,将其加入一个List实例中,该List使得存放检查结果的report可读性更高。这样一个个文件处理下来,就能够生成一个可供后续分析的报告。

这里讲解了如果利用委托模式来检查日志文件并生成一个报告,但是如何利用这个报告,输出如果生成,则是后面要讲的内容。

以上代码请见Github:https://github.com/roghughe/captaindebug/tree/master/error-track

原文链接: captaindebug 翻译: ImportNew.com - 秋双
译文链接: http://www.importnew.com/11659.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 秋双

java码农一枚,兼顾DB和algorithm

查看秋双的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部