on screen logging
-
I'm implementing a log that is displayed on my main window using a QPlainTextEdit.
void Logger::appendMessage(const QString& text) { this->appendPlainText(text); // Adds the message to the widget this->verticalScrollBar()->setValue(this->verticalScrollBar()->maximum()); // Scrolls to the bottom QCoreApplication::processEvents(); }
The way to use it now is by passing the instance of the log to all my classes that have to use it:
ChildClass::ChildClass(QWidget *parent, Logger* _log) { log = _log; } void ChildClass::someFunction() { log->appendMessage(" a message "); }
This is ok but I would like to know if there is a better way to do it. Ideally I would like to call something like:
myLog() << "a message";
Without having to pass the instance of the log to all of my child classes.
Is there any good way in Qt to implement an on screen logging? I guess that it is a very common thing to have... Am I reinventing the wheel?
-
Maybe use the qt internal logging system?
QCoreApplication::processEvents();
please remove this - it's uneeded and potential dangerous.
-
@wasawi2
Consider instead emitting a signal from anywhere wanting to log, with the message as a parameter. You need aQObject
instance to emit signals, probably best done in a singleton class. Connect that (once) to your logging window widget. I think this is preferable to having each emitter accessing the logger directly.The only thing is that this relies on signal processing, and hence the event loop running. May not be great if there are problems there. Then again, if the event loop is messed you wouldn't see the text edit get updated anyway.
There are also QMessageLogger, QLoggingCategory and the global qInstallMessageHandler.
I think it is nicer if you set up to log the message no matter what. Have something to test whether your log window/widget exists and is open, and send the message there additionally if so. This provides flexibility,
-
Thank you @Christian-Ehrlicher and @JonB for your replies.
I have removed
@Christian-Ehrlicher said in on screen logging:
QCoreApplication::processEvents();
And I am now using Signals and Slots instead of passing the instance of my logger to each and every class that uses it. I haven't done the Singleton yet.
What I don't really grasp is how to use the Qt internal logging system or the QMessageLogger while printing to screen at the same time... I guess it can only be done separately, is that correct?
Thanks again!
w -
@wasawi2 said in on screen logging:
I guess it can only be done separately, is that correct?
No, why?
Install a qt message handler and do whatever you want. -
We are using qInstallMessageHandler in our software. It works perfectly well. It's really the best option so you don't have to fully reinvent the wheel for logging. Just concern yourself with how to show the output of the log.
-
Thanks @Christian-Ehrlicher and @SimonSchroeder.
I just got it working. I used code from:
https://stackoverflow.com/questions/4954140/how-to-redirect-qdebug-qwarning-qcritical-etc-output
and
https://stackoverflow.com/questions/22485208/redirect-qdebug-to-qtextedit
It seems that the best way to go is using static variables.here is a description of what i have:
in my main.cpp
#include "mainwindow.h" #include <QApplication> #include <QtDebug> #include <QFile> #include <QTextStream> #include <QDateTime> static QTextStream output_ts; static QFile outFile(QString("%1.log").arg(QDateTime::currentDateTime().toString("ddMMyyyy-hhmmss"))); void myMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) { //https://stackoverflow.com/questions/4954140/how-to-redirect-qdebug-qwarning-qcritical-etc-output QString txt; switch (type) { case QtDebugMsg: txt = QString("Debug: %1").arg(msg); if (MainWindow::s_textEdit != 0) MainWindow::s_textEdit->appendPlainText(msg); break; case QtWarningMsg: txt = QString("Warning: %1").arg(msg); break; case QtCriticalMsg: txt = QString("Critical: %1").arg(msg); break; case QtFatalMsg: txt = QString("Fatal: %1").arg(msg); abort(); } txt.append(" (" + QString::fromUtf8(context.file) + ")"); txt.append(" line: " + QString::number(context.line)); outFile.open(QIODevice::WriteOnly | QIODevice::Append); QTextStream ts(&outFile); ts << txt << Qt::endl; } int main(int argc, char *argv[]) { qInstallMessageHandler(myMessageHandler); QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
and in my mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" QPlainTextEdit* MainWindow::s_textEdit = 0; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindowUI) { ui->setupUi(this); //Logging signals to screen //https://stackoverflow.com/questions/22485208/redirect-qdebug-to-qtextedit s_textEdit = new QPlainTextEdit("Starting .. "); QMenu* menu; menu = this->menuBar()->addMenu("About"); menu->setObjectName(menu->title()); QDockWidget* dock; dock = new QDockWidget("Console", this); dock->setObjectName(dock->windowTitle()); dock->setWidget(s_textEdit); s_textEdit->setReadOnly(true); s_textEdit->setMaximumBlockCount(1000); this->addDockWidget(Qt::RightDockWidgetArea, dock); this->findChild<QMenu*>("About")->addAction(dock->toggleViewAction());
This works well for me so far. I guess it can be improved but so far its good.
Thank you everyone for your replies!
-
-
@wasawi2 said in on screen logging:
It seems that the best way to go is using static variables.
I'm slightly surprised your code with global
static QFile outFile
works.QFile
is derived fromQObject
, and you are not supposed to create until after executingQApplication a(argc, argv);
. Maybe that only applies toQWidget
s?Personally I cannot see why
QTextStream
/QFile
are globals? This would be avoided by putting them inside yourmyMessageHandler()
instead? [You don't even use your globalQTextStream output_ts
?] -
void myMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) { //https://stackoverflow.com/questions/4954140/how-to-redirect-qdebug-qwarning-qcritical-etc-output QString txt; switch (type) { case QtDebugMsg: txt = QString("Debug: %1").arg(msg); if (MainWindow::s_textEdit != 0) MainWindow::s_textEdit->appendPlainText(msg); break;
Please be aware your code is not thread-safe. That is, myMessageHandler() might be called from different threads, and potentially in parallel. This will cause issues if you're not protecting calls to s_textEdit::appendPlainText().
One way to avoid this is to queue the calls to appendPlainText():
if (MainWindow::s_textEdit != 0) QMetaObject::invokeMethod(MainWindow::s_textEdit, "appendPlainText", Qt::QueuedConnection, Q_ARG(QString, msg);
-
@JonB Thank you for your reply!
First of all sorry about my bad programming skills. I'm still learning C++... and i made some mistakes.
You are right. "QTextStream output_ts" is not used and should not be there.
Also Im not sure i need the static keyword on "static QFile outFile(...)". But what i know is that if I put it inside myMessageHandler() then the file is created every time i send something to the log. But instead I just wanted one file everytime the application runs.
and once again sorry about my misleading words. When I said:
It seems that the best way to go is using static variables.
I wanted to say: using global variables!
I was referring to:QPlainTextEdit* MainWindow::s_textEdit = 0;
on mainwindow.cpp.
I have been a bit reluctant to use global variables but in this case I don't see any other option.
Thanks again for your comments!
-
@kkoehne thank you for your suggestion.
I'm not so aware about thread safety specially not in Qt. Thank you for your insight. It helps a lot, specially if the program grows and i start to make use of multiple threads. For now it worked fine so far.
Thank you very much.
-
@wasawi2 said in on screen logging:
then the file is created every time i send something to the log
If that's not what you want then open the file in append mode.
-
@wasawi2 said in on screen logging:
I wanted to say: using global variables!
I just now noticed that you are actually putting
static
in front of your global variable (in the code earlier posted). This makes the global variable not global anymore. It just makes it 'global' for the current translation unit (in most cases a single translation unit is a single .cpp file). If you would have a 'global' variable withstatic
in a header file, each translation unit would get its own copy. As a beginner you most likely never want to usestatic
for global variables. However, you can easily pull that line into yourmyMessageHandler
function (including thestatic
). Inside a functionstatic
means that the variable is created only once. Keep the call to the constructor in the same line as you declare the variable and this will also only be executed once. You would not notice the difference between a global variable and a local static variable.Before calling
outFile.open(...)
you should check if the file is already opened.In the same way you can make the QTextStream persistent by placing a
static
in front. This will work if you don't reopen the file every time you place a message into the log. -
@SimonSchroeder thank you very much for your help. It is more clear now to me the use of static keyword in global variables which I did not understand.
I have moved the static call to myMessageHandler, also checked if file is already open. So everything is safer and more meaningful now.
thanks again for your help!