| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > 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 |
вот а я, значит, велосипеды пишу... только вот стандарт по поводу этого, равно как и __FUNCTION__ молчит. Молчит и ман по g++. В гугле нашёл кое-какие сообщения в мэйл-листах, которые говорят о том, что такое таки есть и его используют. Где найти документацию? |
| Автор: bsa 20.11.2007, 18:25 | ||
http://gcc.gnu.org/onlinedocs/gcc/Function-Names.html |
| Автор: JackYF 20.11.2007, 19:07 |
о! я уже эту страничку видел, но на другом сайте и с чуть другим оформлением, поэтому усомнился в официальности. Респект, ну и + ты точно заслужил, спасибо |
| Автор: archimed7592 20.11.2007, 20:45 |
| bsa, лови и от меня А что думают другие компиляторы относительно __PRETTY_FUNCTION__ ? Другими словами - насколько это переносимо в пределах современных популярных компиляторов? |
| Автор: bsa 20.11.2007, 21:16 | ||
Про __PRETTY_FUNCTION__ не уверен, а вот, судя по той ссылке, __func__ юзать можно на любых компиляторах, совместимых с C99. |
| Автор: jonie 20.11.2007, 22:10 |
| они не думают... ибо не стандарт. да и про __func__ они тоже не должны бы думать (по крайней мере в ansi iso iec 14882(2003) я такого не видел)... может пнете посмотрю ) |
| Автор: SaDFromSpb 21.11.2007, 01:40 |
| _Tanatos_, если сделаешь, кинь, плиз, исходники на всеобщее обозрение. Может, доделаем до состояния конфетки, если оно еще ей не будет являться =). Было бы здорово иметь что-нибудь продуманно-универсальное для этого дела... Добавлено через 3 минуты и 17 секунд Ну или если кто-то знает уже готовые решения для этого, то сообщите. По поводу боязни неуниверсальности __PRETTY_FUNCTION__ - можно использовать reflex, правда есть мысль, что здесь она через чур.... Дополнительную библиотеку все-таки подключать... |
| Автор: archimed7592 21.11.2007, 02:07 |
Если бы только библиотеку подключать... |
| Автор: Mayk 21.11.2007, 04:37 |
| в бусте есть boost/current_function.hpp |
| Автор: chipset 21.11.2007, 04:48 | ||
А у меня такой вот макрос:
Вроде работает на gcc и VCях. |
| Автор: Lazin 21.11.2007, 09:01 |
| _Tanatos_, я так понимаю, что ты хочешь строить дерево, в оперативной памяти, для определения причины сбоя в программе. Дерево придется периодически сохранять на диск, в идеале при добавлении нового элемента... По хорошему лог нужно записывать так:
- в формате лога должна быть следующая информация: код процесса, код потока, время записи, уровень(степень подробности) сообщения, дальше на твое усмотрение, можно еще номер строки и имя файла добавить.. - класс отвечающий за запись в лог(еще лучше просто функция) не должен буферизировать сообщения, иначе, если программа упадет, последние сообщения не запишутся в файл, если программа под windows, то мало просто записывать на диск данные, нужно принудительно очищать буфер файла, если этого не делать в случае вызова Terminate данные могут быть не записаны на диск - опять-же в случае если пишешь под windows смотри MSDN на тему Event Log, ReportEvent и тд. Добавлено через 1 минуту и 23 секунды ps недавно открыл для себя программу grep, которая очень подходит для анализа огромных лог файлов Добавлено через 13 минут и 4 секунды Кстати Event Log в Windows работает очень быстро, надежно, и лучше не изобретать велосипед, а использовать то, что предоставляет платформа для этих целей. |
| Автор: SergeCpp 21.11.2007, 10:09 | ||
http://ru.wikipedia.org/wiki/AWK подходит ещё более очень ![]() (на авторов, на авторов там посмотрите! )Также посмотрите 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
принцип такой, впри входе в ф-ю создается объект Logger. Через который производится запись в лог, каждый такой объект имеет свой номер, по которому можно определить порядок вызовов, в деструкторе, в лог также записывается информация о том что ф-я завершила выполнение. |
| Автор: archimed7592 21.11.2007, 12:01 | ||
Я бы ещё добавил
|
| Автор: SaDFromSpb 21.11.2007, 12:34 | ||
А вот, кстати, содержимое boost/current_function.hpp:
Все весьма банально =) |
| Автор: dumb 21.11.2007, 14:47 | ||
|
| Автор: JackYF 21.11.2007, 17:51 |
* ушёл смотреть * ну вот, уже выложили ну так, в принципе, и предполагалось. Буст подруливает. Maykу однозначно плюс. |
| Автор: _Tanatos_ 22.11.2007, 13:41 |
| Подскажите какой тип лучше использовать для хранения времени события и заодно какие функции для работы с ним. |
| Автор: Lazin 22.11.2007, 13:47 |
| структура FILETIME, функции GetLocalTyme SystemTimeToFileTime |
| Автор: SaDFromSpb 22.11.2007, 15:25 | ||
А что, в <ctime> большой выбор для этого? |
| Автор: StarikanMan 22.11.2007, 16:30 |
| bsa, он написал очень хороший пример |
| Автор: dumb 22.11.2007, 16:32 |
| всем, кто пытается осмыслить посты StarikansBack'а - этот убогий является каким-то обгашенным куском недоразума, генерирующим случайные наборы слов. |
| Автор: Lazin 22.11.2007, 16:48 | ||
Кстати пример кода взят из одного из недавних постов на форуме)) |
| Автор: Lazin 22.11.2007, 17:09 | ||
точнее множество его клонов)) |
| Автор: _Tanatos_ 23.11.2007, 14:17 | ||||||||
| Вот что у меня получилось на данный момент Привожу фрагмент кода с описанием структуры данных одной записи:
Какие есть мысли и соображения? Изначально буду реализовывать функцию регистрации записи с полным перечнем Поля m_Level и m_Time обрабатываются автоматически внутри класса. Предполагаю, что использование полей m_Type, m_File и m_Line будет внутри макроса, тем самым сократиться общая длина записи Макрос __FUNC__ пока не буду использовать, так как нет гарантий, что при смене компилятора работоспособность кода сохранится. Для времени пока остановился на SYSTEMTIME, дабы не вызывать лишние функции преобразования, да и при записи в текстовый файл не надо будетзаниматься обратной конвертацией для форматирования. далее приведено определение обработчика для регистрации новой записи
Вот и сам класс журналирования
Предполагается следующий сценарий использования - имеется глобальная переменная g_LogEngine через нее происходит регистрация всех событий - В данному классу подключаются обработчики выполняющие одну из следующих функций: - запись в файл - отправка лога по почте (например пересылает только критические сообщения) - отображение сообщений в окне отладки (если таковое имеется в программе) - Далее при входе в журналируюмую функцию объявляем вспомогательный класс (экземпляр), через него обеспечивается запись в журнал с увеличением уровня вложенности - используем подготовленные макросы для ведения лога в пределах функции. - вызываем функцию Complete() в вспомогательном классе - при завершении функции вспомогательный класс в деструкторе фиксирует уменьшение уровня вложенности и делает запись либо об успешном выполнении либо об аварийном завершении (если небыло вызова Complete() ) примерно так. Вспомогательный класс опишу чуть позже. Поддержка многопоточности на последний этап, так как это на перспективу и мне пока не нужно. Из актуальных вопросов это регистрация условий обработки - ограничение по минимальному и максимальному уровню для m_Type для каждого отдельного m_UnitID, что позволит сократить объем сохраняемых записей. Пока планирую сделать так:
Соответственно делаем массив таких записей и при регистрации новой записи сначала пробегаемся по этому массиву и проверяем на выполнимость условий, если разрешающего условия не найдено, то завершаем обработку, иначе поочередно вызываем все обработчики. При необходимости задавать условия для каждого обработчика отдельно, это уже реализуется внутри конкретного обработчика. Какие есть мысли, пожелания, предложения? По ходу дела буду выкладывать готовые фрагменты и по результату скину все целиком. |
| Автор: Lazin 23.11.2007, 14:53 | ||||
Мне кажется эта структура сильно избыточна, если знаешь имя ф-ии, то имя файла и номер строки знать не обязательно. И имя ф-ии можно указывать только при входе в ф-ию, с другой стороны нет идентификатора потока, в многопоточном приложении записи из разных потоков смешаются, и получится хаос. Уровень вложенности нужно расчитывать для каждого потока отдельно. Еще можно расчитывать m_Level в зависимости от степени вложенности. |
| Автор: Mayk 23.11.2007, 15:52 | ||||||
не согласен. имя файла и номер строки очень удобны. в любом уважающим себя редакторе есть ф-ция перехода на заданную строку. Перейти на определение ф-ции значительно сложнее. К тому же иногда хотелось бы посмортреть до каких строк работает прога, а до каких она не доходит.
если хочешь портируемый код, то надо избавиться от не портируемого __closure.
Кто выделяет и освобождает память для этих полей? только 256 типов сообщений? а не мало? |
| Автор: Lazin 23.11.2007, 16:33 | ||||
Ну номер строки еще ладно, но имя файла и имя ф-ии нужно указывать только при их изменении.
так он же написал, что использует Borland? а есть ли какая-нибудь портируемая вльтернатива __closure ? присоединяюсь можно еще добавить поле - "категория", например все сообщения имеющие отношение к работе с базой данных должны иметь одну категорию, все, что имеют отношение к взаимодействию между процессами, другую, плюс должна быть возможность комбинировать категории. Это поможет в итоге отфильтровать лог, при поиске конкретной проблемы. |
| Автор: _Tanatos_ 23.11.2007, 16:38 | ||||||
А надо больше? Сколько не прикидывал, пока больше 10 не набиралось Думаю использовать более 10 уровней детализации не имеет смысла ибо ни один программист не сможет в них свободно ориентироваться. На данный момент планирую, что память внутри обработки журнала не будет ни выделяться, ни освобождаться, а следовательно всем будет заниматься тот кто вызывает функцию. Эти данные актуальны только на этапе обработки сообщения и никоим образом не могут быть использованы после. А значит указатель будет ссылаться на достоверные данные. Позже скину пример использования.
А чем заменить? Если просто убрать, то указатель будет на отдельную функцию, а мне надо на функцию класса.
В данном случае больше не меньше. Реальный вызов гораздо более компактный. И потом я планирую сделать браузер для логов, который позволит просматривать исходники ... если будет время добавлю поддержку компиляторов, чтобы файлы открывались именно в них (но это не скоро). |
| Автор: Mayk 23.11.2007, 18:18 | ||||||||
При использование __closure есть работоспосбность кода при смене компилятора не сохранится вообще.
boost::bind + boost::function, например.
А если мне хочется нумеровать сообщения, начиная с 0x10000000[например сообщения из .exe], а в другом месте с 0x200000[например, сообщения из .dll]? или использование юзеровских типов не предусмотрено? а почему тогда не использовать enum?
ОпределенноХорошаяМысль. |
| Автор: _Tanatos_ 23.11.2007, 19:14 | ||||||||
Принимается, добавлю. ТОлько распишите поподробней что имеется в виду под "возможность комбинировать"? Как себе представляете этот процесс. Какого типа хотелось бы видеть категорию?
Посмотрю - как разберусь исправлю. Спасибо за информацию!
Нет проблем, пользуйте m_Proc или m_Unit. Кстати спрашивали насчет потоков ... под них так же планируется использовать m_Proc - ИМХО предостаточно. Смысла писать туда реальный идентификатор потока не вижу, так как он от раза к разу меняется, можно его скинуть как данные при старте потока или процесса. А с логе использовать внутренние идентификаторы.
А зачем? Предполагаю использовать отрицательные значения для нескольких предопределенных значений, остальное на усмотрение программиста, так как никакой нагрузки смысловой (в пределах кода журнала) эти данные не несут - а значит и ограничивать нет смысла. Пусть каждый сам разобьет на то количество уровней которое ему нужно, а для варианта по умолчанию уже есть enum но его можно или использовать или нет. |
| Автор: SaDFromSpb 23.11.2007, 22:54 | ||
Тогда, на всякий случай определи его как signed char, так как стандарт этого не гарантирует по умолчанию. И давай скорее пример использования, а то так разбираться лениво |
| Автор: _Tanatos_ 26.11.2007, 13:52 | ||||||||||
| На суд уважаемых форумчан выдвигается несколько вариантов решения с вложенностью, а именно Вариант №1: (мне нравится больше всего) вложенность управляется при помощи параметра m_Level. Его изменение производится исключительно классом TtLibLogEngine. Командой для изменения служат зарезервированные значения типа сообщения m_Type: -1 - Увеличиваем вложенность -2 - Уменьшаем вложенность Плюс данного решения в том, что нет необходимости вводить дополнительные переменные, функции и т.д., минус нельзя одновременно зафиксировать запись пользовательского типа с вложенностью. Пример использования:
Вариант №2: идентичен предыдущему варианту, но для управления вложенностью выделяется отдельные функции (можно конечно сделать и отдельный параметр, но уж больно много параметров). Пример использования:
Вариант №3: Вынести управление в отдельную функцию, которая будет вызываться перед регистрацией сообщения и будет модифицировать одну последующую запись. ИМХО хуже из-за лишнего вызова, хотя можно сделать с модификатором inline. Пример использования:
-------------------- Предложение, насчет передачи имени класса и функции только один раз, отклюняю, так как: 1) Никто не мешает сделать это собственными силами. Объявляйте константы, ссылки или просто записывайте в лог один раз, а остальные записи оставляйте пустыми. 2) Как Вы себе представляете восстановление данных значений после выхода из вложенной функции??? --------------------
Принято.
На этой неделе будет выложен исходник с использованием. Пока пишу библиотеку в перерывах (не многочисленных) между основной работой :(, так что сделано достаточно мало. |
| Автор: Lazin 26.11.2007, 14:23 | ||||||
Правильнее использовать rtti, создаешь в стеке объект, в конструкторе вызывается нужная функция(увеличивает вложенность), в деструкторе уменьшаем вложенность, а как передавать - детали. Нужно различать поток, из которого вызывается ф-я, и вычислять вложенность отдельно для каждого потока. Это можно сделать внутри TtLibLogEngine, при каждом вызове ф-ии получать ThreadId потока, и дальше вычислять для него степень вложенности. Я зделал что то похожее, но у меня - просто надстройка над библиотекой ведения логов, которая немного форматирует текст.
|
| Автор: _Tanatos_ 26.11.2007, 14:46 | ||||
Так оно и будет. Будет дополнительный класс CtLibLogHelper, который будет в конструкторе регистрировать сообщение с увеличением вложенности, а в деструкторе собщение с уменьшением вложенности. За пример спасибо. Изучаю. |
| Автор: _Tanatos_ 26.11.2007, 15:06 |
Выложи здесь реализацию данной функции? Заранее благодарю! |
| Автор: Lazin 26.11.2007, 15:21 | ||||
Я тут немного названия изменял, так-что могут быть небольшие косяки. Плюс там используется для записи в лог библиотека - разработка нашей фирмы, и я ее не могу выложить по понятным причинам. |
| Автор: _Tanatos_ 26.11.2007, 15:55 | ||
| Выкладываю предполагаемый вариант использования. Обращаю внимание, что макросы пока не использую, дабы было понят но что есть что. Не обращайте внимание на работу со строками, написал так для сокращения
В конструкторе класса CtLibLogHelper указываем идентификатор Процесса, потока и имя класса и функции - данные указываются один раз для одной функции. Соответственно все записи производятся через вспомогательный класс. Функция Error производит запись в журнал и сама генерирует исключение с указанным кодом (в данном случае 134). |
| Автор: Lazin 26.11.2007, 16:05 | ||
Мне кажется это противоестественным. Пользователь класса вряд-ли будет расчитывать на то что писалка в лог кидает исключения. Не стоит смешивать подсистему обработки ошибок и подсистему записи в лог. Если к примеру ты захочешь убрать запись в лог, это приведет к изменению логики работы программы, а запись логов не должна на логику влиять. |
| Автор: _Tanatos_ 26.11.2007, 16:16 | ||||
Согласен. Принято к изменению. |
| Автор: _Tanatos_ 26.11.2007, 17:44 | ||||||
Попробовал - неработает! после неудачи порылся в инете на предмет использования, вот http://msdn2.microsoft.com/en-us/library/k1atwat8(VS.80).aspx пример не работает Что делаю не так? ИМХО лучше по старинке через флаг внутри класса и дополнительную функцию для его сброса.
|
| Автор: archimed7592 26.11.2007, 18:19 |
Наверное это причуды твоего BCB |
| Автор: Lazin 27.11.2007, 08:40 | ||
Обрати внимание, на то, что std::uncaught_exception( ) срабатывает только если объект был создан внутри блока try, иначе он не вызывается. С Builderoм STL нормально работает... |
| Автор: _Tanatos_ 27.11.2007, 20:15 | ||||||||||
Не знаю как нормально, но у меня не получилось добиться работы. Добавлено через 13 минут и 38 секунд Выкладываю на ваш суд черновой вариант классов Начну с вспомогательных структур:
Далее привожу класс CtLibLogEngine. Пока реализацию кинул в библиотечный файл, в будущем там останутся только inline функции
Смотрим вспомогательный класс CtLibLogHelper
А теперь пример использования:
Тут даже без макросов все довольно компактно получается. Имя функции и файла указываются только один раз. |
| Автор: _Tanatos_ 27.11.2007, 20:32 |
| Из текущих задач: - сделать код безопасным для многопоточных приложений - сделать глобальный объект указателем для поддержки DLL (при инициализации DLL надо соответственно инициировать указатель, а в основной программе соответственно создавать сам объект) - Разработать класс (образец) подписчик для записи журнала в файл - добавить раздельную нумерацию строк для потоков Высказывайте свои пожелания, предложения и критику |
| Автор: _Tanatos_ 27.11.2007, 21:07 | ||||
Примерно так выглядит класс записывающий данные в файл:
Пример использования
|
| Автор: UnrealMan 28.11.2007, 00:59 |
Хреново без макросов получается. Во-первых, необходимость всюду прописывать __LINE__ - это ж... Кроме того, отладочная информация по логике вещей должна выводиться в лог только в отладочной версии программы. И желательно, чтобы в неотладочной версии параметры, представляющие собой отладочную информацию, зря не вычислялись. Указание типа лога через константу - IMHO, не самая лучшая идея. Этот логгер неудобно будет использовать для вывода составных данных - когда строка формируется из подстрок, чисел и прочих данных с нужным форматированием. Придётся самому вычислять строку и передавать её логгеру - это как минимум громоздко. |
| Автор: Lazin 28.11.2007, 09:00 | ||||||
| Можно сделать так что-бы ф-я SubscriberAdd получала не указатель на ф-ю с модификатором __closure, а указатель на класс, а все "подписчики" имели-бы одинаковый интерфейс, это было-бы переносимо на другие компиляторы. Этот класс не учитывает одного простого факта - что возможен запуск нескольких копий одного приложения, в этом случае в файле будет мусор. Нужно синхронизировать доступ к файлу, например создать именованный мьютекс и захватывать его пере записью, и выводить в лог код процесса, что-бы можно было различать вывод разных процессов.
Возможное решение этой проблеммы - использовать полиморфизм, передавать в объект logEngine объекты, содержащие нужную информацию. CtLibLogHelper может выступать в роли фабрики классов. Добавлено через 2 минуты и 29 секунд выглядеть это может так
|
| Автор: _Tanatos_ 28.11.2007, 18:51 | ||||||||
Принято:
В класс входит только одна функция, так как пока не вижу что еще может потребоваться. Можно добавить туда же унифицированные функции настройки фильтра, аналогично классу Engine. Надо? Добавлено через 4 минуты и 24 секунды
Не хочу чересчур усложнять класс, который должен работать максимально быстро. ИМХО это обходится крайне просто, при инициализации лога добавьте к имени файла идентификатор процесса. А если нужна паралельная запись в файл, то надо сделать отдельный класс для этой задачи Таких классов-обработчиков может быть много. Например для накопления сообщений и отсылки их по почте и т.д. В данном случае мой класс является лишь примером. Сначала закончу с ядром, а уж потом займусь переферией. Добавлено через 8 минут и 54 секунды
Совершенно непонятное заявление! Поясни, что тебе не нравится? Не нравятся константы, пишы числа, переменные, выражения, да что угодно - главное чтобы результат был signed char. А лучше всего напиши чем именно не нравится и для какой задачи тебе это не подходит. P.S. Не ленитесь подробней изложить свою мысль, ведь от этого напрямую зависит результат |
| Автор: _Tanatos_ 28.11.2007, 19:15 | ||||
А как ты себе представляешь класс логгера должен форматировать твои данные? Мне кажется Вы на этот класс возлагаете не его задачи. И потом громоздкое и длинное сообщение будет неудобоваримым. А если речь идет о данных, то для них есть отдельное поле (см ниже). Как вспомогательный инструмент можно написать функцию аналогичную sprintf, но возвращающую указатель на готовую строку и вставлять эту конструкцию вместо сообщения: LogHelper.Message(lltInfo, Format(" Этап №%d", Number), "", __LINE__); Годится такой вариант?
Опять же непонятно зачем полиморфизм и причем тут фабрика классов? Приведенный тобой пример вполне подходит для формирования поля Data, если вызов функции AddValue будет приводить к добавлению строчки с именем переменной и ее значением. Если так, то согласен и постараюсь добавить несколько функций для работы с различными типами данных. Только проблема заключается в том, что надо будет так же добавлять функцию Post() в конце для определения момента когда сообщение полностью сформированно и его надо передать в класс Engine. |
| Автор: UnrealMan 28.11.2007, 21:02 | ||||||
Так же, как это делают потоки типа fstream или хотя бы как fprintf.
Это не первостепенная задача логгера, но тут можно провести аналогию с файловыми потоками и простым сишным выводом через fprintf. Для того, чтобы записать текст в файл, можно воспользоваться строковым потоком или sprintf и для результата вызвать write/fwrite. Но вряд ли кто-то захочет так делать, потому что проще не вводить лишние звенья, а сразу воспользоваться оператором << файлового потока или fprintf.
Это неудобный вариант. |
| Автор: _Tanatos_ 29.11.2007, 12:42 | ||
| В вашем примере указано только одно поле, а их как минимум два Сообщение и данные. Такой вариант приемлим?
P.S. To All: Спасибо всем, кто принимает участие в обсуждении! Стараюсь учесть пожелания и предложения каждого Сорри что изменения не так быстро воплощаются в код. P.S.S. Повторюсь, макросы пока не пишу так как они не сказываются на функциональности. Понятное дело, что с ними короче и удобней, понятно что они будут и это даже не обсуждается. Будут сделаны когда утоится состав и формат всех функций, так что не сердчайте на их отсутствие. Но! Если здесь писать все в виде макросов, то совершенно непонятно получается что должно быть внутри. |
| Автор: _Tanatos_ 29.11.2007, 13:48 | ||
| Не хочется изобретать велосипед и раз уж речь зашла о форматировании, то хотелось бы использовать sprintf. Только как передать переменное кол-во аргументов дальше?
Уважаемые гуру, нужен Ваш совет! Писать множество отдельных функций для различных типов данных ИМХО нелогично (что делать если надо сразу два аргумента передать при форматировании одной строки?). |
| Автор: UnrealMan 29.11.2007, 13:48 |
А какой резон отделять данные от сообщения? |
| Автор: UnrealMan 29.11.2007, 14:04 | ||
Добавлено через 3 минуты и 32 секунды Есть одно маленькое "но": теперь тебе придётся помнить о возможности переполнения буфера, используемого для sprintf. |
| Автор: _Tanatos_ 29.11.2007, 14:17 | ||||||||
| Спасибо! Сообщение от данных отделяю, для удобства. Данных может быть много, но при беглом просмотре лога они лишь отвлекают глаз. В сообщении содержится короткое сообщение по сути. В данных абсолютно все что угодно, что может быть полезным для отладки или даже можно написать подсказку для юзера Вот пример использования:
У меня получилось немного иначе, так как при многократном вызове нужно сохранять предыдущую часть. Ну и требования по длинне сообщения смегчаются Так выглядит содержимое функции:
И парочку сервисных функций:
Естественно добавился вызов ExReset() в деструктор Еще добавилось два конструктора:
для инициализации класса без создания сообщения в конструкторе. |
| Автор: Lazin 29.11.2007, 15:48 | ||||||
| Я не очень понимаю что нужно получить в итоге, наверное лучше сначала составить какой нибудь список требований к библиотеке т.е. что нам от нее надо.... использование элипсисов я считаю тупиковым, так как в данном случае теряется гибкость и расширяемость, если я сделаю свой класс, как мне расширить класс, что-бы он выводил в лог объекты моего класса. Для этого придется наследовать, от класса - логгера, и переопределять ф-ю. Лучше сделать это так, как это сделано в библиотеках iostream или boost::serializaion etc. должно выглядеть примерно так:
Можно использовать интерфейс как в iostream для изменения параметров (там это называется манипуляторами)
с таким интерфейсом проще написать макросы
и так можно написать для любого кол. параметров |
| Автор: _Tanatos_ 29.11.2007, 15:55 | ||||||||
Переработал класс CtLibLogHelper
Примеры использования:
В результате получаем следующий лог:
Если последний вызов LogHelper.Post(lltInfo, __LINE__, true); заменить на обычный LogHelper.Post(lltInfo, __LINE__); получим: [/code] В результате получаем следующий лог:
Последняя строка сформируется автоматически. При использовании макросов можно будет формировать как однострочные записи в журнал, так и многострочные с форматированием. Единственное неудобство будет состоять в том, что надо будет отслеживать использование различных макросов для первой строки (где нужно объявление) и для остальных. Пока не знаю как это обойти. Есть идеи? |
| Автор: _Tanatos_ 29.11.2007, 16:12 | ||||
Эта ветка для этого и создана Конечно в идеале сначала составить что-то вроде ТЗ, а потом уже писать. Но! я давненько не программировал, так что пользуясь случаем восстанавливаю навыки.
С таким аргументом не поспоришь Да и возможность записать все в одну строку прильщает. Непонятно только как добавлять вспомогательную информацию и как разделять сообщение и данные? Склоняюсь к использованию дополнительного класса 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 | ||||
А как такой вариант?
Опробовал вполне работоспособный вариант. С макросом вообще сказка получается:
Остается правда вопрос! Как с помощью макроса при финальной компиляции исключить из кода запись в журнал? |