francescmm / QLogger

Multi-threading logger for Qt applications
https://www.francescmm.com
GNU Lesser General Public License v2.1
57 stars 21 forks source link

Log with the << operator #4

Open MartinDelille opened 4 years ago

MartinDelille commented 4 years ago

I really enjoy the << operator used by std::stream and QDebug. Do you think your logger could be compatible with it?

francescmm commented 4 years ago

Hi @MartinDelille I can take a look to it, of course!

MartinDelille commented 4 years ago

I ask you that because I have my own logging system based upon QDebug which is quite powerful but I might change it to benefit QLogger features. Of course I would need keeping the current feature.

My current logging system code is not yet publicly availlable but here is a short description:

Logging system

There is three main concept to understand when using my logging system:

Logger identifier

Each class inheriting MDLogger can log on the default output:

MDINFO << "info";
MDDEBUG << "debug";
MDWARN << "warn";
MDERR << "error";

The mechanism use QDebug and QLoggingCategory under the hood.

In order to do so, the class must pass to MDLogger constructor an identifier.

Category

There is 4 categories (information, debug, warning and error) but only warning and error are displayed by default.

Enabling or disabling categories is possible by modifying a configuration file (~/Library/Preferences/QtProject/qtlogging.ini on MacOS):

[Rules]
# Allow debug log for the FormTest window:
MyOrganisation.FormTest.MDWindow.debug=true

Message pattern

The message pattern display only the time and the message by default but you can enable other information by changing these settings to true:

muhammetasan commented 4 years ago

But many of these are out of scope of operator <<

francescmm commented 4 years ago

@MartinDelille I've check what I could do in QLogger, but I see some problems with your description. I'm using global functions instead of inheritance. The other thing is that I don't plan to support colored log.

Things easy achievable are:

  1. Enabling/disabling: in QLogger I could easily do that just by adding another method. In addition the change of state in QLogger would be in runtime without need of accessing any init file.
  2. Log file and line it's a multi-platform feature that the C already supports. I could easily change from global functions to macros and that would enable easily those features.
Light-Wizzard commented 4 years ago

I am also interested in this, you need to implement it like QMessageLogger, and you need to handle File, Line, and Function: FILE, LINE, FUNCTION, as well as the message. If you do not pass this info, you have no idea where the message came from.

I am working on this here https://github.com/Light-Wizzard/WeBookClient, you can see a working example of it.

I was thinking about a small wrapper:

static QString myModule = "";
static QString myLogFile = "";

#if defined(QLOGGER_LIBRARY)
#  define QLOGGERSHARED_EXPORT Q_DECL_EXPORT
#else
#  define QLOGGERSHARED_EXPORT Q_DECL_IMPORT
#endif
/******************************************************************************
** class QLoggerWrapper                                                                              *
*******************************************************************************/
//class QLOGGERSHARED_EXPORT QLoggerWrapper
class QLoggerWrapper
{
        //Q_DISABLE_COPY(QLoggerWrapper)

    public:
        QLoggerWrapper(LogLevel level, const char *file, int line, const char *function);
        ~QLoggerWrapper();

        void write(const char* msg, ...)
#if defined(Q_CC_GNU) && !defined(__INSURE__)
#  if defined(Q_CC_MINGW) && !defined(Q_CC_CLANG)
        __attribute__ ((format (gnu_printf, 2, 3)))
#  else
        __attribute__ ((format (printf, 2, 3)))
#  endif
#endif
        ;

    private:
        LogLevel            myLogLevel;
        const char         *myFileName;
        int                 myLine;
        const char         *myFunctionName;
        QString             myMessage;

}; // end class QLoggerWrapper
/******************************************************************************
** QLoggerWrapper Constructor                                                                  *
*******************************************************************************/
QLoggerWrapper::QLoggerWrapper(LogLevel level, const char *file, int line, const char *function) : myLogLevel(level), myFileName(file), myLine(line), myFunctionName(function)
{

} // end CuteMessageLogger
/******************************************************************************
** ~CuteMessageLogger Deconstructor                                                       *
** manager = QLogger::QLoggerManager::getInstance();
** manager->addDestination(QLogger::myLogFile, QLogger::myModule, QLogger::LogLevel::Debug);
*******************************************************************************/
QLoggerWrapper::~QLoggerWrapper()
{
    const auto manager = QLoggerManager::getInstance();

    QMutexLocker(&manager->mutex);

    const auto logWriter = manager->getLogWriter(myModule);

    QString thisMessage = QString("%1 (%2:%3 =>%4)").arg(myMessage).arg(myFileName).arg(myLine).arg(myFunctionName);

    if (logWriter && !logWriter->isStop() && logWriter->getLevel() <= myLogLevel)
    {
        manager->writeAndDequeueMessages(myModule);
        logWriter->write(myModule, thisMessage, myLogLevel);
    }
    else if (!logWriter)
        manager->queueMessage(myModule, { thisMessage, static_cast<int>(myLogLevel), QDateTime::currentDateTime().toString("dd-MM-yyyy hh:mm:ss.zzz") });
} // end ~QLoggerWrapper
/******************************************************************************
** write                                                                                                         *
*******************************************************************************/
void QLoggerWrapper::write(const char *msg, ...)
{
    va_list va;
    va_start(va, msg);
    myMessage = QString::vasprintf(msg, va);
    va_end(va);
} // end write
#  define Q_FUNC_INFO       __PRETTY_FUNCTION__
//#  define Q_ALIGNOF(type)   __alignof__(type)
#  define Q_TYPEOF(expr)    __typeof__(expr)
#  define Q_DECL_DEPRECATED __attribute__ ((__deprecated__))
//#  define Q_DECL_ALIGN(n)   __attribute__((__aligned__(n)))
#  define Q_DECL_UNUSED     __attribute__((__unused__))
#  define Q_LIKELY(expr)    __builtin_expect(!!(expr), true)
#  define Q_UNLIKELY(expr)  __builtin_expect(!!(expr), false)
#  define Q_NORETURN        __attribute__((__noreturn__))
//#  define Q_REQUIRED_RESULT __attribute__ ((__warn_unused_result__))
#  define Q_DECL_PURE_FUNCTION __attribute__((pure))
#  define Q_DECL_CONST_FUNCTION __attribute__((const))
#  define Q_DECL_COLD_FUNCTION __attribute__((cold))
#  if !defined(QT_MOC_CPP)
#    define Q_PACKED __attribute__ ((__packed__))
#    ifndef __ARM_EABI__
#      define QT_NO_ARM_EABI
#    endif
#  endif
#  if Q_CC_GNU >= 403 && !defined(Q_CC_CLANG)
#      define Q_ALLOC_SIZE(x) __attribute__((alloc_size(x)))
#  endif

#define QLOG_TRACE      QLogger::QLoggerWrapper(QLogger::LogLevel::Trace,   __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_DEBUG      QLogger::QLoggerWrapper(QLogger::LogLevel::Debug,   __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_INFO       QLogger::QLoggerWrapper(QLogger::LogLevel::Info,    __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_WARNING    QLogger::QLoggerWrapper(QLogger::LogLevel::Warning, __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_ERROR      QLogger::QLoggerWrapper(QLogger::LogLevel::Error,   __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_FATAL      QLogger::QLoggerWrapper(QLogger::LogLevel::Fatal,   __FILE__, __LINE__, Q_FUNC_INFO).write

What are your thoughts about how you want to implement it?

Flesh

francescmm commented 4 years ago

Hi @Light-Wizzard that wrapper looks cool. I think we may need to sync about it because I was doing something similar in a branch.

The only thing I'd like is to support MSVC, so some compiler functions would need to be wrapped as well. But we can solve that.

Regarding the changes I'm doing, could you please check this branch: https://github.com/francescmm/QLogger/tree/multithread ?

Even that says multithread I've improved the log output as well. Now you will get the file and line (not the caller function yet, since I'm not sure at what level of verbosity would be better).

Light-Wizzard commented 4 years ago

AppVeyor needs MSVS support for sure.

One idea about how to use it is in a custom function for qInstallMessageHandler.

Flesh

Light-Wizzard commented 4 years ago

I have updated my project, it is not connected to yours yet, I need to know more details.

I added a Wiki, to better communicate with https://github.com/Light-Wizzard/WeBookClient/wiki/3.Logging, with the Client and Server App being a Real-World Test Case.

Here is one change I would recommend:

#include <QObject>
#include <QMetaEnum>
namespace QLogger
{
    class QLogger
    {
            Q_GADGET

        public:
            /**
             * @brief The LogLevel enum class defines the level of the log message.
             */
            enum LogLevel
            {
                Trace = 0,  //! \c Trace \brief Trace level is used for internal code tracing.
                Debug,      //! \c Debug \brief Debug level is used for writing custom debug output.
                Info,       //! \c Info \brief Info level is used for informational messages.
                Warning,    //! \c Warning \brief Warning level is used to report warnings and recoverable errors in your application.
                Error,      //! \c Error \brief Error level is used for writing critical error messages and reporting system errors.
                Critical,   //! \c Critical \brief Critical level is used for writing critical error messages and reporting system errors more sever than Error.
                Fatal       //! \c Fatal \brief Fatal level is used for writing fatal error messages shortly before exiting.
            };

            Q_ENUM(LogLevel)

            static QString levelToText(const QLogger::LogLevel &value)
            {
                auto metaEnum = QMetaEnum::fromType<QLogger::QLogger::LogLevel>();
                return QString(metaEnum.valueToKey(value));
            }
    }; // end class QLogger
} // end namespace QLogger

You can see how I integrated this change into your code, which I have in a folder called src/QLogger. I have a few questions that puzzle me, maybe an example of how to use it will help, I am stuck at that part in the conversion.

modules, you use them as String List for threads, if you look at how I am trying to use it:

static QString myModule = "WeBookClient";
static QString myLogPath = "logs";
#define QLOG_TRACE      QLogger::QLoggerWrapper(myModule, myLogPath, QLogger::QLogger::LogLevel::Trace,   __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_DEBUG      QLogger::QLoggerWrapper(myModule, myLogPath, QLogger::QLogger::LogLevel::Debug,   __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_INFO       QLogger::QLoggerWrapper(myModule, myLogPath, QLogger::QLogger::LogLevel::Info,    __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_WARNING    QLogger::QLoggerWrapper(myModule, myLogPath, QLogger::QLogger::LogLevel::Warning, __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_ERROR      QLogger::QLoggerWrapper(myModule, myLogPath, QLogger::QLogger::LogLevel::Error,   __FILE__, __LINE__, Q_FUNC_INFO).write
#define QLOG_FATAL      QLogger::QLoggerWrapper(myModule, myLogPath, QLogger::QLogger::LogLevel::Fatal,   __FILE__, __LINE__, Q_FUNC_INFO).write

The Static would be in a module using it, I do not like to use a Static, this was just a fast example.

Thanks, Flesh

Light-Wizzard commented 4 years ago

In case you did not know it, your website has a reCAPTCHA key error: https://www.francescmm.com/about-me-software-engineer/ so your contact page does not work for me anyway, I am on Arch Linux Manjaro, and I tried both Chrome and FireFox.

My full name, replacing spaces with dots at Gmail will get to me.

Thanks, Jeffrey Scott Flesher

francescmm commented 4 years ago

Hi @Light-Wizzard,

First of all, thanks for such a work! I think that you have several great ideas in there. It has been a looong week but I think I can find some time during the weekend to check more in detail what you'd like to achieve and see if (or how actually) this can be adapted.

Thanks for noticing me the problem in the site, I've already fixed it :)

Light-Wizzard commented 4 years ago

I have it working, a start, currently the log does not write till exit, I will fix that, but the main issue is the way I wrap it, I need to rethink that, and I need to write a flush function to fix the issue with not writing till exit.

https://github.com/Light-Wizzard/WeBookClient

As you see, the Common file is used to get and set QSettings, it takes arguments from the command line, and could also get them from the Environment variables, all before the INI file is read, and the wrappers job is for redirection, but it needs to be merged with the writer now, and add a flush function.

Issues with this line crashing

const auto level = qvariant_cast<QLoggerLevel::LogLevel>(values.at(2).toInt());

// removing .toInt() works for me
const auto level = qvariant_cast<QLoggerLevel::LogLevel>(values.at(2));

LOGLEVEL_CLASS was to test the change in LogLevel, this needs to work across all the modules.

This needs to be optimized, and integrated right, I hacked it in with a wrapper to test the concept, and it seems to work fine.

I changed this to use qInstallMessageHandler in main.cpp, so it just uses normal qDebug, let me know what you think about this idea.

As you know, it still hangs on exit, I now understand why you put an exit(0) above the wait().

Update:

qInstallMessageHandler works fine for GUI windows, but not the command line, so you will see in the Server that I use QLog_Debug.

Flesh