Pull to refresh

Comments 38

Осталось только положить отступы не в статик, а в TLS, чтобы они не поломались в многопоточном приложении. А чтобы совсем хорошо было — добавить и std::flush, тогда при падении будет видно намного больше.
Если несколько потоков будет выводить логи, да еще и с отступами, то в файле лога будет каша.
Основная проблема логов в многопоточной среде — влияние на нормальный ход выполнения программы за счет внесения дополнительных синхронизаций при записи логов.

При этом можно дойти до такой крайности, когда программа будет кое-как работать при включенных логах, но стоит только логи отключить — тут-же падает.
А мы тогда включим логи постоянно и перенаправим их в /dev/null — problem solved! :)
Советую новичкам научится анализировать Crash Dump-ы с использованием нормального отладчика.

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

Также в некоторых случаях стоит освоить работу с ядерным отладчиком для отладки сложных сценариев.
Когда программа портит свой стек такой логгер может пригодиться.
Даже при переездах по памяти windbg зачастую позволяет втащить интересующие куски, особенно если полный дамп, особенно если с включенным appverifier-ом
Советую новичкам научиться пользоваться отладчиком и стараться никогда им не пользоваться. Попытка что-то разбирать с отладчиком означает либо серьезные алгоритмические сложности (для которых отладчик зачастую далеко не самое эффективное средство разбирательства), либо обилие bad practices, которые создали проблемы на пустом месте.

Есть масса других, более эффективных инструментов — продуманная система тестов, статические проверки, lint'ы и т.д.
В этом есть доля истины, но этот совет лучше давать уже не новичкам.

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

Пошаговый просмотр и вообще трассировка программы — это такое специальное зло, которое надо искоренять :) У программы есть блоки разных уровней, какая-то модульная структура. Единица отладки и тестирования — модуль. Если хочется проверить, что он себя ведет так, как надо — то процедура проверки не проделывается руками, а записывается в виде теста, проверки — в виде assert'ов.
А вообще я говорил не о пошаговой отладке, а об анализе падений программы в первую очередь.

То есть когда программа падает, она создает dump своего состояния, который затем отправляется на сайт Microsoft, откуда его можно скачать, если зарегистрировать свою компанию и версию продукта. См. Windows Error Reporting.

Такие дампы поступают от пользователей, у которых происходят критические сбои программы.

Также дамп можно создать вручную, если программа к примеру зависла.
При том на Windows 7 это можно сделать прямо из Task Manager-а.

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

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

И уж совсем тяжелая артиллерия — ядерный отладчик, это когда отладчик подключается к самой операционной системе и отлаживается вся операционная система со всеми программами. Так обычно отлаживаются драйвера, но и простые программы тоже можно отлаживать, к примеру на этапе загрузки системы, или при сложном взаимодействии нескольких процессов.
>>Если хочется проверить, что он себя ведет так, как надо — то процедура проверки не проделывается руками, а записывается в виде теста, проверки — в виде assert'ов.

Это все прекрасно и необходимо, но всего не предугадаешь. Программы все равно будут сбоить, портить память, зависать и т.п. И никакие юнит-тесты это не исправят.

Вот как Вы проанализируете deadlock программы при помощи асертов?

Под отладчиком (WinDBG) можно хоть посмотреть кто залочил критическую секцию или мьютекс.
Посмотреть состояние всех потоков, памяти и т.п.
Это всё хорошо работает в стране розовых единорогов и сферических коней в вакууме. Ещё это хорошо работает для программ на 1000 строк. Но это плохо работает в реальном мире, где исходный код продуктов может иметь возраст 15 лет и больше, где код объемом в миллионы строк писали в общей сложности сотни людей, разного опыта и поколений, где применялись разные практики и парадигмы.
Кому не нужны велосипеды, но хочется схожего функционала могут использовать boost.log
Какой у вас компилятор? У msvc, например, есть опция для «инструментирования» каждого входа (и выхода) в функцию. Думаю, у вашего компилятора (если это не msvc) тоже что-то подобное есть.
А зачем? Для этого есть профайлеры. Во время ошибки тоже можно определить.
Имхо, стоящая идея. Довольно просто, но действенно. Разве что, для пущей совместимости я бы сделал с sprintf, а не с std::string и cout.
Заходя под кат надеялся увидеть что-то типа: svn.pld-linux.org/svn/backtracexx.
В GCC и MSVS есть все средства для расчета call stack'a, и не надо ничего в каждую функцию добавлять.
В собственном продукте я реализовывал базовые классы исключение с использованием подобного функционала, чтобы у любого исключения можно было спросить call stack на момент его генерации. Если такое исключение никем не поймано, то в лог выводится call stack этого исключения.
Плюс такого подхода, что при наличии pdb файлов он работает и в Release конфигурации.
В остальных случаях, как по мне, call stack удобно видеть при выводе ошибки в лог файл, но никак не на каждую функцию при нормальной работе программы.
Есть один неприятный момент: стек вызовов обычно хотят печатать после получения SIGSEGV/SIGBUS, но есть ненулевая вероятность (и на практике такое встречается не слишком редко), что кто-то проедется по стеку и вытащить вызовы функций гццшными функциями будет нельзя. + ещё при их использовании надо крайне аккуратно с памятью работать. Коллега подсказал, что в gdb есть свои функции для раскручивания стека, и что они действительно лучше работают, в чём он убеждался на практике.
Согласен. Но я считаю лучше хотя бы такой механизм чем ничего. Не всегда есть возможность использовать gdb. У нас иногда случается, что определенная ошибка повторяется только на компьютере клиента, и нет возможности ехать к нему на другой континент. Тогда хотя бы обрывки call stack'а очень помогают в определении не правильного поведения.
Мы в свое время развлекались с структурным логгером (самописанным, кода там на пару сотен строчек), который умеет в XHTML результаты дампить. Для проекта, работающего с изображениями это оказалось очень удобно:

Желательно переделать LOG_TRACE так:
#define LOG_TRACE() TraceLogger traceLogger(__FILE__, __FUNCTION__, __LINE__)

Чтобы её использование выглядело как вызов функции и заканчивалось точкой с запятой:
void abc() {
    LOG_TRACE();
}

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

Поэтому ничего страшного нет в коде: int main() { ;;; }

Поэтому, например, я всегда в конце макросов ставлю точку с запятой: кто хочет приписывает в конце вызова еще одну, кому лень — не нужно.
При использовании макросов в операциях "? :" и "," точка с запятой окажутся лишними. А если под макросом скрывается возвращаемое значение, то, при наличии точки с запятой, его нельзя будет использовать в условных операторах.
Речь о конкретно этом слушае. А так вы правы, да.
__FUNCTINON__ если не ошибаюсь, это расширение.
А вот в с++11 добавлен __func__, используйте лучше его. Или хотя бы учитывайте его возможное применение в будущем
Что ещё можно сделать? Конечно же, отключить всё это безобразие в релиз-версии
Вот именно для Release-версии это и нужно! Если вам надо программу из beta-версии перевести в качественный и отказоустойчивый продукт, то она должна уметь сообщать свое состояние в случае ошибки. «Состояние» — это 1) потоки, 2) стек функций в них и 3) аргументы функций. Имея эту информацию, можно достоверно понять что произошло.
Информация по потокам — несложно. Так же вполне несложно добавлять стек вызовов для каждого потока. А вот аргументы — это уже посложней…
И, если уж идею развивать дальше, то хорошо бы, чтобы вся эта информация была бы… ну-у, свернутой, что ли. А вот если произошла ошибка — то ее развернуть в полный вид. Т. е. в случае корректной работы подробная (и вообще какая бы то ни было) информация о состоянии не нужна. А вот в случае ошибки нужно побольше информации.
Altap Salamander (зарубежный платный и весьма качественный аналог Total Commander-а) имеет такую функцию. Он в случае падения пишет в свою директорию salamander.bug. При старте, если он там обнаруживает этот файл, он предлагает его выслать разработчикам. Я туда заглядывал — там есть весь стек и состояние операционной системы. Вот аргументов функций нет.
Я, как говорится, «дал удочку и показал, где река», а пытливый ум всегда найдёт способ что-нибудь улучшить.

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

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

И так далее…
она должна уметь сообщать свое состояние в случае ошибки. «Состояние» — это 1) потоки, 2) стек функций в них и 3) аргументы функций.
Вы всё правильно пишете, только вот логгировать каждый вызов функции, включая аргументы — сомнительное решение. Программа должна уметь в случае критической ошибки сохранять свой дамп и завершаться без порчи пользовательских данных. А при наличии у разработчика отладочных символов он из этого дампа вынет и стеки всех потоков, и аргументы вызываемых функций и много чего ещё.
> предопределённый макрос __FUNCTION__

BOOST_CURRENT_FUNCTION более переносимо.
Можно еще timestamp'ы добавить.
Многие не учитывают что такая штука может оказаться полезной при embedded отладке, когда из средств отладки только uart :)
UFO just landed and posted this here
Приведу пример ещё одного проекта где это всё уже давно реализовано и хорошо работает, не требует никакого изменения кода, и запросто можно держать активным даже в deployed проекте (просто сохранять крашы в лог на пользовательских компьютерах и к примеру посылать их разработчикам когда надо. И это лучше цем посылать обычные crashdump — лог можно просто прочитать и чтобы получить информацию о функциях, стэке и т.д., не нужно будет его загружать никуда, как с crashdumps).: www.codeproject.com/KB/threads/StackWalker.aspx
Отличная статья, в мемориз.

Люто бешено проплюсовал.

Что-то как-то руки все не доходили сделать нормальный отладочный вывод, мучился с префиксами WRN, ERR, уровнем отладочных сообщений, но это все не то.
В деструкторе TraceLogger с помощью функции std::uncaught_exception можно проверить, как завершает работу функция: это нормальный возврат или было выброшено исключение.
Sign up to leave a comment.

Articles