Общий шаблон для обнаружения ошибки следует за этим сценарием:
- Соблюдайте странности, например, отсутствие вывода или зависание программы.
- Найдите соответствующее сообщение в журнале или выходе программы, например, «Не удалось найти Foo». (Следующее применимо только в том случае, если этот путь используется для обнаружения ошибки. Если трассировка стека или другая отладочная информация легко доступны, это уже другая история.)
- Найдите код, где печатается сообщение.
- Отладка кода между первым местом, где Foo вводит (или должен вводить) картинку, и местом, где печатается сообщение.
На третьем этапе процесс отладки часто останавливается, потому что в коде есть много мест, где выводится «Не удалось найти Foo» (или шаблонную строку Could not find {name}
). Фактически, несколько раз орфографическая ошибка помогала мне находить фактическое местоположение намного быстрее, чем я в противном случае - это делало сообщение уникальным во всей системе и часто во всем мире, что немедленно приводило к попаданию в поисковую систему.
Очевидный вывод из этого состоит в том, что мы должны использовать глобально уникальные идентификаторы сообщений в коде, жестко кодировать его как часть строки сообщения и, возможно, проверять наличие только одного вхождения каждого идентификатора в базе кода. Что касается удобства обслуживания, что, по мнению этого сообщества, являются наиболее важными плюсами и минусами этого подхода, и как бы вы реализовали этот или иным образом гарантировали, что его реализация никогда не станет необходимой (при условии, что в программном обеспечении всегда будут ошибки)?
Ответы:
В целом это действительная и ценная стратегия. Вот несколько мыслей.
Эта стратегия также известна как «телеметрия» в том смысле, что при объединении всей такой информации они помогают «триангулировать» трассировку выполнения и позволяют специалисту по устранению неполадок понять, что пытается выполнить пользователь / приложение, и что на самом деле произошло ,
Некоторые важные части данных, которые должны быть собраны (которые мы все знаем):
Часто традиционные подходы к ведению журналов оказываются неудачными из-за невозможности отследить низкоуровневое сообщение журнала до команды самого высокого уровня, которая его запускает. Трассировка стека захватывает только имена вышестоящих функций, которые помогали обрабатывать команду самого высокого уровня, а не детали (данные), которые иногда необходимы для характеристики этой команды.
Обычно программное обеспечение не было написано для реализации такого рода требований прослеживаемости. Это затрудняет соотнесение сообщения низкого уровня с командой высокого уровня. Эта проблема особенно остро стоит в свободно многопоточных системах, где многие запросы и ответы могут перекрываться, и обработка может быть выгружена в другой поток, чем исходный поток получения запросов.
Таким образом, чтобы получить максимальную отдачу от телеметрии, потребуются изменения в общей архитектуре программного обеспечения. Большинство интерфейсов и вызовов функций необходимо модифицировать, чтобы принимать и распространять аргумент «трассировщик».
Даже служебные функции должны будут добавить аргумент «tracer», чтобы в случае сбоя сообщение журнала позволяло соотноситься с определенной высокоуровневой командой.
Еще одна ошибка, которая усложняет отслеживание телеметрии, - отсутствие ссылок на объекты (нулевые указатели или ссылки). Когда некоторые важные данные отсутствуют, может быть невозможно сообщить что-либо полезное для сбоя.
С точки зрения написания журнала сообщений:
источник
Представьте, что у вас есть тривиальная служебная функция, которая используется в сотнях мест в вашем коде:
Если бы мы делали, как вы предлагаете, мы могли бы написать
Ошибка, которая может произойти, если вход был нулем; это приведет к делению на ноль исключений.
Допустим, вы видите 27349262 в ваших выходных данных или в ваших журналах. Где вы смотрите, чтобы найти код, который передал нулевое значение? Помните, что функция с ее уникальным идентификатором используется в сотнях мест. Таким образом, вы можете знать, что деление на ноль произошло, но вы не знаете, чье
0
это.Мне кажется, если вы собираетесь заняться регистрацией идентификаторов сообщений, вы также можете записать трассировку стека.
Если вас беспокоит многословность трассировки стека, вам не нужно выводить ее как строку, как это дает среда выполнения. Вы можете настроить его. Например, если вам нужна сокращенная трассировка стека, идущая только по
n
уровням, вы можете написать что-то вроде этого (если вы используете c #):И используйте это так:
Выход:
Может быть, проще, чем поддерживать идентификаторы сообщений, и более гибко.
Украсть мой код из DotNetFiddle
источник
SAP NetWeaver делает это десятилетиями.
Он оказался ценным инструментом при устранении ошибок в огромном кодовом чудовище, которое является типичной системой SAP ERP.
Сообщения об ошибках управляются в центральном хранилище, где каждое сообщение идентифицируется по классу и номеру сообщения.
Когда вы хотите вывести сообщение об ошибке, вы указываете только переменные класса, числа, серьезности и сообщения. Текстовое представление сообщения создается во время выполнения. Обычно вы видите класс и номер сообщения в любом контексте, где появляются сообщения. Это имеет несколько полезных эффектов:
Вы можете автоматически найти любые строки кода в базе кода ABAP, которые создают конкретное сообщение об ошибке.
Вы можете установить динамические точки останова отладчика, которые срабатывают, когда генерируется конкретное сообщение об ошибке.
Вы можете искать ошибки в статьях базы знаний SAP и получать более релевантные результаты поиска, чем если бы вы искали «Не удалось найти Foo».
Текстовые представления сообщений являются переводимыми. Таким образом, поощряя использование сообщений вместо строк, вы также получаете возможности i18n.
Пример всплывающего сообщения об ошибке с номером сообщения:
Поиск этой ошибки в репозитории ошибок:
Найдите это в кодовой базе:
Однако есть и недостатки. Как видите, эти строки кода больше не являются самодокументируемыми. Когда вы читаете исходный код и видите
MESSAGE
утверждение, подобное приведенному на снимке экрана выше, из контекста вы можете только понять, что оно на самом деле означает. Кроме того, иногда люди реализуют пользовательские обработчики ошибок, которые получают класс и номер сообщения во время выполнения. В этом случае ошибка не может быть найдена автоматически или не может быть найдена в месте, где ошибка действительно произошла. Обходной путь для первой проблемы состоит в том, чтобы сделать привычкой всегда добавлять комментарий в исходный код, сообщающий читателю, что означает сообщение. Вторая проблема решается добавлением мертвого кода, чтобы убедиться, что автоматический поиск сообщений работает. Пример:Но есть некоторые ситуации, когда это невозможно. Например, существуют некоторые инструменты моделирования бизнес-процессов на основе пользовательского интерфейса, в которых можно настроить сообщения об ошибках, которые будут отображаться при нарушении бизнес-правил. Реализация этих инструментов полностью основана на данных, поэтому эти ошибки не будут отображаться в списке мест использования. Это означает, что слишком много полагаться на список использованных источников при попытке найти причину ошибки может представлять собой красную сельдь.
источник
Проблема с этим подходом состоит в том, что он ведет к более детальному ведению журнала. 99,9999% из которых вы никогда не увидите.
Вместо этого я рекомендую записывать состояние в начале вашего процесса и успех / провал процесса.
Это позволяет вам воспроизводить ошибку локально, шагая по коду и ограничивая ваше ведение журнала двумя местами на процесс. например.
Теперь я могу использовать точно такое же состояние на моем компьютере разработчика, чтобы воспроизвести ошибку, пройтись по коду в моем отладчике и написать новый модульный тест для подтверждения исправления.
Кроме того, я могу при необходимости избежать больше регистрации, только регистрируя сбои или сохраняя состояние в другом месте (база данных? Очередь сообщений?)
Очевидно, что мы должны быть особенно осторожны при регистрации конфиденциальных данных. Так что это особенно хорошо работает, если ваше решение использует очереди сообщений или шаблон хранилища событий. По логу нужно только сказать "Сообщение xyz Failed"
источник
Я бы предположил, что ведение журнала - это не способ, а скорее то обстоятельство, которое считается исключительным (оно блокирует вашу программу), и следует выдать исключение. Скажи, что твой код был:
Похоже, что вызывающий код не настроен для того, чтобы справиться с тем фактом, что Foo не существует, и вы могли бы потенциально быть:
И это вернет трассировку стека вместе с исключением, которое можно использовать для отладки.
С другой стороны, если мы ожидаем, что Foo может быть нулевым при получении, и это нормально, нам нужно исправить вызывающие сайты:
Тот факт, что ваше программное обеспечение зависает или работает «странно» при неожиданных обстоятельствах, кажется мне неправильным - если вам нужен Foo, и вы не можете справиться с его отсутствием, то кажется, что лучше выйти из строя, чем пытаться идти по пути, который может испортить вашу систему.
источник
Правильные библиотеки журналов предоставляют механизмы расширения, поэтому, если вы хотите узнать метод, откуда возникло сообщение журнала, они могут сделать это «из коробки». Это оказывает влияние на выполнение, поскольку процесс требует генерации трассировки стека и ее обхода, пока вы не выйдете из библиотеки журналов.
Тем не менее, это действительно зависит от того, что вы хотите, чтобы ваш идентификатор сделал для вас:
Все эти вещи могут быть сделаны из коробки с надлежащим программным обеспечением регистрации (то есть не
Console.WriteLine()
илиDebug.WriteLine()
).Лично, что более важно, это способность восстанавливать пути исполнения. Вот для чего предназначены инструменты типа Zipkin . Один идентификатор для отслеживания поведения одного пользовательского действия во всей системе. Поместив свои журналы в центральную поисковую систему, вы можете не только найти самые продолжительные действия, но и вызвать журналы, которые относятся к этому единственному действию (например, стек ELK ).
Непрозрачные идентификаторы, которые меняются с каждым сообщением, не очень полезны. Последовательный идентификатор, используемый для отслеживания поведения через целый набор микросервисов ... очень полезен.
источник