Files
TTW_Bot/logmanager.cpp
T
2026-02-07 08:28:56 +03:00

833 lines
23 KiB
C++

#include "logmanager.h"
#include <QCoreApplication>
#include <QStandardPaths>
#include <QDir>
#include <QDateTime>
#include <QThread>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
// ============================================================================
// СТАТИЧЕСКИЕ ПЕРЕМЕННЫЕ
// ============================================================================
LogManager* LogManager::m_instance = nullptr;
// ============================================================================
// МЕТОДЫ LOGENTRY
// ============================================================================
QString LogEntry::toString() const
{
QStringList parts;
if (!timestamp.isNull()) {
parts.append(timestamp.toString("hh:mm:ss"));
}
parts.append(levelToString(level));
if (!module.isEmpty()) {
parts.append(module);
}
if (!method.isEmpty()) {
parts.append(method);
}
parts.append(message);
return parts.join(" | ");
}
QString LogEntry::toHtml() const
{
QString color = levelToColor(level).name();
QString html = QString(
"<div style=\"margin: 2px 0; padding: 2px; border-left: 3px solid %1;\">"
"<span style=\"color: gray; font-size: 0.8em;\">%2</span> "
"<span style=\"color: %1; font-weight: bold;\">[%3]</span> "
"<span style=\"color: darkblue;\">%4</span> "
"<span style=\"color: green;\">%5</span>: "
"<span>%6</span>"
"</div>")
.arg(color)
.arg(timestamp.toString("hh:mm:ss"))
.arg(levelToString(level))
.arg(module)
.arg(method)
.arg(message.toHtmlEscaped());
return html;
}
QString LogEntry::toCsv() const
{
return QString("\"%1\",\"%2\",\"%3\",\"%4\",\"%5\"")
.arg(timestamp.toString("yyyy-MM-dd hh:mm:ss"))
.arg(levelToString(level))
.arg(module)
.arg(method)
.arg(message);
}
QString LogEntry::toJson() const
{
QJsonObject obj;
obj["timestamp"] = timestamp.toString(Qt::ISODate);
obj["level"] = levelToString(level);
obj["module"] = module;
obj["method"] = method;
obj["message"] = message;
obj["threadId"] = static_cast<qint64>(threadId);
if (!sourceFile.isEmpty()) {
obj["sourceFile"] = sourceFile;
obj["sourceLine"] = sourceLine;
}
QJsonDocument doc(obj);
return doc.toJson(QJsonDocument::Compact);
}
QString LogEntry::levelToString(LogLevel level)
{
switch (level) {
case LogLevel::Info: return "INFO";
case LogLevel::Warning: return "WARNING";
case LogLevel::Error: return "ERROR";
case LogLevel::Debug: return "DEBUG";
case LogLevel::Critical: return "CRITICAL";
default: return "UNKNOWN";
}
}
QColor LogEntry::levelToColor(LogLevel level)
{
switch (level) {
case LogLevel::Info: return Qt::darkGreen;
case LogLevel::Warning: return Qt::darkYellow;
case LogLevel::Error: return Qt::red;
case LogLevel::Debug: return Qt::gray;
case LogLevel::Critical: return Qt::darkRed;
default: return Qt::black;
}
}
QString LogEntry::formatDateTime(const QDateTime& dt)
{
return dt.toString("dd.MM.yyyy hh:mm:ss.zzz");
}
// ============================================================================
// РЕАЛИЗАЦИЯ LOGMANAGER
// ============================================================================
LogManager::LogManager(QObject* parent)
: QObject(parent)
, m_maxEntries(10000)
{
// Настройки по умолчанию
m_settings.logToFile = true;
m_settings.logToConsole = true;
m_settings.logToDatabase = false;
// Стандартный путь к файлу логов
QString appDataPath = QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation);
QDir dir(appDataPath);
if (!dir.exists()) {
dir.mkpath(".");
}
m_settings.logFilePath = dir.filePath("application.log");
// Цвета по умолчанию
m_settings.colors[LogLevel::Info] = Qt::darkGreen;
m_settings.colors[LogLevel::Warning] = Qt::darkYellow;
m_settings.colors[LogLevel::Error] = Qt::red;
m_settings.colors[LogLevel::Debug] = Qt::gray;
m_settings.colors[LogLevel::Critical] = Qt::darkRed;
m_settings.showTimestamp = true;
m_settings.showModule = true;
m_settings.showMethod = true;
}
LogManager::~LogManager()
{
if (m_logFile.isOpen()) {
m_logFile.close();
}
}
LogManager* LogManager::instance()
{
if (!m_instance) {
m_instance = new LogManager();
}
return m_instance;
}
void LogManager::initialize(const LogSettings& settings)
{
if (m_instance) {
m_instance->updateSettings(settings);
} else {
m_instance = new LogManager();
m_instance->updateSettings(settings);
}
}
void LogManager::cleanup()
{
if (m_instance) {
delete m_instance;
m_instance = nullptr;
}
}
void LogManager::log(LogLevel level, const QString& module,
const QString& method, const QString& message)
{
log(level, module, method, message, "", 0);
}
void LogManager::log(LogLevel level, const QString& module,
const QString& method, const QString& message,
const QString& sourceFile, int sourceLine)
{
QMutexLocker locker(&m_mutex);
// Создаем запись
LogEntry entry;
entry.timestamp = QDateTime::currentDateTime();
entry.level = level;
entry.module = module;
entry.method = method;
entry.message = message;
entry.threadId = reinterpret_cast<qint64>(QThread::currentThreadId());
if (!sourceFile.isEmpty()) {
entry.sourceFile = sourceFile;
entry.sourceLine = sourceLine;
}
// Добавляем в список
m_entries.append(entry);
// Ограничиваем количество записей
if (m_entries.size() > m_maxEntries) {
m_entries.removeFirst();
}
// Записываем в файл
if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) {
writeToFile(entry);
}
// Выводим в консоль
if (m_settings.logToConsole) {
QString formatted = formatForConsole(entry);
switch (level) {
case LogLevel::Error:
case LogLevel::Critical:
qCritical().noquote() << formatted;
break;
case LogLevel::Warning:
qWarning().noquote() << formatted;
break;
case LogLevel::Debug:
qDebug().noquote() << formatted;
break;
default:
qInfo().noquote() << formatted;
break;
}
}
locker.unlock();
// Отправляем сигналы
emit entryAdded(entry);
// Сигналы для конкретных уровней
switch (level) {
case LogLevel::Info:
emit infoAdded(entry);
break;
case LogLevel::Warning:
emit warningAdded(entry);
break;
case LogLevel::Error:
emit errorAdded(entry);
break;
case LogLevel::Debug:
emit debugAdded(entry);
break;
case LogLevel::Critical:
emit criticalAdded(entry);
break;
}
}
void LogManager::info(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Info, module, method, message);
}
void LogManager::warning(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Warning, module, method, message);
}
void LogManager::error(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Error, module, method, message);
}
void LogManager::debug(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Debug, module, method, message);
}
void LogManager::critical(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Critical, module, method, message);
}
QList<LogEntry> LogManager::allEntries() const
{
QMutexLocker locker(&m_mutex);
return m_entries;
}
QList<LogEntry> LogManager::entriesByTime(const QDateTime& from,
const QDateTime& to) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
if (entry.timestamp >= from && entry.timestamp <= to) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::entriesByLevel(LogLevel level) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
if (entry.level == level) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::entriesByModule(const QString& module) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
if (entry.module == module) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::search(const QString& text, bool caseSensitive) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
Qt::CaseSensitivity sensitivity = caseSensitive ?
Qt::CaseSensitive : Qt::CaseInsensitive;
for (const auto& entry : m_entries) {
if (entry.message.contains(text, sensitivity) ||
entry.module.contains(text, sensitivity) ||
entry.method.contains(text, sensitivity)) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::filter(const QList<LogLevel>& levels,
const QString& moduleFilter,
const QString& methodFilter) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
// Проверка уровня
if (!levels.contains(entry.level)) {
continue;
}
// Проверка модуля
if (!moduleFilter.isEmpty() && entry.module != moduleFilter) {
continue;
}
// Проверка метода
if (!methodFilter.isEmpty() && entry.method != methodFilter) {
continue;
}
result.append(entry);
}
return result;
}
void LogManager::clear()
{
QMutexLocker locker(&m_mutex);
m_entries.clear();
locker.unlock();
emit logCleared();
}
bool LogManager::saveToFile(const QString& filePath, bool append)
{
QMutexLocker locker(&m_mutex);
QFile file(filePath);
QIODevice::OpenMode mode = QIODevice::WriteOnly | QIODevice::Text;
if (append) {
mode |= QIODevice::Append;
}
if (!file.open(mode)) {
emit fileError(QString("Не удалось открыть файл: %1").arg(file.errorString()));
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
// Заголовок
stream << "Дата,Уровень,Модуль,Метод,Сообщение\n";
for (const auto& entry : m_entries) {
stream << entry.toCsv() << "\n";
}
file.close();
return true;
}
bool LogManager::loadFromFile(const QString& filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
// Пропускаем заголовок
QString header = stream.readLine();
QList<LogEntry> loadedEntries;
while (!stream.atEnd()) {
QString line = stream.readLine();
QStringList parts = line.split('"');
if (parts.size() < 9) continue;
LogEntry entry;
entry.timestamp = QDateTime::fromString(parts[1], "yyyy-MM-dd hh:mm:ss");
QString levelStr = parts[3];
if (levelStr == "INFO") entry.level = LogLevel::Info;
else if (levelStr == "WARNING") entry.level = LogLevel::Warning;
else if (levelStr == "ERROR") entry.level = LogLevel::Error;
else if (levelStr == "DEBUG") entry.level = LogLevel::Debug;
else if (levelStr == "CRITICAL") entry.level = LogLevel::Critical;
entry.module = parts[5];
entry.method = parts[7];
entry.message = parts[9];
loadedEntries.append(entry);
}
QMutexLocker locker(&m_mutex);
m_entries = loadedEntries;
return true;
}
bool LogManager::exportToFormat(const QString& filePath, const QString& format)
{
QMutexLocker locker(&m_mutex);
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
if (format.toLower() == "html") {
stream << "<!DOCTYPE html>\n<html>\n<head>\n";
stream << "<meta charset=\"UTF-8\">\n";
stream << "<title>Логи приложения</title>\n";
stream << "<style>\n";
stream << "body { font-family: monospace; }\n";
stream << ".info { color: darkgreen; }\n";
stream << ".warning { color: darkorange; }\n";
stream << ".error { color: red; }\n";
stream << ".debug { color: gray; }\n";
stream << ".critical { color: darkred; }\n";
stream << "</style>\n";
stream << "</head>\n<body>\n";
for (const auto& entry : m_entries) {
stream << entry.toHtml() << "\n";
}
stream << "</body>\n</html>";
} else if (format.toLower() == "json") {
QJsonArray array;
for (const auto& entry : m_entries) {
QJsonObject obj;
obj["timestamp"] = entry.timestamp.toString(Qt::ISODate);
obj["level"] = LogEntry::levelToString(entry.level);
obj["module"] = entry.module;
obj["method"] = entry.method;
obj["message"] = entry.message;
array.append(obj);
}
QJsonDocument doc(array);
stream << doc.toJson();
} else { // TXT (по умолчанию)
for (const auto& entry : m_entries) {
stream << entry.toString() << "\n";
}
}
file.close();
return true;
}
QMap<LogLevel, int> LogManager::statistics() const
{
QMutexLocker locker(&m_mutex);
QMap<LogLevel, int> stats;
for (const auto& entry : m_entries) {
stats[entry.level]++;
}
return stats;
}
int LogManager::count() const
{
QMutexLocker locker(&m_mutex);
return m_entries.size();
}
int LogManager::maxEntries() const
{
QMutexLocker locker(&m_mutex);
return m_maxEntries;
}
void LogManager::setMaxEntries(int max)
{
QMutexLocker locker(&m_mutex);
m_maxEntries = max;
// Удаляем лишние записи
while (m_entries.size() > m_maxEntries) {
m_entries.removeFirst();
}
}
LogSettings LogManager::settings() const
{
QMutexLocker locker(&m_mutex);
return m_settings;
}
void LogManager::updateSettings(const LogSettings& newSettings)
{
QMutexLocker locker(&m_mutex);
// Закрываем старый файл, если изменился путь
if (m_settings.logFilePath != newSettings.logFilePath && m_logFile.isOpen()) {
m_logFile.close();
}
m_settings = newSettings;
// Инициализируем файл
if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) {
if (!m_logFile.isOpen()) {
m_logFile.setFileName(m_settings.logFilePath);
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
} else {
m_logStream.setDevice(&m_logFile);
m_logStream.setCodec("UTF-8");
}
}
}
locker.unlock();
emit settingsChanged();
}
void LogManager::saveSettings(QSettings& settings)
{
QMutexLocker locker(&m_mutex);
settings.beginGroup("Logging");
settings.setValue("logToFile", m_settings.logToFile);
settings.setValue("logToConsole", m_settings.logToConsole);
settings.setValue("logFilePath", m_settings.logFilePath);
settings.setValue("maxFileSizeMB", m_settings.maxFileSizeMB);
settings.setValue("maxFileCount", m_settings.maxFileCount);
settings.setValue("showTimestamp", m_settings.showTimestamp);
settings.setValue("showModule", m_settings.showModule);
settings.setValue("showMethod", m_settings.showMethod);
settings.setValue("maxEntries", m_maxEntries);
settings.endGroup();
}
void LogManager::loadSettings(QSettings& settings)
{
QMutexLocker locker(&m_mutex);
settings.beginGroup("Logging");
m_settings.logToFile = settings.value("logToFile", true).toBool();
m_settings.logToConsole = settings.value("logToConsole", true).toBool();
m_settings.logFilePath = settings.value("logFilePath", m_settings.logFilePath).toString();
m_settings.maxFileSizeMB = settings.value("maxFileSizeMB", 10).toInt();
m_settings.maxFileCount = settings.value("maxFileCount", 5).toInt();
m_settings.showTimestamp = settings.value("showTimestamp", true).toBool();
m_settings.showModule = settings.value("showModule", true).toBool();
m_settings.showMethod = settings.value("showMethod", true).toBool();
m_maxEntries = settings.value("maxEntries", 10000).toInt();
settings.endGroup();
}
void LogManager::setLogToFileEnabled(bool enabled)
{
QMutexLocker locker(&m_mutex);
m_settings.logToFile = enabled;
if (enabled && !m_logFile.isOpen()) {
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
}
} else if (!enabled && m_logFile.isOpen()) {
m_logFile.close();
}
}
void LogManager::setLogToConsoleEnabled(bool enabled)
{
QMutexLocker locker(&m_mutex);
m_settings.logToConsole = enabled;
}
void LogManager::setLogFilePath(const QString& path)
{
QMutexLocker locker(&m_mutex);
if (m_logFile.isOpen()) {
m_logFile.close();
}
m_settings.logFilePath = path;
if (m_settings.logToFile) {
m_logFile.setFileName(path);
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
}
}
}
void LogManager::setLevelColors(const QMap<LogLevel, QColor>& colors)
{
QMutexLocker locker(&m_mutex);
m_settings.colors = colors;
}
void LogManager::setLevelColor(LogLevel level, const QColor& color)
{
QMutexLocker locker(&m_mutex);
m_settings.colors[level] = color;
}
void LogManager::setFormat(bool showTimestamp, bool showModule, bool showMethod)
{
QMutexLocker locker(&m_mutex);
m_settings.showTimestamp = showTimestamp;
m_settings.showModule = showModule;
m_settings.showMethod = showMethod;
}
void LogManager::writeToFile(const LogEntry& entry)
{
if (!m_logFile.isOpen()) {
return;
}
QString formatted = formatForFile(entry);
m_logStream << formatted << "\n";
m_logStream.flush();
// Проверяем размер файла
if (checkFileSize()) {
rotateLogFile();
}
}
void LogManager::rotateLogFile()
{
if (!m_logFile.isOpen()) {
return;
}
m_logFile.close();
QString basePath = m_settings.logFilePath;
QFileInfo fi(basePath);
QString baseName = fi.baseName();
QString suffix = fi.suffix();
QString dir = fi.path();
// Удаляем самый старый файл
QString oldestFile = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(m_settings.maxFileCount)
.arg(suffix);
if (QFile::exists(oldestFile)) {
QFile::remove(oldestFile);
}
// Переименовываем остальные файлы
for (int i = m_settings.maxFileCount - 1; i >= 1; i--) {
QString oldFile = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(i)
.arg(suffix);
QString newFile = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(i + 1)
.arg(suffix);
if (QFile::exists(oldFile)) {
QFile::rename(oldFile, newFile);
}
}
// Переименовываем текущий файл
QString firstBackup = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(1)
.arg(suffix);
QFile::rename(basePath, firstBackup);
// Открываем новый файл
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов после ротации: %1")
.arg(m_logFile.errorString()));
}
}
bool LogManager::checkFileSize()
{
if (!m_logFile.isOpen()) {
return false;
}
qint64 size = m_logFile.size();
qint64 maxSize = m_settings.maxFileSizeMB * 1024 * 1024;
return size > maxSize;
}
QString LogManager::formatEntry(const LogEntry& entry) const
{
QStringList parts;
if (m_settings.showTimestamp) {
parts.append(entry.timestamp.toString("hh:mm:ss"));
}
parts.append(LogEntry::levelToString(entry.level));
if (m_settings.showModule && !entry.module.isEmpty()) {
parts.append(entry.module);
}
if (m_settings.showMethod && !entry.method.isEmpty()) {
parts.append(entry.method);
}
parts.append(entry.message);
return parts.join(" | ");
}
QString LogManager::formatForConsole(const LogEntry& entry) const
{
QString formatted = formatEntry(entry);
// Добавляем цвет для консоли (если поддерживается)
if (m_settings.colors.contains(entry.level)) {
// Для консоли цвет добавляется через escape-последовательности
QColor color = m_settings.colors[entry.level];
// Это упрощенный вариант, можно расширить для реальной консоли
return formatted;
}
return formatted;
}
QString LogManager::formatForFile(const LogEntry& entry) const
{
// Для файла используем CSV формат
return entry.toCsv();
}