mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 14:40:26 -05:00
Telemetry: Add event allowlist/denylist filtering
Extend opentelemetry_config.json with optional event_allowlist and event_denylist arrays. Patterns support wildcard suffix (e.g. grpc.*). Allowlist is checked first; denylist further restricts within the allowed set. crash.* events always bypass all filters and queue limits. Add RiaConnectorTools-Test.cpp covering all JSON value types including the new array handling.
This commit is contained in:
1 parent
7cc1853302
commit
74324d14a4
7 files changed
+269
-4
No files matched your search
@@ -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();
|
||||
}
|
||||
@@ -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 ) );
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -64,6 +64,8 @@ public:
|
||||
double samplingRate() const;
|
||||
int connectionTimeoutMs() const;
|
||||
LoggingState loggingState() const;
|
||||
QStringList eventAllowlist() const;
|
||||
QStringList eventDenylist() const;
|
||||
|
||||
protected:
|
||||
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||
@@ -81,4 +83,6 @@ private:
|
||||
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;
|
||||
};
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QTextStream>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
@@ -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;
|
||||
@@ -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 );
|
||||
|
||||
@@ -145,6 +145,9 @@ private:
|
||||
bool isCircuitBreakerOpen() const;
|
||||
void resetCircuitBreaker();
|
||||
|
||||
// Event filtering
|
||||
bool isEventAllowed( const std::string& eventName ) const;
|
||||
|
||||
// Health monitoring
|
||||
void updateHealthMetrics( bool success );
|
||||
void sendHealthSpan();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"] );
|
||||
}
|
||||
Reference in new issue
Block a user