Многофайловая программа на C, как лучше реализовать необязательное ведение журнала? - PullRequest
8 голосов
/ 29 ноября 2008

У меня многофайловая программа на Си. Я бы хотел, чтобы пользователь мог указывать разные уровни отладки во время выполнения.

Как лучше всего это реализовать?

Я думал о том, чтобы функция типа debug (level, "message") экспортировалась и использовалась повсеместно. Есть идеи получше / другие?

Ответы [ 4 ]

10 голосов
/ 30 ноября 2008

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

Существует облегченная версия заголовка журнала, который я использую:

#define LOG_FATAL    (1)
#define LOG_ERR      (2)
#define LOG_WARN     (3)
#define LOG_INFO     (4)
#define LOG_DBG      (5)

#define LOG(level, ...) do {  \
                            if (level <= debug_level) { \
                                fprintf(dbgstream,"%s:%d:", __FILE__, __LINE__); \
                                fprintf(dbgstream, __VA_ARGS__); \
                                fprintf(dbgstream, "\n"); \
                                fflush(dbgstream); \
                            } \
                        } while (0)
extern FILE *dbgstream;
extern int  debug_level;

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

LOG(LOG_ERR, "I/O error %s occured while opening file %s", strerror(errno), filename);

Во время инициализации программы необходимо указать значения для dbgstream (обычно по умолчанию stderr) и debug_level.

Для реальных проектов вместо того, чтобы вызывать fprintf много раз, я просто вызываю свою функцию из LOG макроса и передаю __FILE__, __LINE__ и __VA_ARGS_ в качестве аргумента - эта функция также печатает дату, время и pid в записывать в журнал и не делать fflush() каждый раз - только когда счетчик буферизации превышает заданное значение - это значительно повышает производительность ведения журнала.

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

3 голосов
/ 29 ноября 2008

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

Другая проблема, не решенная вашим вопросом, - это как включить отладку во время выполнения. Я всегда использовал параметры командной строки - обычно «-d» для «базовой отладки на уровне 3» и «-D nn» для отладки на уровне nn. Или со сложной системой: «-D input=3,macros=5,rules=1». Нетрудно иметь переменную окружения с такой же семантикой.

Из заголовка, который реализует это:

/*
** Usage:  TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x)    db_print x
#else
#define TRACE(x)    do { if (0) db_print x; } while (0)
#endif /* DEBUG */

#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */

#include <stdio.h>

extern int      db_getdebug(void);
extern int      db_newindent(void);
extern int      db_oldindent(void);
extern int      db_setdebug(int level);
extern int      db_setindent(int i);
extern void     db_print(int level, const char *fmt,...);
extern void     db_setfilename(const char *fn);
extern void     db_setfileptr(FILE *fp);
extern FILE    *db_getfileptr(void);

/* Semi-private function */
extern const char *db_indent(void);

/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/

/*
** Usage:  MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x)  db_mdprint x
#else
#define MDTRACE(x)  do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */

extern int      db_mdgetdebug(int subsys);
extern int      db_mdparsearg(char *arg);
extern int      db_mdsetdebug(int subsys, int level);
extern void     db_mdprint(int subsys, int level, const char *fmt,...);
extern void     db_mdsubsysnames(char const * const *names);
3 голосов
/ 29 ноября 2008

В Windows (и широко в Microsoft) мы широко используем Event Tracing для Windows (ETW). ETW - эффективный механизм статического каротажа. Ядро NT и многие компоненты очень хорошо оснащены. ETW имеет много преимуществ:

  • Любой поставщик событий ETW может быть динамически включен / отключен во время выполнения - перезагрузка или перезапуск процесса не требуются. Большинство провайдеров ETW обеспечивают детальный контроль над отдельными событиями или группами событий.
  • Форматирование данных события не выполняется во время выполнения (что может быть очень дорого). Это делается, когда трассировка события постобработана.
  • ETW является основным механизмом для Журнала событий Windows . Если вы правильно построите свои инструменты, вы получите бесплатную регистрацию на уровне приложений.
  • Ваш компонент может поддерживать очень детальное включение событий по отдельности, по уровням или в группах (или в любой комбинации). Вы также можете указать несколько поставщиков в нашем коде.
  • События от любого провайдера (наиболее важно, ядра) можно объединить в одну трассировку, чтобы все события можно было коррелировать.
  • Объединенную трассировку можно скопировать из коробки и полностью обработать - с помощью символов.
  • Прерывание профиля образца ядра NT может генерировать событие ETW - это дает очень легкий профиль образца, который можно использовать в любое время
  • В Vista и Windows Server 2008 регистрация событий не блокируется и полностью поддерживает несколько ядер - поток на каждом процессоре может независимо регистрировать события без синхронизации между ними.
  • ETW также эффективен, когда регистрация отключена - это просто простая логическая проверка (ETW делает это, или вы можете сделать это явно). Обратите внимание, что не требует перехода в режим ядра. Все сделано в процессе.

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

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

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

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

В окнах есть несколько инструментов, которые помогут вам с регистраторами и событиями на основе ETW. К ним относятся logman.exe и tracerpt.exe. Существуют также xperf tools , которые ориентированы на производительность, но также могут контролировать любого провайдера ETW и дамп-файлы журналов.

3 голосов
/ 29 ноября 2008

Существует очень хороший порт C для log4j, log4c .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...