logger.cpp
1 /********************************************************************************
2  * FARSA Utilities Library *
3  * Copyright (C) 2007-2011 Gianluca Massera <emmegian@yahoo.it> *
4  * *
5  * This program is free software; you can redistribute it and/or modify *
6  * it under the terms of the GNU General Public License as published by *
7  * the Free Software Foundation; either version 2 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License *
16  * along with this program; if not, write to the Free Software *
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
18  ********************************************************************************/
19 
20 #include "logger.h"
21 #include <QFile>
22 #include <QTextStream>
23 #include <QTextEdit>
24 #include <QDateTime>
25 #include <iostream>
26 #include <QObject>
27 #include <QEvent>
28 #include <QApplication>
29 #include <QMutex>
30 #include <QMutexLocker>
31 #include <QMessageBox>
32 
33 namespace farsa {
34 
35 namespace {
36  //--- This and the following are utilities class for updating textEdit into the GUI Thread
37  class TextToAppend : public QEvent {
38  public:
39  TextToAppend( QString text, QString level, QString pureMessage ) :
40  QEvent((Type)type),
41  text(text),
42  level(level),
43  pureMessage(pureMessage) { };
44  QString getText() {
45  return text;
46  };
47  QString getLevel() {
48  return level;
49  };
50  QString getPureMessage() {
51  return pureMessage;
52  };
53  private:
54  static int type;
55  QString text;
56  QString level;
57  QString pureMessage;
58  };
59  int TextToAppend::type = QEvent::registerEventType();
60 
61  class TextEditUpdater : public QObject {
62  public:
63  TextEditUpdater() :
64  QObject(),
65  textEdit(NULL)
66  {
67  }
68 
69  void setTextEditToUpdate(QTextEdit* e)
70  {
71  textEdit = e;
72  }
73 
74  bool hasTextEdit() const
75  {
76  return (textEdit != NULL);
77  }
78 
79  protected:
80  virtual void customEvent( QEvent* event ) {
81  // If textEdit is NULL we will crash here (this should never happend)
82  TextToAppend* tevent = dynamic_cast<TextToAppend*>( event );
83  if ( tevent ) {
84  textEdit->append( tevent->getText() );
85  textEdit->moveCursor( QTextCursor::End );
86  textEdit->moveCursor( QTextCursor::StartOfLine );
87  if ( tevent->getLevel() == "ERROR" ) {
88  QMessageBox::critical( 0, "Error from Component", tevent->getPureMessage() );
89  }
90  tevent->accept();
91  } else {
92  QObject::customEvent( event );
93  }
94  };
95 
96  QTextEdit* textEdit;
97  };
98 
99  // This class contains the core functionalities for logging. It is implemented
100  // as a singleton to have the correct initialization of all needed variables
101  // when the Logger is used for the first time
102  class LoggerImplementation
103  {
104  public:
105  // Returns the only instance of this class
106  static LoggerImplementation& getInstance();
107 
108  void info(QString msg);
109 
110  void warning(QString msg);
111 
112  void error(QString msg);
113 
114  void setQTextEdit(QTextEdit* textedit);
115 
116  void enableStdOut(bool enabled);
117 
118  void setLogLevel(Logger::LogLevel level);
119 
120  void setLogFilename(QString logfile);
121 
122  private:
123  // Constructor
124  LoggerImplementation();
125 
126  // Destructor
127  ~LoggerImplementation();
128 
129  // This is the main function for logging. This function is thread-safe
130  void logIt(QString level, QString msg);
131 
132  bool stdOut;
133  QFile* file;
134  QTextStream* fileStream;
135  QTextStream* outStream;
136  TextEditUpdater* textEditUpdater;
137  Logger::LogLevel logLevel;
138  // The semaphores protecting the streams. The logger can be called from multiple threads simultaneously
139  QMutex outStreamMutex;
140  QMutex fileStreamMutex;
141 
142  private:
143  // Copy constructor, not implemented
144  LoggerImplementation(LoggerImplementation&);
145 
146  // Copy operator, not implemented
147  LoggerImplementation& operator=(LoggerImplementation&);
148  };
149 
150  LoggerImplementation& LoggerImplementation::getInstance()
151  {
152  // The meyer singleton
153  static LoggerImplementation loggerImplementation;
154 
155  return loggerImplementation;
156  }
157 
158  void LoggerImplementation::info(QString msg)
159  {
160  if (logLevel <= Logger::LogAll) {
161  logIt("INFO", msg);
162  }
163  }
164 
165  void LoggerImplementation::warning(QString msg)
166  {
167  if (logLevel <= Logger::Warning) {
168  logIt("WARNING", msg);
169  }
170  }
171 
172  void LoggerImplementation::error(QString msg)
173  {
174  if (logLevel <= Logger::Quiet) {
175  logIt("ERROR", msg);
176  }
177  }
178 
179  void LoggerImplementation::setQTextEdit(QTextEdit* textedit)
180  {
181  textEditUpdater->setTextEditToUpdate(textedit);
182  }
183 
184  void LoggerImplementation::enableStdOut(bool enabled)
185  {
186  stdOut = enabled;
187  }
188 
189  void LoggerImplementation::setLogLevel(Logger::LogLevel level)
190  {
191  logLevel = level;
192  }
193 
194  void LoggerImplementation::setLogFilename(QString logfile)
195  {
196  QFile* oldfile = file;
197  file = new QFile(logfile);
198  fileStream->setDevice(file);
199  //--- the delete on the File has to be after the setDevice on QTextStream
200  delete oldfile;
201  }
202 
203  LoggerImplementation::LoggerImplementation() :
204  stdOut(true),
205  file(NULL),
206  fileStream(new QTextStream()),
207  outStream(new QTextStream(stdout)),
208  textEditUpdater(new TextEditUpdater()),
209  logLevel(Logger::LogAll),
210  outStreamMutex(),
211  fileStreamMutex()
212  {
213  }
214 
215  LoggerImplementation::~LoggerImplementation()
216  {
217  delete textEditUpdater;
218  delete outStream;
219  delete fileStream;
220  delete file;
221 
222  // These lines are here to have a "clean" crash if somebody tries to access
223  // the logger after returning from the main function
224  textEditUpdater = NULL;
225  outStream = NULL;
226  fileStream = NULL;
227  file = NULL;
228  }
229 
230  //--- this is the main function for logging. This function is thread-safe
231  void LoggerImplementation::logIt(QString level, QString msg)
232  {
233  QString logtmpl("[%1] %2: %3");
234  QString timestamp = QDateTime::currentDateTime().toString( "dd-MM-yyyy hh:mm:ss.zzz" );
235  QString logmsg = logtmpl.arg( timestamp ).arg( level, -10 ).arg( msg );
236  if ( stdOut ) {
237  QMutexLocker locker(&outStreamMutex);
238 
239  (*outStream) << logmsg << "\n";
240  outStream->flush();
241  }
242  if ( textEditUpdater->hasTextEdit() ) {
243  // No semaphore here as we use the thread-safe postEvent function
244  QString color = "#ffffff";
245  if ( level == "INFO" ) {
246  color = "#afeeee";
247  } else if ( level == "WARNING" ) {
248  color = "#f0e68c";
249  } else if ( level == "ERROR" ) {
250  color = "#ff4500";
251  }
252  // --- here the postEvent is used because it is not possible to modify directly the content of
253  // textEdit because it is not thread-safe. In fact, this function might be called from multiple
254  // threads and outside the GUI thread
255  qApp->postEvent( textEditUpdater, new TextToAppend( QString("<pre style=\"margin-top: 0px; margin-bottom: 0px; color: ")+color+";\">"+logmsg+QString("</pre>"), level, msg ) );
256  //textEdit->append( QString("<pre style=\"margin-top: 0px; margin-bottom: 0px; color: ")+color+";\">"+logmsg+QString("</pre>") );
257  //textEdit->moveCursor( QTextCursor::End );
258  //textEdit->moveCursor( QTextCursor::StartOfLine );
259  }
260  if ( fileStream && fileStream->device() ) {
261  QMutexLocker locker(&fileStreamMutex);
262 
263  (*fileStream) << logmsg << "\n";
264  fileStream->flush();
265  }
266  }
267 } //end anonymous namespace for LoggerImplementation class
268 
269 void Logger::info(QString msg)
270 {
271  LoggerImplementation::getInstance().info(msg);
272 }
273 
274 void Logger::warning(QString msg)
275 {
276  LoggerImplementation::getInstance().warning(msg);
277 }
278 
279 void Logger::error(QString msg)
280 {
281  LoggerImplementation::getInstance().error(msg);
282 }
283 
284 void Logger::setLogFilename(QString logfile)
285 {
286  LoggerImplementation::getInstance().setLogFilename(logfile);
287 }
288 
289 void Logger::setQTextEdit(QTextEdit* textedit)
290 {
291  LoggerImplementation::getInstance().setQTextEdit(textedit);
292 }
293 
294 void Logger::enableStdOut(bool enabled)
295 {
296  LoggerImplementation::getInstance().enableStdOut(enabled);
297 }
298 
300  LoggerImplementation::getInstance().setLogLevel(level);
301 }
302 
304 {
305  QString str = "unknown";
306 
307  switch(level)
308  {
309  case LogAll:
310  str = "LogAll";
311  break;
312  case Warning:
313  str = "Warning";
314  break;
315  case Quiet:
316  str = "Quiet";
317  break;
318  case Superquiet:
319  str = "Superquiet";
320  break;
321  }
322 
323  return str;
324 }
325 
327 {
328  LogLevel l = LogAll;
329 
330  if (level.toUpper() == "LOGALL") {
331  l = LogAll;
332  } else if (level.toUpper() == "WARNING") {
333  l = Warning;
334  } else if (level.toUpper() == "QUIET") {
335  l = Quiet;
336  } else if (level.toUpper() == "SUPERQUIET") {
337  l = Superquiet;
338  }
339 
340  return l;
341 }
342 
343 } // end namespace farsa