Skip to content

Commit

Permalink
[processing][FEATURE] Add "Save Log to File" algorithm for models
Browse files Browse the repository at this point in the history
This algorithm saves the contents of the execution log (right up to
the point in the model at which the 'save log' algorithm executes)
to a file.

It can be used to automatically store the debugging log when running
models for later reference and transparency.
  • Loading branch information
nyalldawson committed Mar 31, 2020
1 parent 0684d41 commit 60af337
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 11 deletions.
Expand Up @@ -48,6 +48,7 @@ Abstract base class for processing algorithms.
FlagKnownIssues,
FlagCustomException,
FlagPruneModelBranchesBasedOnAlgorithmResults,
FlagSkipGenericModelLogging,
FlagDeprecated,
};
typedef QFlags<QgsProcessingAlgorithm::Flag> Flags;
Expand Down
Expand Up @@ -108,7 +108,7 @@ Pushes a summary of the QGIS (and underlying library) version information to the
.. versionadded:: 3.4.7
%End

QString htmlLog() const;
virtual QString htmlLog() const;
%Docstring
Returns the HTML formatted contents of the log, which contains all messages pushed to the feedback object.

Expand All @@ -117,7 +117,7 @@ Returns the HTML formatted contents of the log, which contains all messages push
.. versionadded:: 3.14
%End

QString textLog() const;
virtual QString textLog() const;
%Docstring
Returns the plain text contents of the log, which contains all messages pushed to the feedback object.

Expand Down Expand Up @@ -172,6 +172,9 @@ to scale the current progress to account for progress through the overall proces

virtual void pushConsoleInfo( const QString &info );

virtual QString htmlLog() const;

virtual QString textLog() const;

};

Expand Down
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -131,6 +131,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmreverselinedirection.cpp
processing/qgsalgorithmrotate.cpp
processing/qgsalgorithmruggedness.cpp
processing/qgsalgorithmsavelog.cpp
processing/qgsalgorithmsaveselectedfeatures.cpp
processing/qgsalgorithmsegmentize.cpp
processing/qgsalgorithmserviceareafromlayer.cpp
Expand Down
93 changes: 93 additions & 0 deletions src/analysis/processing/qgsalgorithmsavelog.cpp
@@ -0,0 +1,93 @@
/***************************************************************************
qgsalgorithmsavelog.cpp
---------------------
begin : March 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsalgorithmsavelog.h"

///@cond PRIVATE

QString QgsSaveLogToFileAlgorithm::name() const
{
return QStringLiteral( "savelog" );
}

QgsProcessingAlgorithm::Flags QgsSaveLogToFileAlgorithm::flags() const
{
return QgsProcessingAlgorithm::flags() | FlagHideFromToolbox | FlagSkipGenericModelLogging;
}

QString QgsSaveLogToFileAlgorithm::displayName() const
{
return QObject::tr( "Save log to file" );
}

QStringList QgsSaveLogToFileAlgorithm::tags() const
{
return QObject::tr( "record,messages,logged" ).split( ',' );
}

QString QgsSaveLogToFileAlgorithm::group() const
{
return QObject::tr( "Modeler tools" );
}

QString QgsSaveLogToFileAlgorithm::groupId() const
{
return QStringLiteral( "modelertools" );
}

QString QgsSaveLogToFileAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm saves the model's execution log to a file.\n"
"Optionally, the log can be saved in a HTML formatted version." );
}

QString QgsSaveLogToFileAlgorithm::shortDescription() const
{
return QObject::tr( "Saves the model's log contents to a file." );
}

QgsSaveLogToFileAlgorithm *QgsSaveLogToFileAlgorithm::createInstance() const
{
return new QgsSaveLogToFileAlgorithm();
}

void QgsSaveLogToFileAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Log file" ), QObject::tr( "Text files (*.txt);;HTML files (*.html *.HTML)" ) ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_HTML" ), QObject::tr( "Use HTML formatting" ), false ) );
}

QVariantMap QgsSaveLogToFileAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
const QString file = parameterAsFile( parameters, QStringLiteral( "OUTPUT" ), context );
const bool useHtml = parameterAsBool( parameters, QStringLiteral( "USE_HTML" ), context );
if ( !file.isEmpty() )
{
QFile exportFile( file );
if ( !exportFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
{
throw QgsProcessingException( QObject::tr( "Could not save log to file %1" ).arg( file ) );
}
QTextStream fout( &exportFile );
fout << ( useHtml ? feedback->htmlLog() : feedback->textLog() );
}
QVariantMap res;
res.insert( QStringLiteral( "OUTPUT" ), file );
return res;
}

///@endcond
57 changes: 57 additions & 0 deletions src/analysis/processing/qgsalgorithmsavelog.h
@@ -0,0 +1,57 @@
/***************************************************************************
qgsalgorithmsavelog.h
---------------------
begin : March 2020
copyright : (C) 2020 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSALGORITHMSAVELOG_H
#define QGSALGORITHMSAVELOG_H

#define SIP_NO_FILE

#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"
#include "qgsapplication.h"

///@cond PRIVATE

/**
* Native save log to file algorithm.
*/
class QgsSaveLogToFileAlgorithm : public QgsProcessingAlgorithm
{
public:
QgsSaveLogToFileAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
Flags flags() const override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QString shortDescription() const override;
QgsSaveLogToFileAlgorithm *createInstance() const override SIP_FACTORY;

protected:

QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback * ) override;

};


///@endcond PRIVATE

#endif // QGSALGORITHMSAVELOG_H
2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -125,6 +125,7 @@
#include "qgsalgorithmreverselinedirection.h"
#include "qgsalgorithmrotate.h"
#include "qgsalgorithmruggedness.h"
#include "qgsalgorithmsavelog.h"
#include "qgsalgorithmsaveselectedfeatures.h"
#include "qgsalgorithmsegmentize.h"
#include "qgsalgorithmserviceareafromlayer.h"
Expand Down Expand Up @@ -331,6 +332,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsReverseLineDirectionAlgorithm() );
addAlgorithm( new QgsRotateFeaturesAlgorithm() );
addAlgorithm( new QgsRuggednessAlgorithm() );
addAlgorithm( new QgsSaveLogToFileAlgorithm() );
addAlgorithm( new QgsSaveSelectedFeatures() );
addAlgorithm( new QgsSegmentizeByMaximumAngleAlgorithm() );
addAlgorithm( new QgsSegmentizeByMaximumDistanceAlgorithm() );
Expand Down
14 changes: 8 additions & 6 deletions src/core/processing/models/qgsprocessingmodelalgorithm.cpp
Expand Up @@ -282,18 +282,21 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
continue;

executedAlg = true;
if ( feedback )
feedback->pushDebugInfo( QObject::tr( "Prepare algorithm: %1" ).arg( childId ) );

const QgsProcessingModelChildAlgorithm &child = mChildAlgorithms[ childId ];
std::unique_ptr< QgsProcessingAlgorithm > childAlg( child.algorithm()->create( child.configuration() ) );

const bool skipGenericLogging = childAlg->flags() & QgsProcessingAlgorithm::FlagSkipGenericModelLogging;
if ( feedback && !skipGenericLogging )
feedback->pushDebugInfo( QObject::tr( "Prepare algorithm: %1" ).arg( childId ) );

QgsExpressionContext expContext = baseContext;
expContext << QgsExpressionContextUtils::processingAlgorithmScope( child.algorithm(), parameters, context )
<< createExpressionContextScopeForChildAlgorithm( childId, context, parameters, childResults );
context.setExpressionContext( expContext );

QVariantMap childParams = parametersForChildAlgorithm( child, parameters, childResults, expContext );
if ( feedback )
if ( feedback && !skipGenericLogging )
feedback->setProgressText( QObject::tr( "Running %1 [%2/%3]" ).arg( child.description() ).arg( executed.count() + 1 ).arg( toExecute.count() ) );

QStringList params;
Expand All @@ -303,7 +306,7 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
child.algorithm()->parameterDefinition( childParamIt.key() )->valueAsPythonString( childParamIt.value(), context ) );
}

if ( feedback )
if ( feedback && !skipGenericLogging )
{
feedback->pushInfo( QObject::tr( "Input Parameters:" ) );
feedback->pushCommandInfo( QStringLiteral( "{ %1 }" ).arg( params.join( QStringLiteral( ", " ) ) ) );
Expand All @@ -313,7 +316,6 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
childTime.start();

bool ok = false;
std::unique_ptr< QgsProcessingAlgorithm > childAlg( child.algorithm()->create( child.configuration() ) );
QVariantMap results = childAlg->run( childParams, context, &modelFeedback, &ok, child.configuration() );
if ( !ok )
{
Expand Down Expand Up @@ -389,7 +391,7 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa

childAlg.reset( nullptr );
modelFeedback.setCurrentStep( executed.count() );
if ( feedback )
if ( feedback && !skipGenericLogging )
feedback->pushInfo( QObject::tr( "OK. Execution took %1 s (%2 outputs)." ).arg( childTime.elapsed() / 1000.0 ).arg( results.count() ) );
}

Expand Down
1 change: 1 addition & 0 deletions src/core/processing/qgsprocessingalgorithm.h
Expand Up @@ -78,6 +78,7 @@ class CORE_EXPORT QgsProcessingAlgorithm
FlagKnownIssues = 1 << 9, //!< Algorithm has known issues
FlagCustomException = 1 << 10, //!< Algorithm raises custom exception notices, don't use the standard ones
FlagPruneModelBranchesBasedOnAlgorithmResults = 1 << 11, //!< Algorithm results will cause remaining model branches to be pruned based on the results of running the algorithm
FlagSkipGenericModelLogging = 1 << 12, //!< When running as part of a model, the generic algorithm setup and results logging should be skipped
FlagDeprecated = FlagHideFromToolbox | FlagHideFromModeler, //!< Algorithm is deprecated
};
Q_DECLARE_FLAGS( Flags, Flag )
Expand Down
20 changes: 20 additions & 0 deletions src/core/processing/qgsprocessingfeedback.cpp
Expand Up @@ -104,6 +104,16 @@ void QgsProcessingFeedback::pushVersionInfo( const QgsProcessingProvider *provid
}
}

QString QgsProcessingFeedback::htmlLog() const
{
return mHtmlLog;
}

QString QgsProcessingFeedback::textLog() const
{
return mTextLog;
}


QgsProcessingMultiStepFeedback::QgsProcessingMultiStepFeedback( int childAlgorithmCount, QgsProcessingFeedback *feedback )
: mChildSteps( childAlgorithmCount )
Expand Down Expand Up @@ -149,6 +159,16 @@ void QgsProcessingMultiStepFeedback::pushConsoleInfo( const QString &info )
mFeedback->pushConsoleInfo( info );
}

QString QgsProcessingMultiStepFeedback::htmlLog() const
{
return mFeedback->htmlLog();
}

QString QgsProcessingMultiStepFeedback::textLog() const
{
return mFeedback->textLog();
}

void QgsProcessingMultiStepFeedback::updateOverallProgress( double progress )
{
double baseProgress = 100.0 * static_cast< double >( mCurrentStep ) / mChildSteps;
Expand Down
7 changes: 4 additions & 3 deletions src/core/processing/qgsprocessingfeedback.h
Expand Up @@ -113,15 +113,15 @@ class CORE_EXPORT QgsProcessingFeedback : public QgsFeedback
* \see textLog()
* \since QGIS 3.14
*/
QString htmlLog() const { return mHtmlLog; }
virtual QString htmlLog() const;

/**
* Returns the plain text contents of the log, which contains all messages pushed to the feedback object.
*
* \see htmlLog()
* \since QGIS 3.14
*/
QString textLog() const { return mTextLog; }
virtual QString textLog() const;

private:
bool mLogFeedback = true;
Expand Down Expand Up @@ -168,7 +168,8 @@ class CORE_EXPORT QgsProcessingMultiStepFeedback : public QgsProcessingFeedback
void pushCommandInfo( const QString &info ) override;
void pushDebugInfo( const QString &info ) override;
void pushConsoleInfo( const QString &info ) override;

QString htmlLog() const override;
QString textLog() const override;
private slots:

void updateOverallProgress( double progress );
Expand Down
32 changes: 32 additions & 0 deletions tests/src/analysis/testqgsprocessingalgs.cpp
Expand Up @@ -114,6 +114,8 @@ class TestQgsProcessingAlgs: public QObject

void filterByLayerType();

void saveLog();

private:

QString mPointLayerPath;
Expand Down Expand Up @@ -2379,5 +2381,35 @@ void TestQgsProcessingAlgs::filterByLayerType()
QVERIFY( !results.contains( QStringLiteral( "VECTOR" ) ) );
}

void TestQgsProcessingAlgs::saveLog()
{
std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:savelog" ) ) );
QVERIFY( alg != nullptr );

QVariantMap parameters;
parameters.insert( QStringLiteral( "OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );

bool ok = false;
std::unique_ptr< QgsProcessingContext > context = qgis::make_unique< QgsProcessingContext >();
QgsProcessingFeedback feedback;
feedback.reportError( QStringLiteral( "test" ) );
QVariantMap results;
results = alg->run( parameters, *context, &feedback, &ok );
QVERIFY( ok );

QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
QFile file( results.value( QStringLiteral( "OUTPUT" ) ).toString() );
QVERIFY( file.open( QFile::ReadOnly | QIODevice::Text ) );
QCOMPARE( file.readAll(), QStringLiteral( "test\n" ) );

parameters.insert( QStringLiteral( "USE_HTML" ), true );
results = alg->run( parameters, *context, &feedback, &ok );
QVERIFY( ok );
QVERIFY( !results.value( QStringLiteral( "OUTPUT" ) ).toString().isEmpty() );
QFile file2( results.value( QStringLiteral( "OUTPUT" ) ).toString() );
QVERIFY( file2.open( QFile::ReadOnly | QIODevice::Text ) );
QCOMPARE( file2.readAll(), QStringLiteral( "<span style=\"color:red\">test</span><br/>" ) );
}

QGSTEST_MAIN( TestQgsProcessingAlgs )
#include "testqgsprocessingalgs.moc"

0 comments on commit 60af337

Please sign in to comment.