Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions ApplicationLibCode/Application/RiaPreferencesOpenTelemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ RiaPreferencesOpenTelemetry::RiaPreferencesOpenTelemetry()
CAF_PDM_InitField( &m_memoryThresholdMb, "memoryThresholdMb", 50, "Memory Threshold (MB)" );
CAF_PDM_InitField( &m_samplingRate, "samplingRate", 1.0, "Sampling Rate" );
CAF_PDM_InitField( &m_connectionTimeoutMs, "connectionTimeoutMs", 10000, "Connection Timeout (ms)" );
CAF_PDM_InitField( &m_eventAllowlist, "eventAllowlist", QString(), "Event Allowlist" );
CAF_PDM_InitField( &m_eventDenylist, "eventDenylist", QString(), "Event Denylist" );

setFieldStates();
}
Expand Down Expand Up @@ -105,6 +107,14 @@ void RiaPreferencesOpenTelemetry::setData( const std::map<QString, QString>& key
{
m_connectionTimeoutMs = value.toInt();
}
else if ( key == "event_allowlist" )
{
m_eventAllowlist = value;
}
else if ( key == "event_denylist" )
{
m_eventDenylist = value;
}
else
{
RiaLogging::warning( QString( "Unknown OpenTelemetry config key: '%1'" ).arg( key ) );
Expand Down Expand Up @@ -151,6 +161,8 @@ void RiaPreferencesOpenTelemetry::defineUiOrdering( QString uiConfigName, caf::P
group->add( &m_memoryThresholdMb );
group->add( &m_samplingRate );
group->add( &m_connectionTimeoutMs );
group->add( &m_eventAllowlist );
group->add( &m_eventDenylist );
}
uiOrdering.skipRemainingFields();
}
Expand Down Expand Up @@ -234,3 +246,19 @@ RiaPreferencesOpenTelemetry::LoggingState RiaPreferencesOpenTelemetry::loggingSt
{
return m_loggingState();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RiaPreferencesOpenTelemetry::eventAllowlist() const
{
return m_eventAllowlist().split( ',', Qt::SkipEmptyParts );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RiaPreferencesOpenTelemetry::eventDenylist() const
{
return m_eventDenylist().split( ',', Qt::SkipEmptyParts );
}
4 changes: 4 additions & 0 deletions ApplicationLibCode/Application/RiaPreferencesOpenTelemetry.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class RiaPreferencesOpenTelemetry : public caf::PdmObject
double samplingRate() const;
int connectionTimeoutMs() const;
LoggingState loggingState() const;
QStringList eventAllowlist() const;
QStringList eventDenylist() const;

protected:
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
Expand All @@ -81,4 +83,6 @@ class RiaPreferencesOpenTelemetry : public caf::PdmObject
caf::PdmField<int> m_memoryThresholdMb;
caf::PdmField<double> m_samplingRate;
caf::PdmField<int> m_connectionTimeoutMs;
caf::PdmField<QString> m_eventAllowlist;
caf::PdmField<QString> m_eventDenylist;
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTextStream>
Expand Down Expand Up @@ -141,6 +142,14 @@ std::map<QString, QString> RiaConnectorTools::readKeyValuePairs( const QString&
{
valueStr = value.toBool() ? "true" : "false";
}
else if ( value.isArray() )
{
QJsonArray arr = value.toArray();
QStringList parts;
for ( const auto& elem : arr )
if ( elem.isString() ) parts << elem.toString();
valueStr = parts.join( "," );
}

keyValuePairs[it.key()] = valueStr;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QProcessEnvironment>
#include <QRegularExpression>
#include <QString>
#include <QSysInfo>
#include <QTimer>
Expand Down Expand Up @@ -223,20 +224,59 @@ static std::string hashUsername( const std::string& username )
return hash.toHex().toStdString();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiaOpenTelemetryManager::isEventAllowed( const std::string& eventName ) const
{
// Crash events are always reported regardless of filter settings
if ( eventName.starts_with( "crash." ) ) return true;

auto* prefs = RiaPreferencesOpenTelemetry::current();
if ( !prefs ) return true;

const QString name = QString::fromStdString( eventName );

auto matches = []( const QString& name, const QStringList& patterns ) -> bool
{
for ( const QString& pattern : patterns )
{
QRegularExpression re( QRegularExpression::wildcardToRegularExpression( pattern.trimmed() ) );
if ( re.match( name ).hasMatch() ) return true;
}
return false;
};

const QStringList allowlist = prefs->eventAllowlist();
if ( !allowlist.isEmpty() && !matches( name, allowlist ) ) return false;

const QStringList denylist = prefs->eventDenylist();
if ( !denylist.isEmpty() && matches( name, denylist ) ) return false;

return true;
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaOpenTelemetryManager::reportEventAsync( const std::string& eventName, const std::map<std::string, std::string>& attributes )
{
if ( !isEnabled() || isCircuitBreakerOpen() )
const bool isCrashEvent = eventName.starts_with( "crash." );

if ( !isCrashEvent && ( !isEnabled() || isCircuitBreakerOpen() ) )
{
return;
}

if ( !isEventAllowed( eventName ) )
{
return;
}

std::unique_lock<std::mutex> lock( m_queueMutex );

// Check queue size and apply backpressure
if ( m_backpressureEnabled && m_eventQueue.size() >= m_maxQueueSize )
// Check queue size and apply backpressure (crash events always bypass the queue limit)
if ( !isCrashEvent && m_backpressureEnabled && m_eventQueue.size() >= m_maxQueueSize )
{
m_healthMetrics.eventsDropped++;
return;
Expand All @@ -246,7 +286,6 @@ void RiaOpenTelemetryManager::reportEventAsync( const std::string& eventName, co
// Note: crash events already have real username added in reportCrash()
std::map<std::string, std::string> enrichedAttributes = attributes;

bool isCrashEvent = ( eventName == "crash.signal_handler" );
if ( !isCrashEvent )
{
std::lock_guard<std::mutex> configLock( m_configMutex );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ class RiaOpenTelemetryManager : public QObject
bool isCircuitBreakerOpen() const;
void resetCircuitBreaker();

// Event filtering
bool isEventAllowed( const std::string& eventName ) const;

// Health monitoring
void updateHealthMetrics( bool success );
void sendHealthSpan();
Expand Down
1 change: 1 addition & 0 deletions ApplicationLibCode/UnitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ set(SOURCE_UNITTEST_FILES
${CMAKE_CURRENT_LIST_DIR}/RimMockSummaryCase.h
${CMAKE_CURRENT_LIST_DIR}/RimMockSummaryCase-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RimSummaryCalculation-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaConnectorTools-Test.cpp
)

if(RESINSIGHT_ENABLE_GRPC)
Expand Down
181 changes: 181 additions & 0 deletions ApplicationLibCode/UnitTests/RiaConnectorTools-Test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026- Equinor ASA
//
// ResInsight 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 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////

#include "gtest/gtest.h"

#include "Cloud/RiaConnectorTools.h"

#include <QTemporaryFile>
#include <QTextStream>

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_nonExistentFile )
{
auto result = RiaConnectorTools::readKeyValuePairs( "/this/path/does/not/exist.json" );
EXPECT_TRUE( result.empty() );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_emptyFile )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
EXPECT_TRUE( result.empty() );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_stringValue )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

{
QTextStream out( &file );
out << R"({"connection_string": "InstrumentationKey=abc123"})";
}

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
ASSERT_EQ( 1u, result.size() );
EXPECT_EQ( QString( "InstrumentationKey=abc123" ), result["connection_string"] );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_numericValue )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

{
QTextStream out( &file );
out << R"({"batch_timeout_ms": 5000, "sampling_rate": 0.5})";
}

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
ASSERT_EQ( 2u, result.size() );
EXPECT_EQ( QString( "5000" ), result["batch_timeout_ms"] );
EXPECT_EQ( QString( "0.5" ), result["sampling_rate"] );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_boolValue )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

{
QTextStream out( &file );
out << R"({"enabled": true, "verbose": false})";
}

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
ASSERT_EQ( 2u, result.size() );
EXPECT_EQ( QString( "true" ), result["enabled"] );
EXPECT_EQ( QString( "false" ), result["verbose"] );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_arrayValue )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

{
QTextStream out( &file );
out << R"({"event_allowlist": ["app.started", "crash.*", "grpc.*"]})";
}

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
ASSERT_EQ( 1u, result.size() );
EXPECT_EQ( QString( "app.started,crash.*,grpc.*" ), result["event_allowlist"] );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_emptyArray )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

{
QTextStream out( &file );
out << R"({"event_denylist": []})";
}

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
ASSERT_EQ( 1u, result.size() );
EXPECT_EQ( QString( "" ), result["event_denylist"] );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_arrayIgnoresNonStringElements )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

{
QTextStream out( &file );
out << R"({"mixed_array": ["health.status", 42, true, "test.*"]})";
}

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
ASSERT_EQ( 1u, result.size() );
EXPECT_EQ( QString( "health.status,test.*" ), result["mixed_array"] );
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaConnectorToolsTest, readKeyValuePairs_mixedTypes )
{
QTemporaryFile file;
ASSERT_TRUE( file.open() );

{
QTextStream out( &file );
out << R"({
"connection_string": "InstrumentationKey=key1",
"max_batch_size": 512,
"enabled": true,
"event_denylist": ["health.status", "test.*"]
})";
}

auto result = RiaConnectorTools::readKeyValuePairs( file.fileName() );
ASSERT_EQ( 4u, result.size() );
EXPECT_EQ( QString( "InstrumentationKey=key1" ), result["connection_string"] );
EXPECT_EQ( QString( "512" ), result["max_batch_size"] );
EXPECT_EQ( QString( "true" ), result["enabled"] );
EXPECT_EQ( QString( "health.status,test.*" ), result["event_denylist"] );
}