#include "logmanager.h" #include #include #include #include #include #include #include #include #include // ============================================================================ // СТАТИЧЕСКИЕ ПЕРЕМЕННЫЕ // ============================================================================ 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( "
" "%2 " "[%3] " "%4 " "%5: " "%6" "
") .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(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(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 LogManager::allEntries() const { QMutexLocker locker(&m_mutex); return m_entries; } QList LogManager::entriesByTime(const QDateTime& from, const QDateTime& to) const { QMutexLocker locker(&m_mutex); QList result; for (const auto& entry : m_entries) { if (entry.timestamp >= from && entry.timestamp <= to) { result.append(entry); } } return result; } QList LogManager::entriesByLevel(LogLevel level) const { QMutexLocker locker(&m_mutex); QList result; for (const auto& entry : m_entries) { if (entry.level == level) { result.append(entry); } } return result; } QList LogManager::entriesByModule(const QString& module) const { QMutexLocker locker(&m_mutex); QList result; for (const auto& entry : m_entries) { if (entry.module == module) { result.append(entry); } } return result; } QList LogManager::search(const QString& text, bool caseSensitive) const { QMutexLocker locker(&m_mutex); QList 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 LogManager::filter(const QList& levels, const QString& moduleFilter, const QString& methodFilter) const { QMutexLocker locker(&m_mutex); QList 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 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 << "\n\n\n"; stream << "\n"; stream << "Логи приложения\n"; stream << "\n"; stream << "\n\n"; for (const auto& entry : m_entries) { stream << entry.toHtml() << "\n"; } stream << "\n"; } 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 LogManager::statistics() const { QMutexLocker locker(&m_mutex); QMap 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& 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(); }