/*! * * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file Logger.h * \author (last) Behrouz NematiPour * \date (last) 04-Apr-2024 * \author (original) Behrouz NematiPour * \date (original) 26-Aug-2020 * */ #pragma once // Qt #include #include #include // Project #include "main.h" // Doxygen : do not remove #include "StorageGlobals.h" #include "GuiGlobals.h" // Define #define _Logger Storage::Logger::I() #define LOG_EXPORTLOG(vExportName) _Logger.concurrentExportLogs(vExportName) #define LOG_EXPORTERR(vExportName) _Logger.concurrentExportErrs(vExportName) #define LOG_EXPORTTRT(vExportName) _Logger.concurrentExportTrts(vExportName) #define ADD_APPED_HEADER emit Storage::Logger::I().didLog(_headerA, Storage::Logger::LogType::eLogAppED, false) #define ADD_DEBUG_HEADER emit Storage::Logger::I().didLog(_headerD, Storage::Logger::LogType::eLogDebug, false) #define LOG_APPED(vCONTENT) emit Storage::Logger::I().didLog(vCONTENT, Storage::Logger::LogType::eLogAppED, true ) #define LOG_DEBUG(vCONTENT) emit Storage::Logger::I().didLog(vCONTENT, Storage::Logger::LogType::eLogDebug, true ) #define LOG_APPED_UI(vCONTENT) emit Storage::Logger::I().didLog(" ,UI," + vCONTENT, Storage::Logger::LogType::eLogAppED, true ) #define LOG_APPED_PO(vCONTENT) emit Storage::Logger::I().didLog("POST,UI," + vCONTENT, Storage::Logger::LogType::eLogAppED, true ) #define LOG_APPED_MSG(vID, vTEXT) LOG_APPED(QString("%1,%2").arg(QString("%1").arg(vID,4,16,QLatin1Char('0')).toUpper()).arg(vTEXT)) #define LOG_APPED_CS(vCONTENT) emit Storage::Logger::I().didLog(" ,CS," + vCONTENT, Storage::Logger::LogType::eLogAppED, true ) // forward declarations class tst_logging; using namespace Gui; namespace Storage { /*! * \brief The Logger class * \details Main logger class that has all the required implementation for logging. * The provided interface is the LOG_EVENT, LOG_DEBUG, LOG_EXPORT defines * and no other methods. * * This object is logging all the registered Denali Messages transactions over the CANBus in CSV format text file. * Logger class currently has 3 types which are Event, Error, Datum. * The Event and Data messages are logged in the csv file with "log" extension * and the UI Application specific errors are logged in a csv file with the "err" extension. * Logs file names format start with current system date by "" format and "_denali.". * The content of the file is depending on each log type and has ",,,,". * Currently, the file name and content formatting can be modified by changing the formatting values in Logger class. * * This class has its own thread for logging. * Also for exporting the log files it uses asynchronous thread calling by QtConcurrent. * It communicated with other classes only with Signal/Slots for thread safety and will notify other classes when the export is done by signals. * * \note * PLEASE BE CAREFUL THIS CLASS IS USING QtConcurrent::run FOR THE EXPORT LOG FILES. * AND ONLY PRIVATE VOID LOG (,) IS CALLING IN POOLED THREAD * PLEASE BE VERY CAREFUL. * ALL THE OTHER CLASSES TO USE THIS CLASS SHOULD ONLY USE LOG_EVENT, LOG_DEBUG * TO DO THE LOGGING */ class Logger : public QObject { Q_OBJECT // Singleton SINGLETON(Logger) // friends friend class ::tst_logging; public : enum LogType { eLogNone = -1, eLogAppED, ///< Application Events and Data : Massages on the CANBus eLogDebug, ///< Application Error : Service logs eLogCloud, ///< CloudSync Log Files : CloudSync debug logs eLogTrtmt, ///< Treatment Rep Files : Treatment Report files eLogType_Count , eLogFiles , }; Q_ENUM(LogType) private: bool _logStorageReady = true; const char *_headerA = "TimeStamp,ID,SubSys,Name,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40"; const char *_headerD = "TimeStamp,Description"; QDir _dir; // The HSSerial is going to be the HD Serial always, // but while booting the logger is initialized first before the HD sends its serial number. // therefore during the bootup and POST that we don't have the serial, will use BootPOST. const QString _logFileNameHDSN_default = "BootPOST" ; QString _logFileNameHDSN = _logFileNameHDSN_default ; // The Mode is the device main states comming from HD_OpMode // this is not exactly the HD_OpModes and will be interpreted in desired cycles. // At the moment we decided to have Standby, Treatment, Disinfection, and I am adding Initial as well. // NoRunning // Treatment // Disinfect // DisinfectHeat // later: the UI message needs to get updated and not using SubMode // DisinfectChem // later: the UI message needs to get updated and not using SubMode // DisinfectChemFlsh // later: the UI message needs to get updated and not using SubMode // DisinfectFlsh // later: the UI message needs to get updated and not using SubMode // "Disinfect" // "Treatment" const char *_logFileNameMode_init = "NoRunning"; QString _logFileNameMode = "" ; QString _logFileNameDate = "" ; QString _logFileNameTime = "" ; typedef QHash TLogData; typedef QHash TLogMaxUse; TLogData _logPathNames; const TLogData _logBasePathNames { { LogType::eLogAppED, Storage::Log_Folder_Application}, { LogType::eLogDebug, Storage::Log_Folder_Service }, { LogType::eLogCloud, Storage::Log_Folder_CloudSync }, { LogType::eLogTrtmt, Storage::Txr_Folder_Treatment }, }; const TLogData _logNames { // Will be used for the logging in the file { LogType::eLogAppED, "Log" }, { LogType::eLogDebug, "Service" }, { LogType::eLogCloud, "Cloud" }, { LogType::eLogTrtmt, "Treatment" }, }; const char * _logFileNamePendingSubExt = "u."; const char * _logFileNameCompressExt = Storage::gzipExt; const TLogData _logFileNameExt { { LogType::eLogAppED, ".log" }, // Application log { LogType::eLogDebug, ".err" }, // Application error { LogType::eLogCloud, ".log" }, // CloudSync debug { LogType::eLogTrtmt, ".txr" }, // Treatment report }; // !!!!!!!!!! IMPORTANT !!!!!!!!!! // be careful with these percentages // please refer to the Storage namespace. const TLogMaxUse _logTypeMaxUsageLimit { { LogType::eLogAppED, Storage::Log_Max_Allowable_AppED_Space_Percent }, { LogType::eLogDebug, Storage::Log_Max_Allowable_Debug_Space_Percent }, { LogType::eLogCloud, Storage::Log_Max_Allowable_Cloud_Space_Percent }, { LogType::eLogTrtmt, Storage::Txr_Max_Allowable_Trtmt_Space_Percent }, }; const char *_fileDateFormat = "yyyyMMdd" ; // date used in the file name const char *_fileTimeFormat = "HHmmss" ; // timestamp in the file const char *_timeFormat = "HH:mm:ss.zzz"; // timestamp in the file const char *_fileSeparator = "_"; // used in filename const char *_separator = ","; bool _enableConsoleOut = false; QString _logFileName = ""; QMutex _logRemoveRunning; LogType _exportLogsType = eLogNone; QFutureWatcher _exportLogsWatcher; QFutureWatcher _removeLogsWatcher; QThread *_thread = nullptr; bool _init = false; public: QString logFileNamePendingSubExt() { return _logFileNamePendingSubExt; } QString logFileNameExt ( LogType vLogType ) { return _logFileNameExt[vLogType]; } QString logPathName(LogType vLogType) { return _logPathNames[vLogType]; } QString logFileNameHDSN() { return _logFileNameHDSN; } QString fileSeparator() { return _fileSeparator; } QString logFileNameHDSN_default() { return _logFileNameHDSN_default; } bool isBootPOST(const QString &vFileName) { return vFileName.contains(_Logger.logFileNameHDSN_default()); } /*! * \brief logFileNameExt_AD * \details Finds the type of the log by log file extention. * \note Since the log file ext of the cloud and UI are the same, this finction will ignore the eLogCloud and will return eLogAppED type instead. * \param vLogExt - the log file extention. * \return */ LogType logFileNameExt_AD ( const QString &vLogExt ) { if ( _logFileNameExt.values().contains(vLogExt) ) { LogType logType = _logFileNameExt.key(vLogExt); if ( logType == eLogCloud) logType = eLogAppED; return logType; } return eLogNone; } LogType logFileLogType (const QString &vFileName, QString &vFilePath) { QString fileName = vFileName; QFileInfo fileInfo(fileName.remove(_logFileNameCompressExt)); QString ext = fileInfo.suffix(); LogType logType = logFileNameExt_AD("." + ext); QString logFilePath; if ( logType != eLogNone ) { logFilePath = Log_Folder_Base + _logBasePathNames[logType]; } if ( fileInfo.exists(logFilePath + vFileName) ) { vFilePath = logFilePath; return logType; } return eLogNone; } public: void enableConsoleOut(bool vEnabled); void postInit(); signals: void didLogPathSet ( Logger::LogType vLogType, const QString &vLogPath ); void didLogIOFail (); void didLogBackup ( const QString &vFileName ); void didRetentionLogCS ( quint8 vPercent ); public slots: bool init(); bool init(QThread &vThread); void quit(); private: void initConnections(); void initThread(QThread &vThread); void quitThread(); bool checkThread(); private: // ----- setting up void checkLogPath (); void setLogBasePath (bool vUseTempPath = false); bool setLogPath (); bool setLogPath (LogType vLogType); public: const QString &logPath(Logger::LogType vLogType); // ----- Export structure private : bool exportList (const GuiStringIndexMap &vExportList, LogType vLogType); bool exportLogs (const GuiStringIndexMap &vExportList); bool exportErrs (const GuiStringIndexMap &vExportList); bool exportTrts (const GuiStringIndexMap &vExportList); public slots: // this slot is thread safe and can be called from outside by LOG_EXPORT. bool concurrentExportIsOk (); bool concurrentExportLogs (const GuiStringIndexMap &vExportList); bool concurrentExportErrs (const GuiStringIndexMap &vExportList); bool concurrentExportTrts (const GuiStringIndexMap &vExportList); void onExportLogs (); signals: void didExportLogs(); void didExportStat(quint32 vIndex, const QString &vFileName, quint8 vPercent); // ----- Remove Old Logs structure private: int removeLogs(LogType vLogType = eLogFiles); private slots: // this slot is thread safe and can be called from outside but preferred not to. bool concurrentRemoveLogs(LogType vLogType = eLogFiles); void onRemoveLogs(); void onCryptSetupMount (bool vPass); void onActionReceive (GuiActionType vAction, const QVariantList &vData); signals: /*! * \brief didRemoveLogs * \details This signal will be emitted mainly for DeviceController to not to emit the signal that Logger is connected to * , while the logging cleanup is in progress, otherwise if the log cleanup takes more that 1sec (current interval) * in the DeviceController then the cleanup will be called again for no good reason. * \param vInProgress - true if the log cleanup is in progress, false otherwise. */ void didRemoveLogs(bool vInProgress); // ----- Available space is low private slots: void onSDCardStateChange(bool vReady, bool vReadonly); void onSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent); void onSettingsPartitionStateChange(bool vReady, bool vReadonly); void onSettingsPartitionSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent); // ----- logging structure private slots: void onLog (const QString &vContent, LogType vLogType, bool vTimestamp); private: void log (const QString &vContent, LogType vLogType, bool vTimestamp); signals: /*! * \brief didLog * \details Notifies the logger on a request for log * \param vContent - content as type of string to be logged * \param vLogType - the type of logging of type Storage::Logger::LogType */ void didLog (const QString &vContent, LogType vLogType, bool vTimestamp); }; }