Russian Qt Forum
Ноябрь 10, 2024, 22:47 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 [2]   Вниз
  Печать  
Автор Тема: Log4Qt - система ведения журналов сообщений за несколько минут  (Прочитано 26924 раз)
asvil
Гость
« Ответ #15 : Апрель 22, 2010, 12:39 »

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

Для того чтобы передать сообщение "по сети" необходимо выполнить следующие действия:
  • Создать приложение-сервер принимающее сообщения
  • В главном приложении создать Log4Qt::WriterAppender, выводящий сообщения в QTcpSocket

Приложение сервер запускается, бесконечно долго ждет подключения, после установки подключения выводит все данные пришедшие по подключению в qDebug(), после разрыва соединения завершает работу. Исходный код состоит из одного файла main.cpp:
Код:

#include <QtCore>

#include <QtNetwork>

int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);

QTcpServer *server = new QTcpServer;
// слушаем на всех интерфейсах порт, 10600 - log00
server->listen(QHostAddress::Any, 10600);

QTcpSocket *connection;
// ждем подключения
if (server->waitForNewConnection(-1)) {
// получаем соект
connection = server->nextPendingConnection();

// пока не поизойдет разрыва соединения
while(true) {
// ждем данных одну секунду
if (connection->waitForReadyRead(1000))
// направляем все пришедшие данные в qDebug()
qDebug() << connection->readAll();
// Если произошел разрыв, выходим из цикла
if (connection->state() == QAbstractSocket::UnconnectedState)
break;
}
}

// вычурно, но удобно
QTimer::singleShot(1000, &app, SLOT(quit()));
return app.exec();
}

Исходный код главного приложения, подвергаемого журналированию, также состоит из одного файла main.cpp:
Код:

#include <QtCore>

#include <QtNetwork>

#include <Log4Qt/logmanager.h>

#include <Log4Qt/simpletimelayout.h>

#include <Log4Qt/consoleappender.h>

int main(int argc, char** argv)
{
using namespace Log4Qt;
QCoreApplication app(argc, argv);

QTranslator trans;
trans.load("log4qt_" + QLocale::system().name()
, QLibraryInfo::location(QLibraryInfo::TranslationsPath));

qDebug() << "transl empty" << trans.isEmpty();

app.installTranslator(&trans);

LogManager::rootLogger();

LogManager::setHandleQtMessages(true);

// создаем объект форматирующий сообщения
SimpleTimeLayout *timeLayout = new SimpleTimeLayout();
timeLayout->setName("TimeLayout");
timeLayout->activateOptions();

// выводим в консоль
ConsoleAppender *console = new ConsoleAppender(timeLayout, ConsoleAppender::STDOUT_TARGET);
console->setName("Console");
console->activateOptions();


LogManager::rootLogger()->addAppender(console);

// сокет для подключения
QTcpSocket *socket = new QTcpSocket();

// подключаемся
socket->connectToHost("localhost", 10600);

// Ждем 5 секунд
if (socket->waitForConnected(50000)) {
qDebug() << "Connected";

// Создаем текстовый поток, но основе сокета
QTextStream *textStream = new QTextStream(socket);

// Создаем объект выводящий сообщения на основе текстового потока
WriterAppender *socketAppender = new WriterAppender(timeLayout, textStream);
socketAppender->setName("SocketAppender");
// обязательно активируем
socketAppender->activateOptions();

// Добавляем вывод к журналу
LogManager::rootLogger()->addAppender(socketAppender);
} else
qDebug() << "Not connected";

LogManager::rootLogger();

qDebug() << "From qDebug() ";

LogManager::startup();

LogManager::logger("Logger")->info("First message");

LogManager::logger("Logger")->warn("Warn message");


// ждем отправки данных 5 секунд
socket->waitForBytesWritten(5000);

QTimer::singleShot(1000, &app, SLOT(quit()));

app.exec();
}

Создание выводящего объекта сводилось к следующим строкам:
Код:
QTcpSocket *socket = new QTcpSocket();
socket->connectToHost("localhost", 10600);
if (socket->waitForConnected(50000)) {
  QTextStream *textStream = new QTextStream(socket);
  WriterAppender *socketAppender = new WriterAppender(timeLayout, textStream);
  socketAppender->setName("SocketAppender");
  socketAppender->activateOptions();
  LogManager::rootLogger()->addAppender(socketAppender);
}
Записан
asvil
Гость
« Ответ #16 : Апрель 22, 2010, 13:57 »

В win* среде SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 1) не влияет на stdout.
В colorconsoleappender.cpp необходимо изменить (выделено жирным):
Код:
#if defined(__WIN32__) || defined(WIN) || defined(WIN32) || defined(Q_OS_WIN32)
.....
writer()->operator <<(messsage);
Код:
.....
#endif // #if defined(__WIN32__) || defined(WIN) || defined(WIN32) || defined(Q_OS_WIN32)
на следующие строки:
Код:
// print message to output console
//writer()->operator <<(message);
// MINI HACK in win stdout not friend with setConsoleTextAttribute
DWORD res;
WriteConsole(hConsole, message.toLatin1().data(), message.toLatin1().size(), &res, 0);
Совпадение констант цветов между win* и *nix лежит на совести французов.
Записан
SABROG
Гость
« Ответ #17 : Апрель 22, 2010, 15:00 »

Во, теперь работает. Правда тускловато Улыбающийся



Заметил, что строка "красится" целиком. Возможно ли частичное "раскрашивание", как на некоторых строках CMake? Было бы здорово добавить некий аналог HTML тегов для цвета (правда для вывода в файл эти теги не нужны).

Закомментировав эту строчку:

Код
C++ (Qt)
writer()->operator <<(messsage);
 

Мы сводим на нет возможность перегрузки оператора <<?
« Последнее редактирование: Апрель 22, 2010, 15:14 от SABROG » Записан
asvil
Гость
« Ответ #18 : Апрель 23, 2010, 21:32 »

Во, теперь работает. Правда тускловато Улыбающийся
Небольшой "обходной путь": в win среде устновка 4 бита цвета увеличивает яркость.
Например зеленый цвет с большей яркостью: (ColorConsoleAppender::ForegroundLightGreen | 8  )
Но в CMake не все цвета осветленные. Я еще не успел полностью сравнить палитру.
Возможно ли частичное "раскрашивание", как на некоторых строках CMake
Используя класс ColorConsoleAppender невозможно. Я еще не придумал простую и удобною реализацию интерфеса класса для  частичного раскрашивания. Реализация системы тегов порождает, как уже сказано, проблему вывода сообщений в других направлениях.
Исходя из архитектуры проекта форматированием сообщений должен заниматься класс типа Log4Qt::*Layout, а выводом Log4Qt::*Appender. Для *nix такая логика подходит, так как Layout может вставить в некоторых местах QString "\e[ddm", а Appender впоследствии выведет это в консоль. В win* системах цвет выводимого сообщения необходимо менять вызовом функции и это создает небольшие трудности.

Но лучше я поставлю вопрос по другому. Подойдет ли такой интерфейс?
  • Создаем объект форматирующий сообщения.
  • Назначаем ему шаблон вывода. Шаблон вывода в свою очередь состоит из следующих возможных placeholders.
      Параметры тегов в квадратных скобках необязательны.
    • %t[time_format_str] - время, time_format_str шаблон вывода времени, применяемый QDateTime::toString()
    • %l - уровень сообщения (Debug, Warn, Info и т.д.)
    • %f - поток, в котором было сгенерировано сообщение
    • %s - название журнала
    • %m - текст сообщение
    • %e[FG_COLOR[,BG_COLOR]][/rexp] - форматирование следующих символов цветом. Форматирование отменяется следующим тегом %e. Если тег %e вставлен без параметров устанавливается цвет по умолчанию. Если установлена строка rexp, то фоматирование строки цветом будет только в случае совпадения с регулярным выражением.
  • Создаем объект осущесвтляющий цветной вывод в консоль.
  • Назначаем выводящему объекту форматирующий объект
  • Устанавливаем выводящий объект журналу

Мы сводим на нет возможность перегрузки оператора <<?
Нет, не сводим. Функция writer возвращает указатель на QTextStream и поэтому я использую явный вызов функции-оператора. Функциональность оператора в сочетании с qDebug() не нарушается.[/list]
« Последнее редактирование: Апрель 23, 2010, 21:39 от asvil » Записан
SABROG
Гость
« Ответ #19 : Апрель 23, 2010, 22:36 »

Подойдет ли такой интерфейс?

Думаю это сильно бы упростило работу с логами и укоротило код. Было бы полезным заготовить стандартные распространенные шаблоны, которые бы можно было использовать из "коробки".
Записан
asvil
Гость
« Ответ #20 : Апрель 25, 2010, 11:07 »

Исходные коды log4qt были обновлены в репозитории. Все заинтересовавшиеся скачайте обновленную версию версию в виде тарбола master-ветки или выполнив git pull в папке клонированного репозитория.
Note:Интерфейс класса Log4Qt::ColorConsoleAppender был полностью изменен.
Кроссплатформенный цветной вывод в консоль.
Для того изменить цвет выводимой строки в *nix системах применяются esc-последовательности. Для win* я сделал небольшой парсер, меняющий цвета вывода в зависимости от встретившейся последовательности.
Для того чтобы данной возможностью воспользоваться в log4qt необходимо:
  • Создать Log4Qt::PatternLayout.
  • Задать шаблон форматирования сообщения.
  • Создать Log4Qt::ColorConsoleAppender
  • Назначить ему созданный PatternLayout
  • "Активировать" ColorConsoleAppender
  • Назначить какому-либо журналу созданный ColorConsoleAppender

Самое интересное здесь это шаблон форматирования. Итак, он может состоять из следующих тэгов обозначенных симоволом %, параметры тегов заключаются в фигурные скобки:
  • m - текст сообщения
  • p - название уровня сообщения (WARN, DEBUG, INFO и т.д.)
  • е - имя потока, в котором было сгенерировано сообщение.
  • x - ndc name (не знаю)
  • X - mdc name (не знаю)
  • c{digit} - имя журнала сообщений, параметр digit должен быть представлен целым числов и является необязательным. Данный параметр указывает количество выводимых секций названия журнала начиная с конца названия. Секции в названии журнала разделяются знаком :: (два двоеточия).
  • d{format} - дата/время, параметр format должен быть представлен строкой, используемой для форматирования времени методом QDateTime::toString(). Если данный параметр опущен, используется ISO8601.
  • r - выводится время отсчитываемое с момента запуска программы.
  • n - перевод строки (*nix - \n, win* - \r\n)
ESC-последовательность в C-строках начинается с символов:\e[ или \033[ и завершается симоволом m. Между данными символами перечисляются параметры, представленные цылыми числами, разделенными знаком ; (точка с запятой). Данные параметры назначают форматирование следующим символам строки, до тех пор пока следующая ESC-последовательность не изменит данное форматирование.
Возможные параметры ESC-последовательности:
  • 0 - отменяет все форматирование
  • 1 - увеличивает яркость цвета. А для *nix также делает шрифт жирным.
  • 30-37 - назначает цвет шрифта
  • 40-47 - назначает цвет фона
Палитра для *nix систем:

Палитра для win* систем:


Пример использования всего вышеперечисленного:
Код:
#include <QtCore/QCoreApplication>

#include <QtCore/QTimer>

#include <Log4Qt/patternlayout.h>
#include <Log4Qt/colorconsoleappender.h>
#include <Log4Qt/logmanager.h>

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

using namespace Log4Qt;

PatternLayout *layout = new PatternLayout("\e[1;32m%d{dd.MM.yyyy hh:mm} \e[1;33m %p \e[44m%c [%t] - \e[47;1;31m%m\e[0m%n");

ColorConsoleAppender *appender = new ColorConsoleAppender(layout, ColorConsoleAppender::STDOUT_TARGET);
appender->activateOptions();

LogManager::logger("Logger")->addAppender(appender);


LogManager::logger("Logger")->info("Colored info message");

QTimer::singleShot(1000, &a, SLOT(quit()));
return a.exec();
}
Строка форматирования сообщения, используемая в программе:
"\e[1;32m%d{dd.MM.yyyy hh:mm} \e[1;33m %p \e[44m%c [%t] - \e[47;1;31m%m\e[0m%n"
Проект и скриншот работы программы прикреплен к сообщению.
Использованные источники:
http://www.frexx.de/xterm-256-notes/
http://msdn.microsoft.com/en-us/library/ms682088(v=VS.85).aspx#_win32_character_attributes

Записан
SABROG
Гость
« Ответ #21 : Апрель 25, 2010, 18:50 »

PatternLayout добавляется ко всему журналу. Можно ли как-то задать разные шаблоны для разных уровней сообщений одного журнала?

Код:
LogManager::logger("Logger")->info("Colored info message"); // info - белый цвет например (шаблон 1)
LogManager::logger("Logger")->warn("Colored warning message"); // warn - желтый цвет (шаблон 2)

и т.п. для всех этих методов разные цвета (fatal красный к примеру):

Код:
debug()
error()
fatal()
info()
log()
trace()
warn()

Сделать что-то типа частичного наследования шаблона. Например не хотелось бы дублировать целиком шаблон, чтобы подкрасить только текст (%m) другим цветом или какую-либо другую часть при другом уровне сообщения:

Код:
"\e[1;32m%d{dd.MM.yyyy hh:mm} \e[1;33m %p \e[44m%c [%t] - \e[47;1;31m%m\e[0m%n"

То есть как это сделано в Style Sheet's Qt:

Код
CSS
ClassName {
color: red;
border: 1px;
}
 
ClassName::sub-control {
color: green;
background: black;
}
 

Соответственно срабатывают сразу 2 правила - общее и частное.

Код
CSS
Logger {
color: white;
background: black;
}
 
Logger::level::info {
color: white;
}
 
Logger::level::warn {
color: yellow;
}
 
« Последнее редактирование: Апрель 25, 2010, 19:04 от SABROG » Записан
asvil
Гость
« Ответ #22 : Апрель 25, 2010, 21:57 »

Пока можно задать разные шаблоны для разных уровней только создав n-ое количество PatternLayout, ColorConsoleAppender, LevelMatchFilter.
На данный момент для того, чтобы выполнить форматирование в зависимости от полей сообщения, я могу предложить только наследование класса Log4Qt::Layout с последующим переопределением метода QString format(const LoggingEvent& rEvent).
Пример класса (colorlayout.h):
Код:
#include <QtCore/QHash>
#include <Log4Qt/layout.h>

namespace Log4Qt
{
class ColorLayout : public Layout
{
Q_OBJECT
public:
ColorLayout(QObject* parent = 0);

virtual QString format(const LoggingEvent &rEvent);

protected:
virtual QDebug debug(QDebug &rDebug) const;
};

}
Пример класса (colorlayout.cpp):
Код:
#include "colorlayout.h"
#include <QtCore/QDebug>

#include <Log4Qt/loggingevent.h>
#include <Log4Qt/loggingevent.h>
#include <Log4Qt/helpers/datetime.h>

namespace Log4Qt
{
ColorLayout::ColorLayout(QObject *parent)
:Layout(parent) {}

QString ColorLayout::format(const LoggingEvent &rEvent)
{
QString message;
message += DateTime::fromMilliSeconds(rEvent.timeStamp()).toString("dd.MM.yyyy hh:mm");
message += " ";
if (rEvent.level().toInt() == Level::FATAL_INT)
message += "\e[1;31m";
message += rEvent.level().toString();
message += " ";
message += rEvent.loggerName();
message += " [";
message += rEvent.threadName();
message += "] - ";
message += rEvent.message();
message += "\e[0m";
message += endOfLine();
return message;
}

QDebug ColorLayout::debug(QDebug &rDebug) const
{
rDebug.nospace() << "ColorLayout("
<< "name:" << objectName()
<< ")";
return rDebug.space();
}
}
Назначать объект данного класса необходимо объекту ColorConsoleAppender.
Данный способ является наиболее универсальным и быстрым, так как алгоритм форматирования может быть сколь угодно сложным и создание класса не занимает много времени.
Единственным минусом является невозможность динамического преобразования формата вывода. Но это проблема может быть решена с помощью QtScriptBindingsGenerator.
Записан
Страниц: 1 [2]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.095 секунд. Запросов: 21.