Паттерн написания универсальной системы ошибок приложения

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

Всем доброго времени суток.

1. Мотивация к написанию данной статьи
За свою карьеру написал больше 100 микросервисов и около 30 брал на сопровождение, рефакторинг и доработку. Среди них были сервисы аутентификации, криптографии, адаптеры, прокси, эмитенты токенов, DataStore/DataMart, калькулирующие измерения к срезам статистики на холодных данных и на потоке, оркестраторы с широким спектром смежных систем (пример на хабре) etc. Писал на таких языках, как С#, Java, Kotlin, Scala, Node.js. И некоторое время проходил "день сурка" в момент проектирования или рефакторинга полученного в наследство кода, когда руки доходят до аспекта логирования, мониторинга, обработки ошибок etc. В этой статье опишу с какими реализациями слоя обработки ошибок я сталкивался или находил в качестве best practice, как обычно ее интегрируют в SLA, метрики и логи, почему стал изобретать велосипед и к чему пришел, а также сравню собирательный образ классических подходов с выбраным по итогу проб и ошибок.


2. Собирательный образ классической реализации
2.1 В объектно-ориентированных языках создается целая система кастомных ошибок, в фундамент которой выбира[ею]тся наиболее подходящи[йе] из предложенных языком в качестве суперкласса.
Минусы:
- если система наследования ошибок не прямая как гвоздь, то усложнится и система проверок и рефлексии в логике обработки ошибок;
- чем длиннее жизнь сервиса и/или обширней его бизнес логика (особенно актуально для оркестраторов), тем больше кастомных классов ошибок, что замусоревает код и структуру проекта, а полную карту ошибок можно увидеть только в аналитике к сервису или бегая по пакетам проекта с помощью возможностей IDE (этот минус особенно стреляет в ноги новым разработчикам или тебе, когда забудешь о написанном и решишь себе экскурсию устроить);
- аналитика сервиса требует актуализации при добавлении нового класса-ошибки;

- трата времени на поиск наиболее подходящего причине возникновения ошибки суперкласса, если выбор самого базового в языке не устраивает (неоднократно фиксировал такую дотошность в pr-ах и грумминге).

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

2.3
Система ошибок подразумевает объединение нескольких причин возникновения не целевого поведения в рамках одной сущности или класса-ошибки.
Минусы:
- усложняется мониторинг через метрики, на потоке непонятно сколько, каких причин возникает;
- troubleshooting будет отъедать больше времени, а отдел сопровождения - частенько задавать тебе вопросы приходя с боевыми кейсами;
- отслеживать - какие причины объединены случайно, а какие специально, порой неочевидно, особенно спустя n-месяцев;

2.4 Система ошибок является не сквозной и ограничена контуром твоего сервиса. Например сервису-клиенту отдаются другие представления ошибок, в худшем варианте реализации перекрывающие целые пулы причин не целевого поведения под одним кодом (например если сервис имеет REST API, то ограничиваются HTTP кодом и строковым описанием).
Минусы:
- не прозрачность, добавляет лишний уровень абстракции без сохранения изначальной информации о причине (если отдел сопровождения твоего сервиса и сервиса-клиента не один и тот же, то в случае проблем интеграции между сервисами, жди вопросы от обоих отделов и команды разработки сервиса-клиента, ну а тебе вспоминать связи между двумя системами ошибок);
- доработки бизнес-логики в сервисе-клиенте на нецелевые поведения твоего сервиса с течением времени может потребовать изменений системы ошибок на твоей стороне или контракта между вами для уточнения причины ошибки.

2.5
Ошибку можно идентифицировать только по текстовой информации (названию, stack-trace, description, message etc.).
Минусы:
- обработка таких ошибок, особенно если их вариаций будет много, приведет к "зубной боли" при взгляде на код их парсинга и условий рефлексии на них на клиентской части, что скажется на вероятности багов с клиентской стороны и вовлечении тебя в их разборы на живых кейсах из прома;
- отделу сопровождения и клиентам сервиса будет сложно привыкнуть к ошибкам и запомнить, что является причиной каждой из них (тем более учитывая ротацию персонала в их рядах), тем сложнее, чем больше текстовое описание ошибки.

2.6 Ошибки пишутся в систему мониторинга в несколько метрик или не пишутся вообще.
Минусы:
- чем больше видов метрик ошибок, тем сложнее в этой системе ориентироваться, сложно разобраться сколько негативных исходов с неизвестной причиной на боевом стенде за целевой интервал.

Есть и другие черты таких систем. Думаю на этом этапе посыл уже ясен, что есть масса нюансов, которые стоит учесть в процессе создания описанного слоя.

3. Пилим идеальную систему ошибок
+ у каждой ошибки должен быть свой code типа int;
+ если есть необходимость использовать в архитектуре сквозную систему кодов ошибок и управляющих статусов, то -int будет ошибкой, а +int управляющим статусом;
+ если есть необходимость в сквозной системе ошибок между сервисами, то обычно шаг < 10000 достаточный на сервис;
+ ошибки объединены в интервалы code:
1 - 99 // зарезервированы под внутренние ошибки кода твоего сервиса;
100 - 199 // зарезервированы под первую интеграцию, например ошибки клиентских сторон;
100 - 199 // зарезервированы под первую интеграцию, например ошибки клиентских
сторон;
200-299 // под вторую интеграцию, например базы данных;
300-399 // под третью интеграцию, например кафки;
400-499 // под четвертую интеграцию, например смежный сервис 1;
500-599 // под третью интеграцию, например смежный сервис 2;
+ в объектно-ориентированных языках можно ограничиться созданием Enum с кастовым полем int code, тогда Enum.name будет давать суть причины, ёмко и в текстовом виде, a code - станет якорем в справочнике ошибок твоего приложения, далее создай один кастомный класс ошибки от самого базового класса (например Throwable для Java), добавь ему поле Enum и готово;
+ система ошибок должна прошивать насквозь код твоего сервиса->метрики->логи->ответы клиентам:
* код твоего сервиса - все ошибки в месте возникновения должны оборачиваться в твою кастомную ошибку;
* метрики - например в Prometheus можно создать единственную метрику errors с лэйблом code, что объединит все исходы в одной метрике, круглые (с 2-мя нулями) границы интервалов позволят коллегам из сопровождения и смежникам ориентироваться в интервалах, определяя сторону к которой нужно идти с вопросами или в целом в каком направлении расследовать причины ошибки (если code равен первому значению из интервала);
* в логах также желательно выделить code, например если ELK, то в отдельное поле индекса;
* клиентам в ответ также необходимо отдавать code (если требования к безопасности воспрещают такое поведение, то хотя бы первое значение из интервала в который данный код входит).


Первый code ошибки из любого интервала является дефолтным и процент его возникновения от общего количества ошибок является процентом ТВОЕЙ лени или незнания. Например, если в метрики приложения вынести частотность возникновения той или иной ошибки по code, то стремиться нужно будет к будущему, в котором нет исходов с 1, 100, 200, 300, 400, 500 ошибками, а значит все возможные негативные исходы тебе известны и обработаны тобой в частном порядке.

И да, это все. Такая система лишена всех выше описанных недостатков. Прошла проверку временем.

Источник: https://habr.com/ru/articles/786864/


Интересные статьи

Интересные статьи

В статическом анализаторе PVS-Studio начали появляться диагностические правила для выявления багов, специфичных для Unreal Engine проектов. Однако без сообщества разработчиков игр здесь не обойтись....
Перед вами обновлённая коллекция вредных советов для C++ программистов, которая превратилась в целую электронную книгу. Всего их 60, и каждый сопровождается пояснением, почему на самом деле ему не с...
WOO Network — это экосистема, которая объединяет 3 биржи (одну централизованную и две децентрализованные) вокруг токена WOO, с целью предоставления трейдерам глубокой ликвидности по низкой цене.WOO Ne...
Привет, Хабр! Меня зовут Николай Николаев, я руководитель отдела тестирования Учи.ру. Недавно мы внедрили автоматизацию: ускорили процессы и повысили качество тестирования. Далее я расскажу о пройденн...
За последние несколько лет подкасты в России уже успели появиться, заглохнуть и вновь поймать хайп. Аналитики не перестают считать объем этого рынка, журналисты выпускают подборки из лучших прогр...