Skip to content

Commit 2e91c29

Browse files
authoredSep 14, 2018
Merge pull request #7814 from elemoine/ele_logging
Support QGIS Server logs to stderr
2 parents a78a7e7 + 9eaee8f commit 2e91c29

File tree

13 files changed

+299
-48
lines changed

13 files changed

+299
-48
lines changed
 

‎python/core/auto_generated/qgsmessagelog.sip.in

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,43 @@ class QgsMessageLogConsole : QObject
107107
%Docstring
108108
Default implementation of message logging interface
109109

110-
This class outputs log messages to the standard output. Therefore it might
111-
be the right choice for apps without GUI.
110+
This class outputs log messages to the standard error. Therefore it might
111+
be the right choice for applications without GUI.
112112
%End
113113

114114
%TypeHeaderCode
115115
#include "qgsmessagelog.h"
116116
%End
117117
public:
118+
118119
QgsMessageLogConsole();
120+
%Docstring
121+
Constructor for QgsMessageLogConsole.
122+
%End
123+
124+
protected:
125+
126+
QString formatLogMessage( const QString &message, const QString &tag, Qgis::MessageLevel level = Qgis::Info ) const;
127+
%Docstring
128+
Formats a log message. Used by child classes.
129+
130+
:param message: the message to format
131+
:param tag: the tag of the message
132+
:param level: the log level of the message
133+
134+
.. versionadded:: 3.4
135+
%End
119136

120137
public slots:
121-
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
138+
139+
virtual void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
140+
%Docstring
141+
Logs a message to stderr.
142+
143+
:param message: the message to format
144+
:param tag: the tag of the message
145+
:param level: the log level of the message
146+
%End
122147
};
123148

124149
/************************************************************************
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/server/qgsserverlogger.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
14+
class QgsServerLogger : QgsMessageLogConsole
15+
{
16+
%Docstring
17+
Writes message log into server logfile
18+
19+
.. versionadded:: 2.8
20+
%End
21+
22+
%TypeHeaderCode
23+
#include "qgsserverlogger.h"
24+
%End
25+
public:
26+
27+
static QgsServerLogger *instance();
28+
%Docstring
29+
Gets the singleton instance
30+
%End
31+
32+
Qgis::MessageLevel logLevel() const;
33+
%Docstring
34+
Gets the current log level
35+
36+
:return: the log level
37+
38+
.. versionadded:: 3.0
39+
%End
40+
41+
void setLogLevel( Qgis::MessageLevel level );
42+
%Docstring
43+
Set the current log level
44+
45+
:param level: the log level
46+
47+
.. versionadded:: 3.0
48+
%End
49+
50+
void setLogFile( const QString &filename = QString() );
51+
%Docstring
52+
Set the current log file
53+
%End
54+
55+
void setLogStderr();
56+
%Docstring
57+
Activates logging to stderr.
58+
59+
.. versionadded:: 3.4.
60+
%End
61+
62+
public slots:
63+
64+
virtual void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
65+
66+
%Docstring
67+
Log a message from the server context
68+
69+
:param message: the message
70+
:param tag: tag of the message
71+
:param level: log level of the message
72+
%End
73+
74+
protected:
75+
QgsServerLogger();
76+
77+
};
78+
79+
/************************************************************************
80+
* This file has been generated automatically from *
81+
* *
82+
* src/server/qgsserverlogger.h *
83+
* *
84+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
85+
************************************************************************/

‎python/server/auto_generated/qgsserversettings.sip.in

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ Returns the QGS project file to use.
9494
Returns the log file.
9595

9696
:return: the path of the log file or an empty string if none is defined.
97+
%End
98+
99+
bool logStderr() const;
100+
%Docstring
101+
Returns whether logging to stderr is activated.
102+
103+
:return: true if logging to stderr is activated, false otherwise.
104+
105+
.. versionadded:: 3.4
97106
%End
98107

99108
qint64 cacheSize() const;

‎python/server/server_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
%Include auto_generated/qgsmapserviceexception.sip
44
%Include auto_generated/qgscapabilitiescache.sip
55
%Include auto_generated/qgsconfigcache.sip
6+
%Include auto_generated/qgsserverlogger.sip
67
%Include auto_generated/qgsserversettings.sip
78
%Include auto_generated/qgsserverparameters.sip
89
%Include auto_generated/qgsbufferserverrequest.sip

‎src/core/qgsmessagelog.cpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
#include "qgslogger.h"
1919
#include <QDateTime>
2020
#include <QMetaType>
21+
#include <QTextStream>
2122
#include <iostream>
23+
#include <stdio.h>
2224

2325
class QgsMessageLogConsole;
2426

@@ -47,12 +49,19 @@ QgsMessageLogConsole::QgsMessageLogConsole()
4749

4850
void QgsMessageLogConsole::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
4951
{
50-
std::cerr
51-
<< tag.toLocal8Bit().data() << "[" <<
52-
( level == Qgis::Info ? "INFO"
53-
: level == Qgis::Warning ? "WARNING"
54-
: "CRITICAL" )
55-
<< "]: " << message.toLocal8Bit().data() << std::endl;
52+
QString formattedMessage = formatLogMessage( message, tag, level );
53+
QTextStream cerr( stderr );
54+
cerr << formattedMessage;
55+
}
56+
57+
QString QgsMessageLogConsole::formatLogMessage( const QString &message, const QString &tag, Qgis::MessageLevel level ) const
58+
{
59+
const QString time = QTime::currentTime().toString();
60+
const QString levelStr = level == Qgis::Info ? QStringLiteral( "INFO" ) :
61+
level == Qgis::Warning ? QStringLiteral( "WARNING" ) :
62+
QStringLiteral( "CRITICAL" );
63+
const QString pid = QString::number( QCoreApplication::applicationPid() );
64+
return QStringLiteral( "%1 %2 %3[%4]: %5\n" ).arg( time, levelStr, tag, pid, message );
5665
}
5766

5867
//

‎src/core/qgsmessagelog.h

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,20 +128,44 @@ class CORE_EXPORT QgsMessageLogNotifyBlocker
128128

129129
/**
130130
* \ingroup core
131-
\brief Default implementation of message logging interface
132-
133-
This class outputs log messages to the standard output. Therefore it might
134-
be the right choice for apps without GUI.
135-
*/
131+
* \brief Default implementation of message logging interface
132+
*
133+
* This class outputs log messages to the standard error. Therefore it might
134+
* be the right choice for applications without GUI.
135+
*/
136136
class CORE_EXPORT QgsMessageLogConsole : public QObject
137137
{
138138
Q_OBJECT
139139

140140
public:
141+
142+
/**
143+
* Constructor for QgsMessageLogConsole.
144+
*/
141145
QgsMessageLogConsole();
142146

147+
protected:
148+
149+
/**
150+
* Formats a log message. Used by child classes.
151+
*
152+
* \param message the message to format
153+
* \param tag the tag of the message
154+
* \param level the log level of the message
155+
* \since QGIS 3.4
156+
*/
157+
QString formatLogMessage( const QString &message, const QString &tag, Qgis::MessageLevel level = Qgis::Info ) const;
158+
143159
public slots:
144-
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
160+
161+
/**
162+
* Logs a message to stderr.
163+
*
164+
* \param message the message to format
165+
* \param tag the tag of the message
166+
* \param level the log level of the message
167+
*/
168+
virtual void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
145169
};
146170

147171
#endif

‎src/server/qgsserver.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,14 @@ bool QgsServer::init()
186186
// init and configure logger
187187
QgsServerLogger::instance();
188188
QgsServerLogger::instance()->setLogLevel( sSettings.logLevel() );
189-
QgsServerLogger::instance()->setLogFile( sSettings.logFile() );
189+
if ( ! sSettings.logFile().isEmpty() )
190+
{
191+
QgsServerLogger::instance()->setLogFile( sSettings.logFile() );
192+
}
193+
else if ( sSettings.logStderr() )
194+
{
195+
QgsServerLogger::instance()->setLogStderr();
196+
}
190197

191198
// log settings currently used
192199
sSettings.logSummary();

‎src/server/qgsserverlogger.cpp

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,44 +36,47 @@ QgsServerLogger *QgsServerLogger::instance()
3636
}
3737

3838
QgsServerLogger::QgsServerLogger()
39-
: mLogFile( nullptr )
39+
: QgsMessageLogConsole()
4040
{
41-
connect( QgsApplication::messageLog(), static_cast<void ( QgsMessageLog::* )( const QString &, const QString &, Qgis::MessageLevel )>( &QgsMessageLog::messageReceived ), this,
42-
&QgsServerLogger::logMessage );
4341
}
4442

45-
void QgsServerLogger::setLogLevel( Qgis::MessageLevel level )
43+
void QgsServerLogger::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
4644
{
47-
mLogLevel = level;
45+
if ( mLogLevel > level )
46+
{
47+
return;
48+
}
49+
if ( mLogFile.isOpen() )
50+
{
51+
QString formattedMessage = formatLogMessage( message, tag, level );
52+
mTextStream << formattedMessage;
53+
mTextStream.flush();
54+
}
55+
else if ( mLogStderr )
56+
{
57+
QgsMessageLogConsole::logMessage( message, tag, level );
58+
}
4859
}
4960

50-
void QgsServerLogger::setLogFile( const QString &f )
61+
void QgsServerLogger::setLogLevel( const Qgis::MessageLevel level )
5162
{
52-
if ( ! f.isEmpty() )
53-
{
54-
if ( mLogFile.exists() )
55-
{
56-
mTextStream.flush();
57-
mLogFile.close();
58-
}
59-
60-
mLogFile.setFileName( f );
61-
if ( mLogFile.open( QIODevice::Append ) )
62-
{
63-
mTextStream.setDevice( &mLogFile );
64-
}
65-
}
63+
mLogLevel = level;
6664
}
6765

68-
void QgsServerLogger::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
66+
void QgsServerLogger::setLogFile( const QString &filename )
6967
{
70-
Q_UNUSED( tag );
71-
if ( !mLogFile.isOpen() || mLogLevel > level )
68+
mTextStream.flush();
69+
mLogFile.close();
70+
mLogFile.setFileName( filename );
71+
72+
if ( ( ! filename.isEmpty() ) && mLogFile.open( QIODevice::Append ) )
7273
{
73-
return;
74+
mTextStream.setDevice( &mLogFile );
7475
}
76+
}
7577

76-
mTextStream << ( "[" + QString::number( qlonglong( QCoreApplication::applicationPid() ) ) + "]["
77-
+ QTime::currentTime().toString() + "] " + message + "\n" );
78-
mTextStream.flush();
78+
void QgsServerLogger::setLogStderr()
79+
{
80+
setLogFile();
81+
mLogStderr = true;
7982
}

‎src/server/qgsserverlogger.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,21 @@
1818
#ifndef QGSSERVERLOGGER_H
1919
#define QGSSERVERLOGGER_H
2020

21-
#define SIP_NO_FILE
22-
2321

2422
#include "qgsmessagelog.h"
2523

2624
#include <QFile>
2725
#include <QObject>
2826
#include <QString>
2927
#include <QTextStream>
28+
#include "qgis_server.h"
3029

3130
/**
3231
* \ingroup server
3332
* \brief Writes message log into server logfile
3433
* \since QGIS 2.8
3534
*/
36-
class QgsServerLogger: public QObject
35+
class SERVER_EXPORT QgsServerLogger : public QgsMessageLogConsole
3736
{
3837
Q_OBJECT
3938
public:
@@ -60,7 +59,13 @@ class QgsServerLogger: public QObject
6059
/**
6160
* Set the current log file
6261
*/
63-
void setLogFile( const QString &f );
62+
void setLogFile( const QString &filename = QString() );
63+
64+
/**
65+
* Activates logging to stderr.
66+
* \since QGIS 3.4.
67+
*/
68+
void setLogStderr();
6469

6570
public slots:
6671

@@ -71,7 +76,7 @@ class QgsServerLogger: public QObject
7176
* \param tag tag of the message
7277
* \param level log level of the message
7378
*/
74-
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level );
79+
void logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level ) override;
7580

7681
protected:
7782
QgsServerLogger();
@@ -80,6 +85,7 @@ class QgsServerLogger: public QObject
8085
static QgsServerLogger *sInstance;
8186

8287
QFile mLogFile;
88+
bool mLogStderr = false;
8389
QTextStream mTextStream;
8490
Qgis::MessageLevel mLogLevel = Qgis::None;
8591
};

‎src/server/qgsserversettings.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ void QgsServerSettings::initSettings()
8787
};
8888
mSettings[ sLogFile.envVar ] = sLogFile;
8989

90+
// log to stderr
91+
const Setting sLogStderr = { QgsServerSettingsEnv::QGIS_SERVER_LOG_STDERR,
92+
QgsServerSettingsEnv::DEFAULT_VALUE,
93+
"Activate/Deactivate logging to stderr",
94+
"",
95+
QVariant::Bool,
96+
QVariant( false ),
97+
QVariant()
98+
};
99+
mSettings[ sLogStderr.envVar ] = sLogStderr;
100+
90101
// project file
91102
const Setting sProject = { QgsServerSettingsEnv::QGIS_PROJECT_FILE,
92103
QgsServerSettingsEnv::DEFAULT_VALUE,
@@ -281,6 +292,11 @@ QString QgsServerSettings::logFile() const
281292
return value( QgsServerSettingsEnv::QGIS_SERVER_LOG_FILE ).toString();
282293
}
283294

295+
bool QgsServerSettings::logStderr() const
296+
{
297+
return value( QgsServerSettingsEnv::QGIS_SERVER_LOG_STDERR ).toBool();
298+
}
299+
284300
Qgis::MessageLevel QgsServerSettings::logLevel() const
285301
{
286302
return static_cast<Qgis::MessageLevel>( value( QgsServerSettingsEnv::QGIS_SERVER_LOG_LEVEL ).toInt() );

‎src/server/qgsserversettings.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class SERVER_EXPORT QgsServerSettingsEnv : public QObject
5656
QGIS_SERVER_MAX_THREADS,
5757
QGIS_SERVER_LOG_LEVEL,
5858
QGIS_SERVER_LOG_FILE,
59+
QGIS_SERVER_LOG_STDERR,
5960
QGIS_PROJECT_FILE,
6061
MAX_CACHE_LAYERS,
6162
QGIS_SERVER_CACHE_DIRECTORY,
@@ -148,6 +149,13 @@ class SERVER_EXPORT QgsServerSettings
148149
*/
149150
QString logFile() const;
150151

152+
/**
153+
* Returns whether logging to stderr is activated.
154+
* \returns true if logging to stderr is activated, false otherwise.
155+
* \since QGIS 3.4
156+
*/
157+
bool logStderr() const;
158+
151159
/**
152160
* Returns the cache size.
153161
* \returns the cache size.

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ ENDIF (ENABLE_ORACLETEST)
257257

258258
IF (WITH_SERVER)
259259
ADD_PYTHON_TEST(PyQgsServer test_qgsserver.py)
260+
ADD_PYTHON_TEST(PyQgsServerLogger test_qgsserverlogger.py)
260261
ADD_PYTHON_TEST(PyQgsServerPlugins test_qgsserver_plugins.py)
261262
ADD_PYTHON_TEST(PyQgsServerWMS test_qgsserver_wms.py)
262263
ADD_PYTHON_TEST(PyQgsServerWMSGetMap test_qgsserver_wms_getmap.py)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsServerLogger.
3+
"""
4+
__author__ = 'Eric Lemoine'
5+
__date__ = '11/09/2018'
6+
__copyright__ = 'Copyright 2018, The QGIS Project'
7+
__revision__ = '$Format:%H$'
8+
9+
import os
10+
11+
from qgis.testing import unittest
12+
from qgis.server import QgsServerLogger
13+
14+
from utilities import unitTestDataPath
15+
16+
17+
class TestQgsServerLogger(unittest.TestCase):
18+
19+
log_file = os.path.join(unitTestDataPath('qgis_server'), 'qgis_server_test.log')
20+
21+
@staticmethod
22+
def remove_file(filename):
23+
try:
24+
os.remove(filename)
25+
except FileNotFoundError:
26+
pass
27+
28+
def setUp(self):
29+
self.logger = QgsServerLogger.instance()
30+
self.logger.setLogLevel(0)
31+
self.logger.setLogFile(self.log_file)
32+
exists = os.access(self.log_file, os.R_OK)
33+
self.assertTrue(exists)
34+
self.remove_file(self.log_file)
35+
36+
def tearDown(self):
37+
self.remove_file(self.log_file)
38+
39+
def test_logging_no_log_file(self):
40+
self.logger.setLogFile('')
41+
exists = os.access(self.log_file, os.R_OK)
42+
self.assertFalse(exists)
43+
44+
def test_logging_log_file(self):
45+
self.logger.setLogFile(self.log_file)
46+
exists = os.access(self.log_file, os.R_OK)
47+
self.assertTrue(exists)
48+
49+
def test_logging_log_file_stderr(self):
50+
self.logger.setLogFile('stderr')
51+
exists = os.access(self.log_file, os.R_OK)
52+
self.assertFalse(exists)
53+
54+
def test_logging_stderr(self):
55+
self.logger.setLogStderr()
56+
exists = os.access(self.log_file, os.R_OK)
57+
self.assertFalse(exists)

0 commit comments

Comments
 (0)
Please sign in to comment.