Qt_Logger_Example/lib_logger/private/logger.cpp

606 lines
18 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/****************************************************************************
** Copyright (c) 2025 Evgeny Teterin (nayk) <nayk@nxt.ru>
** All right reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**
****************************************************************************/
#include "logger.h"
#include <QtCore/QMetaType>
#include <QtCore/QMetaObject>
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QDebug>
#include <QtCore/QDateTime>
#include <QtCore/QMutexLocker>
#include <QtCore/QMetaEnum>
#include <QtCore/QTextStream>
#include <iostream>
#include "log_worker.h"
#include "application_config.h"
//==============================================================================
std::atomic<size_t> FunctionLogger::counter {0};
//==============================================================================
void qtLogMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if ((type == QtWarningMsg) || (type == QtCriticalMsg) || (type == QtFatalMsg)) {
std::cerr << msg.toLocal8Bit().constData() << std::endl;
}
Logger::instance().writeLogQtType(type, context, msg);
}
//==============================================================================
//
// Logger - general class
//
//==============================================================================
bool Logger::isRunning()
{
QCoreApplication *application = QCoreApplication::instance();
return application
&& !application->property("loggerRun").isNull()
&& application->property("loggerRun").toBool();
}
//==============================================================================
Logger &Logger::instance()
{
static Logger loggerInstance;
return loggerInstance;
}
//==============================================================================
QString Logger::logTypeToString(LogType logType)
{
switch (logType) {
case LogType::LogInfo:
return "[inf]";
case LogType::LogWarning:
return "[wrn]";
case LogType::LogError:
return "[err]";
case LogType::LogInput:
return "[<<<]";
case LogType::LogOutput:
return "[>>>]";
case LogType::LogDebug:
return "[dbg]";
default:
break;
}
return "[inf]";
}
//==============================================================================
QString Logger::logTypeToString(quint8 logType)
{
QMetaEnum metaEnum = QMetaEnum::fromType<Logger::LogType>();
const char* name = metaEnum.valueToKey(logType);
if (!name) {
return "[inf]";
}
return logTypeToString( static_cast<LogType>(logType) );
}
//==============================================================================
Logger::LogType Logger::qtMsgToLogType(QtMsgType type)
{
switch (type) {
case QtDebugMsg:
return Logger::LogType::LogDebug;
case QtInfoMsg:
return Logger::LogType::LogInfo;
case QtWarningMsg:
return Logger::LogType::LogWarning;
case QtCriticalMsg:
return Logger::LogType::LogError;
case QtFatalMsg:
return Logger::LogType::LogError;
}
return Logger::LogType::LogInfo;
}
//==============================================================================
const QLoggingCategory &Logger::inputData()
{
static const QLoggingCategory category("input");
return category;
}
//==============================================================================
const QLoggingCategory &Logger::outputData()
{
static const QLoggingCategory category("output");
return category;
}
//==============================================================================
QString Logger::logDirectory()
{
return Application::applicationProfilePath() + "log/";
}
//==============================================================================
void Logger::start()
{
if (logWorker || workThread) return;
QString logDir { logDirectory() };
QDir logPath {logDir};
if (!logPath.mkpath(logDir)) {
qCritical() << "Error make path: " << logDir;
return;
}
QDateTime startDateTime {QDateTime::currentDateTime()};
QString logFileName {startDateTime.toString("yyMMdd_HHmmss_zzz") + ".log"};
logWorker = new LogWorker(logDir + logFileName, nullptr);
workThread = new QThread(this);
logWorker->moveToThread(workThread);
connect(workThread, &QThread::started, logWorker, &LogWorker::start, Qt::QueuedConnection);
connect(logWorker, &LogWorker::stopped, workThread, &QThread::quit);
workThread->start();
qInstallMessageHandler( qtLogMessageOutput );
writeLog(tr("Удаление старых лог файлов из '%1', "
"количество сохраняемых лог файлов: %2")
.arg(logDir).arg(maximumLogCount));
QDir dir(logDir);
QStringList list = dir.entryList(QStringList() << "*.log",
QDir::Files | QDir::Writable,
QDir::Name);
writeLog(tr("Найдено лог файлов: %1").arg(list.size()), LogType::LogDebug);
while (list.size() > maximumLogCount) {
QString fileName = list.takeFirst();
if (QFile::remove(logDir + fileName)) {
writeLog(tr("Удаление файла '%1'").arg(fileName), LogType::LogDebug);
}
else {
writeLog(tr("Ошибка при удалении файла '%1'").arg(fileName), LogType::LogError);
}
}
}
//==============================================================================
void Logger::stop()
{
if (!logWorker && !workThread) return;
qInstallMessageHandler(0);
if (logWorker) {
QMetaObject::invokeMethod(
logWorker,
&LogWorker::stop,
Qt::QueuedConnection
);
}
if (workThread && workThread->isRunning()) {
if (!workThread->wait(1500)) {
workThread->quit();
if (!workThread->wait(1500)) {
workThread->terminate();
workThread->wait();
}
}
}
if (workThread) delete workThread;
if (logWorker) delete logWorker;
workThread = nullptr;
logWorker = nullptr;
}
//==============================================================================
void Logger::writeLogQtType(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if (!logWorker) return;
QString category(context.category);
LogType logType {LogType::LogInfo};
QString objectName;
if (category.indexOf("input") == 0) {
logType = LogType::LogInput;
if (category.length() > 5) objectName = category.mid(5);
}
else if (category.indexOf("output") == 0) {
logType = LogType::LogOutput;
if (category.length() > 6) objectName = category.mid(6);
}
else {
logType = qtMsgToLogType(type);
if (!category.isEmpty() && (category != "default")) objectName = category;
}
writeLog(msg, logType, objectName);
}
//==============================================================================
void Logger::writeLog(const QString &msg, LogType type, const QString &objectName)
{
if (!logWorker) return;
static bool dbg {Application::isDebug()};
if (!dbg && ((type == LogType::LogDebug) || (type == LogType::LogInput)
|| (type == LogType::LogOutput))) return;
int typeInt { static_cast<int>(type) };
QMetaObject::invokeMethod(
logWorker,
"writeLog",
Qt::QueuedConnection,
Q_ARG(QString, msg),
Q_ARG(int, typeInt),
Q_ARG(QString, objectName)
);
}
//==============================================================================
Logger::LogType Logger::stringToLogType(const QString &stringType)
{
if (stringType.contains("wrn", Qt::CaseInsensitive)
|| stringType.contains("warn", Qt::CaseInsensitive))
return LogType::LogWarning;
else if (stringType.contains("dbg", Qt::CaseInsensitive)
|| stringType.contains("debug", Qt::CaseInsensitive))
return LogType::LogDebug;
else if (stringType.contains("err", Qt::CaseInsensitive)
|| stringType.contains("critic", Qt::CaseInsensitive)
|| stringType.contains("fatal", Qt::CaseInsensitive))
return LogType::LogError;
else if (stringType.contains(">>>", Qt::CaseInsensitive)
|| stringType.contains("output", Qt::CaseInsensitive))
return LogType::LogOutput;
else if (stringType.contains("<<<", Qt::CaseInsensitive)
|| stringType.contains("input", Qt::CaseInsensitive))
return LogType::LogInput;
return LogType::LogInfo;
}
//==============================================================================
Logger::Logger(QObject *parent) : QObject{parent}
{
if ( !QMetaType::isRegistered( qMetaTypeId<Logger::LogType>() ) )
qRegisterMetaType<Logger::LogType>();
QCoreApplication *application = QCoreApplication::instance();
if (application) application->setProperty("loggerRun", true);
}
//==============================================================================
Logger::~Logger()
{
stop();
QCoreApplication *application = QCoreApplication::instance();
if (application) application->setProperty("loggerRun", false);
}
//==============================================================================
//==============================================================================
//
// LogWorker - internal class
//
//==============================================================================
LogWorker::LogWorker(const QString &logFilePath, QObject *parent) : QObject{parent}
{
file.setFileName(logFilePath);
}
//==============================================================================
LogWorker::~LogWorker()
{
stop();
}
//==============================================================================
void LogWorker::start()
{
if (running) return;
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
qCritical() << "Не удалось открыть файл для записи: " << file.fileName();
return;
}
linesPosition = 0;
linesCounter = 0;
startTime = QDateTime::currentDateTime();
writeFirstLines();
writeLastLines();
timer = new QTimer(nullptr);
timer->setInterval(queueIntervalMs);
timer->setSingleShot(false);
connect(timer, &QTimer::timeout, this, &LogWorker::checkQueue);
timer->start();
running = true;
emit started();
}
//==============================================================================
void LogWorker::stop()
{
if (!running) return;
QMutexLocker locker(&mutex);
running = false;
if (timer) {
timer->stop();
delete timer;
timer = nullptr;
}
if (file.isOpen()) {
writeAllFromQueue();
writeLastLines();
file.flush();
file.close();
}
emit stopped();
}
//==============================================================================
void LogWorker::writeLog(const QString &msg, int type, const QString &objectName)
{
if (!running) return;
QMutexLocker lock(&mutex);
LogRecord rec {type, msg, objectName, QDateTime::currentDateTime()};
queue.enqueue(rec);
}
//==============================================================================
void LogWorker::checkQueue()
{
if (!running) return;
if (!mutex.tryLock()) return;
writeAllFromQueue();
mutex.unlock();
}
//==============================================================================
void LogWorker::writeFirstLines()
{
if (!file.isOpen()) return;
writeLine( tr("----- Начало %1 -")
.arg(startTime.toString("yyyy-MM-dd HH:mm:ss"))
.leftJustified(80,'-'),
-1);
QString runMode;
if (Application::isDebug()) {
runMode = "DEBUG";
}
if (Application::isPortable()) {
if (!runMode.isEmpty()) runMode += ", ";
runMode += "PORTABLE";
}
if (runMode.isEmpty()) runMode = "NORMAL";
writeLine(tr("ПО: %1").arg(QCoreApplication::applicationName()), -1);
writeLine(tr("Версия: %1").arg(QCoreApplication::applicationVersion()), -1);
writeLine(tr("Разработчик: %1 %2")
.arg( QCoreApplication::organizationName(),
QCoreApplication::organizationDomain().isEmpty()
? QString()
: QString("(" + QCoreApplication::organizationDomain() + ")")
),
-1
);
writeLine(tr("Строка запуска: '%1'").arg( QCoreApplication::arguments().join(' ') ), -1);
writeLine(tr("Режим запуска: %1").arg(runMode), -1);
if (file.write(QByteArray(1,'\n')) == 1) {
++linesCounter;
++linesPosition;
}
file.flush();
}
//==============================================================================
void LogWorker::writeLastLines()
{
if (!file.isOpen()) return;
if ((file.pos() != linesPosition) && !file.seek(linesPosition)) {
return;
}
QDateTime endTime = QDateTime::currentDateTime();
qint64 n = endTime.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch();
qint64 hr = n / 3600000;
qint64 ms = n - 3600000 * hr;
qint64 min = ms / 60000;
ms = ms - 60000 * min;
qint64 sec = ms / 1000;
ms = ms - 1000 * sec;
file.write(QByteArray(1,'\n'));
writeLine(tr("Текущее состояние: %1")
.arg( running ? "в процессе работы" : "работа завершена" ),
-2
);
writeLine(tr("Общее время: %1 ч, %2 мин, %3 сек, %4 мсек")
.arg(hr).arg(min).arg(sec).arg(ms),
-2
);
writeLine(tr("Общее количество строк: %1")
.arg( linesCounter + 5 ),
-2
);
writeLine(tr("----- Окончание %1 -")
.arg( QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss") )
.leftJustified(80,'-'),
-2);
file.flush();
}
//==============================================================================
void LogWorker::writeLine(const QString &msg, int type,
const QString &objectName, const QDateTime &dateTime)
{
QByteArray data {
QString(
dateTime.toString("[HH:mm:ss.zzz] ")
+ QString(type < 0 ? "[sys]" : Logger::logTypeToString(static_cast<quint8>(type)))
+ " "
+ QString(objectName.isEmpty() ? QString() : "{" + objectName + "} ")
+ msg
+ "\n"
).toUtf8()
};
qint64 bytesWrite {0};
int cnt {3};
while ((bytesWrite < data.size()) && (cnt > 0)) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
qint64 n = file.write(data.mid(bytesWrite));
#else
qint64 n = file.write(data.mid(static_cast<int>(bytesWrite)));
#endif
if (n > 0) {
bytesWrite += n;
}
else {
--cnt;
}
}
if (type < -1) return;
linesPosition += bytesWrite;
if (bytesWrite == data.size()) ++linesCounter;
}
//==============================================================================
void LogWorker::writeAllFromQueue()
{
if (queue.isEmpty()) return;
if (!file.isOpen()) {
queue.clear();
return;
}
if ((file.pos() != linesPosition) && !file.seek(linesPosition)) {
return;
}
while (!queue.isEmpty()) {
LogRecord rec = queue.dequeue();
writeLine( rec.msg, rec.type, rec.objectName, rec.dateTime );
}
writeLastLines();
}
//==============================================================================
//
// FunctionLogger
//
//==============================================================================
FunctionLogger::FunctionLogger(const char *file,
int line,
const char *name,
const QLoggingCategory &category)
: functionName{name}
, logCategory{category.categoryName()}
{
id = counter++;
if (logCategory.isEmpty())
logCategory = "Function";
quint64 threadId = reinterpret_cast<quint64>(QThread::currentThreadId());
qDebug() << QString("{%1} ID: %2, Thread: %3, File: '%4', Line: %5, Entering function: %6")
.arg(logCategory)
.arg(id)
.arg(threadId)
.arg(file)
.arg(line)
.arg(functionName)
.toStdString().c_str();
}
//==============================================================================
FunctionLogger::~FunctionLogger()
{
qDebug() << QString("{%1} ID: %2, Exiting function: %3")
.arg(logCategory)
.arg(id)
.arg(functionName)
.toStdString().c_str();
}
//==============================================================================