/*! * * Copyright (c) 2020-2022 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.cpp * \author (last) Behrouz NematiPour * \date (last) 18-Apr-2022 * \author (original) Behrouz NematiPour * \date (original) 26-Aug-2020 * */ #include "Logger.h" // Qt #include #include #include #include #include #include #include // Project #include "DeviceController.h" #include "Threads.h" #include "StorageGlobals.h" #include "MainTimer.h" #include "FileHandler.h" #include "format.h" using namespace Storage; /*! * \brief Logger::Logger * \details Constructor * \param parent - QObject parent owner object. * Qt handles the children destruction by their parent objects life-cycle. */ Logger::Logger(QObject *parent) : QObject(parent) { _logFileNamePrefix = QFileInfo(qApp->applicationFilePath()).baseName(); } /*! * \brief Logger::init * \details Initializes the Class. * \return False if it has been called before. */ bool Logger::init() { if ( _init ) return false; _init = true; // runs in thread checkLogPath(); initConnections(); return true; } /*! * \brief Logger::init * \details Initialized the Class by calling the init() method first * And initializes the thread vThread by calling initThread * on success init(). * \param vThread - the thread * \return returns the return value of the init() method */ bool Logger::init(QThread &vThread) { // disabled coco begin validated: Application is not running in multi-threaded mode for testing // it has been tested and works perfectly fine in normal run. if ( ! init() ) return false; initThread(vThread); return true; } // disabled coco end /*! * \brief Logger::postInit * \details these are the actions that should be executed after the logger has been initialized * but also cannot be done in the Logger class since it is not immediately moving to the thread. */ void Logger::postInit() { if ( ! checkThread() ) return; ADD_EVENT_HEADER; LOG_DEBUG(tr("%1 Initialized").arg(metaObject()->className())); LOG_DEBUG(tr("Application %1 Started").arg(qApp->applicationName())); LOG_EVENT_UI(qApp-> applicationVersion()); } /*! * \brief Logger::checkThread * \details Checks the current thread to be the logger thread * otherwise sends out a message to the console and returns false. * \return false if not logger thread */ bool Logger::checkThread() { bool ok = true; if ( this->thread() != &Threads::_Logger_Thread ) { qDebug() << " ----- " << "The main Log function rejection: The Logger is not initialized for proper use"; ok = false; } return ok; } /*! * \brief Logger quit * \details quits the class * Calls quitThread */ void Logger::quit() { // disabled coco begin validated: Application termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. quitThread(); // validated } // disabled coco end /*! * \brief Logger::initConnections * \details Initializes the required signal/slot connection between this class and other objects * to be able to communicate. * \note No connection has been defined yet. */ void Logger::initConnections() { connect(&_exportLogsWatcher, SIGNAL(finished ()), this , SLOT(onExportLogs())); connect(this, SIGNAL(didLog(QString,LogType,bool)), this, SLOT( onLog(QString,LogType,bool))); connect(&_MainTimer, SIGNAL( didDateChange ()), this , SLOT( concurrentRemoveLogs())); connect(&_removeLogsWatcher, SIGNAL(finished ()), this , SLOT(onRemoveLogs())); connect(&_DeviceController, SIGNAL( didSDCardSpaceChange(bool, qint64, qint64, quint8)), this , SLOT( onSDCardSpaceChange(bool, qint64, qint64, quint8))); } /*! * \brief Logger::initThread * \details Moves this object into the thread vThread. * And checks that this method is called from main thread. * Also connects quitThread to application aboutToQuit. * \param vThread - the thread */ void Logger::initThread(QThread &vThread) { // disabled coco begin validated: Application is not running in multi-threaded mode for testing // it has been tested and works perfectly fine in normal run. // runs in main thread Q_ASSERT_X(QThread::currentThread() == qApp->thread() , __func__, "The Class initialization must be done in Main Thread" ); _thread = &vThread; _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); _thread->start(); moveToThread(_thread); } // disabled coco end /*! * \brief Logger::quitThread * \details Moves this object to main thread to be handled by QApplication * And to be destroyed there. */ void Logger::quitThread() { // disabled coco begin validated: Application termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. if (! _thread) return; // runs in thread moveToThread(qApp->thread()); // validated } // disabled coco end void Logger::onLog(const QString &vContent, LogType vLogType, bool vTimestamp) { log(vContent, vLogType, vTimestamp); } /*! * \brief Logger::checkLogPath * \details Sets the log paths and creates them if didn't exist. */ void Logger::checkLogPath() { setLogBasePath(); // try to use /media/sd_card on device // disabled coco begin validated: It can only happen if the file system is read-only for any reason. // it has been tested and works perfectly fine in normal run. if (! setLogPath()) { // check and create log folders & if unsuccessful then // disabled coco end setLogBasePath(true); // try to use application folder setLogPath ( ); // check and create log folders // Note: it may require to check for write access regarding device setup } } /*! * \brief Logger::setLogBasePath * \details Tries to the set the log path to the default log path (Log_Base_Path_Name) * Will set the application folder as the base log path if cannot set the log path to the default. * Will log the event in that case. * \param vUseApplicationDirPath */ void Logger::setLogBasePath(bool vUseApplicationDirPath) { if (vUseApplicationDirPath) { _dir.setPath(qApp->applicationDirPath()); // NOTE: Do not use LOG_XXXXX, At this moment Logger has not been initialized yet qDebug() << QString("Application path used for events logging (%1)").arg(_dir.path()); } else { _dir.setPath(SDCard_Base_Path_Name); } } /*! * \brief Logger::setLogPath * \details set the log path for each of the Datum, Event, Error log types * \return False if can not st the log paths. */ bool Logger::setLogPath() { bool ok = true; // disabled coco begin validated: Has been tested manually, this needs user interaction to check the file system if ( ok && ! setLogPath(LogType::eLogEvent) ) ok = false; if ( ok && ! setLogPath(LogType::eLogDatum) ) ok = false; if ( ok && ! setLogPath(LogType::eLogDebug) ) ok = false; if ( ok && ! setLogPath(LogType::eLogTrtmt) ) ok = false; return ok; } // disabled coco end /*! * \brief Logger::setLogPath * \details Sets the log path for the log type vLogType * Creates the folder if not exists. * \param vLogType - log type * \return returns false if the path does not exist and folder cannot be created. */ bool Logger::setLogPath(LogType vLogType) { bool ok = false; switch (vLogType) { case LogType::eLogDebug: _logPathNames[vLogType] = _dir.path() + "/" + _logBasePathNames[vLogType]; break; default: _logPathNames[vLogType] = _dir.path() + "/" + _logBasePathNames[vLogType]; break; } ok = FileHandler::makeFolder(_logPathNames[vLogType]); if ( ok ) emit didLogPathSet(vLogType, _logPathNames[vLogType]); return ok; } /*! * \brief Logger::log * \details Logs the content vContent in log type of vLogType. * \param vContent - Log content * \param vLogType - Log type * \note This method is not thread-safe so is private and needs to be called by concurrentLog * Which uses QtConcurrent::run to run in thread and thread-safe. */ void Logger::log(const QString &vContent, LogType vLogType, bool vTimestamp) { if ( ! checkThread() ) return; static QString date; QString mContent; QString currentDate = QDate::currentDate().toString(_dateFormat); if (date != currentDate) { if (!date.isEmpty()) { mContent = _headerE; mContent += "\r\n" ; } date = currentDate; } QString fileName = date + _dateSeparator + _logFileNamePrefix; switch (vLogType) { case LogType::eLogEvent: case LogType::eLogDatum: case LogType::eLogDebug: // case LogType::eLogTrtmt: // this type of log will never happen here. Only put here to make sure it is intentional. fileName += _logFileNameExt[vLogType]; break; default: fileName += _logFileNameExt[eLogDebug]; LOG_DEBUG(QString("Incorrect type of logging %1").arg(vLogType)); } if ( vTimestamp ) mContent = QTime::currentTime().toString(_timeFormat) + _separator; QString logPrefix = _logPrefix[vLogType]; if (vTimestamp && ! logPrefix.isEmpty()) { mContent += logPrefix; mContent += _separator; } mContent += vContent; // some messages like the version having the U08(uchar) parameters which converts to '\0' and causes problems in reading the log file. mContent.replace('\0', "0"); QString logPathName = _logPathNames[vLogType]; if (logPathName.isEmpty()) logPathName = _logPathNames[eLogDebug]; _logFileName = logPathName + fileName; FileHandler::write(_logFileName, mContent + "\r\n", true); // disabled coco begin validated: This code is only for debugging purposes and had been tested manually. if (_enableConsoleOut) { qDebug().noquote() << mContent; } // disabled coco end } /*! * \brief Logger::exportLogs * \details Exports the log files from log folder (Storage::Log_Base_Path_Name_Location) * into USB drive folder (Storage::USB_Mount_Point) * \return true if at least one file has been exported */ bool Logger::exportLogs() { // qDebug() << " ~~~~~~~~~~ " << QThread::currentThread()->objectName(); int result = 0; static QString mOSource; QString mDestination = USB_Mount_Point; for ( const auto &iType : { eLogEvent, eLogDatum /*, eLogDebug*/ } ) { QString mCSource = _logPathNames[iType]; // if the event and datum are mixed (mOSource == mCSource) in one file then no need to go over the same files in same folder and do it again. if (mOSource != mCSource) { mOSource = mCSource; // Copy Folder result = FileHandler::copyFolder( mCSource, mDestination); } } mOSource = ""; return result >= 0; // refer to QProcess::execute(hit F1 on execute) doc. } /*! * \brief Logger::concurrentExportLogs * \details Export logs scheduler. * \return always returns true for now. * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. */ bool Logger::concurrentExportLogs() { // disabled coco begin validated: This needs user interaction to export to USB device // has been tested manually LOG_DEBUG("Export Logs Start"); QFuture future = QtConcurrent::run(this, &Logger::exportLogs); _exportLogsWatcher.setFuture(future); return true; } // disabled coco end /*! * \brief Logger::onExportLogs * \details Export log notification slot which logs result of export. */ void Logger::onExportLogs() { // disabled coco begin validated: This needs user interaction to export to USB device // has been tested manually LOG_DEBUG(QString("Export Logs Ended: %1").arg(_exportLogsWatcher.result())); emit didExportLogs(); } // disabled coco end /*! * \brief Logger::removeLogs * \details Remove old logs by iterating in the log/service folders and look for expired logs. * \return count file(s) have been removed. */ int Logger::removeLogs() { // disabled coco begin validated: This needs user interaction to check the old files deleted // Storage::FileHandler::find("/media/denali/0CAA-40C1/log/", {"*.err"}, 15); return 0; // has been tested manually LOG_DEBUG(tr("Initializing log clean up")); static QString mOSource; static QString mOExtension; int removeCount = 0; QString mLogFileFilter; for ( const auto &iType : { eLogEvent , eLogDatum , eLogDebug , eLogTrtmt } ) { QString mCSource = _logPathNames [iType]; QString mCExtension = _logFileNameExt[iType]; // if the event and datum are mixed (mOSource == mCSource && mCExtension == mOExtension) in one file then no need to go over the same files in same folder and do it again. if (mOSource != mCSource || mCExtension != mOExtension) { mOSource = mCSource; mOExtension = mCExtension; // Remove Logs // TODO: It appeared to me that the filter on extension may not be necessary, since the folder of each type is different. // NOTE: The filter on the extension comes from the idea that in the same folder(log) we may want to separate the event(log) from datum(dat) with different usage percentage. #ifdef MIXED_EVENT_DATUM mLogFileFilter = QString("*%1").arg(".*"); #else mLogFileFilter = QString("*%1").arg(mCExtension); #endif // DEBUG: qDebug() << "@" << mCSource << mLogFileFilter << mCExtension << iType << _logTypeMaxUsageLimit[iType]; QFileInfoList fileInfoList = FileHandler::find(mCSource, {mLogFileFilter}, _logTypeMaxUsageLimit[iType]); removeCount = fileInfoList.count(); // qDebug() << "@" << removeCount << fileInfoList; if (removeCount) { LOG_DEBUG(QString("Removing %1 logs of type (%2) more than %3% limit from folder %4") .arg(removeCount) .arg(mCExtension) .arg(_logTypeMaxUsageLimit[iType]) .arg(mCSource)); for (const auto &info: fileInfoList) { if (info.lastModified().date() == QDate().currentDate()) { LOG_DEBUG(QString("Current day log %1 cannot be deleted").arg(info.fileName())); } else { QString mFileName = mCSource + info.fileName(); // DEBUG: qDebug() << "#" << mFileName; bool ok = QFile::remove(mFileName); if (ok) { LOG_DEBUG(QString("Removing %1 succeeded").arg(mFileName)); } else { LOG_DEBUG(QString("Removing %1 failed" ).arg(mFileName)); } } } } else { LOG_DEBUG("No log file is deleted for " + mCExtension); } } } mOSource = ""; return removeCount; } // disabled coco end /*! * \brief Logger::concurrentRemoveLogs * \details remove logs scheduler. * \return always returns true for now. * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. */ bool Logger::concurrentRemoveLogs() { // disabled coco begin validated: This needs user interaction to check the old files deleted // has been tested manually LOG_DEBUG("Remove Logs Starting"); emit didRemoveLogs(true); QFuture mFuture = QtConcurrent::run(this, &Logger::removeLogs); _removeLogsWatcher.setFuture(mFuture); return true; } // disabled coco end /*! * \brief Logger::onRemoveLogs * \details Remove old logs notification slot which logs result of remove. */ void Logger::onRemoveLogs() { // disabled coco begin validated: This needs user interaction to export to USB device // has been tested manually LOG_DEBUG(tr("Remove Logs Ended: %1").arg(_removeLogsWatcher.result())); emit didRemoveLogs(false); } // disabled coco end /*! * \brief Logger::onSDCardSpaceChange * \details SD Card storage space parameter change slot. * This slot when called is calling the function concurrentRemoveLogs, * if percent of available space vPercent is less than Storage::Available_Space_Percent, * if the SD Card is ready (vReady is true) * \param vReady - The SD Card is Ready * \param vTotal - Total storage space on the SD Card * \param vAvailable - Available storage space on the SD Card * \param vPercent - Percent of available storage space on the SD Card */ void Logger::onSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent) { // disabled coco begin validated: This needs user interaction to change the SD card files system. // has been tested manually Q_UNUSED(vTotal ) Q_UNUSED(vAvailable ) if ( ! vReady ) return; // DEBUG: qDebug() << vPercent << Storage::Available_Space_Percent; if ( Storage::Log_Min_Available_Total_Space_IsLow(vPercent) ) { concurrentRemoveLogs(); } } // disabled coco end /*! * \brief Logger::enableConsoleOut * \details Enables or Disables the console output and logs the status * \param vEnabled - Enable console output if true */ void Logger::enableConsoleOut(bool vEnabled) { // disabled coco begin validated: This code meant to be used only for debugging and tested manually if (_enableConsoleOut == vEnabled) return; _enableConsoleOut = vEnabled; if (_enableConsoleOut) { LOG_DEBUG("Console out Logging enabled"); } else { LOG_DEBUG("Console out Logging disabled"); } } // disabled coco end /*! * \brief Logger::logPath * \details The accessor of the log path for each log type. * \param vLogType - type of the log * \return the log path of the log type vLogType as string * \sa Logger::LogType */ const QString &Logger::logPath(Logger::LogType vLogType) { return _logPathNames[vLogType]; }