/*! * * Copyright (c) 2019-2020 Diality Inc. - All Rights Reserved. * \copyright \n * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, \n * IN PART OR IN WHOLE, \n * WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. \n * * \file logger.cpp * \date 2019/09/30 * \author Behrouz NematiPour * */ #include "logger.h" // Qt #include #include #include #include #include #include // Project #include "DriveWatcher.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(); LOG_EVENT("UI," + tr("%1 Initialized").arg(metaObject()->className())); 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) { // 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; } // coco end /*! * \brief Logger quit * \details quits the class * Calls quitThread */ void Logger::quit() { // coco begin validated: Application termination is not correctly done in coco!!! // it has been tested and works perfectly fine in normal run. quitThread(); // validated } // 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)), this, SLOT( onLog(QString,LogType))); connect(&_MainTimer, SIGNAL( didDateChange ()), this , SLOT( concurrentRemoveLogs())); connect(&_removeLogsWatcher, SIGNAL(finished ()), this , SLOT(onRemoveLogs())); connect(&_DriveWatcher, 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) { // 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); } // coco end /*! * \brief Logger::quitThread * \details Moves this object to main thread to be handled by QApplicaiton * And to be destroyed there. */ void Logger::quitThread() { // 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 } // coco end void Logger::onLog(const QString &vContent, LogType vLogType) { log(vContent,vLogType); } /*! * \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 // coco begin validated: It can only happen if the file system is readonly for any reson. // it has been tested and works perfectly fine in normal run. if (! setLogPath()) { // check and create log folders & if unsuccessful then // 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 can't 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()); // Don't 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; if ( ok && ! setLogPath(LogType::eLogDebug) ) ok = false; if ( ok && ! setLogPath(LogType::eLogEvent) ) ok = false; if ( ok && ! setLogPath(LogType::eLogDatum) ) ok = false; return ok; } /*! * \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 doesn't exist and folder can't be created. */ bool Logger::setLogPath(LogType vLogType) { bool ok = false; switch (vLogType) { case LogType::eLogDebug: _logPathNames[vLogType] = qApp->applicationDirPath() + "/" + _logBasePathNames[vLogType]; break; default: _logPathNames[vLogType] = _dir.path() + "/" + _logBasePathNames[vLogType]; break; } ok = FileHandler::makeFolder(_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) { QString date = QDate::currentDate().toString(_dateFormat); QString fileName = date + _dateSeparator + _logFileNamePrefix; switch (vLogType) { case LogType::eLogEvent: case LogType::eLogDatum: case LogType::eLogDebug: fileName += _logFileNameExt[vLogType]; break; default: fileName += _logFileNameExt[eLogDebug]; LOG_DEBUG(QString("Incorrect type of logging %1").arg(vLogType)); } QString mContent = QTime::currentTime().toString(_timeFormat) + _separator; QString logPrefix = _logPrefix[vLogType]; if ( ! logPrefix.isEmpty()) { mContent += logPrefix; mContent += _separator; } mContent += vContent; QString logPathName = _logPathNames[vLogType]; if (logPathName.isEmpty()) logPathName = _logPathNames[eLogDebug]; _logFileName = logPathName + fileName; FileHandler::write(_logFileName, mContent + "\r\n", true); if (vLogType == eLogDebug) { #ifdef QT_DEBUG //mContent.prepend("\033[1;31m --- @ --- \033[0m"); mContent.prepend(" @ "); #endif qDebug().noquote() << mContent; } } bool Logger::exportLogs() { int result = 0; static QString mOSource; QString mDestination = USB_Mount_Point; for ( const auto &iType : { eLogEvent , eLogDatum } ) { QString mCSource = _logPathNames[iType]; 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 Exports the log files from log folder (Storage::Log_Base_Path_Name_Location) * into USB drive folder (Storage::USB_Mount_Point) * \return always returns true for now. * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. */ bool Logger::concurrentExportLogs() { // 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; } // coco end void Logger::onExportLogs() { // 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(); } // 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() { // coco begin validated: This needs user interaction to check the old files deleted // has been tested manually static QString mOSource; int countRemoved = 0; QStringList mLogFileFilter; QDate mOlderThan ; for ( const auto &iType : { eLogEvent , eLogDatum , eLogDebug } ) { QString mCSource = _logPathNames[iType]; if (mOSource != mCSource) { mOSource = mCSource; // Remove Logs if (_logTypeExpiryDay.value(iType)) { mOlderThan = QDate().currentDate().addDays( _logTypeExpiryDay.value(iType) * -1 /*Please Notice (-1)*/ ); mLogFileFilter = Format::toStringList(_logFileNameExt .values(iType), true, "*"); LOG_DEBUG(QString("Removing logs older than %1 from folder %2").arg(mOlderThan.toString("yyyy-MM-dd")).arg(mCSource)); countRemoved += FileHandler::removeFiles({ mCSource }, mLogFileFilter, mOlderThan); } else { LOG_DEBUG("Current day logs cannot be deletet"); } } } mOSource = ""; return countRemoved; } // coco end /*! * \brief Logger::concurrentRemoveLogs * \details * * \return always returns true for now. * \note This method uses QtConcurrent run to execute the FileHandler copyFolder method. */ bool Logger::concurrentRemoveLogs() { // coco begin validated: This needs user interaction to check the old files deleted // has been tested manually LOG_DEBUG("Remove Logs Start"); QFuture mFuture = QtConcurrent::run(this,&Logger::removeLogs); _removeLogsWatcher.setFuture(mFuture); return true; } // coco end void Logger::onRemoveLogs() { // coco begin validated: This needs user interaction to export to USB device // has been tested manually LOG_DEBUG(QString("Remove Logs Ended: %1").arg(_removeLogsWatcher.result())); emit didRemoveLogs(); } void Logger::onSDCardSpaceChange(bool vReady, qint64 vTotal, qint64 vAvailable, quint8 vPercent) { if (vPercent < _availableSpacePercent ) { concurrentRemoveLogs(); qDebug() << 0 << vReady << vTotal << vAvailable << vPercent; } else { qDebug() << 1 << vReady << vTotal << vAvailable << vPercent; } } // coco end