utilities/src/logger.cpp

00001 /********************************************************************************
00002  *  FARSA Utilities Library                                                     *
00003  *  Copyright (C) 2007-2011 Gianluca Massera <emmegian@yahoo.it>                *
00004  *                                                                              *
00005  *  This program is free software; you can redistribute it and/or modify        *
00006  *  it under the terms of the GNU General Public License as published by        *
00007  *  the Free Software Foundation; either version 2 of the License, or           *
00008  *  (at your option) any later version.                                         *
00009  *                                                                              *
00010  *  This program is distributed in the hope that it will be useful,             *
00011  *  but WITHOUT ANY WARRANTY; without even the implied warranty of              *
00012  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               *
00013  *  GNU General Public License for more details.                                *
00014  *                                                                              *
00015  *  You should have received a copy of the GNU General Public License           *
00016  *  along with this program; if not, write to the Free Software                 *
00017  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  *
00018  ********************************************************************************/
00019 
00020 #include "logger.h"
00021 #include <QFile>
00022 #include <QTextStream>
00023 #include <QTextEdit>
00024 #include <QDateTime>
00025 #include <iostream>
00026 #include <QObject>
00027 #include <QEvent>
00028 #include <QApplication>
00029 #include <QMutex>
00030 #include <QMutexLocker>
00031 #include <QMessageBox>
00032 
00033 namespace farsa {
00034 
00035 namespace {
00036     //--- This and the following are utilities class for updating textEdit into the GUI Thread
00037     class TextToAppend : public QEvent {
00038     public:
00039         TextToAppend( QString text, QString level, QString pureMessage ) :
00040             QEvent((Type)type),
00041             text(text),
00042             level(level),
00043             pureMessage(pureMessage) { };
00044         QString getText() {
00045             return text;
00046         };
00047         QString getLevel() {
00048             return level;
00049         };
00050         QString getPureMessage() {
00051             return pureMessage;
00052         };
00053     private:
00054         static int type;
00055         QString text;
00056         QString level;
00057         QString pureMessage;
00058     };
00059     int TextToAppend::type = QEvent::registerEventType();
00060 
00061     class TextEditUpdater : public QObject {
00062     public:
00063         TextEditUpdater() :
00064             QObject(),
00065             textEdit(NULL)
00066         {
00067         }
00068 
00069         void setTextEditToUpdate(QTextEdit* e)
00070         {
00071             textEdit = e;
00072         }
00073 
00074         bool hasTextEdit() const
00075         {
00076             return (textEdit != NULL);
00077         }
00078 
00079     protected:
00080         virtual void customEvent( QEvent* event ) {
00081             // If textEdit is NULL we will crash here (this should never happend)
00082             TextToAppend* tevent = dynamic_cast<TextToAppend*>( event );
00083             if ( tevent ) {
00084                 textEdit->append( tevent->getText() );
00085                 textEdit->moveCursor( QTextCursor::End );
00086                 textEdit->moveCursor( QTextCursor::StartOfLine );
00087                 if ( tevent->getLevel() == "ERROR" ) {
00088                     QMessageBox::critical( 0, "Error from Component", tevent->getPureMessage() );
00089                 }
00090                 tevent->accept();
00091             } else {
00092                 QObject::customEvent( event );
00093             }
00094         };
00095 
00096         QTextEdit* textEdit;
00097     };
00098 
00099     // This class contains the core functionalities for logging. It is implemented
00100     // as a singleton to have the correct initialization of all needed variables
00101     // when the Logger is used for the first time
00102     class LoggerImplementation
00103     {
00104     public:
00105         // Returns the only instance of this class
00106         static LoggerImplementation& getInstance();
00107 
00108         void info(QString msg);
00109 
00110         void warning(QString msg);
00111 
00112         void error(QString msg);
00113 
00114         void setQTextEdit(QTextEdit* textedit);
00115 
00116         void enableStdOut(bool enabled);
00117 
00118         void setLogLevel(Logger::LogLevel level);
00119 
00120         void setLogFilename(QString logfile);
00121 
00122     private:
00123         // Constructor
00124         LoggerImplementation();
00125 
00126         // Destructor
00127         ~LoggerImplementation();
00128 
00129         // This is the main function for logging. This function is thread-safe
00130         void logIt(QString level, QString msg);
00131 
00132         bool stdOut;
00133         QFile* file;
00134         QTextStream* fileStream;
00135         QTextStream* outStream;
00136         TextEditUpdater* textEditUpdater;
00137         Logger::LogLevel logLevel;
00138         // The semaphores protecting the streams. The logger can be called from multiple threads simultaneously
00139         QMutex outStreamMutex;
00140         QMutex fileStreamMutex;
00141 
00142     private:
00143         // Copy constructor, not implemented
00144         LoggerImplementation(LoggerImplementation&);
00145 
00146         // Copy operator, not implemented
00147         LoggerImplementation& operator=(LoggerImplementation&);
00148     };
00149 
00150     LoggerImplementation& LoggerImplementation::getInstance()
00151     {
00152         // The meyer singleton
00153         static LoggerImplementation loggerImplementation;
00154 
00155         return loggerImplementation;
00156     }
00157 
00158     void LoggerImplementation::info(QString msg)
00159     {
00160         if (logLevel <= Logger::Default) {
00161             logIt("INFO", msg);
00162         }
00163     }
00164 
00165     void LoggerImplementation::warning(QString msg)
00166     {
00167         if (logLevel <= Logger::Warning) {
00168             logIt("WARNING", msg);
00169         }
00170     }
00171 
00172     void LoggerImplementation::error(QString msg)
00173     {
00174         if (logLevel <= Logger::Quiet) {
00175             logIt("ERROR", msg);
00176         }
00177     }
00178 
00179     void LoggerImplementation::setQTextEdit(QTextEdit* textedit)
00180     {
00181         textEditUpdater->setTextEditToUpdate(textedit);
00182     }
00183 
00184     void LoggerImplementation::enableStdOut(bool enabled)
00185     {
00186         stdOut = enabled;
00187     }
00188 
00189     void LoggerImplementation::setLogLevel(Logger::LogLevel level)
00190     {
00191         logLevel = level;
00192     }
00193 
00194     void LoggerImplementation::setLogFilename(QString logfile)
00195     {
00196         QFile* oldfile = file;
00197         file = new QFile(logfile);
00198         fileStream->setDevice(file);
00199         //--- the delete on the File has to be after the setDevice on QTextStream
00200         delete oldfile;
00201     }
00202 
00203     LoggerImplementation::LoggerImplementation() :
00204         stdOut(true),
00205         file(NULL),
00206         fileStream(new QTextStream()),
00207         outStream(new QTextStream(stdout)),
00208         textEditUpdater(new TextEditUpdater()),
00209         logLevel(Logger::Default),
00210         outStreamMutex(),
00211         fileStreamMutex()
00212     {
00213     }
00214 
00215     LoggerImplementation::~LoggerImplementation()
00216     {
00217         delete textEditUpdater;
00218         delete outStream;
00219         delete fileStream;
00220         delete file;
00221 
00222         // These lines are here to have a "clean" crash if somebody tries to access
00223         // the logger after returning from the main function
00224         textEditUpdater = NULL;
00225         outStream = NULL;
00226         fileStream = NULL;
00227         file = NULL;
00228     }
00229 
00230     //--- this is the main function for logging. This function is thread-safe
00231     void LoggerImplementation::logIt(QString level, QString msg)
00232     {
00233         QString logtmpl("[%1] %2: %3");
00234         QString timestamp = QDateTime::currentDateTime().toString( "dd-MM-yyyy hh:mm:ss.zzz" );
00235         QString logmsg = logtmpl.arg( timestamp ).arg( level, -10 ).arg( msg );
00236         if ( stdOut ) {
00237             QMutexLocker locker(&outStreamMutex);
00238 
00239             (*outStream) << logmsg << "\n";
00240             outStream->flush();
00241         }
00242         if ( textEditUpdater->hasTextEdit() ) {
00243             // No semaphore here as we use the thread-safe postEvent function
00244             QString color = "#ffffff";
00245             if ( level == "INFO" ) {
00246                 color = "#afeeee";
00247             } else if ( level == "WARNING" ) {
00248                 color = "#f0e68c";
00249             } else if ( level == "ERROR" ) {
00250                 color = "#ff4500";
00251             }
00252             // --- here the postEvent is used because it is not possible to modify directly the content of
00253             //     textEdit because it is not thread-safe. In fact, this function might be called from multiple
00254             //     threads and outside the GUI thread
00255             qApp->postEvent( textEditUpdater, new TextToAppend( QString("<pre style=\"margin-top: 0px; margin-bottom: 0px; color: ")+color+";\">"+logmsg+QString("</pre>"), level, msg ) );
00256             //textEdit->append( QString("<pre style=\"margin-top: 0px; margin-bottom: 0px; color: ")+color+";\">"+logmsg+QString("</pre>") );
00257             //textEdit->moveCursor( QTextCursor::End );
00258             //textEdit->moveCursor( QTextCursor::StartOfLine );
00259         }
00260         if ( fileStream && fileStream->device() ) {
00261             QMutexLocker locker(&fileStreamMutex);
00262 
00263             (*fileStream) << logmsg << "\n";
00264             fileStream->flush();
00265         }
00266     }
00267 } //end anonymous namespace for LoggerImplementation class
00268 
00269 void Logger::info(QString msg)
00270 {
00271     LoggerImplementation::getInstance().info(msg);
00272 }
00273 
00274 void Logger::warning(QString msg)
00275 {
00276     LoggerImplementation::getInstance().warning(msg);
00277 }
00278 
00279 void Logger::error(QString msg)
00280 {
00281     LoggerImplementation::getInstance().error(msg);
00282 }
00283 
00284 void Logger::setLogFilename(QString logfile)
00285 {
00286     LoggerImplementation::getInstance().setLogFilename(logfile);
00287 }
00288 
00289 void Logger::setQTextEdit(QTextEdit* textedit)
00290 {
00291     LoggerImplementation::getInstance().setQTextEdit(textedit);
00292 }
00293 
00294 void Logger::enableStdOut(bool enabled)
00295 {
00296     LoggerImplementation::getInstance().enableStdOut(enabled);
00297 }
00298 
00299 void Logger::setLogLevel(LogLevel level) {
00300     LoggerImplementation::getInstance().setLogLevel(level);
00301 }
00302 
00303 QString Logger::logLevelToString(Logger::LogLevel level)
00304 {
00305     QString str = "unknown";
00306 
00307     switch(level)
00308     {
00309         case Default:
00310             str = "Default";
00311             break;
00312         case Warning:
00313             str = "Warning";
00314             break;
00315         case Quiet:
00316             str = "Quiet";
00317             break;
00318         case Superquiet:
00319             str = "Superquiet";
00320             break;
00321     }
00322 
00323     return str;
00324 }
00325 
00326 Logger::LogLevel Logger::stringToLogLevel(QString level)
00327 {
00328     LogLevel l = Default;
00329 
00330     if (level.toUpper() == "DEFAULT") {
00331         l = Default;
00332     } else if (level.toUpper() == "WARNING") {
00333         l = Warning;
00334     } else if (level.toUpper() == "QUIET") {
00335         l = Quiet;
00336     } else if (level.toUpper() == "SUPERQUIET") {
00337         l = Superquiet;
00338     }
00339 
00340     return l;
00341 }
00342 
00343 } // end namespace farsa