Merge pull request #7814 from elemoine/ele_logging

Support QGIS Server logs to stderr
This commit is contained in:
Blottiere Paul 2018-09-14 11:14:08 +02:00 committed by GitHub
commit 2e91c29dff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 304 additions and 53 deletions

View File

@ -107,18 +107,43 @@ class QgsMessageLogConsole : QObject
%Docstring
Default implementation of message logging interface
This class outputs log messages to the standard output. Therefore it might
be the right choice for apps without GUI.
This class outputs log messages to the standard error. Therefore it might
be the right choice for applications without GUI.
%End
%TypeHeaderCode
#include "qgsmessagelog.h"
%End
public:
QgsMessageLogConsole();
%Docstring
Constructor for QgsMessageLogConsole.
%End
protected:
QString formatLogMessage( const QString &message, const QString &tag, Qgis::MessageLevel level = Qgis::Info ) const;
%Docstring
Formats a log message. Used by child classes.
:param message: the message to format
:param tag: the tag of the message
:param level: the log level of the message
.. versionadded:: 3.4
%End
public slots:
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
virtual void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
%Docstring
Logs a message to stderr.
:param message: the message to format
:param tag: the tag of the message
:param level: the log level of the message
%End
};
/************************************************************************

View File

@ -0,0 +1,85 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsserverlogger.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsServerLogger : QgsMessageLogConsole
{
%Docstring
Writes message log into server logfile
.. versionadded:: 2.8
%End
%TypeHeaderCode
#include "qgsserverlogger.h"
%End
public:
static QgsServerLogger *instance();
%Docstring
Gets the singleton instance
%End
Qgis::MessageLevel logLevel() const;
%Docstring
Gets the current log level
:return: the log level
.. versionadded:: 3.0
%End
void setLogLevel( Qgis::MessageLevel level );
%Docstring
Set the current log level
:param level: the log level
.. versionadded:: 3.0
%End
void setLogFile( const QString &filename = QString() );
%Docstring
Set the current log file
%End
void setLogStderr();
%Docstring
Activates logging to stderr.
.. versionadded:: 3.4.
%End
public slots:
virtual void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
%Docstring
Log a message from the server context
:param message: the message
:param tag: tag of the message
:param level: log level of the message
%End
protected:
QgsServerLogger();
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsserverlogger.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -94,6 +94,15 @@ Returns the QGS project file to use.
Returns the log file.
:return: the path of the log file or an empty string if none is defined.
%End
bool logStderr() const;
%Docstring
Returns whether logging to stderr is activated.
:return: true if logging to stderr is activated, false otherwise.
.. versionadded:: 3.4
%End
qint64 cacheSize() const;

View File

@ -3,6 +3,7 @@
%Include auto_generated/qgsmapserviceexception.sip
%Include auto_generated/qgscapabilitiescache.sip
%Include auto_generated/qgsconfigcache.sip
%Include auto_generated/qgsserverlogger.sip
%Include auto_generated/qgsserversettings.sip
%Include auto_generated/qgsserverparameters.sip
%Include auto_generated/qgsbufferserverrequest.sip

View File

@ -18,7 +18,9 @@
#include "qgslogger.h"
#include <QDateTime>
#include <QMetaType>
#include <QTextStream>
#include <iostream>
#include <stdio.h>
class QgsMessageLogConsole;
@ -47,12 +49,19 @@ QgsMessageLogConsole::QgsMessageLogConsole()
void QgsMessageLogConsole::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
{
std::cerr
<< tag.toLocal8Bit().data() << "[" <<
( level == Qgis::Info ? "INFO"
: level == Qgis::Warning ? "WARNING"
: "CRITICAL" )
<< "]: " << message.toLocal8Bit().data() << std::endl;
QString formattedMessage = formatLogMessage( message, tag, level );
QTextStream cerr( stderr );
cerr << formattedMessage;
}
QString QgsMessageLogConsole::formatLogMessage( const QString &message, const QString &tag, Qgis::MessageLevel level ) const
{
const QString time = QTime::currentTime().toString();
const QString levelStr = level == Qgis::Info ? QStringLiteral( "INFO" ) :
level == Qgis::Warning ? QStringLiteral( "WARNING" ) :
QStringLiteral( "CRITICAL" );
const QString pid = QString::number( QCoreApplication::applicationPid() );
return QStringLiteral( "%1 %2 %3[%4]: %5\n" ).arg( time, levelStr, tag, pid, message );
}
//

View File

@ -128,20 +128,44 @@ class CORE_EXPORT QgsMessageLogNotifyBlocker
/**
* \ingroup core
\brief Default implementation of message logging interface
This class outputs log messages to the standard output. Therefore it might
be the right choice for apps without GUI.
*/
* \brief Default implementation of message logging interface
*
* This class outputs log messages to the standard error. Therefore it might
* be the right choice for applications without GUI.
*/
class CORE_EXPORT QgsMessageLogConsole : public QObject
{
Q_OBJECT
public:
/**
* Constructor for QgsMessageLogConsole.
*/
QgsMessageLogConsole();
protected:
/**
* Formats a log message. Used by child classes.
*
* \param message the message to format
* \param tag the tag of the message
* \param level the log level of the message
* \since QGIS 3.4
*/
QString formatLogMessage( const QString &message, const QString &tag, Qgis::MessageLevel level = Qgis::Info ) const;
public slots:
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
/**
* Logs a message to stderr.
*
* \param message the message to format
* \param tag the tag of the message
* \param level the log level of the message
*/
virtual void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
};
#endif

View File

@ -186,7 +186,14 @@ bool QgsServer::init()
// init and configure logger
QgsServerLogger::instance();
QgsServerLogger::instance()->setLogLevel( sSettings.logLevel() );
QgsServerLogger::instance()->setLogFile( sSettings.logFile() );
if ( ! sSettings.logFile().isEmpty() )
{
QgsServerLogger::instance()->setLogFile( sSettings.logFile() );
}
else if ( sSettings.logStderr() )
{
QgsServerLogger::instance()->setLogStderr();
}
// log settings currently used
sSettings.logSummary();

View File

@ -36,44 +36,47 @@ QgsServerLogger *QgsServerLogger::instance()
}
QgsServerLogger::QgsServerLogger()
: mLogFile( nullptr )
: QgsMessageLogConsole()
{
connect( QgsApplication::messageLog(), static_cast<void ( QgsMessageLog::* )( const QString &, const QString &, Qgis::MessageLevel )>( &QgsMessageLog::messageReceived ), this,
&QgsServerLogger::logMessage );
}
void QgsServerLogger::setLogLevel( Qgis::MessageLevel level )
{
mLogLevel = level;
}
void QgsServerLogger::setLogFile( const QString &f )
{
if ( ! f.isEmpty() )
{
if ( mLogFile.exists() )
{
mTextStream.flush();
mLogFile.close();
}
mLogFile.setFileName( f );
if ( mLogFile.open( QIODevice::Append ) )
{
mTextStream.setDevice( &mLogFile );
}
}
}
void QgsServerLogger::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
{
Q_UNUSED( tag );
if ( !mLogFile.isOpen() || mLogLevel > level )
if ( mLogLevel > level )
{
return;
}
mTextStream << ( "[" + QString::number( qlonglong( QCoreApplication::applicationPid() ) ) + "]["
+ QTime::currentTime().toString() + "] " + message + "\n" );
mTextStream.flush();
if ( mLogFile.isOpen() )
{
QString formattedMessage = formatLogMessage( message, tag, level );
mTextStream << formattedMessage;
mTextStream.flush();
}
else if ( mLogStderr )
{
QgsMessageLogConsole::logMessage( message, tag, level );
}
}
void QgsServerLogger::setLogLevel( const Qgis::MessageLevel level )
{
mLogLevel = level;
}
void QgsServerLogger::setLogFile( const QString &filename )
{
mTextStream.flush();
mLogFile.close();
mLogFile.setFileName( filename );
if ( ( ! filename.isEmpty() ) && mLogFile.open( QIODevice::Append ) )
{
mTextStream.setDevice( &mLogFile );
}
}
void QgsServerLogger::setLogStderr()
{
setLogFile();
mLogStderr = true;
}

View File

@ -18,8 +18,6 @@
#ifndef QGSSERVERLOGGER_H
#define QGSSERVERLOGGER_H
#define SIP_NO_FILE
#include "qgsmessagelog.h"
@ -27,13 +25,14 @@
#include <QObject>
#include <QString>
#include <QTextStream>
#include "qgis_server.h"
/**
* \ingroup server
* \brief Writes message log into server logfile
* \since QGIS 2.8
*/
class QgsServerLogger: public QObject
class SERVER_EXPORT QgsServerLogger : public QgsMessageLogConsole
{
Q_OBJECT
public:
@ -60,7 +59,13 @@ class QgsServerLogger: public QObject
/**
* Set the current log file
*/
void setLogFile( const QString &f );
void setLogFile( const QString &filename = QString() );
/**
* Activates logging to stderr.
* \since QGIS 3.4.
*/
void setLogStderr();
public slots:
@ -71,7 +76,7 @@ class QgsServerLogger: public QObject
* \param tag tag of the message
* \param level log level of the message
*/
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level ) override;
protected:
QgsServerLogger();
@ -80,6 +85,7 @@ class QgsServerLogger: public QObject
static QgsServerLogger *sInstance;
QFile mLogFile;
bool mLogStderr = false;
QTextStream mTextStream;
Qgis::MessageLevel mLogLevel = Qgis::None;
};

View File

@ -87,6 +87,17 @@ void QgsServerSettings::initSettings()
};
mSettings[ sLogFile.envVar ] = sLogFile;
// log to stderr
const Setting sLogStderr = { QgsServerSettingsEnv::QGIS_SERVER_LOG_STDERR,
QgsServerSettingsEnv::DEFAULT_VALUE,
"Activate/Deactivate logging to stderr",
"",
QVariant::Bool,
QVariant( false ),
QVariant()
};
mSettings[ sLogStderr.envVar ] = sLogStderr;
// project file
const Setting sProject = { QgsServerSettingsEnv::QGIS_PROJECT_FILE,
QgsServerSettingsEnv::DEFAULT_VALUE,
@ -281,6 +292,11 @@ QString QgsServerSettings::logFile() const
return value( QgsServerSettingsEnv::QGIS_SERVER_LOG_FILE ).toString();
}
bool QgsServerSettings::logStderr() const
{
return value( QgsServerSettingsEnv::QGIS_SERVER_LOG_STDERR ).toBool();
}
Qgis::MessageLevel QgsServerSettings::logLevel() const
{
return static_cast<Qgis::MessageLevel>( value( QgsServerSettingsEnv::QGIS_SERVER_LOG_LEVEL ).toInt() );

View File

@ -56,6 +56,7 @@ class SERVER_EXPORT QgsServerSettingsEnv : public QObject
QGIS_SERVER_MAX_THREADS,
QGIS_SERVER_LOG_LEVEL,
QGIS_SERVER_LOG_FILE,
QGIS_SERVER_LOG_STDERR,
QGIS_PROJECT_FILE,
MAX_CACHE_LAYERS,
QGIS_SERVER_CACHE_DIRECTORY,
@ -148,6 +149,13 @@ class SERVER_EXPORT QgsServerSettings
*/
QString logFile() const;
/**
* Returns whether logging to stderr is activated.
* \returns true if logging to stderr is activated, false otherwise.
* \since QGIS 3.4
*/
bool logStderr() const;
/**
* Returns the cache size.
* \returns the cache size.

View File

@ -257,6 +257,7 @@ ENDIF (ENABLE_ORACLETEST)
IF (WITH_SERVER)
ADD_PYTHON_TEST(PyQgsServer test_qgsserver.py)
ADD_PYTHON_TEST(PyQgsServerLogger test_qgsserverlogger.py)
ADD_PYTHON_TEST(PyQgsServerPlugins test_qgsserver_plugins.py)
ADD_PYTHON_TEST(PyQgsServerWMS test_qgsserver_wms.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetMap test_qgsserver_wms_getmap.py)

View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsServerLogger.
"""
__author__ = 'Eric Lemoine'
__date__ = '11/09/2018'
__copyright__ = 'Copyright 2018, The QGIS Project'
__revision__ = '$Format:%H$'
import os
from qgis.testing import unittest
from qgis.server import QgsServerLogger
from utilities import unitTestDataPath
class TestQgsServerLogger(unittest.TestCase):
log_file = os.path.join(unitTestDataPath('qgis_server'), 'qgis_server_test.log')
@staticmethod
def remove_file(filename):
try:
os.remove(filename)
except FileNotFoundError:
pass
def setUp(self):
self.logger = QgsServerLogger.instance()
self.logger.setLogLevel(0)
self.logger.setLogFile(self.log_file)
exists = os.access(self.log_file, os.R_OK)
self.assertTrue(exists)
self.remove_file(self.log_file)
def tearDown(self):
self.remove_file(self.log_file)
def test_logging_no_log_file(self):
self.logger.setLogFile('')
exists = os.access(self.log_file, os.R_OK)
self.assertFalse(exists)
def test_logging_log_file(self):
self.logger.setLogFile(self.log_file)
exists = os.access(self.log_file, os.R_OK)
self.assertTrue(exists)
def test_logging_log_file_stderr(self):
self.logger.setLogFile('stderr')
exists = os.access(self.log_file, os.R_OK)
self.assertFalse(exists)
def test_logging_stderr(self):
self.logger.setLogStderr()
exists = os.access(self.log_file, os.R_OK)
self.assertFalse(exists)