Статьи

Отслеживание исключений приложений с помощью Spring

Несколько недель назад коллега попросил меня провести неделю, выполняя роль поддержки, так как он нуждался в прикрытии, пока он взял хорошо заработанный отпуск, и он не мог найти никого другого. Поскольку я только что закончил особенно сложный проект по кодированию и чувствовал себя немного обессиленным, я сказал «да»; В конце концов, изменения принесут мне пользу.

Часть работы состояла в мониторинге набора довольно важных поддерживаемых процессов, чтобы увидеть, насколько хорошо они работали и шли ли они не так.

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

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

Существует как минимум три способа мониторинга файлов журналов:

  • Никогда
  • реактивно
  • Заблаговременное

«Никогда» означает то, что говорит. Держу пари, что есть такие приложения, которые запускаются, никогда не выходят из строя и никогда не проверяются.

«Реактивная» стратегия очень распространена; Миссис Смит из Донкастера звонит и жалуется, что веб-сайт упал, когда она пыталась купить новую пару обуви. Ей предъявили обвинение дважды, и она так и не получила обувь В этой традиционной компании, где роли DEV и OPS полностью разделены, разработчик, расследующий проблему, запрашивает журналы для времени и даты инцидента у OPS. Разработчик, конечно, возвращает совершенно не связанный раздел журнала и должен повторно запросить правильный раздел — несколько раз. К тому времени прошло несколько недель, и миссис Смит разгневалась. Журнал в конце концов прибывает и проблема решена.

В этом «реактивном» сценарии я предполагаю, что это одна из тех компаний, где разработчики не имеют НИКАКИХ ОБСТОЯТЕЛЬСТВ, которым доверяют или которым разрешено касаться живых серверов. Это слишком часто, и большинство из нас были там в какой-то момент. Разработчикам следует доверять доступ к работающим системам; однако, как разработчик, вы должны помнить, что при работе с живыми системами есть два золотых правила:

  1. Не ломай ничего.
  2. Если вы что-то сломаете, убедитесь, что кто-то другой виноват.

«Упреждающе» означает, что файлы журналов проверяются на регулярной основе: ежедневно, ежечасно или что-либо еще. Даже если ваше приложение содержит множество JMX, http или других зондов, нет гарантии, что они обнаружат проблему. Зонды могут только исследовать то, что вы им говорите, любая другая проблема за ними.

Возвращаясь к моему заклинанию о поддержке… по какой-то, вероятно, исторической причине, большая часть этого мониторинга состояла в ручной проверке файлов журналов на наличие ошибок с использованием набора документированных команд «grep» с помощью небольшого количества операций вырезания и вставки. Если вы добавите время, затрачиваемое на выполнение повторяющейся работы по вырезанию и вставке, то я пойму, что это занимало примерно ½ человека в день в неделю.

И это заставило меня задуматься … Мне действительно не нравится выполнять такие задачи. Я не очень хорош в этом, это руководство, склонное к ошибкам, повторяющееся и очень скучное. При сжигании по 1/2 человека в неделю очевидно, что автоматизировать эту задачу, очевидно, будет экономически выгодно, если не тратить время на то, чтобы сделать решение идеальным. Итак, какие есть варианты?

Если вы посмотрите на профессиональный конец шкалы, то есть такие, как Splunk, который может отслеживать сообщения из множества источников, таких как демон syslog.

Это означает, что простой способ отправки ошибок в Splunk — просто настроить приложение системного журнала Log4j — но это выходит за рамки этого блога …

В быстром и грязном конце спектра вы можете очень быстро написать сценарий оболочки для нескольких команд ‘grep’, записать результаты в файл и отправить его в почтовый ящик Unix.

Тогда я подумал о среднем плане. Насколько сложно написать приложение Spring, которое содержит как можно больше универсальных, повторно используемых классов, периодически запускает проверку целой пачки журналов ошибок и отправляет результаты по электронной почте. Для такого рода вещей электронные письма хороши, потому что вы обычно читаете их по привычке.

Начиная

В любом проекте, подобном этому, есть те нематериальные первые шаги, которые приводят все в движение. Они формируют этот ответ на вопрос о том, какие классы вам нужно написать, чтобы выпустить эту «вещь»? Есть много методов определения того, какие классы вам нужно написать, например, просто выбрать их из воздуха (ощущение инстинкта), используя формальную технику проектирования, такую ​​как UML, быстрое создание прототипов или тестирование на основе проектирования. В каждом случае все, что вы действительно делаете, — это выдвигаете несколько требований, из которых появляются названия некоторых классов. Например, в этом случае мне нужно:

  1. Поиск в заданном каталоге и его подкаталогах (возможно) в поисках файлов определенного типа.
  2. Если файл найден, проверьте его дату: нужно ли искать ошибки?
  3. Если файл достаточно молодой, чтобы его можно было проверить, проверьте его и найдите исключения.
  4. Если в нем есть исключения, это те, кого мы ищем, или они были исключены?
  5. Если он содержит исключения, которые нам нужны, добавьте детали в отчет.
  6. После проверки всех файлов отформатируйте отчет, готовый к публикации.
  7. Опубликуйте отчет по электронной почте или другим способом.
  8. Все это будет работать в определенное время каждый день

Это подбрасывает несколько имен классов. Мне нужны FileLocator , FileValidator , RegexValidator , FileAgeValidator и Report .

Учитывая, что слово «Validator» появляется несколько раз в приведенном выше списке, это означает, что я могу использовать интерфейс, предположительно называемый Validator и создать несколько реализаций для выполнения вышеуказанных задач проверки. Эти реализации затем могут быть объединены для создания согласованного приложения.

Обратите внимание, что это только предварительный список идей. Если вы посмотрите на код, вы не найдете класс с именем Report , он был переименован в Results и подвергся рефакторингу для создания интерфейса HtmlFormatter классов TextFormatter и HtmlFormatter вместе с интерфейсом Publisher и классом EmailPublisher .

С точки зрения запуска этого периодически есть несколько вариантов. Во-первых, вы можете написать код Java вместе с простым сценарием, чтобы вызвать его и предложить его crontab для ваших машин Unix. Это было бы хорошо, но это означает, что приложение не будет работать в Windows и будет ограничено запуском в качестве отдельного приложения. Это может иметь большое значение, так что альтернативой будет использование Spring и Quartz schedular; сделать настройку расписания cron довольно простым.

Что касается отправки отчета по электронной почте; опять же, Spring предоставляет действительно хороший шаблон электронной почты, который упрощает использование классов электронной почты Java.

Итак, это отправная точка: набор туманных идей для классов, которые могут быть связаны между собой каким-то слабо связанным способом создания нашего приложения. Если вы выбрали формальный маршрут, то вы можете потратить время на документирование всего этого, возможно, даже на создание диаграммы классов, которая затем добавляется в документ Word и просматривается несколько раз, пока все не станет каменным; Однако мне не нужно беспокоиться обо всем этом здесь …

Настройка приложения

Как и в любом приложении, необходимо выяснить, как мы собираемся настроить то, что обычно представляет собой целый набор значений свойств, и как они используются приложением. Это приложение управляется файлом app.properties расположенным, как обычно, в каталоге src/main/resources .

01
02
03
04
05
06
07
08
09
10
11
# The path to the log file directory to scan for errors
scan.in=/Library/Tomcat/logs
# A regex defining what to look for - or what not to include
scan.for=^.*Exception.*
exclude=^.*IllegalStateException.*
# The number of following lines to add to the report
following.lines=10
# Where to email the report
# The max age of a file in days
max.days=1000

Первое из этих свойств, которое нас интересует, это свойство scan.in Это расположение каталога журналов нашего веб-сервера и используется в качестве отправной точки для класса FileLocator описанного в следующем разделе.

Поиск файлов

При написании FileLocator я немного вышел за рамки требований и намеренно сделал небольшое «золотое покрытие» .

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

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

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 FileLocator {
 
  private static final Logger logger = LoggerFactory.getLogger(FileLocator.class);
 
  @Value("${scan.in}")
  private String scanIn;
 
  @Autowired
  @Qualifier("fileValidator")
  private Validator validator;
 
  /** Search for the files requested */
  public void findFile() {
 
    logger.info("Searching in... {}", scanIn);
    File file = createFile(scanIn);
    search(file);
  }
 
  @VisibleForTesting
  File createFile(String name) {
    return new File(name);
  }
 
  private void search(File file) {
 
    if (file.isDirectory()) {
      logger.debug("Searching directory: {}", file.getName());
      File[] files = file.listFiles();
      searchFiles(files);
    } else {
      logger.debug("Validating file: {}", file.getName());
      validator.validate(file);
    }
  }
 
  private void searchFiles(File[] files) {
    for (File file : files) {
      search(file);
    }
  }
}

Приведенный выше код использует старый верный метод рекурсии для поиска файлов журнала. Основной точкой входа является findFile() . Он создает объект File из аннотированной переменной экземпляра @Value Spring @Value и передает его в метод search() . Затем он проверяет, является ли этот объект каталогом, и, если это так, он получает все файлы в каталоге и циклически обращается к функции search() для каждого объекта File в списке. Если проверка каталога показывает, что объект File является файлом, то вызывается проверка файла.

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

Одна заключительная мысль: вам нужно исследовать каждую ошибку в вашей системе? Есть старая философская поговорка: если дерево падает в лесу и там никого нет, чтобы услышать это, оно все еще издает звук? Точно так же, если ваше приложение выдает исключение, а пользователь не затронут, это все еще ошибка? Вам нужно потратить время на его изучение? Дайте мне знать, что вы думаете?