Building a logger for in production use with C++/Qt

·

·

A logging mechanism for your software development that can store logs for info, debugging and other information. Building a logger for your Software in production, where no debugging is possible.

Here’s an example of a logger in C++/Qt that can store QString log messages into a text file:


// Logger.h

#ifndef LOGGER_H
#define LOGGER_H

#include <QObject>
#include <QString>
#include <QFile>
#include <QTextStream>

class Logger : public QObject
{
    Q_OBJECT

public:
    static Logger& instance();

    void log(const QString& message);

private:
    Logger(QObject* parent = nullptr);
    ~Logger();

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    QFile m_file;
    QTextStream m_stream;
};

#endif // LOGGER_H
// Logger.cpp

#include "Logger.h"

#include <QDateTime>

Logger& Logger::instance()
{
    static Logger logger;
    return logger;
}

Logger::Logger(QObject* parent)
    : QObject(parent)
{
    m_file.setFileName("log.txt");
    m_file.open(QIODevice::Append | QIODevice::Text);
    m_stream.setDevice(&m_file);
}

Logger::~Logger()
{
    m_file.close();
}

void Logger::log(const QString& message)
{
    QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
    QString formattedMessage = QString("[%1] %2\n").arg(timestamp, message);
    m_stream << formattedMessage;
}

To use this logger, you would call Logger::instance().log("message") to write a log message to the file “log.txt”. The instance() method returns a singleton instance of the logger, so you can access it from anywhere in your code.

To make this logger accessible across multiple DLLs, you would need to make sure that the logger object is only instantiated once, even if multiple DLLs reference it. One way to do this is to use a shared library (.dll or .so) that exports the logger instance as a symbol. Other DLLs can then import this symbol and access the logger through it.

Alternatively, you could define the logger instance in a shared header file and include it in all of your DLLs. This would ensure that all of the DLLs share the same instance of the logger. However, you would need to be careful to avoid linker errors if the same symbol is defined in multiple DLLs.

Here’s an updated version of the logger that includes the module and source code line in the log message:


// Logger.h

#ifndef LOGGER_H
#define LOGGER_H

#include <QObject>
#include <QString>
#include <QFile>
#include <QTextStream>
#include <QMutex>

class Logger : public QObject
{
    Q_OBJECT

public:
    static Logger& instance();

    void log(const QString& module, const QString& sourceFile, int sourceLine, const QString& message);

private:
    Logger(QObject* parent = nullptr);
    ~Logger();

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    QFile m_file;
    QTextStream m_stream;
    QMutex m_mutex;
};

#endif // LOGGER_H

// Logger.cpp

#include "Logger.h"

#include <QDateTime>
#include <QThread>

Logger& Logger::instance()
{
    static Logger logger;
    return logger;
}

Logger::Logger(QObject* parent)
    : QObject(parent)
{
    m_file.setFileName("log.txt");
    m_file.open(QIODevice::Append | QIODevice::Text);
    m_stream.setDevice(&m_file);
}

Logger::~Logger()
{
    m_file.close();
}

void Logger::log(const QString& module, const QString& sourceFile, int sourceLine, const QString& message)
{
    QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate);
    QString threadId = QString::number((quintptr)QThread::currentThreadId(), 16);
    QString formattedMessage = QString("[%1] [0x%2] [%3:%4:%5] %6\n")
        .arg(timestamp, 8, QLatin1Char(' '))
        .arg(threadId, 8, QLatin1Char('0'))
        .arg(module, -20, QLatin1Char(' '))
        .arg(QFileInfo(sourceFile).fileName(), -20, QLatin1Char(' '))
        .arg(sourceLine, 5)
        .arg(message);
    
    QMutexLocker locker(&m_mutex);
    m_stream << formattedMessage;
    m_stream.flush();
}

To use this updated logger, you would call Logger::instance().log("module", __FILE__, __LINE__, "message") to write a log message to the file “log.txt”. The __FILE__ and __LINE__ macros are used to automatically get the source file name and line number where the log message was written.

Note that the logger now includes the thread ID in the log message, as well as padding to align the columns of the module, source file, and source line fields. The logger is also now thread-safe using a mutex to ensure that log messages are not interleaved.


Leave a Reply

Your email address will not be published. Required fields are marked *