Макрос _ITERATOR_DEBUG_LEVEL определяет, включены ли проверенные итераторы и Поддержка итератора отладки . The _ITERATOR_DEBUG_LEVEL macro controls whether checked iterators and debug iterator support are enabled. Этот макрос заменяет и объединяет функциональные возможности старых макросов _SECURE_SCL и _HAS_ITERATOR_DEBUGGING. This macro supersedes and combines the functionality of the older _SECURE_SCL and _HAS_ITERATOR_DEBUGGING macros.

Значение макроса Macro Values

В следующей таблице приведены возможные значения для макроса _ITERATOR_DEBUG_LEVEL. The following table summarizes the possible values for the _ITERATOR_DEBUG_LEVEL macro.

Режим компиляции Compilation mode Значение макроса Macro value Описание Description
Отладка Debug
0 0 Отключает проверенные итераторы и отключает отладку итераторов. Disables checked iterators and disables iterator debugging.
1 1 Включает проверенные итераторы и отключает отладку итераторов. Enables checked iterators and disables iterator debugging.
2 (по умолчанию) 2 (Default) Включает отладку итераторов; проверенные итераторы не имеют значения. Enables iterator debugging; checked iterators are not relevant.
Релиз Release
0 (по умолчанию) 0 (Default) Отключает проверенные итераторы. Disables checked iterators.
1 1 Включает проверенные итераторы; отладка итераторов не имеет значения. Enables checked iterators; iterator debugging is not relevant.

В режиме выпуска компилятор выдает ошибку, если указать _ITERATOR_DEBUG_LEVEL как 2. In release mode, the compiler generates an error if you specify _ITERATOR_DEBUG_LEVEL as 2.

Примечания Remarks

Макрос _ITERATOR_DEBUG_LEVEL определяет, включены ли проверенные итераторы , а также в режиме отладки, включена ли Поддержка итераторов отладки . The _ITERATOR_DEBUG_LEVEL macro controls whether checked iterators are enabled, and in Debug mode, whether debug iterator support is enabled. Если _ITERATOR_DEBUG_LEVEL определен как 1 или 2, проверяемые итераторы гарантируют, что границы контейнеров не перезаписываются. If _ITERATOR_DEBUG_LEVEL is defined as 1 or 2, checked iterators ensure that the bounds of your containers are not overwritten. Если значение _ITERATOR_DEBUG_LEVEL равно 0, то итераторы не проверяются. If _ITERATOR_DEBUG_LEVEL is 0, iterators are not checked. Если _ITERATOR_DEBUG_LEVEL определен как 1, любое ненадежное использование итератора вызывает ошибку времени выполнения и программа завершается. When _ITERATOR_DEBUG_LEVEL is defined as 1, any unsafe iterator use causes a runtime error and the program is terminated. Если _ITERATOR_DEBUG_LEVEL определен как 2, ненадежное использование итератора приводит к появлению диалогового окна "Assert" и "Error Runtime", которое позволяет переключиться на отладчик. When _ITERATOR_DEBUG_LEVEL is defined as 2, unsafe iterator use causes an assert and a runtime error dialog that lets you break into the debugger.

Так как макрос _ITERATOR_DEBUG_LEVEL поддерживает аналогичные функции для макросов _SECURE_SCL и _HAS_ITERATOR_DEBUGGING, возможно, вы не уверены, какое значение макроса и макроса использовать в конкретной ситуации. Because the _ITERATOR_DEBUG_LEVEL macro supports similar functionality to the _SECURE_SCL and _HAS_ITERATOR_DEBUGGING macros, you may be uncertain which macro and macro value to use in a particular situation. Чтобы избежать путаницы, рекомендуется использовать только макрос _ITERATOR_DEBUG_LEVEL. To prevent confusion, we recommend that you use only the _ITERATOR_DEBUG_LEVEL macro. В этой таблице описывается эквивалентное значение макроса _ITERATOR_DEBUG_LEVEL, используемое для различных значений _SECURE_SCL и _HAS_ITERATOR_DEBUGGING в существующем коде. This table describes the equivalent _ITERATOR_DEBUG_LEVEL macro value to use for various values of _SECURE_SCL and _HAS_ITERATOR_DEBUGGING in existing code.

_ITERATOR_DEBUG_LEVEL _ITERATOR_DEBUG_LEVEL _SECURE_SCL _SECURE_SCL _HAS_ITERATOR_DEBUGGING _HAS_ITERATOR_DEBUGGING
0 (значение по умолчанию для релиза) 0 (Release default) 0 (отключено) 0 (disabled) 0 (отключено) 0 (disabled)
1 1 1 (включено) 1 (enabled) 0 (отключено) 0 (disabled)
2 (значение по умолчанию для отладки) 2 (Debug default) (неприменимо) (not relevant) 1 (включено в режиме отладки) 1 (enabled in Debug mode)

Сведения о том, как отключить предупреждения о проверенных итераторах, см. в разделе _SCL_SECURE_NO_WARNINGS. For information on how to disable warnings about checked iterators, see _SCL_SECURE_NO_WARNINGS.

Пример Example

Чтобы указать значение для макроса _ITERATOR_DEBUG_LEVEL, используйте параметр компилятора /d для определения его в командной строке или используйте #define перед включением заголовков C++ стандартной библиотеки в исходные файлы. To specify a value for the _ITERATOR_DEBUG_LEVEL macro, use a /D compiler option to define it on the command line, or use #define before the C++ Standard Library headers are included in your source files. Например, чтобы скомпилировать Sample. cpp в режиме отладки и использовать поддержку итератора отладки, в командной строке можно указать определение макроса _ITERATOR_DEBUG_LEVEL: For example, on the command line, to compile sample.cpp in debug mode and to use debug iterator support, you can specify the _ITERATOR_DEBUG_LEVEL macro definition:

cl /EHsc /Zi /MDd /D_ITERATOR_DEBUG_LEVEL=1 sample.cpp

В исходном файле укажите макрос перед любыми заголовками стандартной библиотеки, определяющими итераторы. In a source file, specify the macro before any standard library headers that define iterators.

Debug — что это? Debug, или отладка. в компьютерном программировании и разработке, — это многоэтапный процесс, который включает определение проблемы, выявление ее источника, а затем исправление сбоя или выбор способа дальнейшей работы. Последним шагом отладки является проверка корректного исправления.

Введение

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

Debug — что это и как работает? Тактика может включать интерактивную отладку, анализ потока управления, модульное и интеграционное тестирование, анализ файлов журналов, мониторинг на уровне приложений или системы, дампы памяти и профилирование.

Debug — что это? Описание процесса

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

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

Debug в контексте MS-DOS

В MS-DOS Debug — что это? Это команда, которая позволяет программистам исследовать и изменять источники содержимого памяти, которые происходят в операционной системе. Методика предоставления инструкций по компьютерным задачам через интерфейс командной строки изначально использовалась в средах MS-DOS для перевода кода ассемблера в рабочий код и машинного языка в исполняемые (debug.exe) файлы.

Debug позволяет разработчикам просматривать содержимое памяти, вносить изменения, а затем выполнять COM, .exe и другие типы файлов.

История

Microsoft впервые представила команду debug в MS-DOS 1.0 в качестве метода тестирования программ. Была добавлена ​​дополнительная функциональность — инструмент ориентировался на различные операционные задачи, такие как отображение содержимого части памяти, ввод данных по указанному адресу, запуск исполняемых файлов памяти, шестнадцатеричная арифметика и манипуляция регистрационной памятью.

Важный этап обнаружения ошибок

После выявления программного сбоя необходимо найти ошибку в коде (Debug error). На этом этапе полезно просмотреть ведение журнала кода и использовать автономный инструмент отладчика или компонент отладки интегрированной среды разработки (IDE). Изначально обнаруживаются и фиксируются ошибки в наиболее популярных функциях. В некоторых случаях модуль, представляющий проблему, очевиден, а сама строка кода — нет. В этом случае модульные тесты, такие как JUnit и xUnit, которые позволяют программисту запускать определенную функцию с конкретными входами, могут быть полезны при отладке.

Процесс отладки

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

Общие инструменты отладки

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

Поиск и удаление ошибок программного обеспечения

Некоторые инструменты, особенно инструменты с открытым исходным кодом и языки сценариев, не запускаются в среде IDE и требуют ручного подхода к отладке. Такие методы включают в себя сброс значений в журнал, расширенные «печатные» заявления, добавленные во время выполнения кода или жестко закодированные debug-команды (например, wait), которые имитируют точку остановки, ожидая ввода клавиатуры в определенное время.

Debug Dump Files — можно ли удалить?

Многие пользователи после возникновения сбоя обнаруживают в месте хранения программы системные файлы. Документы носят наименование Debug Dump Files. Можно ли удалить их? Это отладочные файлы, которые создаются после нарушения работы ПО, чтобы помочь определить причину возникновения ошибки. Если вы не пытаетесь устранить проблему, то можете удалить их.

Описание проблемы

Давайте разберемся, о чем идет речь. Пусть у нас есть Web-сервис. В какой-то момент он начинает сбоить на рабочих серверах, но только для некоторых запросов. Например, сбои происходят только для определенного пользователя. Или только на отдельной точке доступа… Нам необходимо найти причину. В этом случае нам на помощь приходит логирование.

Мы можем вставить в наш код достаточно инструкций логирования, чтобы понять причину проблемы. Эти инструкции обычно ставят в соответствие вашему сообщению некоторый уровень логирования (log level) (Debug, Info, Warning, . ). Кроме того, сам лог также имеет свой уровень. Все сообщения с уровнями выше уровня лога будут записаны в хранилища лога (log sink) (файл, базу данных, . ). Если же уровень сообщения ниже уровня лога, то сообщение будет просто отброшено.

Когда наше приложение работает нормально, мы хотим видеть как можно меньше записей в логе, чтобы его размер оставался маленьким. В то же время, если в приложении происходит сбой, мы хотим, чтобы в логе было достаточно записей, чтобы мы могли понять причину проблемы. Сложность здесь в том, что обычно мы устанавливаем один уровень логирования для всех логгеров в нашем приложении. Если все в порядке, мы держим этот уровень высоким (например, Warning). Если же нам нужно искать причину сбоя, мы устанавливаем его ниже (например, Debug).

Один уровень логирования на все приложение

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

  • Как отделить сообщения проблемных запросов от сообщений других запросов?
  • Запросы, не приводящие к сбоям, все равно тратят свое время на запись в хранилища логов, хотя эти сообщения никогда не будут использованы.
  • Сообщения из успешных запросов будут занимать место в хранилищах логов, хотя эти сообщения никогда не будут использованы.

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

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

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

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

Отдельный уровень логирования для каждого запроса

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

Итак, задача поставлена. Давайте начнем.

Я создам простой Web-сервис на основе .NET Core. У него будет единственный контроллер:

Реализацию свойства Logger обсудим позже. Для этого приложения я буду использовать библиотеку log4net для логирования. Данная библиотека предоставляет интересную возможность. Я говорю о наследовании уровня логирования (level inheritance). Кратко говоря, если в конфигурации этой библиотеки вы говорите, что лог с именем X должен иметь уровень логирования Info, это означает, что все логи с именами, начинающимися с X. (например, X.Y, X.Z, X.A.B) будут наследовать этот самый уровень.

Отсюда проистекает изначальная идея. Для каждого запроса я каким-то образом определю требуемый уровень логирования. Затем я создам новый именованный лог в конфигурации log4net. Этот лог будет иметь требуемый уровень логирования. После этого, все объекты логгеров, созданные в рамках текущего запроса, должны иметь имена начинающиеся с имени этого нового созданного мной лога. Единственной проблемой здесь является то, что log4net никогда не удаляет логи. Будучи однажды созданными, они существуют все время жизни приложения. По этой причине я изначально создаю по одному логу на каждый возможный уровень логирования:

Теперь у меня есть несколько логов с именами EdlinSoftware.Log.XXXX. Эти имена будут служить префиксами имен логов, используемых в запросах. Чтобы избежать коллизий между запросами, я буду хранить вычисленный префикс для данного запроса в экземпляре AsyncLocal. Значение этого объекта будет устанавливаться в новом OWIN middleware:

Когда данное значение установлено, очень легко создавать логгеры с нужным префиксом имени:

Теперь понятно, как получить экземпляр логгера в нашем контроллере:

Осталось сделать только одну вещь: как-то задать правила, по которым мы выбираем уровень логирования для каждого запроса. Это должен быть достаточно гибкий механизм. Основная идея заключается в использовании скриптинга C#. Я создам файл LogLevelRules.json, где определю набор пар “правило — уровень логирования”:

Здесь logLevel — желаемый уровень логирования, а ruleCode — код на C#, который возвращает булевское значение для заданного запроса. Приложение будет запускать коды правил один за другим. Первое правило, чей код вернет true, будет использовано для установки соответствующего уровня логирования. Если все правила вернули false, будет использован уровень по умолчанию.

Для создания делегатов из строкового представления правил, используется класс CSharpScript:

Здесь метод Compile получает список объектов, прочитанных из файла LogLevelRules.json. Он создает делегат runner для каждого правила.

Этот список делегатов может быть сохранен:

и использован в дальнейшем:

Таким образом, при запуске приложения мы читаем LogLevelRules.json, преобразуем его содержимое в список делегатов, используя класс CSharpScript, и сохраняем этот список в поле LogSupport.LogLevelSetters. Затем на каждый запрос мы выполняем делегаты из этого списка, чтобы получить уровень логирования.

Единственной вещью, которую осталось сделать, является отслеживание изменений в файле LogLevelRules.json. Если мы хотим установить уровень логирования для каких-то запросов, мы добавляем новое правило в этот файл. Чтобы заставить наше приложение применить эти изменения без перезапуска, необходимо следить за файлом:

Следует отметить, что ради краткости я опустил код потоковой синхронизации при работе с полем LogSupport.LogLevelSetters. Но на практике такая синхронизация обязательна.

Полный код приложения можно найти на GitHub.

Недостатки

Показанный выше код позволяет решить проблему задания отдельного уровня логирования для каждого запроса. Но у него есть ряд недостатков. Давайте обсудим их.

1. Этот подход меняет имена логов. Так в хранилище логов вместо "MyClassLogger" будет храниться что-то типа "Edlinsoft.Log.Debug.MyClassLogger". С этим можно жить, но это не очень удобно. Возможно, проблему можно преодолеть, изменяя раскладку лога (log layout).

2. Теперь невозможно использовать статические экземпляры классов логгера, поскольку они должны создаваться отдельно для каждого запроса. Наиболее серьезной проблемой здесь, на мой взгляд, является то, что разработчики должны всегда об этом помнить. Кто-нибудь может случайно создать статическое поле с логгером и получить странные результаты. Преодолеть эту ситуацию можно созданием класса-обертки для логгеров вместо прямого использования классов log4net. Такая обертка может всегда создавать новые экземпляры логгеров log4net для каждой операции. В этом случае ее можно свободно использовать и в статических полях.

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

4. Когда мы изменяем JSON-файл с правилами, код правил может содержать ошибки. Очень просто использовать блоки try-catch для того, чтобы эти ошибки не разрушили наше основное приложение. Тем не менее, мы как-то должны узнать, что что-то пошло не так. Есть два типа ошибок:

  • Ошибки времени компиляции кода правил в делегаты.
  • Ошибки времени выполнения этих делегатов.

Каким-то образом мы должны узнавать об этих ошибках, иначе наше логирование просто не будет работать, и мы даже не будем знать об этом.

5. Код правил в JSON-файле может в принципе содержать любые инструкции. Это потенциально может вести к проблемам с безопасностью. Нужно как-то ограничивать возможности этого кода. С другой стороны, если уж злоумышленник получил возможность прямой записи в файлы вашего приложения, то проблема с безопасностью и так налицо.