Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.139.90.131] |
|
Сообщ.
#1
,
|
|
|
Всем привет!
Вот как сделать что то подобное? И можно ли такое сделать? Суть в чем, мне нужно прикрутить логирование. Но оно может писаться например в лог файл, а может в журнал событий винды. Я хочу сделать два класса, один пишет в файл, второй пишет в журнал событий. И завести общий интерфейc ILogger, через который можно было бы логировать либо в файл, либо в журнал событий. Для удобства логирования я перегрузил operator << в своем классе, который пишет в лог, и примерно так же, в идеале я хотел бы написать в своем интерфейсе. Но так как у меня шаблонный оператор вывода в поток, сделать его виртуальным не получится. Тогда я решил пойти другим путем, в интерфейсе сделать виртуальным метод virtual void PrintMessage(const std::string& format, ...) = 0; - обычный метод с переменным числом параметров. но не могу понять как его реализовать(и можно ли) так, чтобы передать потом все это дело во внутрений вариативный шаблонный метод? class ILogger { public: virtual void PrintMessage(const std::string& format, ...) = 0; }; class ConcreteTxt : public ILogger { public: void PrintMessage(const std::string& format, ...) override { internal_print(format.c_str(), "", ???); //! <<<<<<< ???? } template<typename T, typename ... Args> void internal_print(const char* format, T value, Args ... args) { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; internal_print(format + 1, args ...); return; } std::cout << *format; } } }; Спасибо. |
Сообщ.
#2
,
|
|
|
Лучше сделай наоборот, чтобы internal_print вызывал виртуальную PrintMessage(const std::string& msg).
А ещё лучше сделай класс Message, который будет отвечать за форматирование сообщении, и Logger с виртуальным методом, который будет сохранять сообщение Т.е. раздели задачи форматирования и сохранения сообщений. Что-то типа class ILogger { public: void Flush(const std::stringstream &msg) noexcept = 0; }; class CMessage { public: CMessage(ILogger &); ~CMessage() { m_logger.Flush(m_msg); } operator <<(TT&& val) { m_msg << val; } protected: ILogger &m_logger; std::stringstream m_msg; }; ....... class CFileLogger: public ILogger...... class CSystemLogger: public ILogger...... CFileLogger log; CMessage(log) << "!!!!!"; |
Сообщ.
#3
,
|
|
|
Но тогда я начинаю зависеть от двух классов. Мне интерфейс логгирования нужно будет передавать в конструктор другого класса, который ничего не знает о том, какие там логеры вообще есть, он просто у интерфейса будет дергать метод логирования и все.
|
Сообщ.
#4
,
|
|
|
Ты в любом случае будешь зависеть от двух классов, как ни крути. В данном случае у тебя реализация форматирования не будет зависеть от реализации сохранения. Классу Message не нужно знать как будет сохранено то в нем хранится.
Как вариант можно сделать так class CLogger { public: template <typename... TT> void Print(TT&&... args) { std::stringstream out; out << args...... Flush(out); } protected: virtual void Flush(const std::stringstream &) = 0; }; class CFileLogger: public CLogger...... class CSystemLogger: public CLogger...... CFileLogger log1; CSystemLogger log2; log1.Print(1, 2, 3); log2.Print(1, 2, 3); Но это менее гибкое решение |
Сообщ.
#5
,
|
|
|
Ладно, я кажется понял. Там с первым вариантом что ты предложил можно будет поизвращаться.
В принципе такой вариант будет приемлемым. Спасибо за примеры. |
Сообщ.
#6
,
|
|
|
Цитата Олег М @ Мне интерфейс логгирования нужно будет передавать в конструктор другого класса, который ничего не знает о том, какие там логеры вообще есть, он просто у интерфейса будет дергать метод логирования и все. Кстати в метод Flush лучше передавать не stringstream а Message Flush(const CMessage &). Тогда ты сможешь сохранять не только текст, но и параметры сообщения - тип, поток, время и т.д. |
Сообщ.
#7
,
|
|
|
Цитата Олег М @ Что-то типа Сама запись в лог может быть ошибочна, а в таком варианте об этом никак не узнаешь. CMessage не обязателен. "ILogger &m_logger;" может находиться в составе любых объектов я бы как то так сделал: class ILogger { public: bool write (const TCHAR* pFmt,...) = 0; }; class CSomeClass { public: CSomeClass(ILogger &); ~CSomeClass() { } bool SomeRoutine (const TCHAR* pFmt,...) { ... ... if(надо_в лог) { m_logger.write (_T("xxmm=%d\r\n"),xxmm); } ... ... } protected: ILogger &m_logger; }; ....... class CFileLogger: public ILogger...... class CSystemLogger: public ILogger...... CFileLogger log; CSomeClass obj(log); Добавлено Цитата Олег М @ Тогда ты сможешь сохранять не только текст, но и параметры сообщения - тип, поток, время и т.д. А это может быть определено в классах: class CFileLogger: public ILogger...... class CSystemLogger: public ILogger...... |
Сообщ.
#8
,
|
|
|
Цитата ЫукпШ @ Сама запись в лог может быть ошибочна, а в таком варианте об этом никак не узнаешь. Может. И что ты в этом случае делаешь? Лично я ничего, просто пропускаю, все методы у CMessage помечены как noexcept - запись в лог не должна влиять на вызывающий поток. Добавлено Цитата ЫукпШ @ class ILogger { public: bool write (const TCHAR* pFmt,...) = 0; }; Во-первых, лучше не использовать ... без крайней на то необходимости. А в современном с++ такая необходимость не возникает никогда. Во-вторых, повторяюсь - при логировании есть две задачи - форматирование и сохранение, не нужно их мешать в кучу |
Сообщ.
#9
,
|
|
|
Цитата Олег М @ Может. И что ты в этом случае делаешь? Так это зависит от логгера. Если логируем в сеть, так можно об этом и в лог-файл сообщить. А так лучше, чем ничего: if(надо_в лог) { if(!m_logger.write (_T("xxmm=%d\r\n"),xxmm)) { ::OutputDebugString(...); } } поскольку с наибольшей вероятностью такие ошибки могут возникнуть во время самой первоначальной отладки приложений. Добавлено Цитата Олег М @ Во-вторых, повторяюсь - при логировании есть две задачи - форматирование и сохранение, не нужно их мешать в кучу Окончательное форматирование делает объект-логгер. Поскольку интересующая информация зависит от способа логгирования. Напрмер, при логе в файл IP адрес не интересует. Форматирование к моему примеру имеет отдалённое отношение. Это всего лишь удобство произвольного вывода для произвольной точки любого приложения. В самом общем виде. |
Сообщ.
#10
,
|
|
|
Цитата ЫукпШ @ А так лучше, чем ничего: if(надо_в лог) { if(!m_logger.write (_T("xxmm=%d\r\n"),xxmm)) { ::OutputDebugString(...); } } Вообще, хуже. Лучше, чем ничего - это если этот код будет находиться внутри m_logger.write. Цитата ЫукпШ @ Окончательное форматирование делает объект-логгер. Нет, не делает. Логгер добавляет какую-то сопутствующую информацию в нужном формате, необходимую для сохранения сообщения. Собственно строку сообщения он не трогает. Например для сохранения в системный лог он, логгер, заполняет поля соответствующей структуры. Можно, конечно, назвать это "окончательным форматированием", но только с очень большой натяжкой. |
Сообщ.
#11
,
|
|
|
Цитата Олег М @ Цитата ЫукпШ @ А так лучше, чем ничего: if(надо_в лог) { if(!m_logger.write (_T("xxmm=%d\r\n"),xxmm)) { ::OutputDebugString(...); } } Вообще, хуже. Лучше, чем ничего - это если этот код будет находиться внутри m_logger.write. Цитата ЫукпШ @ Окончательное форматирование делает объект-логгер. Нет, не делает. Логгер добавляет какую-то сопутствующую информацию в нужном формате, необходимую для сохранения сообщения. Собственно строку сообщения он не трогает. Например для сохранения в системный лог он, логгер, заполняет поля соответствующей структуры. Можно, конечно, назвать это "окончательным форматированием", но только с очень большой натяжкой. Конечно нет. Это просто пример возможного. ..я вставлю это в "m_logger.write", но "OutputDebugString" не во всех системах и не во всех приложениях работает. В win7 в системных службах это работать не будет, а в XP - будет. В Linux этого вообще нет. Приведённый пример - не универсальный вариант. --- Возвращаемый результат работы функции - это всегда лучше. По определению. При любой задаче и любом контексте происходящего. Если не возвращается результат, это предмет для разбирательства. |
Сообщ.
#12
,
|
|
|
Во всей вашей шляпе хотелось бы видеть нормальное, прозрачное (а лучше, замаскированное) использование отладочных "величин" __FILЕ__ и __LINE__. Но, пока это не сильно представляется возможным.
|
Сообщ.
#13
,
|
|
|
Цитата JoeUser @ Во всей вашей шляпе хотелось бы видеть нормальное, прозрачное (а лучше, замаскированное) использование отладочных "величин" __FILЕ__ и __LINE__. Но, пока это не сильно представляется возможным. А зачем они вообще нужны? Во первых с ними нужно возиться, потому как это макросы. И в большинстве случаев информация не будет соответствовать действительности. Типа вылетел по исключению в какой нибудь верхнеуровневый catch, и уже там записал в лог файл неверную инфу. Но если тебе очень хочется, то почему прям в лог не написать __FILE__ и __LINE__ ? |
Сообщ.
#14
,
|
|
|
Цитата Wound @ А зачем они вообще нужны? Только в отладочных целях. Знать, что во время выполнения произошло что-то, и что оно в таком-то месте, такого-то исходника. Цитата Wound @ Но если тебе очень хочется, то почему прям в лог не написать __FILE__ и __LINE__ ? Да, пока я вижу это единственный вариант. Поэтому и спросил. |
Сообщ.
#15
,
|
|
|
Цитата JoeUser @ Только в отладочных целях. Знать, что во время выполнения произошло что-то, и что оно в таком-то месте, такого-то исходника. Это не по феншую нынче. Ничего ты там не узнаешь, в 100% случаев тебе напишет название файла и строки где произошла запись этих макросов в лог, а не то где именно произошла ошибка. По феншнуюю нужно писать стек выполнения или дамп делать. Вот это по феншую будет. Цитата JoeUser @ Да, пока я вижу это единственный вариант. Поэтому и спросил. Ну я лично пока не вижу необходимости для себя в этих макросах, поэтому и не заморачивался особо. Ну и по сути других вариантов нет. Это же макросы, они раскроются сразу же после препроцессора, перед компиляцией. А это значит я и так могу знать где у меня произошла ошибка. Достаточно поискать по тексту ошибки |
Сообщ.
#16
,
|
|
|
Цитата Wound @ По феншнуюю нужно писать стек выполнения А как ты такое делаешь? |
Сообщ.
#17
,
|
|
|
Цитата JoeUser @ А как ты такое делаешь? Ну можно системными функциями, что то типа этой например: https://docs.microsoft.com/ru-ru/windows/de...nidumpwritedump Ну это если грохнулось с исключением например. Еще можно обрабатывать SEH исключения и получать стек выполнения, всякие функции для работы с этим есть, гуглить нужно. Правда там ньюансы есть небольшие, а именно когда ты вылетаешь из какого нибудь класса, и попатадешь в SEH функцию, то там уже стека нема. Добавлено Ну просто например в C# мне очень нравится система исключений. Вылетело что то - тебе сразу выскакивает окно с ошибкой и можно сразу глянуть стек. И примерно уже узнать где грохнулось. Ну и в лог все это пишется. И нет проблем. А макросы эти __FILE__, __LINE__ в большинстве случаев не дают ровным счетом ничего. Я и так могу скопировать текст из лога и поискать по исходникам. Добавлено Вот тут сходу кстати нашлось как делать трейс стека, если нужно: https://stackoverflow.com/questions/6205981...m-a-running-app |
Сообщ.
#18
,
|
|
|
Wound, пасип. Но мне бы че-нить кроссплатформенное.
|
Сообщ.
#19
,
|
|
|
На юниксах стек и так в coredump файл сохраняется при креше. На сколько я понимаю это где то в настройках можно включить. Поэтому на *nix нет проблемы со стеком в принципе. По крайней мере у меня никогда не было. Берешь core файл, натравливаешь на него дебагер и вперед.
Добавлено https://wiki.archlinux.org/index.php/Core_dump https://wiki.dieg.info/poluchenie_core_dump_v_linux |
Сообщ.
#20
,
|
|
|
Цитата Wound @ На юниксах стек и так в coredump Я это знаю. Вопрос в другом. Не помню точно прогу, но помню ее реакцию на ошибку - сообщение со стеком вызовов, и функция отправки дебаг репорта разработчику. Это такое кроссплатформенное - было бы просто зе бест! |
Сообщ.
#21
,
|
|
|
Цитата JoeUser @ Я это знаю. Вопрос в другом. Не помню точно прогу, но помню ее реакцию на ошибку - сообщение со стеком вызовов, и функция отправки дебаг репорта разработчику. Это такое кроссплатформенное - было бы просто зе бест! Кросплатформенное - это обертка над некросплатформенным. Думаю в придачу в *nix не особо сложно получить стек выполнения. Можно и самому сделать, ну если интересно конечно. |