Статьи

Отслеживание исключений в Spring — часть 2 — шаблон делегата

В своем последнем блоге я начал рассказывать о необходимости выяснить, не работает ли ваше приложение в производственной среде. Я сказал, что одним из методов мониторинга вашего приложения является проверка его журналов на наличие исключений и принятие соответствующих действий, если оно найдено. Очевидно, что файлы журналов могут занимать сотни мегабайт дискового пространства, и нецелесообразно и очень скучно наблюдать за ними вручную.

Я также сказал, что существует несколько способов автоматического мониторинга файлов журналов, и предложил утилиту на основе Spring, которая ежедневно расчесывает файлы журналов и отправляет вам электронное письмо, если / когда обнаруживает какие-либо исключения.

Я только FileLocator описание первого класса: FileLocator , который будет искать каталог и его подкаталоги для файлов журнала. Когда он находит его, он передает его FileValidator .

FileValidator должен выполнить несколько проверок файла. Во-первых, он должен определить, достаточно ли молод файл для проверки на наличие исключений. Идея состоит в том, что, поскольку приложение запускается периодически, нет смысла проверять все файлы, найденные в каталоге, на наличие ошибок, нам нужны только те файлы, которые были созданы или обновлены с момента последнего запуска приложения.

Идея этого проекта состоит в том, чтобы объединить несколько реализаций одного и того же интерфейса, создавая совокупный объект, который отвечает за проверку файлов. Читатель с орлиными глазами заметит, что это реализация шаблона делегата .

Экран + выстрел + 2014-03-08 + на + 16.59.55

На приведенной выше диаграмме классов экземпляры RegexValidator и FileAgeValidator в FileValidator и он делегирует свои задачи проверки этим классам.

Взять каждый из них по очереди и сначала разобраться с интерфейсом Validator

1
2
3
4
5
6
public interface Validator {
 
  /** The validation method */
  public <T> boolean validate(T obj);
 
}

Приведенный выше код демонстрирует простоту интерфейса Validator . Он имеет единственный метод validate(T obj) , который является вызовом универсального метода, который увеличивает гибкость и возможность повторного использования этого интерфейса. Когда классы реализуют этот интерфейс, они могут изменить тип входного аргумента в соответствии со своими собственными целями… как продемонстрировано в первой реализации ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 имеет конструктор с одним аргументом, который принимает строку регулярного выражения. Затем он преобразуется в переменную экземпляра Pattern и используется методом validate(T string) для проверки того, соответствует ли входной аргумент String исходному конструктору регулярное выражение arg. Если это так, тогда validate(T string) вернет true.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@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;
  }
 
}

Вторая реализация Validator(T obj) — это FileAgeValidator показанный выше, и первое, на что следует обратить внимание, это то, что все управляется свойством max.days . Это внедряется в аннотированную переменную экземпляра maxDays @Value maxDays . Эта переменная определяет максимальный возраст файла в днях. Этот файл старше этого значения, затем validate(T obj) вернет false.

В этой реализации аргумент validate(T obj) ‘obj’ приводится к объекту File , который затем используется для преобразования даты файла в объект Calendar . Следующая строка кода преобразует переменную maxDays во второй объект Calendar : ageLimit . Затем ageLimit сравнивается с объектом fileDate . Если fileDate находится после ageLimit тогда validate(T obj) возвращает true.

Последний класс в пакете validatorFileValidator , который, как показано выше, делегирует большую часть своей ответственности остальным трем другим агрегированным валидаторам: одному FileAgeValidator и двум RegexValidator .

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
@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 в качестве аргумента. Его первая обязанность — подтвердить возраст файла. Если этот валидатор возвращает true, то он сообщает классу Report что он нашел новый действительный файл. Затем он проверяет файл на наличие ошибок и добавляет все найденные в экземпляр Report . Это делается с помощью BufferedReader для проверки каждой строки файла по очереди. Перед проверкой, содержит ли строка ошибку, она проверяет, не была ли строка исключена из проверки, то есть, что она не соответствует исключенным исключениям или тем, которые нам не интересны. Если строка не соответствует исключенному исключения, затем проверяются на исключения, которые нам нужно найти, используя второй экземпляр RegexValidator . Если строка содержит ошибку, она добавляется в объект List<String> . Затем из файла, добавленного в список, читается ряд следующих строк, чтобы сделать отчет более читабельным.

Итак, анализ файла продолжается, проверяя каждую строку за раз, выискивая ошибки и создавая отчет, который можно обработать позже.

Эта оболочка проверяет файлы, используя шаблон делегата, добавляя любые найденные исключения в Report , но как работает этот объект Report ? Я не упоминал об этом, и как генерируется вывод? Подробнее об этом в следующий раз.