Quellcode durchsuchen

Merge branch 'error-log-model-to-file'

* error-log-model-to-file:
  ENH: Re-introduce and expose number of logs to keep
  Add support for error log context
  Add support for file logging to ctkErrorLogModel
Jean-Christophe Fillion-Robin vor 11 Jahren
Ursprung
Commit
7901557bd7

+ 4 - 0
Libs/Core/CMakeLists.txt

@@ -41,6 +41,7 @@ set(KIT_SRCS
   ctkDependencyGraph.h
   ctkErrorLogAbstractMessageHandler.cpp
   ctkErrorLogAbstractMessageHandler.h
+  ctkErrorLogContext.h
   ctkErrorLogFDMessageHandler.cpp
   ctkErrorLogFDMessageHandler.h
   ctkErrorLogFDMessageHandler_p.h
@@ -54,6 +55,8 @@ set(KIT_SRCS
   ctkErrorLogTerminalOutput.h
   ctkException.cpp
   ctkException.h
+  ctkFileLogger.cpp
+  ctkFileLogger.h
   ctkHighPrecisionTimer.cpp
   ctkLinearValueProxy.cpp
   ctkLinearValueProxy.h
@@ -97,6 +100,7 @@ set(KIT_MOC_SRCS
   ctkErrorLogLevel.h
   ctkErrorLogQtMessageHandler.h
   ctkErrorLogTerminalOutput.h
+  ctkFileLogger.h
   ctkLinearValueProxy.h
   ctkLogger.h
   ctkModelTester.h

+ 3 - 0
Libs/Core/Testing/Cpp/CMakeLists.txt

@@ -27,6 +27,7 @@ set(KITTests_SRCS
   ctkCallbackTest1.cpp
   ctkCommandLineParserTest1.cpp
   ctkExceptionTest.cpp
+  ctkFileLoggerTest.cpp
   ctkHighPrecisionTimerTest.cpp
   ctkLinearValueProxyTest.cpp
   ctkLoggerTest1.cpp
@@ -89,6 +90,7 @@ set(Tests_Helpers_MOC_SRCS
 
 set(Tests_Helpers_MOC_CPPS
   ctkBooleanMapperTest.cpp
+  ctkFileLoggerTest.cpp
   ctkLinearValueProxyTest.cpp
   ctkUtilsTest.cpp
   )
@@ -143,6 +145,7 @@ SIMPLE_TEST( ctkCommandLineParserTest1 )
 SIMPLE_TEST( ctkDependencyGraphTest1 )
 SIMPLE_TEST( ctkDependencyGraphTest2 )
 SIMPLE_TEST( ctkExceptionTest )
+SIMPLE_TEST( ctkFileLoggerTest )
 SIMPLE_TEST( ctkHighPrecisionTimerTest )
 SIMPLE_TEST( ctkLinearValueProxyTest )
 SIMPLE_TEST( ctkLoggerTest1 )

+ 166 - 0
Libs/Core/Testing/Cpp/ctkErrorLogModelFileLoggingTest1.cpp

@@ -0,0 +1,166 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QTemporaryFile>
+
+// CTK includes
+#include "ctkErrorLogFDMessageHandler.h"
+#include "ctkErrorLogQtMessageHandler.h"
+#include "ctkErrorLogStreamMessageHandler.h"
+
+// STL includes
+#include <cstdlib>
+#include <iostream>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+//-----------------------------------------------------------------------------
+int ctkErrorLogModelFileLoggingTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+  ctkErrorLogModel model;
+
+  // fileLoggingEnabled
+  if (!checkBoolean(__LINE__, "FileLoggingEnabled", model.fileLoggingEnabled(), false).isEmpty())
+    {
+    return EXIT_FAILURE;
+    }
+
+  model.setFileLoggingEnabled(true);
+
+  if (!checkBoolean(__LINE__, "FileLoggingEnabled", model.fileLoggingEnabled(), true).isEmpty())
+    {
+    return EXIT_FAILURE;
+    }
+
+  // Create log file
+  QTemporaryFile logFile(QDir::tempPath() + "/ctkErrorLogModelFileLoggingTest1.XXXXXX");
+  logFile.setAutoRemove(false);
+  logFile.open();
+  logFile.close();
+  QString logFilePath = logFile.fileName();
+
+  // filePath
+  if (!checkString(__LINE__, "FilePath", model.filePath(), "").isEmpty())
+    {
+    return EXIT_FAILURE;
+    }
+
+  model.setFilePath(logFilePath);
+
+  if (!checkString(__LINE__, "FilePath", model.filePath(), logFilePath).isEmpty())
+    {
+    return EXIT_FAILURE;
+    }
+
+  // Monitor Qt messages
+  model.registerMsgHandler(new ctkErrorLogQtMessageHandler);
+  model.setMsgHandlerEnabled(ctkErrorLogQtMessageHandler::HandlerName, true);
+
+  // Monitor Stream messages
+  model.registerMsgHandler(new ctkErrorLogStreamMessageHandler);
+  model.setMsgHandlerEnabled(ctkErrorLogStreamMessageHandler::HandlerName, true);
+
+  // Monitor FD messages
+  model.registerMsgHandler(new ctkErrorLogFDMessageHandler);
+  model.setMsgHandlerEnabled(ctkErrorLogFDMessageHandler::HandlerName, true);
+
+  // Qt messages
+  QString qtMessage0("This is a qDebug message");
+  qDebug().nospace() << qPrintable(qtMessage0);
+
+  QString qtMessage1("This is a qWarning message");
+  qWarning().nospace() << qPrintable(qtMessage1);
+
+  QString qtMessage2("This is a qCritical message");
+  qCritical().nospace() << qPrintable(qtMessage2);
+
+  // Stream messages
+  QString streamMessage0("This is a Cout message");
+  std::cout << qPrintable(streamMessage0) << std::endl;
+
+  QString streamMessage1("This is a Cerr message");
+  std::cerr << qPrintable(streamMessage1) << std::endl;
+
+  // FD messages
+  QString fdMessage0("This is a stdout");
+  fprintf(stdout, "%s", qPrintable(fdMessage0));
+  QString fdMessage0b(" message");
+  fprintf(stdout, "%s\n", qPrintable(fdMessage0b));
+  fdMessage0.append(fdMessage0b);
+  fflush(stdout);
+
+  // XXX FD messages from stderr and stdout are not always reported in the same order
+
+  //QString fdMessage1("This is a 2nd stdout message");
+  //fprintf(stdout, "%s\n", qPrintable(fdMessage1));
+  //fflush(stdout);
+
+  QString fdMessage2("This is a stderr");
+  fprintf(stderr, "%s", qPrintable(fdMessage2));
+  QString fdMessage2b(" message");
+  fprintf(stderr, "%s\n", qPrintable(fdMessage2b));
+  fdMessage2.append(fdMessage2b);
+  fflush(stderr);
+
+  //QString fdMessage3("This is a 2nd stderr message");
+  //fprintf(stderr, "%s\n", qPrintable(fdMessage3));
+  //fflush(stderr);
+
+  // Give enough time to the ErrorLogModel to consider the queued messages.
+  processEvents(1000);
+
+  model.disableAllMsgHandler();
+
+  QList<QStringList> expectedLogEntries;
+  expectedLogEntries << (QStringList() << "DEBUG" << "Qt" << qtMessage0);
+  expectedLogEntries << (QStringList() << "WARNING" << "Qt" << qtMessage1);
+  expectedLogEntries << (QStringList() << "CRITICAL" << "Qt" << qtMessage2);
+  expectedLogEntries << (QStringList() << "INFO" << "Stream" << streamMessage0);
+  expectedLogEntries << (QStringList() << "CRITICAL" << "Stream" << streamMessage1);
+  expectedLogEntries << (QStringList() << "INFO" << "FD" << fdMessage0);
+  //expectedLogEntries << (QStringList() << "INFO" << "FD" << fdMessage1);
+  expectedLogEntries << (QStringList() << "CRITICAL" << "FD" << fdMessage2);
+  //expectedLogEntries << (QStringList() << "CRITICAL" << "FD" << fdMessage3);
+
+  QStringList currentLogEntries = readFile(logFilePath);
+
+  QString expectedLogEntryPatternTemplate("^\\[%1\\]\\[%2\\] [0-9\\.\\s\\:]+ \\[\\] \\(unknown\\:0\\) \\- %3$");
+  for(int entryIndex = 0; entryIndex < expectedLogEntries.size(); ++entryIndex)
+    {
+    QStringList entry = expectedLogEntries.at(entryIndex);
+    QRegExp regexp(expectedLogEntryPatternTemplate.arg(entry.at(0)).arg(entry.at(1)).arg(entry.at(2)));
+    if (!regexp.exactMatch(currentLogEntries.at(entryIndex)))
+      {
+      printErrorMessage(
+            QString("Line %1 - Log entry %2 does NOT math expected regular expression.\n\tLogEntry: %3\n\tRegExp: %4").
+                arg(__LINE__).arg(entryIndex).arg(currentLogEntries.at(entryIndex)).arg(regexp.pattern()));
+      return EXIT_FAILURE;
+      }
+    }
+
+  return EXIT_SUCCESS;
+}

+ 23 - 0
Libs/Core/Testing/Cpp/ctkFileLoggerTest.cpp

@@ -0,0 +1,23 @@
+
+// CTK includes
+#include "ctkFileLogger.h"
+#include "ctkTest.h"
+
+// ----------------------------------------------------------------------------
+class ctkFileLoggerTester: public QObject
+{
+  Q_OBJECT
+private slots:
+  void initTestCase();
+
+};
+
+// ----------------------------------------------------------------------------
+void ctkFileLoggerTester::initTestCase()
+{
+
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkFileLoggerTest)
+#include "moc_ctkFileLoggerTest.cpp"

+ 8 - 2
Libs/Core/ctkErrorLogAbstractMessageHandler.cpp

@@ -20,9 +20,13 @@
 
 #include "ctkErrorLogAbstractMessageHandler.h"
 
+// Qt includes
 #include <QHash>
 #include <QDateTime>
 
+// CTK includes
+#include "ctkErrorLogContext.h"
+
 // --------------------------------------------------------------------------
 // ctkErrorLogAbstractMessageHandlerPrivate
 
@@ -110,7 +114,9 @@ void ctkErrorLogAbstractMessageHandler::setEnabled(bool value)
 // --------------------------------------------------------------------------
 void ctkErrorLogAbstractMessageHandler::handleMessage(const QString& threadId,
                                                       ctkErrorLogLevel::LogLevel logLevel,
-                                                      const QString& origin, const QString& text)
+                                                      const QString& origin,
+                                                      const ctkErrorLogContext& logContext,
+                                                      const QString &text)
 {
   Q_D(ctkErrorLogAbstractMessageHandler);
   if (logLevel <= ctkErrorLogLevel::Info)
@@ -127,7 +133,7 @@ void ctkErrorLogAbstractMessageHandler::handleMessage(const QString& threadId,
       d->TerminalOutputs.value(ctkErrorLogTerminalOutput::StandardError)->output(text);
       }
     }
-  emit this->messageHandled(QDateTime::currentDateTime(), threadId, logLevel, origin, text);
+  emit this->messageHandled(QDateTime::currentDateTime(), threadId, logLevel, origin, logContext, text);
 }
 
 // --------------------------------------------------------------------------

+ 4 - 2
Libs/Core/ctkErrorLogAbstractMessageHandler.h

@@ -32,6 +32,7 @@
 
 //------------------------------------------------------------------------------
 class ctkErrorLogAbstractMessageHandlerPrivate;
+struct ctkErrorLogContext;
 
 //------------------------------------------------------------------------------
 /// \ingroup Core
@@ -52,7 +53,8 @@ public:
   void setEnabled(bool value);
 
   void handleMessage(const QString& threadId, ctkErrorLogLevel::LogLevel logLevel,
-                     const QString& origin, const QString& text);
+                     const QString& origin, const ctkErrorLogContext& logContext,
+                     const QString &text);
 
   ctkErrorLogTerminalOutput* terminalOutput(ctkErrorLogTerminalOutput::TerminalOutput terminalOutputType)const;
   void setTerminalOutput(ctkErrorLogTerminalOutput::TerminalOutput terminalOutputType,
@@ -61,7 +63,7 @@ public:
 Q_SIGNALS:
   void messageHandled(const QDateTime& currentDateTime, const QString& threadId,
                       ctkErrorLogLevel::LogLevel logLevel, const QString& origin,
-                      const QString& text);
+                      const ctkErrorLogContext& logContext, const QString& text);
 
 protected:
   void setHandlerPrettyName(const QString& newHandlerPrettyName);

+ 44 - 0
Libs/Core/ctkErrorLogContext.h

@@ -0,0 +1,44 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#ifndef __ctkErrorLogContext_h
+#define __ctkErrorLogContext_h
+
+// Qt includes
+#include <QString>
+
+// CTK includes
+#include "ctkCoreExport.h"
+
+//------------------------------------------------------------------------------
+/// \ingroup Core
+struct CTK_CORE_EXPORT ctkErrorLogContext
+{
+  ctkErrorLogContext():Line(0),File("unknown"), Function("unknown"), Message(""){}
+  ctkErrorLogContext(const QString& msg):
+    Line(0),File("unknown"), Function("unknown"), Message(msg){}
+  QString Category;
+  int Line;
+  QString File;
+  QString Function;
+  QString Message;
+};
+
+#endif

+ 2 - 0
Libs/Core/ctkErrorLogFDMessageHandler.cpp

@@ -23,6 +23,7 @@
 #include <QFile>
 
 // CTK includes
+#include "ctkErrorLogContext.h"
 #include "ctkErrorLogFDMessageHandler.h"
 #include "ctkErrorLogFDMessageHandler_p.h"
 #include "ctkUtils.h"
@@ -208,6 +209,7 @@ void ctkFDHandler::run()
       ctk::qtHandleToString(QThread::currentThreadId()),
       this->LogLevel,
       this->MessageHandler->handlerPrettyName(),
+      ctkErrorLogContext(line),
       line);
     }
 }

+ 2 - 1
Libs/Core/ctkErrorLogQtMessageHandler.cpp

@@ -24,6 +24,7 @@
 #include <QVariant>
 
 // CTK includes
+#include "ctkErrorLogContext.h"
 #include "ctkErrorLogQtMessageHandler.h"
 #include <ctkUtils.h>
 
@@ -138,7 +139,7 @@ void ctkErrorLogModelQtMessageOutput(QtMsgType type, const char *msg)
 //    //  }
     handler->handleMessage(
           ctk::qtHandleToString(QThread::currentThreadId()),
-          level, handler->handlerPrettyName(), msg);
+          level, handler->handlerPrettyName(), ctkErrorLogContext(msg), msg);
     }
 }
 #endif

+ 6 - 2
Libs/Core/ctkErrorLogStreamMessageHandler.cpp

@@ -24,6 +24,7 @@
 #include <QHash>
 
 // CTK includes
+#include "ctkErrorLogContext.h"
 #include "ctkErrorLogStreamMessageHandler.h"
 #include "ctkUtils.h"
 
@@ -62,6 +63,7 @@ private:
 
   ctkErrorLogStreamMessageHandler * MessageHandler;
   ctkErrorLogLevel::LogLevel LogLevel;
+  ctkErrorLogContext LogContext;
 
   bool Enabled;
 
@@ -137,7 +139,8 @@ std::streambuf::int_type ctkStreamHandler::overflow(std::streambuf::int_type v)
     Q_ASSERT(this->MessageHandler);
     this->MessageHandler->handleMessage(
           ctk::qtHandleToString(QThread::currentThreadId()), this->LogLevel,
-          this->MessageHandler->handlerPrettyName(), this->currentBuffer()->c_str());
+          this->MessageHandler->handlerPrettyName(),
+          ctkErrorLogContext(this->currentBuffer()->c_str()), this->currentBuffer()->c_str());
     this->currentBuffer()->erase(this->currentBuffer()->begin(), this->currentBuffer()->end());
     }
   else
@@ -164,7 +167,8 @@ std::streamsize ctkStreamHandler::xsputn(const char *p, std::streamsize n)
       Q_ASSERT(this->MessageHandler);
       this->MessageHandler->handleMessage(
             ctk::qtHandleToString(QThread::currentThreadId()), this->LogLevel,
-            this->MessageHandler->handlerPrettyName(), tmp.c_str());
+            this->MessageHandler->handlerPrettyName(),
+            ctkErrorLogContext(tmp.c_str()), tmp.c_str());
       this->currentBuffer()->erase(this->currentBuffer()->begin(), this->currentBuffer()->begin() + pos + 1);
       }
     }

+ 142 - 0
Libs/Core/ctkFileLogger.cpp

@@ -0,0 +1,142 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QFile>
+#include <QTextStream>
+
+// CTK includes
+#include "ctkFileLogger.h"
+
+// --------------------------------------------------------------------------
+// ctkFileLoggerPrivate
+
+// --------------------------------------------------------------------------
+class ctkFileLoggerPrivate
+{
+  Q_DECLARE_PUBLIC(ctkFileLogger);
+protected:
+  ctkFileLogger* const q_ptr;
+public:
+  ctkFileLoggerPrivate(ctkFileLogger& object);
+  ~ctkFileLoggerPrivate();
+
+  void init();
+
+  bool Enabled;
+  QString FilePath;
+  int NumberOfFilesToKeep;
+};
+
+// --------------------------------------------------------------------------
+ctkFileLoggerPrivate::ctkFileLoggerPrivate(ctkFileLogger& object)
+  : q_ptr(&object)
+{
+  this->Enabled = true;
+  this->NumberOfFilesToKeep = 10;
+}
+
+// --------------------------------------------------------------------------
+ctkFileLoggerPrivate::~ctkFileLoggerPrivate()
+{
+}
+
+// --------------------------------------------------------------------------
+void ctkFileLoggerPrivate::init()
+{
+}
+
+// --------------------------------------------------------------------------
+// ctkFileLogger
+
+// --------------------------------------------------------------------------
+ctkFileLogger::ctkFileLogger(QObject* parentObject)
+  : Superclass(parentObject)
+  , d_ptr(new ctkFileLoggerPrivate(*this))
+{
+  Q_D(ctkFileLogger);
+  d->init();
+}
+
+// --------------------------------------------------------------------------
+ctkFileLogger::~ctkFileLogger()
+{
+}
+
+// --------------------------------------------------------------------------
+bool ctkFileLogger::enabled()const
+{
+  Q_D(const ctkFileLogger);
+  return d->Enabled;
+}
+
+// --------------------------------------------------------------------------
+void ctkFileLogger::setEnabled(bool value)
+{
+  Q_D(ctkFileLogger);
+  d->Enabled = value;
+}
+
+// --------------------------------------------------------------------------
+QString ctkFileLogger::filePath()const
+{
+  Q_D(const ctkFileLogger);
+  return d->FilePath;
+}
+
+// --------------------------------------------------------------------------
+void ctkFileLogger::setFilePath(const QString& filePath)
+{
+  Q_D(ctkFileLogger);
+  d->FilePath = filePath;
+}
+
+// --------------------------------------------------------------------------
+int ctkFileLogger::numberOfFilesToKeep()const
+{
+  Q_D(const ctkFileLogger);
+  return d->NumberOfFilesToKeep;
+}
+
+// --------------------------------------------------------------------------
+void ctkFileLogger::setNumberOfFilesToKeep(int value)
+{
+  Q_D(ctkFileLogger);
+  d->NumberOfFilesToKeep = value;
+}
+
+// --------------------------------------------------------------------------
+void ctkFileLogger::logMessage(const QString& msg)
+{
+  Q_D(ctkFileLogger);
+  if (!d->Enabled)
+    {
+    return;
+    }
+  QFile f(d->FilePath);
+  if (!f.open(QFile::Append))
+    {
+    return;
+    }
+  QTextStream s(&f);
+  s << msg << "\n";
+  f.close();
+}
+

+ 67 - 0
Libs/Core/ctkFileLogger.h

@@ -0,0 +1,67 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#ifndef __ctkFileLogger_h
+#define __ctkFileLogger_h
+
+// Qt includes
+#include <QObject>
+
+// CTK includes
+#include "ctkCoreExport.h"
+
+//------------------------------------------------------------------------------
+class ctkFileLoggerPrivate;
+
+//------------------------------------------------------------------------------
+/// \ingroup Core
+class CTK_CORE_EXPORT ctkFileLogger : public QObject
+{
+  Q_OBJECT
+  Q_PROPERTY(bool enabled READ enabled WRITE setEnabled)
+  Q_PROPERTY(QString filePath READ filePath WRITE setFilePath)
+
+public:
+  typedef QObject Superclass;
+  typedef ctkFileLogger Self;
+  explicit ctkFileLogger(QObject* parentObject = 0);
+  virtual ~ctkFileLogger();
+
+  bool enabled()const;
+  void setEnabled(bool value);
+
+  QString filePath()const;
+  void setFilePath(const QString& filePath);
+
+  int numberOfFilesToKeep()const;
+  void setNumberOfFilesToKeep(int value);
+
+public Q_SLOTS:
+  void logMessage(const QString& msg);
+
+protected:
+  QScopedPointer<ctkFileLoggerPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkFileLogger);
+  Q_DISABLE_COPY(ctkFileLogger);
+};
+
+#endif

+ 2 - 0
Libs/ImageProcessing/ITK/Core/Testing/Cpp/CMakeLists.txt

@@ -5,6 +5,7 @@ set(KIT ${PROJECT_NAME})
 #
 set(TEST_SOURCES
   ctkITKErrorLogMessageHandlerWithThreadsTest1.cpp
+  ctkITKErrorLogModelFileLoggingTest1.cpp
   ctkITKErrorLogModelTest1.cpp
   )
 
@@ -62,6 +63,7 @@ target_link_libraries(${KIT}CppTests ${LIBRARY_NAME} ${CTK_BASE_LIBRARIES} CTKWi
 #
 
 SIMPLE_TEST( ctkITKErrorLogMessageHandlerWithThreadsTest1 )
+SIMPLE_TEST( ctkITKErrorLogModelFileLoggingTest1 )
 SIMPLE_TEST( ctkITKErrorLogModelTest1 )
 
 #

+ 128 - 0
Libs/ImageProcessing/ITK/Core/Testing/Cpp/ctkITKErrorLogModelFileLoggingTest1.cpp

@@ -0,0 +1,128 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QTemporaryFile>
+
+// CTK includes
+#include "ctkITKErrorLogMessageHandler.h"
+
+// VTK includes
+#include <itkObject.h>
+#include <itkObjectFactory.h>
+#include <itkOutputWindow.h>
+
+// STL includes
+#include <cstdlib>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+// --------------------------------------------------------------------------
+namespace itk
+{
+
+class ctkITKFileLoggingTestObject : public Object
+{
+public:
+  /** Standard class typedefs. */
+  typedef ctkITKFileLoggingTestObject   Self;
+  typedef Object                        Superclass;
+  typedef SmartPointer<Self>            Pointer;
+  typedef SmartPointer<const Self>      ConstPointer;
+
+  /** Standard New method. */
+  itkNewMacro(ctkITKFileLoggingTestObject);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(ctkITKFileLoggingTestObject, Object);
+
+  ctkITKFileLoggingTestObject(){}
+  ~ctkITKFileLoggingTestObject(){}
+
+  void exerciseITKDebugMacro(const QString& text)
+  {
+    itkDebugMacro( << qPrintable(text));
+  }
+
+  void exerciseITKWarningMacro(const QString& text)
+  {
+    itkWarningMacro( << qPrintable(text));
+  }
+};
+
+} // End of itk namespace
+
+//-----------------------------------------------------------------------------
+int ctkITKErrorLogModelFileLoggingTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+  ctkErrorLogModel model;
+  model.setFileLoggingEnabled(true);
+
+  // Create log file
+  QTemporaryFile logFile(QDir::tempPath() + "/ctkITKErrorLogModelFileLoggingTest1.XXXXXX");
+  logFile.setAutoRemove(false);
+  logFile.open();
+  logFile.close();
+  QString logFilePath = logFile.fileName();
+
+  model.setFilePath(logFilePath);
+
+  // Monitor VTK messages
+  model.registerMsgHandler(new ctkITKErrorLogMessageHandler);
+  model.setMsgHandlerEnabled(ctkITKErrorLogMessageHandler::HandlerName, true);
+
+  itk::ctkITKFileLoggingTestObject::Pointer object = itk::ctkITKFileLoggingTestObject::New();
+
+  QString itkMessage0("This is a ITK debug message");
+  object->exerciseITKDebugMacro(itkMessage0);
+
+  QString itkMessage1("This is a ITK warning message");
+  object->exerciseITKWarningMacro(itkMessage1);
+
+
+  // Give enough time to the ErrorLogModel to consider the queued messages.
+  processEvents(1000);
+
+  model.disableAllMsgHandler();
+
+  QStringList logLines = readFile(logFilePath);
+
+  QString expectedLogEntryPatternTemplate(
+        "^\\[%1\\]\\[ITK\\] [0-9\\.\\s\\:]+ \\[ctkITKFileLoggingTestObject \\(0x[a-zA-B0-9]+\\)\\] "
+        "\\(.+ctkITKErrorLogModelFileLoggingTest1\\.cpp\\:%2\\) \\- %3$");
+
+  int entryIndex = 0;
+  QRegExp regexp(expectedLogEntryPatternTemplate.arg("WARNING").arg(70).arg("This is a ITK warning message"));
+  if (!regexp.exactMatch(logLines.at(entryIndex)))
+    {
+    printErrorMessage(
+          QString("Line %1 - Log entry %2 does NOT math expected regular expression.\n\tLogEntry: %3\n\tRegExp: %4").
+              arg(__LINE__).arg(entryIndex).arg(logLines.at(entryIndex)).arg(regexp.pattern()));
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}

+ 37 - 6
Libs/ImageProcessing/ITK/Core/ctkITKErrorLogMessageHandler.cpp

@@ -22,6 +22,7 @@
 #include <QThread>
 
 // CTK includes
+#include "ctkErrorLogContext.h"
 #include "ctkITKErrorLogMessageHandler.h"
 #include "ctkUtils.h"
 
@@ -58,7 +59,8 @@ public:
   /** Run-time type information (and related methods). */
   itkTypeMacro(ctkITKOutputWindow, OutputWindow);
 
-  ctkITKOutputWindow():MessageHandler(0){}
+  ctkITKOutputWindow():MessageHandler(0),
+    ContextRegExp("[a-zA-Z\\s]+: In (.+), line ([\\d]+)\\n(.+\\(0x[a-fA-F0-9]+\\))\\:\\s(.*)"){}
   ~ctkITKOutputWindow(){}
 
   virtual void DisplayText(const char*);
@@ -68,7 +70,11 @@ public:
 
   virtual void DisplayDebugText(const char*);
 
+  QString parseText(const QString &text, ctkErrorLogContext &context);
+
   ctkErrorLogAbstractMessageHandler * MessageHandler;
+
+  QRegExp ContextRegExp;
 };
 
 // --------------------------------------------------------------------------
@@ -81,27 +87,35 @@ void ctkITKOutputWindow::DisplayText(const char* text)
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Info,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), ctkErrorLogContext(), text);
 }
 
 //----------------------------------------------------------------------------
 void ctkITKOutputWindow::DisplayErrorText(const char* text)
 {
   Q_ASSERT(this->MessageHandler);
+
+  ctkErrorLogContext context;
+  this->parseText(text, context);
+
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Error,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), context, text);
 }
 
 //----------------------------------------------------------------------------
 void ctkITKOutputWindow::DisplayWarningText(const char* text)
 {
   Q_ASSERT(this->MessageHandler);
+
+  ctkErrorLogContext context;
+  this->parseText(text, context);
+
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Warning,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), context, text);
 }
 
 //----------------------------------------------------------------------------
@@ -114,10 +128,28 @@ void ctkITKOutputWindow::DisplayGenericWarningText(const char* text)
 void ctkITKOutputWindow::DisplayDebugText(const char* text)
 {
   Q_ASSERT(this->MessageHandler);
+
+  ctkErrorLogContext context;
+  this->parseText(text, context);
+
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Debug,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), context, text);
+}
+
+//----------------------------------------------------------------------------
+QString ctkITKOutputWindow::parseText(const QString& text, ctkErrorLogContext& context)
+{
+  context.Message = text;
+  if (this->ContextRegExp.exactMatch(text))
+    {
+    context.File = this->ContextRegExp.cap(1);
+    context.Category = this->ContextRegExp.cap(3);
+    context.Line = this->ContextRegExp.cap(2).toInt();
+    context.Message = this->ContextRegExp.cap(4);
+    }
+  return context.Message;
 }
 
 } // End of itk namespace
@@ -199,4 +231,3 @@ void ctkITKErrorLogMessageHandler::setEnabledInternal(bool value)
     d->SavedITKOutputWindow = 0;
     }
 }
-

+ 37 - 6
Libs/Visualization/VTK/Core/ctkVTKErrorLogMessageHandler.cpp

@@ -22,6 +22,7 @@
 #include <QThread>
 
 // CTK includes
+#include "ctkErrorLogContext.h"
 #include "ctkVTKErrorLogMessageHandler.h"
 #include "ctkUtils.h"
 
@@ -42,7 +43,8 @@ public:
   vtkTypeMacro(ctkVTKOutputWindow,vtkOutputWindow);
   void PrintSelf(ostream& os, vtkIndent indent);
 
-  ctkVTKOutputWindow():MessageHandler(0){}
+  ctkVTKOutputWindow():MessageHandler(0),
+    ContextRegExp("[a-zA-Z\\s]+: In (.+), line ([\\d]+)\\n(.+\\(0x[a-fA-F0-9]+\\))\\:\\s(.*)"){}
   ~ctkVTKOutputWindow(){}
 
   virtual void DisplayText(const char*);
@@ -52,7 +54,11 @@ public:
 
   virtual void DisplayDebugText(const char*);
 
+  QString parseText(const QString &text, ctkErrorLogContext &context);
+
   ctkErrorLogAbstractMessageHandler * MessageHandler;
+
+  QRegExp ContextRegExp;
 };
 
 // --------------------------------------------------------------------------
@@ -74,27 +80,35 @@ void ctkVTKOutputWindow::DisplayText(const char* text)
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Info,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), ctkErrorLogContext(), text);
 }
 
 //----------------------------------------------------------------------------
 void ctkVTKOutputWindow::DisplayErrorText(const char* text)
 {
   Q_ASSERT(this->MessageHandler);
+
+  ctkErrorLogContext context;
+  QString textOnly = this->parseText(text, context);
+
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Error,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), context, textOnly);
 }
 
 //----------------------------------------------------------------------------
 void ctkVTKOutputWindow::DisplayWarningText(const char* text)
 {
   Q_ASSERT(this->MessageHandler);
+
+  ctkErrorLogContext context;
+  this->parseText(text, context);
+
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Warning,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), context, text);
 }
 
 //----------------------------------------------------------------------------
@@ -107,10 +121,28 @@ void ctkVTKOutputWindow::DisplayGenericWarningText(const char* text)
 void ctkVTKOutputWindow::DisplayDebugText(const char* text)
 {
   Q_ASSERT(this->MessageHandler);
+
+  ctkErrorLogContext context;
+  this->parseText(text, context);
+
   this->MessageHandler->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
         ctkErrorLogLevel::Debug,
-        this->MessageHandler->handlerPrettyName(), text);
+        this->MessageHandler->handlerPrettyName(), context, text);
+}
+
+//----------------------------------------------------------------------------
+QString ctkVTKOutputWindow::parseText(const QString& text, ctkErrorLogContext& context)
+{
+  context.Message = text;
+  if (this->ContextRegExp.exactMatch(text))
+    {
+    context.File = this->ContextRegExp.cap(1);
+    context.Category = this->ContextRegExp.cap(3);
+    context.Line = this->ContextRegExp.cap(2).toInt();
+    context.Message = this->ContextRegExp.cap(4);
+    }
+  return context.Message;
 }
 
 } // End of anonymous namespace
@@ -197,4 +229,3 @@ void ctkVTKErrorLogMessageHandler::setEnabledInternal(bool value)
     d->SavedVTKOutputWindow = 0;
     }
 }
-

+ 2 - 0
Libs/Visualization/VTK/Widgets/Testing/Cpp/CMakeLists.txt

@@ -8,6 +8,7 @@ set(TEST_SOURCES
   ctkVTKDataSetArrayComboBoxTest1.cpp
   ctkVTKDataSetModelTest1.cpp
   ctkVTKErrorLogMessageHandlerWithThreadsTest1.cpp
+  ctkVTKErrorLogModelFileLoggingTest1.cpp
   ctkVTKErrorLogModelTest1.cpp
   ctkVTKHistogramTest1.cpp
   ctkVTKHistogramTest2.cpp
@@ -146,6 +147,7 @@ SIMPLE_TEST( ctkVTKColorTransferFunctionTest1 )
 SIMPLE_TEST( ctkVTKDataSetArrayComboBoxTest1 )
 SIMPLE_TEST( ctkVTKDataSetModelTest1 )
 SIMPLE_TEST( ctkVTKErrorLogMessageHandlerWithThreadsTest1 )
+SIMPLE_TEST( ctkVTKErrorLogModelFileLoggingTest1 )
 SIMPLE_TEST( ctkVTKErrorLogModelTest1 )
 SIMPLE_TEST( ctkVTKHistogramTest1 )
 SIMPLE_TEST( ctkVTKHistogramTest2 )

+ 104 - 0
Libs/Visualization/VTK/Widgets/Testing/Cpp/ctkVTKErrorLogModelFileLoggingTest1.cpp

@@ -0,0 +1,104 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QTemporaryFile>
+
+// CTK includes
+#include "ctkVTKErrorLogMessageHandler.h"
+
+// VTK includes
+#include <vtkNew.h>
+#include <vtkObject.h>
+#include <vtkOutputWindow.h>
+
+// STL includes
+#include <cstdlib>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+//-----------------------------------------------------------------------------
+int ctkVTKErrorLogModelFileLoggingTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+  ctkErrorLogModel model;
+  model.setFileLoggingEnabled(true);
+
+  // Create log file
+  QTemporaryFile logFile(QDir::tempPath() + "/ctkVTKErrorLogModelFileLoggingTest1.XXXXXX");
+  logFile.setAutoRemove(false);
+  logFile.open();
+  logFile.close();
+  QString logFilePath = logFile.fileName();
+
+  model.setFilePath(logFilePath);
+
+  // Monitor VTK messages
+  model.registerMsgHandler(new ctkVTKErrorLogMessageHandler);
+  model.setMsgHandlerEnabled(ctkVTKErrorLogMessageHandler::HandlerName, true);
+
+  vtkNew<vtkObject> object;
+
+  // VTK messages
+  vtkDebugWithObjectMacro(object.GetPointer(), "This is a VTK debug message");
+  vtkWarningWithObjectMacro(object.GetPointer(), "This is a VTK warning message");
+  vtkErrorWithObjectMacro(object.GetPointer(), "This is a VTK error message");
+
+  // Give enough time to the ErrorLogModel to consider the queued messages.
+  processEvents(1000);
+
+  model.disableAllMsgHandler();
+
+  QStringList logLines = readFile(logFilePath);
+
+  QString expectedLogEntryPatternTemplate(
+        "^\\[%1\\]\\[VTK\\] [0-9\\.\\s\\:]+ \\[vtkObject \\(0x[a-zA-B0-9]+\\)\\] "
+        "\\(.+ctkVTKErrorLogModelFileLoggingTest1\\.cpp\\:%2\\) \\- %3$");
+
+  {
+    int entryIndex = 0;
+    QRegExp regexp(expectedLogEntryPatternTemplate.arg("WARNING").arg(66).arg("This is a VTK warning message"));
+    if (!regexp.exactMatch(logLines.at(entryIndex)))
+      {
+      printErrorMessage(
+            QString("Line %1 - Log entry %2 does NOT math expected regular expression.\n\tLogEntry: %3\n\tRegExp: %4").
+                arg(__LINE__).arg(entryIndex).arg(logLines.at(entryIndex)).arg(regexp.pattern()));
+      return EXIT_FAILURE;
+      }
+  }
+  {
+    int entryIndex = 1;
+    QRegExp regexp(expectedLogEntryPatternTemplate.arg("ERROR").arg(67).arg("This is a VTK error message"));
+    if (!regexp.exactMatch(logLines.at(entryIndex)))
+      {
+      printErrorMessage(
+            QString("Line %1 - Log entry %2 does NOT math expected regular expression.\n\tLogEntry: %3\n\tRegExp: %4").
+                arg(__LINE__).arg(entryIndex).arg(logLines.at(entryIndex)).arg(regexp.pattern()));
+      return EXIT_FAILURE;
+      }
+  }
+
+  return EXIT_SUCCESS;
+}

+ 31 - 0
Libs/Widgets/Testing/Cpp/ctkErrorLogModelTestHelper.cpp

@@ -24,6 +24,7 @@
 #include <QMutexLocker>
 #include <QSharedPointer>
 #include <QStringList>
+#include <QTextStream>
 #include <QTimer>
 #include <QThread>
 
@@ -130,6 +131,18 @@ QString checkBoolean(int line, const char* valueName, bool current, bool expecte
 }
 
 //-----------------------------------------------------------------------------
+QString checkString(int line, const char* valueName, QString current, QString expected)
+{
+  if (current != expected)
+    {
+    QString errorMsg("Line %1 - Expected %2: %3 - Current %4: %5\n");
+    return errorMsg.arg(line).arg(valueName).
+        arg(static_cast<QString>(expected)).arg(valueName).arg(static_cast<QString>(current));
+    }
+  return QString();
+}
+
+//-----------------------------------------------------------------------------
 void processEvents(int durationInMSecs)
 {
   QTimer timer;
@@ -156,6 +169,24 @@ void appendToFile(const QString& fileName, const QString& text)
 }
 
 //-----------------------------------------------------------------------------
+QStringList readFile(const QString& filePath)
+{
+  QStringList lines;
+  QFile file(filePath);
+  if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+    {
+    return lines;
+    }
+   QTextStream in(&file);
+   while(!in.atEnd())
+     {
+     lines << in.readLine();
+     }
+   file.close();
+   return lines;
+}
+
+//-----------------------------------------------------------------------------
 class LogMessageThread : public QThread
 {
 public:

+ 81 - 28
Libs/Widgets/ctkErrorLogModel.cpp

@@ -19,6 +19,7 @@
 =========================================================================*/
 
 // Qt includes
+#include <QCoreApplication>
 #include <QDateTime>
 #include <QDebug>
 #include <QFile>
@@ -27,10 +28,13 @@
 #include <QMutexLocker>
 #include <QPointer>
 #include <QStandardItem>
+#include <QThread>
 
 // CTK includes
+#include "ctkErrorLogContext.h"
 #include "ctkErrorLogModel.h"
 #include "ctkErrorLogAbstractMessageHandler.h"
+#include "ctkFileLogger.h"
 
 
 // --------------------------------------------------------------------------
@@ -48,9 +52,6 @@ public:
 
   void init();
 
-  /// Convenient method that could be used for debugging purposes.
-  void appendToFile(const QString& fileName, const QString& text);
-
   void setMessageHandlerConnection(ctkErrorLogAbstractMessageHandler * msgHandler, bool asynchronous);
 
   QStandardItemModel StandardItemModel;
@@ -68,7 +69,8 @@ public:
   ctkErrorLogTerminalOutput StdErrTerminalOutput;
   ctkErrorLogTerminalOutput StdOutTerminalOutput;
 
-  QMutex AppendToFileMutex;
+  ctkFileLogger FileLogger;
+  QString FileLoggingPattern;
 };
 
 // --------------------------------------------------------------------------
@@ -78,10 +80,13 @@ public:
 ctkErrorLogModelPrivate::ctkErrorLogModelPrivate(ctkErrorLogModel& object)
   : q_ptr(&object)
 {
+  qRegisterMetaType<ctkErrorLogContext>("ctkErrorLogContext");
   this->StandardItemModel.setColumnCount(ctkErrorLogModel::MaxColumn);
   this->LogEntryGrouping = false;
   this->AsynchronousLogging = true;
   this->AddingEntry = false;
+  this->FileLogger.setEnabled(false);
+  this->FileLoggingPattern = "[%{level}][%{origin}] %{timestamp} [%{category}] (%{file}:%{line}) - %{msg}";
 }
 
 // --------------------------------------------------------------------------
@@ -110,17 +115,6 @@ void ctkErrorLogModelPrivate::init()
 }
 
 // --------------------------------------------------------------------------
-void ctkErrorLogModelPrivate::appendToFile(const QString& fileName, const QString& text)
-{
-  QMutexLocker locker(&this->AppendToFileMutex);
-  QFile f(fileName);
-  f.open(QFile::Append);
-  QTextStream s(&f);
-  s << QDateTime::currentDateTime().toString() << " - " << text << "\n";
-  f.close();
-}
-
-// --------------------------------------------------------------------------
 void ctkErrorLogModelPrivate::setMessageHandlerConnection(
     ctkErrorLogAbstractMessageHandler * msgHandler, bool asynchronous)
 {
@@ -129,8 +123,8 @@ void ctkErrorLogModelPrivate::setMessageHandlerConnection(
   msgHandler->disconnect();
 
   QObject::connect(msgHandler,
-        SIGNAL(messageHandled(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,QString)),
-        q, SLOT(addEntry(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,QString)),
+        SIGNAL(messageHandled(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,ctkErrorLogContext,QString)),
+        q, SLOT(addEntry(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,ctkErrorLogContext,QString)),
         asynchronous ? Qt::QueuedConnection : Qt::BlockingQueuedConnection);
 }
 
@@ -273,22 +267,12 @@ void ctkErrorLogModel::setTerminalOutputs(
 //------------------------------------------------------------------------------
 void ctkErrorLogModel::addEntry(const QDateTime& currentDateTime, const QString& threadId,
                                 ctkErrorLogLevel::LogLevel logLevel,
-                                const QString& origin, const QString& text)
+                                const QString& origin, const ctkErrorLogContext &context, const QString &text)
 {
   Q_D(ctkErrorLogModel);
 
-//  d->appendToFile("/tmp/ctkErrorLogModel-appendToFile.txt",
-//                  QString("addEntry: %1").arg(QThread::currentThreadId()));
-
   if (d->AddingEntry)
     {
-//    QString str;
-//    QTextStream s(&str);
-//    s << "----------------------------------\n";
-//    s << "text=>" << text << "\n";
-//    s << "\tlogLevel:" << qPrintable(d->ErrorLogLevel(logLevel)) << "\n";
-//    s << "\torigin:" << qPrintable(origin) << "\n";
-//    d->appendToFile("/tmp/ctkErrorLogModel-AddingEntry-true.txt", str);
     return;
     }
 
@@ -375,6 +359,19 @@ void ctkErrorLogModel::addEntry(const QDateTime& currentDateTime, const QString&
 
   d->AddingEntry = false;
 
+  QString fileLogText = d->FileLoggingPattern;
+  fileLogText.replace("%{level}", d->ErrorLogLevel(logLevel).toUpper());
+  fileLogText.replace("%{timestamp}", currentDateTime.toString(timeFormat));
+  fileLogText.replace("%{origin}", origin);
+  fileLogText.replace("%{pid}", QString("%1").arg(QCoreApplication::applicationPid()));
+  fileLogText.replace("%{threadid}", threadId);
+  fileLogText.replace("%{function}", context.Function);
+  fileLogText.replace("%{line}", QString("%1").arg(context.Line));
+  fileLogText.replace("%{file}", context.File);
+  fileLogText.replace("%{category}", context.Category);
+  fileLogText.replace("%{msg}", context.Message);
+  d->FileLogger.logMessage(fileLogText.trimmed());
+
   emit this->entryAdded(logLevel);
 }
 
@@ -508,6 +505,62 @@ void ctkErrorLogModel::setAsynchronousLogging(bool value)
 }
 
 // --------------------------------------------------------------------------
+QString ctkErrorLogModel::filePath()const
+{
+  Q_D(const ctkErrorLogModel);
+  return d->FileLogger.filePath();
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogModel::setFilePath(const QString& filePath)
+{
+  Q_D(ctkErrorLogModel);
+  return d->FileLogger.setFilePath(filePath);
+}
+
+// --------------------------------------------------------------------------
+int ctkErrorLogModel::numberOfFilesToKeep()const
+{
+  Q_D(const ctkErrorLogModel);
+  return d->FileLogger.numberOfFilesToKeep();
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogModel::setNumberOfFilesToKeep(int value)
+{
+  Q_D(ctkErrorLogModel);
+  return d->FileLogger.setNumberOfFilesToKeep(value);
+}
+
+// --------------------------------------------------------------------------
+bool ctkErrorLogModel::fileLoggingEnabled()const
+{
+  Q_D(const ctkErrorLogModel);
+  return d->FileLogger.enabled();
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogModel::setFileLoggingEnabled(bool value)
+{
+  Q_D(ctkErrorLogModel);
+  d->FileLogger.setEnabled(value);
+}
+
+// --------------------------------------------------------------------------
+QString ctkErrorLogModel::fileLoggingPattern()const
+{
+  Q_D(const ctkErrorLogModel);
+  return d->FileLoggingPattern;
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogModel::setFileLoggingPattern(const QString& value)
+{
+  Q_D(ctkErrorLogModel);
+  d->FileLoggingPattern = value;
+}
+
+// --------------------------------------------------------------------------
 QVariant ctkErrorLogModel::logEntryData(int row, int column, int role) const
 {
   Q_D(const ctkErrorLogModel);

+ 19 - 2
Libs/Widgets/ctkErrorLogModel.h

@@ -29,10 +29,10 @@
 #include "ctkErrorLogLevel.h"
 #include "ctkErrorLogTerminalOutput.h"
 
-
 //------------------------------------------------------------------------------
 class ctkErrorLogAbstractMessageHandler;
 class ctkErrorLogModelPrivate;
+struct ctkErrorLogContext;
 
 //------------------------------------------------------------------------------
 /// \ingroup Widgets
@@ -42,6 +42,10 @@ class CTK_WIDGETS_EXPORT ctkErrorLogModel : public QSortFilterProxyModel
   Q_PROPERTY(bool logEntryGrouping READ logEntryGrouping WRITE setLogEntryGrouping)
   Q_PROPERTY(ctkErrorLogTerminalOutput::TerminalOutputs terminalOutputs READ terminalOutputs WRITE setTerminalOutputs)
   Q_PROPERTY(bool asynchronousLogging READ asynchronousLogging WRITE  setAsynchronousLogging)
+  Q_PROPERTY(QString filePath READ filePath WRITE  setFilePath)
+  Q_PROPERTY(int numberOfFilesToKeep READ numberOfFilesToKeep WRITE  setNumberOfFilesToKeep)
+  Q_PROPERTY(bool fileLoggingEnabled READ fileLoggingEnabled WRITE  setFileLoggingEnabled)
+  Q_PROPERTY(QString fileLoggingPattern READ fileLoggingPattern WRITE setFileLoggingPattern)
 public:
   typedef QSortFilterProxyModel Superclass;
   typedef ctkErrorLogModel Self;
@@ -102,6 +106,18 @@ public:
   bool asynchronousLogging()const;
   void setAsynchronousLogging(bool value);
 
+  QString filePath()const;
+  void setFilePath(const QString& filePath);
+
+  int numberOfFilesToKeep()const;
+  void setNumberOfFilesToKeep(int value);
+
+  bool fileLoggingEnabled()const;
+  void setFileLoggingEnabled(bool value);
+
+  QString fileLoggingPattern()const;
+  void setFileLoggingPattern(const QString& value);
+
   /// Return log entry information associated with \a row and \a column.
   /// \internal
   QVariant logEntryData(int row,
@@ -123,7 +139,8 @@ public Q_SLOTS:
 
   /// \sa logEntryGrouping(), asynchronousLogging()
   void addEntry(const QDateTime& currentDateTime, const QString& threadId,
-                ctkErrorLogLevel::LogLevel logLevel, const QString& origin, const QString& text);
+                ctkErrorLogLevel::LogLevel logLevel, const QString& origin,
+                const ctkErrorLogContext &context, const QString& text);
 
 Q_SIGNALS:
   void logLevelFilterChanged();

+ 2 - 1
Libs/Widgets/ctkErrorLogStatusMessageHandler.cpp

@@ -27,6 +27,7 @@
 #include <QThread>
 
 // CTK includes
+#include "ctkErrorLogContext.h"
 #include "ctkErrorLogStatusMessageHandler.h"
 #include "ctkUtils.h"
 
@@ -86,5 +87,5 @@ void ctkErrorLogStatusMessageHandler::statusBarMessageChanged(const QString& tex
     }
   this->handleMessage(
         ctk::qtHandleToString(QThread::currentThreadId()),
-        ctkErrorLogLevel::Status, this->handlerPrettyName(), text);
+        ctkErrorLogLevel::Status, this->handlerPrettyName(), ctkErrorLogContext(text), text);
 }