Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Технология ведения лога (диагностика)


Автор: _Tanatos_ 20.11.2007, 17:18
День добрый!

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

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

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

Состав данных в одной записи:
время
файл-строка
класс-функция
тип записи (информация, предупреждение, ошибка)
код ошибки (число)
текст ошибки (строка)
дополнительный текст (пока неочень понятно нужно или нет, для всякого мусора в общем)
данные (строка в формате имя переменной=значение\n)
+ ссылка на родительскую запись, т.е. получается дерево вызовов

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

И еще, в коде будут использоваться прерывания ... как бы все это совместить ??? пока предполагаю внутри функции всегда ставить try{}__finally{} для фиксации входа и выхода, иначе дерево в случае ошибки просто умирает (пока фиксирую родительскую запись через push в начале функции и pop в конце (в блоке __finally) - родитель всегда последний).

У кого какие мысли есть по этому поводу? Буду благодарен за любую помощь и подсказку!

Автор: bsa 20.11.2007, 17:34
Пишешь класс, допустим Log. Он ведет лог.
Пишешь дружественный класс Logger, у которого в конструктор передается ссылка на экземляр Log (можно и без этого - если экземляр Log глобальный или статический). После этого в начале каждой функции создаешь экземпляр Logger. Если произойдет исключение у Logger'а автоматически вызовется деструктор, который запишет выход из функции.
Более того, это все можно реализовать макросами, тогда есть возможность использовать __PRETTY_FUNCTION__ и прочие определения препроцессора для уменьшения кода.

Автор: JackYF 20.11.2007, 18:15
Цитата(bsa @  20.11.2007,  17:34 Найти цитируемый пост)
__PRETTY_FUNCTION__

вот  smile !

а я, значит, велосипеды пишу... только вот стандарт по поводу этого, равно как и __FUNCTION__ молчит.
Молчит и ман по g++. В гугле нашёл кое-какие сообщения в мэйл-листах, которые говорят о том, что такое таки есть и его используют. Где найти документацию?

Автор: bsa 20.11.2007, 18:25
Цитата(JackYF @ 20.11.2007,  18:15)
Цитата(bsa @  20.11.2007,  17:34 Найти цитируемый пост)
__PRETTY_FUNCTION__

вот  smile !

а я, значит, велосипеды пишу... только вот стандарт по поводу этого, равно как и __FUNCTION__ молчит.
Молчит и ман по g++. В гугле нашёл кое-какие сообщения в мэйл-листах, которые говорят о том, что такое таки есть и его используют. Где найти документацию?

http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html

Автор: JackYF 20.11.2007, 19:07
Цитата(bsa @  20.11.2007,  18:25 Найти цитируемый пост)

http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html 

о! я уже эту страничку видел, но на другом сайте и с чуть другим оформлением, поэтому усомнился в официальности.

Респект, ну и + ты точно заслужил, спасибо smile

Автор: archimed7592 20.11.2007, 20:45
bsa, лови и от меня smile.
А что думают другие компиляторы относительно __PRETTY_FUNCTION__ ? Другими словами - насколько это переносимо в пределах современных популярных компиляторов?

Автор: bsa 20.11.2007, 21:16
Цитата(archimed7592 @ 20.11.2007,  20:45)
bsa, лови и от меня smile.
А что думают другие компиляторы относительно __PRETTY_FUNCTION__ ? Другими словами - насколько это переносимо в пределах современных популярных компиляторов?

Про __PRETTY_FUNCTION__ не уверен, а вот, судя по той ссылке, __func__ юзать можно на любых компиляторах, совместимых с C99.

Автор: jonie 20.11.2007, 22:10
они не думают... ибо не стандарт. да и про __func__ они тоже не должны бы думать (по крайней мере в ansi iso iec 14882(2003) я такого не видел)... может пнете посмотрю )

Автор: archimed7592 20.11.2007, 22:12
Цитата(bsa @  20.11.2007,  21:16 Найти цитируемый пост)
Про __PRETTY_FUNCTION__ не уверен, а вот, судя по той ссылке, __func__ юзать можно на любых компиляторах, совместимых с C99.

Ммм, фишка в том, что программирую я исключительно на С++, а в С++ __func__ официально пока нету. Появится только в 2009 :(.

Добавлено через 2 минуты и 53 секунды
Цитата(jonie @  20.11.2007,  22:10 Найти цитируемый пост)
может пнете посмотрю ) 

Нет, нет, это точно, что оффициально появится только в С++09. Я об этом даже где-то писал...



Цитата(jonie @  20.11.2007,  22:10 Найти цитируемый пост)
ansi iso iec 14882(2003)

smile.

Автор: SaDFromSpb 21.11.2007, 01:40
_Tanatos_, если сделаешь, кинь, плиз, исходники на всеобщее обозрение. Может, доделаем до состояния конфетки, если оно еще ей не будет являться  =). Было бы здорово иметь что-нибудь продуманно-универсальное для этого дела...

Добавлено через 3 минуты и 17 секунд
Ну или если кто-то знает уже готовые решения для этого, то сообщите.
По поводу боязни неуниверсальности __PRETTY_FUNCTION__ - можно использовать reflex, правда есть мысль, что здесь она через чур.... Дополнительную библиотеку все-таки подключать...


Автор: archimed7592 21.11.2007, 02:07
Цитата(SaDFromSpb @  21.11.2007,  01:40 Найти цитируемый пост)
Дополнительную библиотеку все-таки подключать...

Если бы только библиотеку подключать...

Автор: Mayk 21.11.2007, 04:37
в бусте есть boost/current_function.hpp 

Автор: chipset 21.11.2007, 04:48
А у меня такой вот макрос: 

Код

#define CURRENT_FUNCTION __PRETTY_FUNCTION__
#ifdef __FUNCTION__
#define CURRENT_FUNCTION __FUNCTION__
#endif



Вроде работает на gcc и VCях.

Автор: Lazin 21.11.2007, 09:01
_Tanatos_, я так понимаю, что ты хочешь строить дерево, в оперативной памяти, для определения причины сбоя в программе. Дерево придется периодически сохранять на диск, в идеале при добавлении нового элемента...
По хорошему лог нужно записывать так:
    - должен быть простой формат лога, никаких xml и прочего, для того что-бы можно было не загружать весь файл полностью (представь что там несколько гигов), тот же xml придется парсить целиком.
    - в формате лога должна быть следующая информация: код процесса, код потока, время записи, уровень(степень подробности) сообщения, дальше на твое усмотрение, можно еще номер строки и имя файла добавить..
    - класс отвечающий за запись в лог(еще лучше просто функция) не должен буферизировать сообщения, иначе, если программа упадет, последние сообщения не запишутся в файл, если программа под windows, то мало просто записывать на диск данные, нужно принудительно очищать буфер файла, если этого не делать в случае вызова Terminate данные могут быть не записаны на диск
    - опять-же в случае если пишешь под windows смотри MSDN на тему Event Log, ReportEvent и тд.
самое главное, это должна быть самая простая и надежная часть программы, если хочешь сделать надежную библиотеку для ведения логов, оставь ООП изыски тем кому это надо, к примеру деструкторы не вызываются в случае если процесс аварийно завершается, но можно к примеру установить фильтр необработанных исключений(SetUnhandledExceptionFilter) и он отработает по любому.

Добавлено через 1 минуту и 23 секунды
ps
недавно открыл для себя программу grep, которая очень подходит для анализа огромных лог файлов

Добавлено через 13 минут и 4 секунды
Кстати Event Log в Windows работает очень быстро, надежно, и лучше не изобретать велосипед, а использовать то, что предоставляет платформа для этих целей.

Автор: SergeCpp 21.11.2007, 10:09
Цитата(Lazin @  21.11.2007,  11:01 Найти цитируемый пост)
недавно открыл для себя программу grep, которая очень подходит для анализа огромных лог файлов


http://ru.wikipedia.org/wiki/AWK подходит ещё более очень user posted image
(на авторов, на авторов там посмотрите! user posted image)

Также посмотрите http://ru.wikipedia.org/wiki/Sed

Автор: _Tanatos_ 21.11.2007, 11:13
Благодарю всех за активное участие и ценные советы.

В силу обстоятельств на начальной стадии проекта буду писать на C++ Builder а он не знает не __func__, __FUNCTION__ и __PRETTY_FUNCTION__. Ну да ладно, меня не ломает скопировать название функции и класса, и потом можно написать простую тулзу которая перебирая перед компиляцией все исходники будет править имя класса и функции - так что это решаемо.

Отказаться от использования ООП думаю не оправданно. Конечно остаются некоторые вероятности, но отказать может почти все. Например от отключения питания не спасут никакие технологии программирования (кстати ИБП тоже подыхают периодически).

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

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

Будут результаты отпишусь, может и правда совместными усилиями сделаем конфетку.

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

Автор: Lazin 21.11.2007, 11:54
О чем то похожем говорил bsa, можно вынести часть функционала в отдельный класс и использовать raii

Код

//класс - лог-файл - записывает данные в файл в каком-то формате
class Log
{
 static long node;
 void write(long n, LPCSTR msg);
};

//класс - непосредственно для записи сообщений пользовательским кодом
class Logger
{
friend class Log;
static Log s_log;
long current_node;
public:
 Logger() : current_node(Log::node)
 {
    ++Log::node;//здесь нужна синхронизация
 }

 ~Logger()
 {
   s_log.write(current_node, "return");
 }

 void Write(LPCSTR str)
 {
    s_log.write(current_node, str);
 }
};


принцип такой, впри входе в ф-ю создается объект Logger. Через который производится запись в лог, каждый такой объект имеет свой номер, по которому можно определить порядок вызовов, в деструкторе, в лог также записывается информация о том что ф-я завершила выполнение.

Автор: archimed7592 21.11.2007, 12:01
Цитата(Lazin @  21.11.2007,  11:54 Найти цитируемый пост)
 ~Logger()
 {
   s_log.write(current_node, "return");
 }

Я бы ещё добавил
Код
s_log.write(current_node, std::uncaught_exception() ? "return(stack unwinding)" : "return");

Автор: SaDFromSpb 21.11.2007, 12:34
А вот, кстати, содержимое boost/current_function.hpp:
Код

namespace boost
{

namespace detail
{

inline void current_function_helper()
{

#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600))

# define BOOST_CURRENT_FUNCTION __PRETTY_FUNCTION__

#elif defined(__FUNCSIG__)

# define BOOST_CURRENT_FUNCTION __FUNCSIG__

#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))

# define BOOST_CURRENT_FUNCTION __FUNCTION__

#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)

# define BOOST_CURRENT_FUNCTION __FUNC__

#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)

# define BOOST_CURRENT_FUNCTION __func__

#else

# define BOOST_CURRENT_FUNCTION "(unknown)"

#endif

}

} // namespace detail

} // namespace boost


Все весьма банально =)

Автор: dumb 21.11.2007, 14:47
Цитата(_Tanatos_ @  21.11.2007,  11:13 Найти цитируемый пост)
буду писать на C++ Builder а он не знает не __func__, __FUNCTION__ и __PRETTY_FUNCTION__
он знает __FUNC__. smile

Автор: JackYF 21.11.2007, 17:51
Цитата(Mayk @  21.11.2007,  04:37 Найти цитируемый пост)
в бусте есть boost/current_function.hpp  

* ушёл смотреть *

ну вот, уже выложили smile
ну так, в принципе, и предполагалось.

Буст подруливает.
Maykу однозначно плюс.

Автор: _Tanatos_ 22.11.2007, 13:41
Подскажите какой тип лучше использовать для хранения времени события и заодно какие функции для работы с ним.

Автор: Lazin 22.11.2007, 13:47
структура FILETIME, функции GetLocalTyme SystemTimeToFileTime

Автор: SaDFromSpb 22.11.2007, 15:25
Цитата(_Tanatos_ @  22.11.2007,  13:41 Найти цитируемый пост)
Подскажите какой тип лучше использовать для хранения времени события и заодно какие функции для работы с ним. 

А что, в <ctime> большой выбор для этого?  smile  Или что-то нестандартное хочешь использовать? Время события системное хочешь использовать, или считая от запуска программы?

Автор: StarikanMan 22.11.2007, 16:30
bsa, он написал очень хороший пример

Автор: dumb 22.11.2007, 16:32
всем, кто пытается осмыслить посты StarikansBack'а - этот убогий является каким-то обгашенным куском недоразума, генерирующим случайные наборы слов.

Автор: Lazin 22.11.2007, 16:48
Цитата(dumb @  22.11.2007,  16:32 Найти цитируемый пост)
всем, кто пытается осмыслить посты StarikansBack'а - этот убогий является каким-то обгашенным куском недоразума, генерирующим случайные наборы слов. 

Кстати пример кода взят из одного из недавних постов на форуме))

Автор: Lazin 22.11.2007, 17:09
Цитата(Alexeis @  22.11.2007,  16:55 Найти цитируемый пост)
[offtop]Это по ходу обиженный на Гуеду RinOSPro, который мстит за то что его банили.[/offtop] 

точнее множество его клонов))

Автор: _Tanatos_ 23.11.2007, 14:17
Вот что у меня получилось на данный момент 
Привожу фрагмент кода с описанием структуры данных одной записи:

Код

typedef struct
{
    unsigned long   m_RowID;    // Порядковый номер строки
    int             m_Level;    // Уровень вложенности
    int             m_ProcID;   // Идентификатор процесса
    int             m_UnitID;   // Идентификатор модуля
    char            m_Type;     // Тип сообщенияы
    int             m_Error;    // Код ошибки
    char*           m_Msg;      // Текст сообщения
    char*           m_Data;     // Дополнительные данные
    char*           m_Function; // Имя функции
    char*           m_File;     // Имя файла
    int             m_Line;     // Номер строки
    SYSTEMTIME      m_Time;     // Время события
}
TtLibLogRecord;


Какие есть мысли и соображения?
Изначально буду реализовывать функцию регистрации записи с полным перечнем 
Поля m_Level и m_Time обрабатываются автоматически внутри класса.
Предполагаю, что использование полей m_Type, m_File и m_Line будет внутри макроса, тем самым сократиться общая длина записи
Макрос __FUNC__ пока не буду использовать, так как нет гарантий, что при смене компилятора работоспособность кода сохранится.
Для времени пока остановился на SYSTEMTIME, дабы не вызывать лишние функции преобразования, да и при записи в текстовый файл не надо будетзаниматься обратной конвертацией для форматирования.

далее приведено определение обработчика для регистрации новой записи
Код

typedef void (__closure *TtLibOnLogAdd)(CtLibLogEngine& LogEngine);
typedef std::vector<TtLibOnLogAdd> TtLibLogSubscribers;


Вот и сам класс журналирования
Код

class CtLibLogEngine
{
private:
    TtLibLogRecord      m_Record;
    unsigned long       m_RowID;
    TtLibLogSubscribers m_Subscribers;
protected:
public:
   ~CtLibLogEngine();
    CtLibLogEngine();

    inline const TtLibLogRecord& getRecord() const;

    inline void SubscriberAdd(TtLibOnLogAdd OnLogAdd);
    inline void SubscriberRemove(TtLibOnLogAdd OnLogAdd);

    inline void RegisterMsg(int ProcID, int UnitID, char Type, int Error, const char* Msg, const char* Data, const char* Function, const char* File, int Line);
};


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

примерно так.
Вспомогательный класс опишу чуть позже.

Поддержка многопоточности на последний этап, так как это на перспективу и мне пока не нужно.

Из актуальных вопросов это регистрация условий обработки - ограничение по минимальному и максимальному уровню для m_Type для каждого отдельного m_UnitID, что позволит сократить объем сохраняемых записей.

Пока планирую сделать так:
Код

typedef struct
{
    int     m_UnitID;   // Идентификатор модуля
    char    m_Min;      // Минимальное значение для m_Type
    char    m_Max;      // Максимальное значение для m_Type
}
TtLibLogCondition;


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

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

Какие есть мысли, пожелания, предложения?
По ходу дела буду выкладывать готовые фрагменты и по результату скину все целиком.

Автор: Lazin 23.11.2007, 14:53
Цитата(_Tanatos_ @  23.11.2007,  14:17 Найти цитируемый пост)

Код

typedef struct
{
    unsigned long   m_RowID;    // Порядковый номер строки
    int             m_Level;    // Уровень вложенности
    int             m_ProcID;   // Идентификатор процесса
    int             m_UnitID;   // Идентификатор модуля
    char            m_Type;     // Тип сообщенияы
    int             m_Error;    // Код ошибки
    char*           m_Msg;      // Текст сообщения
    char*           m_Data;     // Дополнительные данные
    char*           m_Function; // Имя функции
    char*           m_File;     // Имя файла
    int             m_Line;     // Номер строки
    SYSTEMTIME      m_Time;     // Время события
}
TtLibLogRecord;

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

Автор: Mayk 23.11.2007, 15:52
Цитата(Lazin @  23.11.2007,  18:53 Найти цитируемый пост)

Мне кажется эта структура сильно избыточна, если знаешь имя ф-ии, то имя файла и номер строки знать не обязательно. 

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

Цитата(_Tanatos_ @  23.11.2007,  18:17 Найти цитируемый пост)
typedef void (__closure *TtLibOnLogAdd)(CtLibLogEngine& LogEngine);

если хочешь портируемый код, то надо избавиться от не портируемого __closure. 
Цитата(_Tanatos_ @  23.11.2007,  18:17 Найти цитируемый пост)
    char*           m_Msg;      // Текст сообщения
    char*           m_Data;     // Дополнительные данные

Кто выделяет и освобождает память для этих полей? 

Цитата(_Tanatos_ @  23.11.2007,  18:17 Найти цитируемый пост)
    char            m_Type;     // Тип сообщенияы

только 256 типов сообщений? а не мало?

Автор: Lazin 23.11.2007, 16:33
Цитата(Mayk @  23.11.2007,  15:52 Найти цитируемый пост)
не согласен. имя файла и номер строки очень удобны. в любом уважающим себя редакторе есть ф-ция перехода на заданную строку. Перейти на определение ф-ции значительно сложнее. К тому же иногда хотелось бы посмортреть до каких строк работает прога, а до каких она не доходит. 

Ну номер строки еще ладно, но имя файла и имя ф-ии нужно указывать только при их изменении.
Цитата(Mayk @  23.11.2007,  15:52 Найти цитируемый пост)
если хочешь портируемый код, то надо избавиться от не портируемого __closure. 

так он же написал, что использует Borland? а есть ли какая-нибудь портируемая вльтернатива __closure ?
Цитата(Mayk @  23.11.2007,  15:52 Найти цитируемый пост)
Кто выделяет и освобождает память для этих полей? 

присоединяюсь

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

Автор: _Tanatos_ 23.11.2007, 16:38
Цитата

только 256 типов сообщений? а не мало?

А надо больше? Сколько не прикидывал, пока больше 10 не набиралось smile. Тип означает: Info, Warning, Error и аналогичное деление.
Думаю использовать более 10 уровней детализации не имеет смысла ибо ни один программист не сможет в них свободно ориентироваться.

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

Цитата

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

А чем заменить? Если просто убрать, то указатель будет на отдельную функцию, а мне надо на функцию класса.

Цитата

Мне кажется эта структура сильно избыточна, если знаешь имя ф-ии, то имя файла и номер строки знать не обязательно. 

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

Автор: Mayk 23.11.2007, 18:18
Цитата(Lazin @  23.11.2007,  20:33 Найти цитируемый пост)

так он же написал, что использует Borland?

Цитата(_Tanatos_ @  23.11.2007,  18:17 Найти цитируемый пост)
Макрос __FUNC__ пока не буду использовать, так как нет гарантий, что при смене компилятора работоспособность кода сохранится.

При использование __closure есть  работоспосбность кода при смене компилятора не сохранится вообще.

Цитата(_Tanatos_ @  23.11.2007,  20:38 Найти цитируемый пост)

А чем заменить? Если просто убрать, то указатель будет на отдельную функцию, а мне надо на функцию класса.

Цитата(Lazin @  23.11.2007,  20:33 Найти цитируемый пост)
а есть ли какая-нибудь портируемая вльтернатива __closure ?

boost::bind + boost::function, например. 

Цитата(_Tanatos_ @  23.11.2007,  20:38 Найти цитируемый пост)

Думаю использовать более 10 уровней детализации не имеет смысла ибо ни один программист не сможет в них свободно ориентироваться.

А если мне хочется нумеровать сообщения, начиная с 0x10000000[например сообщения из .exe], а в другом месте с 0x200000[например, сообщения из .dll]? или использование юзеровских типов не предусмотрено? а почему тогда не использовать enum? 

Цитата(Lazin @  23.11.2007,  20:33 Найти цитируемый пост)

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

ОпределенноХорошаяМысль.

Автор: _Tanatos_ 23.11.2007, 19:14
Цитата

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


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

Цитата

А чем заменить? Если просто убрать, то указатель будет на отдельную функцию, а мне надо на функцию класса.
а есть ли какая-нибудь портируемая вльтернатива __closure ?
boost::bind + boost::function, например.


Посмотрю - как разберусь исправлю. Спасибо за информацию!

Цитата

А если мне хочется нумеровать сообщения, начиная с 0x10000000[например сообщения из .exe], а в другом месте с 0x200000[например, сообщения из .dll]? или использование юзеровских типов не предусмотрено? а почему тогда не использовать enum?


Нет проблем, пользуйте m_Proc или m_Unit.
Кстати спрашивали насчет потоков ... под них так же планируется использовать m_Proc - ИМХО предостаточно.
Смысла писать туда реальный идентификатор потока не вижу, так как он от раза к разу меняется, можно его скинуть как данные при старте потока или процесса. А с логе использовать внутренние идентификаторы.

Цитата

а почему тогда не использовать enum? 

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

Автор: SaDFromSpb 23.11.2007, 22:54
Цитата(_Tanatos_ @  23.11.2007,  19:14 Найти цитируемый пост)
Предполагаю использовать отрицательные значения для нескольких предопределенных значений

Тогда, на всякий случай определи его как signed char, так как стандарт этого не гарантирует по умолчанию.

И давай скорее пример использования, а то так разбираться лениво  smile ...

Автор: _Tanatos_ 26.11.2007, 13:52
На суд уважаемых форумчан выдвигается несколько вариантов решения с вложенностью, а именно

Вариант №1: (мне нравится больше всего) вложенность управляется при помощи параметра m_Level. Его изменение производится исключительно классом TtLibLogEngine. Командой для изменения служат зарезервированные значения типа сообщения m_Type:
-1 - Увеличиваем вложенность
-2 - Уменьшаем вложенность
Плюс данного решения в том, что нет необходимости вводить дополнительные переменные, функции и т.д., минус нельзя одновременно зафиксировать запись пользовательского типа с вложенностью.
Пример использования:
Код

...
    // Фрагмент из библтотеки
    lltBegin        = -1,
    lltComplete     = -2,
...
    // Фрагмент из кода программы, использующую библиотеку
void SomeFunction()
{
    // Первая строчка функции (при использовании макроса будет еще короче, но макросы на потом)
    g_LogEngine.RegisterMsg(0, 0, lltBegin, 0, "Начинаем важное дело ...", "None", "::SomeFunction", __FILE__, __LINE__);
    ...
    // Последняя строчка функции.
    g_LogEngine.RegisterMsg(0, 0, lltComplete, 0, "... Выполнено!", "None", "::SomeFunction", __FILE__, __LINE__);
}


Вариант №2: идентичен предыдущему варианту, но для управления вложенностью выделяется отдельные функции (можно конечно сделать и отдельный параметр, но уж больно много параметров). 
Пример использования:
Код

void SomeFunction()
{
    // Первая строчка функции (при использовании макроса будет еще короче, но макросы на потом)
    g_LogEngine.RegisterMsgBegin(0, 0, UserType, 0, "Начинаем важное дело ...", "None", "::SomeFunction", __FILE__, __LINE__);
    ...
    // Последняя строчка функции.
    g_LogEngine.RegisterMsgEnd(0, 0, UserType, 0, "... Выполнено!", "None", "::SomeFunction", __FILE__, __LINE__);
}


Вариант №3: Вынести управление в отдельную функцию, которая будет вызываться перед регистрацией сообщения и будет модифицировать одну последующую запись. ИМХО хуже из-за лишнего вызова, хотя можно сделать с модификатором inline.
Пример использования:
Код

void SomeFunction()
{
    // Первая строчка функции (при использовании макроса будет еще короче, но макросы на потом)
    g_LogEngine.Begin()
    g_LogEngine.RegisterMsg(0, 0, UserType, 0, "Начинаем важное дело ...", "None", "::SomeFunction", __FILE__, __LINE__);
    ...
    // Последняя строчка функции.
    g_LogEngine.End()
    g_LogEngine.RegisterMsg(0, 0, UserType, 0, "... Выполнено!", "None", "::SomeFunction", __FILE__, __LINE__);
}


--------------------

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

--------------------

Цитата

Тогда, на всякий случай определи его как signed char

Принято.

Цитата

И давай скорее пример использования

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

Автор: Lazin 26.11.2007, 14:23
Цитата(_Tanatos_ @  26.11.2007,  13:52 Найти цитируемый пост)
TtLibLogEngine. Командой для изменения служат зарезервированные значения типа сообщения m_Type:
-1 - Увеличиваем вложенность
-2 - Уменьшаем вложенность

Правильнее использовать rtti, создаешь в стеке объект, в конструкторе вызывается нужная функция(увеличивает вложенность), в деструкторе уменьшаем вложенность, а как передавать - детали. Нужно различать поток, из которого вызывается ф-я, и вычислять вложенность отдельно для каждого потока. Это можно сделать внутри TtLibLogEngine, при каждом вызове ф-ии получать ThreadId потока, и дальше вычислять для него степень вложенности.
Я зделал что то похожее, но у меня - просто надстройка над библиотекой ведения логов, которая немного форматирует текст.


Код

#ifndef LoggerH
#define LoggerH


#    include <windows.h>
#    include <map>
#    include "mydeb.h"

#    define myBASE_LOG_INDENT 1
#    define myMAX_DBG_LEVEL 7

///RAII класс для записи иерархии сообщений в лог
class Logger
{
protected:
    int        m_level;
    long    m_indent;
public:

    static int GetCurrentDbgLevel();

    Logger(const char* func_name, int lvl);

    ~Logger();

    void Write(const char* msg) const;
};

///Класс для записи заголовка лог-файла
class LogHeader
{
public:
    LogHeader(const char* msg);

    const LogHeader& operator () (const char* msg) const;

    const LogHeader& ModuleVersion (HINSTANCE inst) const;

    const LogHeader& ModuleVersion(const char* filename) const;
};

///    myMACRO
#    define LOG_FUNCTION_AT_LEVEL(l) Logger __my_log ( __FUNCSIG__, l);
#    define LOG_FUNCTION() Logger __my_log ( __FUNCSIG__, Logger::GetCurrentDbgLevel() );
#    define LOG_MESSAGE(s) __my_log.Write( (s) );

#endif




Код

#include "Logger.h"
#include <string>
#include <exception>

///mylog----------------------------------------------------------------------------------
class myLog
{
    friend class Logger;
    friend class LogHeader;
private:
    std::map<DWORD, long> indent_level;
    CRITICAL_SECTION lock;
public:
    myLog();

    ~myLog();
private:
    DWORD InitializeCurrentThreadContext();

    long GetIndentLevel();

    void Enter();

    void Leave();
    
    void Write(int level, const char* msg) const;

    void AddHeaderStr( const char* msg ) const;

    void AddVersionForInst(HINSTANCE inst) const;

    void AddVersionForName(const char* fname) const;
};


static oikLog s_log;


oikLog :: myLog() : indent_level()
{
    InitializeCriticalSection(&lock);
    InitializeCurrentThreadContext();
}

myLog :: ~myLog()
{
    DeleteCriticalSection(&lock);
}

DWORD myLog :: InitializeCurrentThreadContext()
{
    EnterCriticalSection(&lock);
    DWORD id = GetCurrentThreadId();
    if ( indent_level.find(id) == indent_level.end() )
    {
        indent_level[id] = myBASE_LOG_INDENT;
    }
    LeaveCriticalSection(&lock);
    return id;
}

long oikLog :: GetIndentLevel()
{
    DWORD id = InitializeCurrentThreadContext();
    EnterCriticalSection(&lock);
    long indent = indent_level[id];
    LeaveCriticalSection(&lock);
    return indent;
}

void oikLog :: Enter()
{
    DWORD id = InitializeCurrentThreadContext();
    EnterCriticalSection(&lock);
    indent_level[id]++;
    LeaveCriticalSection(&lock);
}

void oikLog :: Leave()
{        
    DWORD id = InitializeCurrentThreadContext();
    EnterCriticalSection(&lock);
    indent_level[id]--;
    LeaveCriticalSection(&lock);
}

void oikLog :: Write(int level, const char* msg) const
{
    DebugMsg( level, (LPSTR)msg );//запись в лог
}

void oikLog :: AddHeaderStr( const char* msg ) const
{
    AddHdrString( (LPSTR)msg );//запись в лог
}

void oikLog :: AddVersionForInst(HINSTANCE inst) const
{
    AddVerInfoHdrFoInstance(inst);//запись в лог
}

void oikLog :: AddVersionForName(const char* fname) const
{
    AddVerInfoHdrFoFileName((LPSTR)fname);//запись в лог
}


///logger----------------------------------------------------------------------------------
int Logger :: GetCurrentDbgLevel()
{
    long indent = s_log.GetIndentLevel();
    return (indent > myMAX_DBG_LEVEL) ? (int)myMAX_DBG_LEVEL : (int)indent;
}

Logger :: Logger(const char* func_name, int lvl) : m_level( lvl ), m_indent( s_log.GetIndentLevel() )
{
    Write(func_name);
    Write("{");
    s_log.Enter();
    m_indent = s_log.GetIndentLevel();
}

Logger :: ~Logger()
{
    if (std::uncaught_exception())
        Write("#return: stack unwinding");
    else
        Write("#return");
    s_log.Leave();
    m_indent--;
    Write("}");
}

void Logger :: Write(const char* msg) const
{
    std::string m;
    m.assign(m_indent*4, ' ');
    m.append(msg);
    s_log.Write(m_level, m.c_str());
}



///logheader-------------------------------------------------------------------------------
LogHeader :: LogHeader(const char* msg)
{
    s_log.AddHeaderStr(msg);
}

const LogHeader& LogHeader :: operator () (const char* msg) const
{
    s_log.AddHeaderStr(msg);
    return *this;
}

const LogHeader& LogHeader :: ModuleVersion (HINSTANCE inst) const
{
    s_log.AddVersionForInst(inst);
    return *this;
}

const LogHeader& LogHeader :: ModuleVersion(const char* filename) const
{
    s_log.AddVersionForName(filename);
    return *this;
}


Автор: _Tanatos_ 26.11.2007, 14:46
Цитата

Цитата
TtLibLogEngine. Командой для изменения служат зарезервированные значения типа сообщения m_Type:
-1 - Увеличиваем вложенность
-2 - Уменьшаем вложенность

Правильнее использовать rtti, создаешь в стеке объект, в конструкторе вызывается нужная функция(увеличивает вложенность), в деструкторе уменьшаем вложенность, а как передавать - детали. Нужно различать поток, из которого вызывается ф-я, и вычислять вложенность отдельно для каждого потока. Это можно сделать внутри TtLibLogEngine, при каждом вызове ф-ии получать ThreadId потока, и дальше вычислять для него степень вложенности.


Так оно и будет. Будет дополнительный класс CtLibLogHelper, который будет в конструкторе регистрировать сообщение с увеличением вложенности, а в деструкторе собщение с уменьшением вложенности.

За пример спасибо. Изучаю.

Автор: _Tanatos_ 26.11.2007, 15:06
Цитата(Lazin @  26.11.2007,  14:23 Найти цитируемый пост)
static int GetCurrentDbgLevel();


Выложи здесь реализацию данной функции?
Заранее благодарю!

Автор: Lazin 26.11.2007, 15:21
Цитата(Lazin @  26.11.2007,  14:23 Найти цитируемый пост)
Код

///logger----------------------------------------------------------------------------------
int Logger :: GetCurrentDbgLevel()
{
    long indent = s_log.GetIndentLevel();
    return (indent > myMAX_DBG_LEVEL) ? (int)myMAX_DBG_LEVEL : (int)indent;
}

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

Автор: _Tanatos_ 26.11.2007, 15:55
Выкладываю предполагаемый вариант использования.
Обращаю внимание, что макросы пока не использую, дабы было понят но что есть что.
Не обращайте внимание на работу со строками, написал так для сокращения

Код

void CDocument::Save(const char* FileName)
{
      CtLibLogHelper LogHelper(ProcID, ThreadID, "CDocument::Save");
      LogHelper.Begin("Сохраняем документ...", "Имя файла: "+FileName, __FILE__, __LINE__);

      // Открываем файл
      LogHelper.Message(lltInfo, "Открываем файл", "", __FILE__, __LINE__);
      ...
      if(File == INVALID)
          LogHelper.Error(lltError, 134, "невозможно создать файл.", "", __FILE__, __LINE__);
      // Что-то пишем
      ...

      // Закрываем файл
      CloseHandle(File);
      LogHelper.Complete("... Готово!", "Записанно:"+Count+" строк", __FILE__, __LINE__);
}


В конструкторе класса CtLibLogHelper указываем идентификатор Процесса, потока и имя класса и функции - данные указываются один раз для одной функции. Соответственно все записи производятся через вспомогательный класс. Функция Error производит запись в журнал и сама генерирует исключение с указанным кодом (в данном случае 134).

Автор: Lazin 26.11.2007, 16:05
Цитата(_Tanatos_ @  26.11.2007,  15:55 Найти цитируемый пост)
Функция Error производит запись в журнал и сама генерирует исключение с указанным кодом (в данном случае 134). 

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

Автор: _Tanatos_ 26.11.2007, 16:16
Цитата(Lazin @ 26.11.2007,  16:05)
Цитата(_Tanatos_ @  26.11.2007,  15:55 Найти цитируемый пост)
Функция Error производит запись в журнал и сама генерирует исключение с указанным кодом (в данном случае 134). 

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

Согласен. Принято к изменению. 

Автор: _Tanatos_ 26.11.2007, 17:44
Цитата(archimed7592 @ 21.11.2007,  12:01)
Цитата(Lazin @  21.11.2007,  11:54 Найти цитируемый пост)
 ~Logger()
 {
   s_log.write(current_node, "return");
 }

Я бы ещё добавил
Код
s_log.write(current_node, std::uncaught_exception() ? "return(stack unwinding)" : "return");

Попробовал - неработает!
после неудачи порылся в инете на предмет использования, вот http://msdn2.microsoft.com/en-us/library/k1atwat8(VS.80).aspx пример не работает
Что делаю не так?


ИМХО лучше по старинке через флаг внутри класса и дополнительную функцию для его сброса.
Код

        if(m_Complete)
            g_LogEngine->RegisterMsg(...);
        else
            g_LogEngine->RegisterMsg(...);



Автор: archimed7592 26.11.2007, 18:19
Цитата(_Tanatos_ @  26.11.2007,  17:44 Найти цитируемый пост)
Попробовал - неработает!

Наверное это причуды твоего BCB smile.

Автор: Lazin 27.11.2007, 08:40
Цитата(_Tanatos_ @  26.11.2007,  17:44 Найти цитируемый пост)
Попробовал - неработает!
после неудачи порылся в инете на предмет использования, вот такой пример не работает
Что делаю не так?

Обрати внимание, на то, что std::uncaught_exception( ) срабатывает только если объект был создан внутри блока try, иначе он не вызывается. С Builderoм STL нормально работает...

Автор: _Tanatos_ 27.11.2007, 20:15
Цитата

Обрати внимание, на то, что std::uncaught_exception( ) срабатывает только если объект был создан внутри блока try, иначе он не вызывается. С Builderoм STL нормально работает...

Не знаю как нормально, но у меня не получилось добиться работы.

Добавлено через 13 минут и 38 секунд
Выкладываю на ваш суд черновой вариант классов

Начну с вспомогательных структур:
Код

//---------------------------------------------------------------------------
// Набор констант для использования с полем Type
typedef enum
{
    // Отрицательные значения зарезервированы, не использовать!
    lltBegin        = -1,
    lltComplete     = -2,

    lltDebug        = 0,
    lltInfo         = 1,
    lltNotice       = 2,
    lltError        = 3,
    lltCritical     = 4,
    lltAlert        = 5,
    lltEmergency    = 6,
}
TtLibLogType;
//---------------------------------------------------------------------------
class CtLibLogEngine;
class CtLibLogHelper;
//---------------------------------------------------------------------------
// Глобальный экземпляр класса
// TODO: в принципе надо заменить на указатель дабы
// для DLL можно было подключать логгер из основного процесса
extern CtLibLogEngine g_LogEngine;
//---------------------------------------------------------------------------
// Указатель на функцию-подписчик на событие добавления записи в журнал
typedef void (__closure *TtLibOnLogAdd)(CtLibLogEngine* LogEngine);
//---------------------------------------------------------------------------
// Массив подписчиков
typedef std::vector<TtLibOnLogAdd> TtLibLogSubscribers;
//---------------------------------------------------------------------------
// Эта структура описывает все данные одной записи
typedef struct
{
    unsigned long   m_RowID;    // Порядковый номер строки
    int             m_Level;    // Уровень вложенности
    signed char     m_LevelCh;  // Изменение уровня (пока не задействовано)
    int             m_ProcID;   // Идентификатор процесса
    int             m_UnitID;   // Идентификатор модуля
    signed char     m_Type;     // Тип сообщенияы
    int             m_Error;    // Код ошибки
    char*           m_Msg;      // Текст сообщения
    char*           m_Data;     // Дополнительные данные
    char*           m_Function; // Имя функции
    char*           m_File;     // Имя файла
    int             m_Line;     // Номер строки
    SYSTEMTIME      m_Time;     // Время события
}
TtLibLogRecord;
//---------------------------------------------------------------------------
// Структура для хранения условий обработки по модулям
typedef struct
{
    int     m_UnitID;   // Идентификатор модуля
    char    m_Min;      // Минимальное значение для m_Type
    char    m_Max;      // Максимальное значение для m_Type
}
TtLibLogCondition;
//---------------------------------------------------------------------------
// Массив условий
typedef std::vector<TtLibLogCondition> TtLibLogConditions;
//---------------------------------------------------------------------------


Далее привожу класс CtLibLogEngine.
Пока реализацию кинул в библиотечный файл, в будущем там останутся только inline функции
Код

class CtLibLogEngine
{
friend class CtLibLogHelper;
private:
    unsigned long       m_RowID;
    int                 m_Level;

    const char*         m_MsgBreakError;
    const char*         m_MsgBreakOk;
protected:
    TtLibLogRecord      m_Record;           // Данные сообщения
    TtLibLogConditions  m_Conditions;       // Условия фильтрации сообщений
    TtLibLogSubscribers m_Subscribers;      // Массив подписчиков на сообщения
public:
   ~CtLibLogEngine()
        {};
    CtLibLogEngine()
        : m_MsgBreakError(""),
          m_MsgBreakOk(""),
          m_RowID(0),
          m_Level(0),
          m_Record(TtLibLogRecord())
        {};

    void Initialize(const char* MsgBreakError, const char* MsgBreakOk)
    {
        m_MsgBreakError = MsgBreakError;
        m_MsgBreakOk = MsgBreakOk;
    }

    inline const TtLibLogRecord& getRecord() const
        { return m_Record; }

    void ConditionAdd(int UnitID, char Min, char Max)
        {
            m_Conditions.push_back();
            m_Conditions.back().m_UnitID = UnitID;
            m_Conditions.back().m_Min = Min;
            m_Conditions.back().m_Max = Max;
        }

    inline void SubscriberAdd(TtLibOnLogAdd OnLogAdd)
        {
            for(std::size_t i=0; i<m_Subscribers.size(); i++)
                if(m_Subscribers[i] == OnLogAdd)
                    return;
            m_Subscribers.push_back(OnLogAdd);
        }
    inline void SubscriberRemove(TtLibOnLogAdd OnLogAdd)
        {
            for(std::size_t i=0; i<m_Subscribers.size(); i++)
            {
                if(m_Subscribers[i] == OnLogAdd)
                {
                    for(std::size_t j=i; j<m_Subscribers.size()-1; j++)
                        m_Subscribers[j] = m_Subscribers[j+1];
                    m_Subscribers[m_Subscribers.size()-1] = NULL;
                    m_Subscribers.resize(m_Subscribers.size()-1);
                    return;
                }
            }
        };

    inline void RegisterMsg(int ProcID, int UnitID, char Type, int Error, const char* Msg, const char* Data, const char* Function, const char* File, int Line)
        {
            bool Ok = true;

            for(std::size_t i=0; i<m_Conditions.size(); i++)
                if(m_Conditions[i].m_UnitID)
                {
                    if(m_Conditions[i].m_Min<=Type && m_Conditions[i].m_Max>=Type)
                        { Ok = true; break; }
                    else
                        Ok = false;
                }

            m_RowID++;

            if(Ok)
            {
                m_Record.m_Level    = m_Level;
                m_Record.m_LevelCh  = 0;
                m_Record.m_RowID    = m_RowID;
                m_Record.m_ProcID   = ProcID;
                m_Record.m_UnitID   = UnitID;
                m_Record.m_Type     = Type;
                m_Record.m_Error    = Error;
                m_Record.m_Msg      = (char*) Msg;
                m_Record.m_Data     = (char*) Data;
                m_Record.m_Function = (char*) Function;
                m_Record.m_File     = (char*) File;
                m_Record.m_Line     = Line;
                GetLocalTime(&m_Record.m_Time);

                for(std::size_t i=0; i<m_Subscribers.size(); i++)
                    m_Subscribers[i](this);
            }
        }
};


Смотрим вспомогательный класс CtLibLogHelper
Код

class CtLibLogHelper
{
private:
    int             m_ProcID;       // Идентификатор процесса
    int             m_UnitID;       // Идентификатор модуля
    const char*     m_Function;     // Имя класса и функции
    const char*     m_File;         // Имя файла
    bool            m_Complete;     // Признак успешного завершения работы
protected:
public:
   ~CtLibLogHelper()
    {
        // Завершающая запись (номер строки неизвестен)
        if(m_Complete)
            g_LogEngine.RegisterMsg(m_ProcID, m_UnitID, lltComplete, 0, g_LogEngine.m_MsgBreakOk, "", m_Function, m_File, 0);
        else
            g_LogEngine.RegisterMsg(m_ProcID, m_UnitID, lltComplete, 0, g_LogEngine.m_MsgBreakError, "", m_Function, m_File, 0);
        g_LogEngine.m_Level--;
    }

    CtLibLogHelper(int ProcID, int UnitID, char Type, const char* Msg, const char* Data, const char* Function, const char* File, int Line)
        : m_Complete(false),
          m_ProcID(ProcID),
          m_UnitID(UnitID),
          m_Function(Function),
          m_File(File)
    {
        g_LogEngine.RegisterMsg(m_ProcID, m_UnitID, Type, 0, Msg, Data, m_Function, m_File, Line);
        g_LogEngine.m_Level++;
    }
    CtLibLogHelper(char Type, const char* Msg, const char* Data, const char* Function, const char* File, int Line)
        : m_Complete(false),
          m_ProcID(0),
          m_UnitID(0),
          m_Function(Function),
          m_File(File)
    {
        g_LogEngine.RegisterMsg(m_ProcID, m_UnitID, Type, 0, Msg, Data, m_Function, m_File, Line);
        g_LogEngine.m_Level++;
    }

    void Message(char Type, const char* Msg, const char* Data, int Line)
    {
        g_LogEngine.RegisterMsg(m_ProcID, m_UnitID, Type, 0, Msg, Data, m_Function, m_File, Line);
    }

    void Error(char Type, int Error, const char* Msg, const char* Data, int Line)
    {
        g_LogEngine.RegisterMsg(m_ProcID, m_UnitID, Type, Error, Msg, Data, m_Function, m_File, Line);
    }

    void Complete(char Type, const char* Msg, const char* Data, int Line)
    {
        m_Complete = true;
        g_LogEngine.RegisterMsg(m_ProcID, m_UnitID, Type, 0, Msg, Data, m_Function, m_File, Line);
    }
};


А теперь пример использования:
Код

// Инициализация
    g_LogEngine.Initialize("...return (stack unwinding)", "...return (complete)");
// У меня эта функция отображает на экране лог в виде дерева
    g_LogEngine.SubscriberAdd(OnLogAdd);

// пример использования для записи в журнал
void __fastcall TForm1::Button2Click(TObject *Sender)
{
    CtLibLogHelper LogHelper(lltBegin, "Процедура проверки вложенности...", ("Sender = " + Sender->ClassName()).c_str(), __FUNC__, __FILE__, __LINE__);
    LogHelper.Message(lltInfo, "Запускаем процедуру проверки", "", __LINE__);
    LogHelper.Message(lltInfo, "  Этап №1", "", __LINE__);
    LogHelper.Message(lltInfo, "  Этап №2", "", __LINE__);
    LogHelper.Message(lltInfo, "  Этап №3", "", __LINE__);
    LogHelper.Message(lltError, 12, "  Принудительный сбой в работе", "Здесь добавляем отладочную информацию.", __LINE__);
    LogHelper.Complete(lltInfo, "Процедура проверки выполнена!", "", __LINE__);
}

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

Автор: _Tanatos_ 27.11.2007, 20:32
Из текущих задач:
- сделать код безопасным для многопоточных приложений
- сделать глобальный объект указателем для поддержки DLL (при инициализации DLL надо соответственно инициировать указатель, а в основной программе соответственно создавать сам объект)
- Разработать класс (образец) подписчик для записи журнала в файл
- добавить раздельную нумерацию строк для потоков

Высказывайте свои пожелания, предложения и критику

Автор: _Tanatos_ 27.11.2007, 21:07
Примерно так выглядит класс записывающий данные в файл:

Код

class CtLibLogToFile
{
private:
    bool    m_Initialize;
    HANDLE  m_hFile;
protected:
public:
   ~CtLibLogToFile()
        {
            if(m_hFile)
                CloseHandle(m_hFile);
            m_hFile = NULL;
        }
    CtLibLogToFile()
        : m_Initialize(false),
          m_hFile(NULL)
        {};
    CtLibLogToFile(const char* FileName)
    {
        Initialize(FileName);
    };

    void Initialize(const char* FileName)
    {
        if(m_hFile==NULL && FileName!=NULL && m_Initialize==false)
        {
            m_hFile = CreateFile(FileName, GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            if(m_hFile != INVALID_HANDLE_VALUE)
                m_Initialize = true;
        }
        else
            // Повторная инициализация невозможна
            throw;
    };

    void OnLogAdd(CtLibLogEngine* LogEngine)
    {
        unsigned long   R;
        static char     Buffer[256];
        static char     Level[50] = "                                                         ";

        const TtLibLogRecord& Rec = LogEngine->getRecord();

        // Level
        WriteFile(m_hFile, Level, Rec.m_Level, &R, NULL);

        // №№ | Дата | Время
        sprintf(Buffer, "%010d  %04d//%02d//%02d %02d:%02d:%02d[%d]", Rec.m_RowID, Rec.m_Time.wYear, Rec.m_Time.wMonth, Rec.m_Time.wDay, Rec.m_Time.wHour, Rec.m_Time.wMinute, Rec.m_Time.wSecond, Rec.m_Time.wMilliseconds);
        WriteFile(m_hFile, Buffer, strlen(Buffer), &R, NULL);
        // Код модуля | Код процесса/потока | Тип сообщения | Код ошибки |
        sprintf(Buffer, "  %04d %04d %03d %06d", Rec.m_UnitID, Rec.m_ProcID, Rec.m_Type, Rec.m_Error);
        WriteFile(m_hFile, Buffer, strlen(Buffer), &R, NULL);

        // Текст сообщения | Данные | Файл | Строка |
        sprintf(Buffer, "  %s || %s || %s [%0d]", Rec.m_Msg, Rec.m_Data, Rec.m_File, Rec.m_Line);
        WriteFile(m_hFile, Buffer, strlen(Buffer), &R, NULL);

        sprintf(Buffer, "\n");
        WriteFile(m_hFile, Buffer, strlen(Buffer), &R, NULL);
    };
};


Пример использования
Код

// Глобально
CtLibLogToFile LogToFile;
...
// В рамках инициализации
    LogToFile.Initialize("D:\\Temp\\Log.txt"); // <===

    g_LogEngine = g_LogEngine->Initialize("...return (stack unwinding)", "...return (complete)");
    g_LogEngine->SubscriberAdd(OnLogAdd);
    g_LogEngine->SubscriberAdd(LogToFile.OnLogAdd); // <===


Автор: UnrealMan 28.11.2007, 00:59
Цитата(_Tanatos_ @  27.11.2007,  20:15 Найти цитируемый пост)
Тут даже без макросов все довольно компактно получается.

Хреново без макросов получается. Во-первых, необходимость всюду прописывать __LINE__ - это ж... Кроме того, отладочная информация по логике вещей должна выводиться в лог только в отладочной версии программы. И желательно, чтобы в неотладочной версии параметры, представляющие собой отладочную информацию, зря не вычислялись. Указание типа лога через константу - IMHO, не самая лучшая идея.

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

Автор: Lazin 28.11.2007, 09:00
Можно сделать так что-бы ф-я SubscriberAdd получала не указатель на ф-ю с модификатором __closure, а указатель на класс, а все "подписчики" имели-бы одинаковый интерфейс, это было-бы переносимо на другие компиляторы.
Цитата(_Tanatos_ @  27.11.2007,  21:07 Найти цитируемый пост)
Примерно так выглядит класс записывающий данные в файл:

Этот класс не учитывает одного простого факта - что возможен запуск нескольких копий одного приложения, в этом случае в файле будет мусор. Нужно синхронизировать доступ к файлу, например создать именованный мьютекс и захватывать его пере записью, и выводить в лог код процесса, что-бы можно было различать вывод разных процессов.
Цитата(UnrealMan @  28.11.2007,  00:59 Найти цитируемый пост)
Указание типа лога через константу - IMHO, не самая лучшая идея.


Цитата(UnrealMan @  28.11.2007,  00:59 Найти цитируемый пост)
Этот логгер неудобно будет использовать для вывода составных данных - когда строка формируется из подстрок, чисел и прочих данных с нужным форматированием. Придётся самому вычислять строку и передавать её логгеру - это как минимум громоздко. 

Возможное решение этой проблеммы - использовать полиморфизм, передавать в объект logEngine объекты, содержащие нужную информацию. CtLibLogHelper может выступать в роли фабрики классов.

Добавлено через 2 минуты и 29 секунд
выглядеть это может так
Код

LogHelper.Message("blablabla");
LogHelper.AddValue("blablabla", val);
e.t.c.

Автор: _Tanatos_ 28.11.2007, 18:51
Цитата(Lazin @  28.11.2007,  09:00 Найти цитируемый пост)
Можно сделать так что-бы ф-я SubscriberAdd получала не указатель на ф-ю с модификатором __closure, а указатель на класс, а все "подписчики" имели-бы одинаковый интерфейс, это было-бы переносимо на другие компиляторы.


Принято:
Код

// Класс-подписчик на событие добавления записи в журнал
class CtLibLogSubscribe
{
public:
//    virtual ~CtLibLogSubscribe()=0;
    virtual void OnLogAdd(CtLibLogEngine* LogEngine)=0;
};

//---------------------------------------------------------------------------
typedef std::vector<CtLibLogSubscribe*> TtLibLogSubscribers;
//---------------------------------------------------------------------------

...

    inline void SubscriberAdd(CtLibLogSubscribe* Subscriber)
    inline void SubscriberRemove(CtLibLogSubscribe* Subscriber)

...

class CtLibLogToFile :
    public CtLibLogSubscribe
{
private:
...
}

В класс входит только одна функция, так как пока не вижу что еще может потребоваться.
Можно добавить туда же унифицированные функции настройки фильтра, аналогично классу Engine. Надо?

Добавлено через 4 минуты и 24 секунды
Цитата(Lazin @  28.11.2007,  09:00 Найти цитируемый пост)
Цитата(_Tanatos_ @  27.11.2007,  21:07 )Примерно так выглядит класс записывающий данные в файл:Этот класс не учитывает одного простого факта - что возможен запуск нескольких копий одного приложения, в этом случае в файле будет мусор. Нужно синхронизировать доступ к файлу, например создать именованный мьютекс и захватывать его пере записью, и выводить в лог код процесса, что-бы можно было различать вывод разных процессов.


Не хочу чересчур усложнять класс, который должен работать максимально быстро.
ИМХО это обходится крайне просто, при инициализации лога добавьте к имени файла идентификатор процесса.
А если нужна паралельная запись в файл, то надо сделать отдельный класс для этой задачи
Таких классов-обработчиков может быть много. Например для накопления сообщений и отсылки их по почте и т.д.
В данном случае мой класс является лишь примером. Сначала закончу с ядром, а уж потом займусь переферией.

Добавлено через 8 минут и 54 секунды
Цитата(Lazin @  28.11.2007,  09:00 Найти цитируемый пост)
Цитата(UnrealMan @  28.11.2007,  00:59 )Указание типа лога через константу - IMHO, не самая лучшая идея.

Совершенно непонятное заявление! Поясни, что тебе не нравится? Не нравятся константы, пишы числа, переменные, выражения, да что угодно - главное чтобы результат был signed char. А лучше всего напиши чем именно не нравится и для какой задачи тебе это не подходит.

P.S.
Не ленитесь подробней изложить свою мысль, ведь от этого напрямую зависит результат

Автор: _Tanatos_ 28.11.2007, 19:15
Цитата(UnrealMan @  28.11.2007,  00:59 Найти цитируемый пост)
Этот логгер неудобно будет использовать для вывода составных данных - когда строка формируется из подстрок, чисел и прочих данных с нужным форматированием. Придётся самому вычислять строку и передавать её логгеру - это как минимум громоздко. 

А как ты себе представляешь класс логгера должен форматировать твои данные? Мне кажется Вы на этот класс возлагаете не его задачи. И потом громоздкое и длинное сообщение будет неудобоваримым. А если речь идет о данных, то для них есть отдельное поле (см ниже). Как вспомогательный инструмент можно написать функцию аналогичную sprintf, но возвращающую указатель на готовую строку и вставлять эту конструкцию вместо сообщения:

LogHelper.Message(lltInfo, Format("  Этап №%d", Number), "", __LINE__);

Годится такой вариант?

Цитата(Lazin @  28.11.2007,  09:00 Найти цитируемый пост)
Возможное решение этой проблеммы - использовать полиморфизм, передавать в объект logEngine объекты, содержащие нужную информацию. CtLibLogHelper может выступать в роли фабрики классов.

Опять же непонятно зачем полиморфизм и причем тут фабрика классов? Приведенный тобой пример вполне подходит для формирования поля Data, если вызов функции AddValue будет приводить к добавлению строчки с именем переменной и ее значением. Если так, то согласен и постараюсь добавить несколько функций для работы с различными типами данных. Только проблема заключается в том, что надо будет так же добавлять функцию Post() в конце для определения момента когда сообщение полностью сформированно и его надо передать в класс Engine.

Автор: UnrealMan 28.11.2007, 21:02
Цитата(_Tanatos_ @  28.11.2007,  19:15 Найти цитируемый пост)
А как ты себе представляешь класс логгера должен форматировать твои данные?

Так же, как это делают потоки типа fstream или хотя бы как fprintf.

Код
LOG_INFO("pass: ", std::setw(4), pass);
LOG_INFO_F("pass: %4.d", pass);


Цитата(_Tanatos_ @  28.11.2007,  19:15 Найти цитируемый пост)
Мне кажется Вы на этот класс возлагаете не его задачи. 

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

Цитата(_Tanatos_ @  28.11.2007,  19:15 Найти цитируемый пост)
Как вспомогательный инструмент можно написать функцию аналогичную sprintf, но возвращающую указатель на готовую строку и вставлять эту конструкцию вместо сообщения:

LogHelper.Message(lltInfo, Format("  Этап №%d", Number), "", __LINE__);

Годится такой вариант?

Это неудобный вариант.

Автор: _Tanatos_ 29.11.2007, 12:42
В вашем примере указано только одно поле, а их как минимум два Сообщение и данные.
Такой вариант приемлим?
Код

LOG_INFO_MSG("Connect to %s", ServerName);
LOG_INFO_Data("Log: %4.d", login);
LOG_INFO_Data("pass: %4.d", pass);
LOG_WRITE;


P.S.
To All:
Спасибо всем, кто принимает участие в обсуждении!
Стараюсь учесть пожелания и предложения каждого
Сорри что изменения не так быстро воплощаются в код.

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

Автор: _Tanatos_ 29.11.2007, 13:48
Не хочется изобретать велосипед и раз уж речь зашла о форматировании, то хотелось бы использовать sprintf.
Только как передать переменное кол-во  аргументов дальше?

Код

void ExMsg(const char* Message, ...)
{
    ......
    sprintf(Temp, Message, /*Что здесь писать???*/);
    ......
}
    
Уважаемые гуру, нужен Ваш совет!
Писать множество отдельных функций для различных типов данных ИМХО нелогично (что делать если надо сразу два аргумента передать при форматировании одной строки?). 

Автор: UnrealMan 29.11.2007, 13:48
Цитата(_Tanatos_ @  29.11.2007,  12:42 Найти цитируемый пост)
а их как минимум два Сообщение и данные

А какой резон отделять данные от сообщения?

Автор: UnrealMan 29.11.2007, 14:04
Цитата(_Tanatos_ @  29.11.2007,  13:48 Найти цитируемый пост)
Только как передать переменное кол-во  аргументов дальше?

Код

#include <cstdarg>
#include <cstdio>

void Test(const char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    
    char tmp[0x1000];
    
    vsprintf(tmp, format, ap);
    va_end(ap);

    printf(tmp);
}

int main()
{
    Test("Text: %s%d%s", "str1 ", 123, " str2 ");
}


Добавлено через 3 минуты и 32 секунды
Есть одно маленькое "но": теперь тебе придётся помнить о возможности переполнения буфера, используемого для sprintf.

Автор: _Tanatos_ 29.11.2007, 14:17
Спасибо!

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

Вот пример использования:
Код

    LogHelper.ExMsg("Connect...");
    LogHelper.ExData("Address: %s\n", "address");
    LogHelper.ExData("Login: %s\n", "log");
    LogHelper.ExData("Password: %s\n", "pass123");
    LogHelper.ExPost(lltInfo, __LINE__);


У меня получилось немного иначе, так как при многократном вызове нужно сохранять предыдущую часть. Ну и требования по длинне сообщения смегчаются smile ограничена лишь максимальная длина сообщения для ОДНОЙ операции, но не общая
Так выглядит содержимое функции:
Код

    void ExMsg(const char* Message, ...)
    {
        int     Len = m_ExMessage ? strlen(m_ExMessage) : 0;
        char*   Temp = new char[Len+1024];
        va_list ap;

        if(m_ExMessage)
            strcpy(Temp, m_ExMessage);

        va_start(ap, Message);
        vsprintf(Temp+Len, Message, ap);
        va_end(ap);

        if(m_ExMessage)
        {
            delete m_ExMessage;
            m_ExMessage = NULL;
        }
        m_ExMessage = Temp;
    }


И парочку сервисных функций:
Код

    void ExPost(char Type, int Line)
    {
        g_LogEngine->RegisterMsg(m_ProcID, m_UnitID, Type, 0, m_ExMessage, m_ExData, m_Function, m_File, Line);
        ExReset();
    }
    void ExReset()
    {
        if(m_ExMessage)
        {
            delete m_ExMessage;
            m_ExMessage = NULL;
        }
        if(m_ExData)
        {
            delete m_ExData;
            m_ExData = NULL;
        }
    }

Естественно добавился вызов ExReset() в деструктор smile

Еще добавилось два конструктора:
Код

    CtLibLogHelper(int ProcID, int UnitID, char Type, const char* Function, const char* File, int Line);
    CtLibLogHelper(char Type, const char* Function, const char* File);

для инициализации класса без создания сообщения в конструкторе.

Автор: Lazin 29.11.2007, 15:48
Я не очень понимаю что нужно получить в итоге, наверное лучше сначала составить какой нибудь список требований к библиотеке т.е. что нам от нее надо....
использование элипсисов я считаю тупиковым, так как в данном случае теряется гибкость и расширяемость, если я сделаю свой класс, как мне расширить класс, что-бы он выводил в лог объекты моего класса. Для этого придется наследовать, от класса - логгера, и переопределять ф-ю. Лучше сделать это так, как это сделано в библиотеках iostream или boost::serializaion etc. 
должно выглядеть примерно так:
Код

int a = 10;
g_logger << "value a = " << a << my_logger::end;//выводим данные, потом выводим специальное значение, которое информирует объект о том что мы закончили формировать сообщение
//в лог должно после этого добавиться "**** value a = 10"
------
my_logger& operator << (my_logger& l, TPoint& p)//оператор вывода в лог объектов класса TPoint
{
 l << p.x << p.y;
 return *this;
}
------
TPoint p(10,20);
g_logger << "Point p = " << p << my_logger::end;

Можно использовать интерфейс как в iostream для изменения параметров (там это называется манипуляторами)
Код

g_logger << my_logger::step_into;//говорим логгеру, что нужно увеличить вложенность(новая функция)
g_logger << my_logger::return;//говорим логгеру, что нужно уменьшить степень вложенности

с таким интерфейсом проще написать макросы
Код

#define TRACE_MESSAGE(p1,p2,p3) \
            g_logger << (p1) << (p2) << (p3) << my_logger::end;

и так можно написать для любого кол. параметров

Автор: _Tanatos_ 29.11.2007, 15:55
Переработал класс CtLibLogHelper
Код

class CtLibLogHelper
{
private:
    int             m_ProcID;       // Идентификатор процесса
    int             m_UnitID;       // Идентификатор модуля
    const char*     m_Function;     // Имя класса и функции
    const char*     m_File;         // Имя файла
    bool            m_Complete;     // Признак успешного завершения работы
    bool            m_LevelUp;      // Признак инициализации с увеличением уровня

    char*           m_ExMessage;    // Сооьщение
    char*           m_ExData;       // Данные
protected:
public:
   ~CtLibLogHelper();

    // Инициализация класса
    // Если LevelUp=true то первая запись будет заголовком, остальные вложенными
    // Если LevelUp=false то все записи будут в пределах одного уровня
    CtLibLogHelper(const char* File, const char* Function, bool LevelUp=true, int UnitID=0, int ProcID=0);

    // Сохраняет фрагмент сообщения
    void AddMessage(const char* Message, ...);
    void AddData(const char* Data, ...);
    // Записывает подготовленное сообщение
    // Если Complete=true, то выставляется соответствующий флаг, на false не реагирует никак
    void Post(signed char Type, int Line, bool Complete = false);
    // Принудительное освобождение памяти занятой при формировании сообщения (всегда вызывается из Post)
    void Reset();
};


Примеры использования:
Код


void SubFunc()
{
// Инициируем класс, сохраняем имя файла, функции, признак вложенности отключаем!!!, код модуля и процесса/потока
    CtLibLogHelper LogHelper(__FILE__, __FUNC__, false);

// Формируем сообщение
    LogHelper.AddMessage("...Test...");
// Записываем подготовленное сообщение (никаких вложенностей, см. конструктор)
    LogHelper.Post(lltInfo, __LINE__);
}

// Функция которая ведет вложженную запись
void Simmple()
{
// Инициируем класс, сохраняем имя файла, функции, признак вложенности, код модуля и процесса/потока
    CtLibLogHelper LogHelper(__FILE__, __FUNC__);

// Формируем заголовок
    LogHelper.AddMessage("Begin");
// Записываем подготовленное сообщение (последующие сообщения будут вложенными)
    LogHelper.Post(lltBegin, __LINE__);

// Формируем новое сообщение
    LogHelper.AddMessage("Connect...");
    LogHelper.AddData("Address: %s\n", "address");
    LogHelper.AddData("Login: %s\n", "log");
    LogHelper.AddData("Password: %s\n", "pass123");
// Записываем подготовленное сообщение
    LogHelper.Post(lltInfo, __LINE__);

// Вызываем функцию
    SubFunc();

// Формируем завершающее сообщение
    LogHelper.AddMessage("...Ok!");
// Записываем подготовленное сообщение, устанавливаем флаг успешного завершения!!!!
    LogHelper.Post(lltInfo, __LINE__, true);
}

В результате получаем следующий лог:
Код

Begin
  Connect...                                Address: address|Login: log|Password: pass123
  ...Test...
  ...Ok!


Если последний вызов
    LogHelper.Post(lltInfo, __LINE__, true);
заменить на обычный
    LogHelper.Post(lltInfo, __LINE__);
получим:
[/code]
В результате получаем следующий лог:
Код

Begin
  Connect...                                Address: address|Login: log|Password: pass123
  ...Test...
  ...return (stack unwinding)

Последняя строка сформируется автоматически.

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

Автор: _Tanatos_ 29.11.2007, 16:12
Цитата(Lazin @  29.11.2007,  15:48 Найти цитируемый пост)
Я не очень понимаю что нужно получить в итоге, наверное лучше сначала составить какой нибудь список требований к библиотеке т.е. что нам от нее надо.... 


Эта ветка для этого и создана smile.
Конечно в идеале сначала составить что-то вроде ТЗ, а потом уже писать.
Но! я давненько не программировал, так что пользуясь случаем восстанавливаю навыки.

Цитата(Lazin @  29.11.2007,  15:48 Найти цитируемый пост)
использование элипсисов я считаю тупиковым, так как в данном случае теряется гибкость и расширяемость, если я сделаю свой класс, как мне расширить класс, что-бы он выводил в лог объекты моего класса. 

С таким аргументом не поспоришь smile
Да и возможность записать все в одну строку прильщает.
Непонятно только как добавлять вспомогательную информацию и  как разделять сообщение и данные?

Склоняюсь к использованию дополнительного класса CtLibLogHelper для управления вложенностью и запретить прямой доступ извне. Это позволит гарантировать, что иерархия будет корректной всегда. Иначе можно сделать 10 увеличений вложенности а потом по исключению (или недосмотру) словить неправильную иерархию. А сам класс CtLibLogEngine тогда будет использоваться для записи сообщений в текущий уровень.

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

Какие еще есть пожелания?

Автор: JackYF 29.11.2007, 16:23
Не насилуйте va_args, юзайте boost::format...

Автор: Lazin 29.11.2007, 16:25
как вариант, определить оператор << в классе LogHelper, будет более логично...
тем более LogHelper отвечает за контроль иерархии записей

Автор: _Tanatos_ 29.11.2007, 16:47
А как такой вариант?
Код

LogHelper.Log(Type, Error, __LINE__) << "Message with error" << loData << "Data" << loPost;
LogHelper.Log(Type, __LINE__) << "Message with complete" << loData << "Data" << loComplete;

Опробовал вполне работоспособный вариант.

С макросом вообще сказка получается:
Код

LOG_Info << "Message" << loData << "Data" << loPost;
LOG_Error(12)  << "Message with error" << loData << "Data" << loPost;
LOG_Complete << "Message with complete" << loData << "Data" << loComplete;


Остается правда вопрос! Как с помощью макроса при финальной компиляции исключить из кода запись в журнал?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)