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:
Magne Sjaastad committed 2026-02-23 12:29:41 +01:00
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();