mirror of
https://github.com/OPM/ResInsight.git
synced 2026-08-27 05:37:21 -05:00
Adds OpenTelemetry integration
Adds support for OpenTelemetry to enable telemetry and crash reporting. This change introduces: - New RiaPreferencesOpenTelemetry class for managing OpenTelemetry configuration. - RiaOpenTelemetryManager class for handling the OpenTelemetry lifecycle, event processing, and error handling. - Configuration options in preferences to enable, disable, and configure OpenTelemetry. - Asynchronous event reporting. - Crash reporting capabilities. - Health monitoring features.
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
#include "RiaLogging.h"
|
||||
#include "RiaPreferencesGeoMech.h"
|
||||
#include "RiaPreferencesGrid.h"
|
||||
#include "RiaPreferencesOpenTelemetry.h"
|
||||
#include "RiaPreferencesOpm.h"
|
||||
#include "RiaPreferencesOsdu.h"
|
||||
#include "RiaPreferencesSummary.h"
|
||||
@@ -284,6 +285,9 @@ RiaPreferences::RiaPreferences()
|
||||
caf::PdmUiPushButtonEditor::configureEditorLabelHidden( &m_deleteSumoToken );
|
||||
m_deleteSumoToken.xmlCapability()->disableIO();
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_openTelemetryPreferences, "openTelemetryPreferences", "openTelemetryPreferences" );
|
||||
m_openTelemetryPreferences = new RiaPreferencesOpenTelemetry;
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_importPreferences, "importPreferences", "Import From File" );
|
||||
caf::PdmUiPushButtonEditor::configureEditorLabelHidden( &m_importPreferences );
|
||||
|
||||
@@ -522,6 +526,14 @@ void RiaPreferences::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering&
|
||||
sumoGroup->setCollapsedByDefault();
|
||||
m_sumoPreferences()->uiOrdering( uiConfigName, *sumoGroup );
|
||||
sumoGroup->add( &m_deleteSumoToken );
|
||||
|
||||
caf::PdmUiGroup* openTelemetryGroup = uiOrdering.addNewGroup( "OpenTelemetry" );
|
||||
openTelemetryGroup->setCollapsedByDefault();
|
||||
#ifdef RESINSIGHT_OPENTELEMETRY_ENABLED
|
||||
m_openTelemetryPreferences()->uiOrdering( uiConfigName, *openTelemetryGroup );
|
||||
#else
|
||||
openTelemetryGroup->addNewLabel( "OpenTelemetry support is not enabled in this build of ResInsight. " );
|
||||
#endif
|
||||
}
|
||||
else if ( RiaApplication::enableDevelopmentFeatures() && uiConfigName == RiaPreferences::tabNameSystem() )
|
||||
{
|
||||
@@ -1078,6 +1090,14 @@ RiaPreferencesSumo* RiaPreferences::sumoPreferences() const
|
||||
return m_sumoPreferences();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaPreferencesOpenTelemetry* RiaPreferences::openTelemetryPreferences() const
|
||||
{
|
||||
return m_openTelemetryPreferences();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -47,6 +47,7 @@ class RiaPreferencesOsdu;
|
||||
class RiaPreferencesGrid;
|
||||
class RiaPreferencesSumo;
|
||||
class RiaPreferencesOpm;
|
||||
class RiaPreferencesOpenTelemetry;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
@@ -122,13 +123,14 @@ public:
|
||||
|
||||
bool storeBackupOfProjectFiles() const;
|
||||
|
||||
RiaPreferencesGeoMech* geoMechPreferences() const;
|
||||
RiaPreferencesSummary* summaryPreferences() const;
|
||||
RiaPreferencesSystem* systemPreferences() const;
|
||||
RiaPreferencesOsdu* osduPreferences() const;
|
||||
RiaPreferencesSumo* sumoPreferences() const;
|
||||
RiaPreferencesGrid* gridPreferences() const;
|
||||
RiaPreferencesOpm* opmPreferences() const;
|
||||
RiaPreferencesGeoMech* geoMechPreferences() const;
|
||||
RiaPreferencesSummary* summaryPreferences() const;
|
||||
RiaPreferencesSystem* systemPreferences() const;
|
||||
RiaPreferencesOsdu* osduPreferences() const;
|
||||
RiaPreferencesSumo* sumoPreferences() const;
|
||||
RiaPreferencesGrid* gridPreferences() const;
|
||||
RiaPreferencesOpm* opmPreferences() const;
|
||||
RiaPreferencesOpenTelemetry* openTelemetryPreferences() const;
|
||||
|
||||
void importPreferenceValuesFromFile( const QString& fileName );
|
||||
void exportPreferenceValuesToFile( const QString& fileName );
|
||||
@@ -241,6 +243,9 @@ private:
|
||||
caf::PdmChildField<RiaPreferencesSumo*> m_sumoPreferences;
|
||||
caf::PdmField<bool> m_deleteSumoToken;
|
||||
|
||||
// OpenTelemetry settings
|
||||
caf::PdmChildField<RiaPreferencesOpenTelemetry*> m_openTelemetryPreferences;
|
||||
|
||||
// 3d view
|
||||
caf::PdmField<caf::AppEnum<RiaDefines::MeshModeType>> m_defaultMeshModeType;
|
||||
caf::PdmField<caf::AppEnum<RiaDefines::RINavigationPolicy>> m_navigationPolicy;
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2025- 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 "RiaPreferencesOpenTelemetry.h"
|
||||
|
||||
#include "RiaApplication.h"
|
||||
#include "RiaLogging.h"
|
||||
#include "RiaPreferences.h"
|
||||
#include "RiaVersionInfo.h"
|
||||
|
||||
#include "cafPdmUiTextEditor.h"
|
||||
|
||||
namespace caf
|
||||
{
|
||||
template <>
|
||||
void RiaPreferencesOpenTelemetry::LoggingStateType::setUp()
|
||||
{
|
||||
addItem( RiaPreferencesOpenTelemetry::LoggingState::DISABLED, "DISABLED", "Disabled" );
|
||||
addItem( RiaPreferencesOpenTelemetry::LoggingState::DEFAULT, "DEFAULT", "Default" );
|
||||
addItem( RiaPreferencesOpenTelemetry::LoggingState::ALL, "ALL", "All" );
|
||||
setDefault( RiaPreferencesOpenTelemetry::LoggingState::DEFAULT );
|
||||
}
|
||||
} // namespace caf
|
||||
|
||||
CAF_PDM_SOURCE_INIT( RiaPreferencesOpenTelemetry, "RiaPreferencesOpenTelemetry" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaPreferencesOpenTelemetry::RiaPreferencesOpenTelemetry()
|
||||
{
|
||||
CAF_PDM_InitObject( "OpenTelemetry Configuration", "", "", "Configuration for OpenTelemetry crash reporting and telemetry" );
|
||||
|
||||
CAF_PDM_InitField( &m_loggingState, "loggingState", LoggingStateType( LoggingState::DISABLED ), "Logging State" );
|
||||
CAF_PDM_InitField( &m_connectionString, "connectionString", QString(), "Azure Connection String" );
|
||||
m_connectionString.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
|
||||
|
||||
CAF_PDM_InitField( &m_batchTimeoutMs, "batchTimeoutMs", 5000, "Batch Timeout (ms)" );
|
||||
CAF_PDM_InitField( &m_maxBatchSize, "maxBatchSize", 512, "Max Batch Size" );
|
||||
CAF_PDM_InitField( &m_maxQueueSize, "maxQueueSize", 10000, "Max Queue Size" );
|
||||
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)" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaPreferencesOpenTelemetry::~RiaPreferencesOpenTelemetry()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaPreferencesOpenTelemetry* RiaPreferencesOpenTelemetry::current()
|
||||
{
|
||||
return RiaApplication::instance()->preferences()->openTelemetryPreferences();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaPreferencesOpenTelemetry::setData( const std::map<QString, QString>& keyValuePairs )
|
||||
{
|
||||
for ( const auto& [key, value] : keyValuePairs )
|
||||
{
|
||||
if ( key == "connection_string" )
|
||||
{
|
||||
m_connectionString = value;
|
||||
}
|
||||
else if ( key == "batch_timeout_ms" )
|
||||
{
|
||||
m_batchTimeoutMs = value.toInt();
|
||||
}
|
||||
else if ( key == "max_batch_size" )
|
||||
{
|
||||
m_maxBatchSize = value.toInt();
|
||||
}
|
||||
else if ( key == "max_queue_size" )
|
||||
{
|
||||
m_maxQueueSize = value.toInt();
|
||||
}
|
||||
else if ( key == "memory_threshold_mb" )
|
||||
{
|
||||
m_memoryThresholdMb = value.toInt();
|
||||
}
|
||||
else if ( key == "sampling_rate" )
|
||||
{
|
||||
m_samplingRate = value.toDouble();
|
||||
}
|
||||
else if ( key == "connection_timeout_ms" )
|
||||
{
|
||||
m_connectionTimeoutMs = value.toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
RiaLogging::warning( QString( "Unknown OpenTelemetry config key: '%1'" ).arg( key ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaPreferencesOpenTelemetry::setFieldsReadOnly()
|
||||
{
|
||||
std::vector<caf::PdmFieldHandle*> fields = this->fields();
|
||||
for ( auto field : fields )
|
||||
{
|
||||
// Keep logging state editable
|
||||
if ( field == &m_loggingState )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
field->uiCapability()->setUiReadOnly( true );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaPreferencesOpenTelemetry::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||
{
|
||||
uiOrdering.add( &m_loggingState );
|
||||
|
||||
// Only show configuration fields if not disabled
|
||||
if ( m_loggingState() != LoggingState::DISABLED )
|
||||
{
|
||||
uiOrdering.add( &m_connectionString );
|
||||
uiOrdering.add( &m_batchTimeoutMs );
|
||||
uiOrdering.add( &m_maxBatchSize );
|
||||
uiOrdering.add( &m_maxQueueSize );
|
||||
uiOrdering.add( &m_memoryThresholdMb );
|
||||
uiOrdering.add( &m_samplingRate );
|
||||
uiOrdering.add( &m_connectionTimeoutMs );
|
||||
}
|
||||
uiOrdering.skipRemainingFields();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RiaPreferencesOpenTelemetry::serviceName() const
|
||||
{
|
||||
return QStringLiteral( "ResInsight" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RiaPreferencesOpenTelemetry::serviceVersion() const
|
||||
{
|
||||
return QString( STRPRODUCTVER );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RiaPreferencesOpenTelemetry::connectionString() const
|
||||
{
|
||||
return m_connectionString;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
int RiaPreferencesOpenTelemetry::batchTimeoutMs() const
|
||||
{
|
||||
return m_batchTimeoutMs;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
int RiaPreferencesOpenTelemetry::maxBatchSize() const
|
||||
{
|
||||
return m_maxBatchSize;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
int RiaPreferencesOpenTelemetry::maxQueueSize() const
|
||||
{
|
||||
return m_maxQueueSize;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
int RiaPreferencesOpenTelemetry::memoryThresholdMb() const
|
||||
{
|
||||
return m_memoryThresholdMb;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
double RiaPreferencesOpenTelemetry::samplingRate() const
|
||||
{
|
||||
return m_samplingRate;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
int RiaPreferencesOpenTelemetry::connectionTimeoutMs() const
|
||||
{
|
||||
return m_connectionTimeoutMs;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaPreferencesOpenTelemetry::LoggingState RiaPreferencesOpenTelemetry::loggingState() const
|
||||
{
|
||||
return m_loggingState();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2025- 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.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cafAppEnum.h"
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmObject.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
//==================================================================================================
|
||||
//
|
||||
// OpenTelemetry preferences for ResInsight
|
||||
// Follows the same pattern as RiaPreferencesOsdu
|
||||
//
|
||||
//==================================================================================================
|
||||
class RiaPreferencesOpenTelemetry : public caf::PdmObject
|
||||
{
|
||||
CAF_PDM_HEADER_INIT;
|
||||
|
||||
public:
|
||||
enum class LoggingState
|
||||
{
|
||||
DISABLED,
|
||||
DEFAULT,
|
||||
ALL
|
||||
};
|
||||
using LoggingStateType = caf::AppEnum<LoggingState>;
|
||||
|
||||
RiaPreferencesOpenTelemetry();
|
||||
~RiaPreferencesOpenTelemetry() override;
|
||||
|
||||
static RiaPreferencesOpenTelemetry* current();
|
||||
|
||||
void setData( const std::map<QString, QString>& keyValuePairs );
|
||||
void setFieldsReadOnly();
|
||||
|
||||
// Service name and version are hardcoded, not configurable
|
||||
QString serviceName() const;
|
||||
QString serviceVersion() const; // Read from ResInsightVersion.cmake
|
||||
|
||||
// Getters for configuration values
|
||||
QString connectionString() const;
|
||||
int batchTimeoutMs() const;
|
||||
int maxBatchSize() const;
|
||||
int maxQueueSize() const;
|
||||
int memoryThresholdMb() const;
|
||||
double samplingRate() const;
|
||||
int connectionTimeoutMs() const;
|
||||
LoggingState loggingState() const;
|
||||
|
||||
protected:
|
||||
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||
|
||||
private:
|
||||
caf::PdmField<LoggingStateType> m_loggingState;
|
||||
caf::PdmField<QString> m_connectionString;
|
||||
caf::PdmField<int> m_batchTimeoutMs;
|
||||
caf::PdmField<int> m_maxBatchSize;
|
||||
caf::PdmField<int> m_maxQueueSize;
|
||||
caf::PdmField<int> m_memoryThresholdMb;
|
||||
caf::PdmField<double> m_samplingRate;
|
||||
caf::PdmField<int> m_connectionTimeoutMs;
|
||||
};
|
||||
@@ -0,0 +1,729 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2025- 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 "RiaOpenTelemetryManager.h"
|
||||
|
||||
#include "RiaLogging.h"
|
||||
#include "RiaPreferencesOpenTelemetry.h"
|
||||
#include "RifJsonEncodeDecode.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QString>
|
||||
#include <QSysInfo>
|
||||
#include <QTimer>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
double RiaOpenTelemetryManager::HealthSnapshot::getSuccessRate() const
|
||||
{
|
||||
uint64_t total = eventsSent + eventsDropped;
|
||||
if ( total == 0 ) return 1.0;
|
||||
return static_cast<double>( eventsSent ) / total;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::HealthSnapshot::isHealthy() const
|
||||
{
|
||||
// Consider healthy if success rate > 90% and we've had recent successful sends
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto timeSinceLastSuccess = now - lastSuccessfulSend;
|
||||
|
||||
return getSuccessRate() > 0.9 && timeSinceLastSuccess < std::chrono::minutes( 5 );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaOpenTelemetryManager& RiaOpenTelemetryManager::instance()
|
||||
{
|
||||
static RiaOpenTelemetryManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaOpenTelemetryManager::RiaOpenTelemetryManager()
|
||||
: QObject( nullptr )
|
||||
{
|
||||
m_healthMetrics.systemStartTime = std::chrono::steady_clock::now();
|
||||
m_healthMetrics.lastSuccessfulSend = m_healthMetrics.systemStartTime;
|
||||
|
||||
// Initialize Qt networking components
|
||||
m_networkAccessManager = new QNetworkAccessManager( this );
|
||||
|
||||
// Initialize timers
|
||||
m_processTimer = new QTimer( this );
|
||||
connect( m_processTimer, &QTimer::timeout, this, &RiaOpenTelemetryManager::onProcessEventTimer );
|
||||
|
||||
m_healthTimer = new QTimer( this );
|
||||
connect( m_healthTimer, &QTimer::timeout, this, &RiaOpenTelemetryManager::sendHealthSpan );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaOpenTelemetryManager::~RiaOpenTelemetryManager()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::initialize()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_configMutex );
|
||||
|
||||
// Check if OpenTelemetry is disabled in preferences
|
||||
auto* prefs = RiaPreferencesOpenTelemetry::current();
|
||||
if ( prefs && prefs->loggingState() == RiaPreferencesOpenTelemetry::LoggingState::DISABLED )
|
||||
{
|
||||
RiaLogging::info( "OpenTelemetry is disabled in preferences" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( m_initialized.load() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( !initializeProvider() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start event processing timer (100ms interval for responsive processing)
|
||||
m_isShuttingDown = false;
|
||||
m_processTimer->start( 100 );
|
||||
|
||||
// Start health monitoring timer (5 minutes interval)
|
||||
if ( m_healthMonitoringEnabled )
|
||||
{
|
||||
m_healthTimer->start( 5 * 60 * 1000 ); // 5 minutes in milliseconds
|
||||
sendHealthSpan();
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
m_enabled = true;
|
||||
|
||||
RiaLogging::info( "OpenTelemetry initialized successfully" );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::shutdown( std::chrono::seconds timeout )
|
||||
{
|
||||
if ( !m_initialized.load() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RiaLogging::info( "Shutting down OpenTelemetry" );
|
||||
|
||||
m_isShuttingDown = true;
|
||||
m_enabled = false;
|
||||
|
||||
// Stop timers
|
||||
if ( m_processTimer )
|
||||
{
|
||||
m_processTimer->stop();
|
||||
}
|
||||
if ( m_healthTimer )
|
||||
{
|
||||
m_healthTimer->stop();
|
||||
}
|
||||
|
||||
// Flush pending events
|
||||
flushPendingEvents();
|
||||
|
||||
m_initialized = false;
|
||||
RiaLogging::info( "OpenTelemetry shutdown complete" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::reportEventAsync( const std::string& eventName, const std::map<std::string, std::string>& attributes )
|
||||
{
|
||||
if ( !isEnabled() || isCircuitBreakerOpen() || !shouldSampleEvent() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock( m_queueMutex );
|
||||
|
||||
// Check queue size and apply backpressure
|
||||
if ( m_backpressureEnabled && m_eventQueue.size() >= m_maxQueueSize )
|
||||
{
|
||||
m_healthMetrics.eventsDropped++;
|
||||
return;
|
||||
}
|
||||
|
||||
m_eventQueue.emplace( eventName, attributes );
|
||||
m_healthMetrics.eventsQueued++;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::reportCrash( int signalCode, const std::stacktrace& trace )
|
||||
{
|
||||
if ( !isEnabled() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Format stack trace using existing ResInsight formatter
|
||||
std::stringstream ss;
|
||||
int frame = 0;
|
||||
for ( const auto& entry : trace )
|
||||
{
|
||||
ss << " [" << frame++ << "] " << entry.description() << " at " << entry.source_file() << ":" << entry.source_line() << "\n";
|
||||
}
|
||||
|
||||
std::string rawStackTrace = ss.str();
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes["crash.signal"] = std::to_string( signalCode );
|
||||
attributes["crash.thread_id"] = std::to_string( std::hash<std::thread::id>{}( std::this_thread::get_id() ) );
|
||||
attributes["crash.stack_trace"] = rawStackTrace;
|
||||
attributes["service.name"] = RiaPreferencesOpenTelemetry::current()->serviceName().toStdString();
|
||||
attributes["service.version"] = RiaPreferencesOpenTelemetry::current()->serviceVersion().toStdString();
|
||||
|
||||
// Report with high priority (bypass sampling)
|
||||
std::unique_lock<std::mutex> lock( m_queueMutex );
|
||||
m_eventQueue.emplace( "crash.signal_handler", attributes );
|
||||
m_healthMetrics.eventsQueued++;
|
||||
lock.unlock();
|
||||
|
||||
RiaLogging::error( QString( "Crash reported to OpenTelemetry (signal: %1)" ).arg( signalCode ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::reportTestCrash( const std::stacktrace& trace )
|
||||
{
|
||||
if ( !isEnabled() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Format stack trace
|
||||
std::stringstream ss;
|
||||
int frame = 0;
|
||||
for ( const auto& entry : trace )
|
||||
{
|
||||
ss << " [" << frame++ << "] " << entry.description() << " at " << entry.source_file() << ":" << entry.source_line() << "\n";
|
||||
}
|
||||
|
||||
std::string rawStackTrace = ss.str();
|
||||
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes["test.type"] = "manual_stack_trace";
|
||||
attributes["test.thread_id"] = std::to_string( std::hash<std::thread::id>{}( std::this_thread::get_id() ) );
|
||||
attributes["test.stack_trace"] = rawStackTrace;
|
||||
attributes["service.name"] = RiaPreferencesOpenTelemetry::current()->serviceName().toStdString();
|
||||
attributes["service.version"] = RiaPreferencesOpenTelemetry::current()->serviceVersion().toStdString();
|
||||
|
||||
reportEventAsync( "test.stack_trace", attributes );
|
||||
|
||||
RiaLogging::info( "Test stack trace reported to OpenTelemetry" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::isEnabled() const
|
||||
{
|
||||
return m_enabled.load() && m_initialized.load();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::isInitialized() const
|
||||
{
|
||||
return m_initialized.load();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::setErrorCallback( ErrorCallback callback )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_configMutex );
|
||||
m_errorCallback = callback;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::setMaxQueueSize( size_t maxEvents )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_configMutex );
|
||||
m_maxQueueSize = maxEvents;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::enableBackpressure( bool enable )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_configMutex );
|
||||
m_backpressureEnabled = enable;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::setMemoryThreshold( size_t maxMemoryMB )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_configMutex );
|
||||
m_memoryThresholdMB = maxMemoryMB;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::setSamplingRate( double rate )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_configMutex );
|
||||
m_samplingRate = std::clamp( rate, 0.0, 1.0 );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
size_t RiaOpenTelemetryManager::getCurrentQueueSize() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_queueMutex );
|
||||
return m_eventQueue.size();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RiaOpenTelemetryManager::HealthSnapshot RiaOpenTelemetryManager::getHealthMetrics() const
|
||||
{
|
||||
HealthSnapshot result;
|
||||
result.eventsQueued = m_healthMetrics.eventsQueued.load();
|
||||
result.eventsSent = m_healthMetrics.eventsSent.load();
|
||||
result.eventsDropped = m_healthMetrics.eventsDropped.load();
|
||||
result.networkFailures = m_healthMetrics.networkFailures.load();
|
||||
result.lastSuccessfulSend = m_healthMetrics.lastSuccessfulSend;
|
||||
result.systemStartTime = m_healthMetrics.systemStartTime;
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::isHealthy() const
|
||||
{
|
||||
return getHealthMetrics().isHealthy() && !isCircuitBreakerOpen();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::enableHealthMonitoring( bool enable )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_configMutex );
|
||||
m_healthMonitoringEnabled = enable;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::initializeProvider()
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( !createExporter() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setupResourceAttributes();
|
||||
return true;
|
||||
}
|
||||
catch ( const std::exception& e )
|
||||
{
|
||||
handleError( TelemetryError::InternalError, QString( "Failed to initialize provider: %1" ).arg( e.what() ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Parse Azure Application Insights connection string
|
||||
/// Format: InstrumentationKey=<key>;IngestionEndpoint=<endpoint>;...
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
static std::map<QString, QString> parseAzureConnectionString( const QString& connectionString )
|
||||
{
|
||||
std::map<QString, QString> result;
|
||||
QStringList parts = connectionString.split( ';', Qt::SkipEmptyParts );
|
||||
|
||||
for ( const QString& part : parts )
|
||||
{
|
||||
int equalPos = part.indexOf( '=' );
|
||||
if ( equalPos > 0 )
|
||||
{
|
||||
QString key = part.left( equalPos ).trimmed();
|
||||
QString value = part.mid( equalPos + 1 ).trimmed();
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::createExporter()
|
||||
{
|
||||
try
|
||||
{
|
||||
auto* prefs = RiaPreferencesOpenTelemetry::current();
|
||||
if ( !prefs )
|
||||
{
|
||||
handleError( TelemetryError::ConfigurationError, "Failed to get OpenTelemetry Preferences" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse and validate connection string
|
||||
auto connectionParams = parseAzureConnectionString( prefs->connectionString() );
|
||||
if ( !connectionParams.contains( "InstrumentationKey" ) )
|
||||
{
|
||||
handleError( TelemetryError::ConfigurationError, "InstrumentationKey not found in connection string" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !connectionParams.contains( "IngestionEndpoint" ) )
|
||||
{
|
||||
handleError( TelemetryError::ConfigurationError, "IngestionEndpoint not found in connection string" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Using Application Insights REST API for telemetry
|
||||
RiaLogging::info( QString( "Application Insights REST API configured for production environment" ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
catch ( const std::exception& e )
|
||||
{
|
||||
handleError( TelemetryError::NetworkError, QString( "Failed to create exporter: %1" ).arg( e.what() ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::setupResourceAttributes()
|
||||
{
|
||||
// Resource attributes are typically set during provider creation
|
||||
// This would be expanded with system information, process details, etc.
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::onProcessEventTimer()
|
||||
{
|
||||
if ( !m_isShuttingDown.load() )
|
||||
{
|
||||
processEvents();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::processEvents()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock( m_queueMutex );
|
||||
|
||||
if ( m_eventQueue.empty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Process a batch of events
|
||||
std::queue<Event> batch;
|
||||
auto* prefs = RiaPreferencesOpenTelemetry::current();
|
||||
int maxBatchSize = prefs ? prefs->maxBatchSize() : 100;
|
||||
|
||||
for ( int i = 0; i < maxBatchSize && !m_eventQueue.empty(); ++i )
|
||||
{
|
||||
batch.push( m_eventQueue.front() );
|
||||
m_eventQueue.pop();
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// Process events outside of lock
|
||||
while ( !batch.empty() )
|
||||
{
|
||||
processEvent( batch.front() );
|
||||
batch.pop();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::processEvent( const Event& event )
|
||||
{
|
||||
try
|
||||
{
|
||||
auto* prefs = RiaPreferencesOpenTelemetry::current();
|
||||
if ( !prefs )
|
||||
{
|
||||
handleError( TelemetryError::InternalError, QString( "Failed to access Open Telemetry Preferences." ) );
|
||||
updateHealthMetrics( false );
|
||||
return;
|
||||
}
|
||||
|
||||
// Use Application Insights REST API
|
||||
auto connectionParams = parseAzureConnectionString( prefs->connectionString() );
|
||||
|
||||
if ( !connectionParams.contains( "InstrumentationKey" ) || !connectionParams.contains( "IngestionEndpoint" ) )
|
||||
{
|
||||
updateHealthMetrics( false );
|
||||
return;
|
||||
}
|
||||
|
||||
// Format timestamp - must match Application Insights format exactly
|
||||
auto time_t = std::chrono::system_clock::to_time_t( event.timestamp );
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( event.timestamp.time_since_epoch() ) % 1000;
|
||||
|
||||
char timeBuffer[100];
|
||||
std::strftime( timeBuffer, sizeof( timeBuffer ), "%Y-%m-%dT%H:%M:%S", std::gmtime( &time_t ) );
|
||||
|
||||
// Pad milliseconds to 3 digits
|
||||
char msBuffer[8];
|
||||
std::snprintf( msBuffer, sizeof( msBuffer ), ".%03dZ", static_cast<int>( ms.count() ) );
|
||||
|
||||
std::string timestamp = std::string( timeBuffer ) + msBuffer;
|
||||
|
||||
// Convert attributes to JSON properties
|
||||
QMap<QString, QVariant> properties;
|
||||
for ( const auto& [key, value] : event.attributes )
|
||||
{
|
||||
properties[QString::fromStdString( key )] = QString::fromStdString( value );
|
||||
}
|
||||
|
||||
// Add system information
|
||||
properties["os.type"] = QSysInfo::productType();
|
||||
properties["os.version"] = QSysInfo::productVersion();
|
||||
properties["os.name"] = QSysInfo::prettyProductName();
|
||||
|
||||
// Create Application Insights telemetry item
|
||||
QMap<QString, QVariant> baseData;
|
||||
baseData["name"] = QString::fromStdString( event.name );
|
||||
baseData["properties"] = properties;
|
||||
|
||||
QMap<QString, QVariant> data;
|
||||
data["baseType"] = "EventData";
|
||||
data["baseData"] = baseData;
|
||||
|
||||
QMap<QString, QVariant> telemetryItem;
|
||||
telemetryItem["time"] = QString::fromStdString( timestamp );
|
||||
telemetryItem["iKey"] = connectionParams["InstrumentationKey"];
|
||||
telemetryItem["name"] = "Microsoft.ApplicationInsights.Event";
|
||||
telemetryItem["data"] = data;
|
||||
|
||||
// Convert to JSON string
|
||||
QString jsonPayload = ResInsightInternalJson::Json::encode( telemetryItem, false );
|
||||
|
||||
// Send to Application Insights using QNetworkAccessManager
|
||||
QString url = connectionParams["IngestionEndpoint"] + "/v2/track";
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setUrl( QUrl( url ) );
|
||||
request.setHeader( QNetworkRequest::ContentTypeHeader, "application/json" );
|
||||
request.setHeader( QNetworkRequest::KnownHeaders( QNetworkRequest::UserAgentHeader ), "ResInsight-OpenTelemetry" );
|
||||
request.setTransferTimeout( prefs->connectionTimeoutMs() );
|
||||
|
||||
QNetworkReply* reply = m_networkAccessManager->post( request, jsonPayload.toUtf8() );
|
||||
|
||||
// Handle response asynchronously
|
||||
connect( reply,
|
||||
&QNetworkReply::finished,
|
||||
this,
|
||||
[this, reply]()
|
||||
{
|
||||
if ( reply->error() == QNetworkReply::NoError )
|
||||
{
|
||||
updateHealthMetrics( true );
|
||||
resetCircuitBreaker();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString errorMsg = QString( "HTTP %1: %2" )
|
||||
.arg( reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() )
|
||||
.arg( reply->errorString() );
|
||||
handleError( TelemetryError::NetworkError, QString( "Failed to send telemetry: %1" ).arg( errorMsg ) );
|
||||
updateHealthMetrics( false );
|
||||
}
|
||||
reply->deleteLater();
|
||||
} );
|
||||
}
|
||||
catch ( const std::exception& e )
|
||||
{
|
||||
handleError( TelemetryError::InternalError, QString( "Failed to process event: %1" ).arg( e.what() ) );
|
||||
updateHealthMetrics( false );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::shouldSampleEvent() const
|
||||
{
|
||||
if ( m_samplingRate >= 1.0 )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static thread_local std::mt19937 gen( std::random_device{}() );
|
||||
static thread_local std::uniform_real_distribution<double> dis( 0.0, 1.0 );
|
||||
|
||||
return dis( gen ) < m_samplingRate;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::flushPendingEvents()
|
||||
{
|
||||
// Process remaining events in the queue
|
||||
processEvents();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::handleError( TelemetryError error, const QString& context )
|
||||
{
|
||||
m_consecutiveFailures++;
|
||||
|
||||
if ( m_consecutiveFailures >= 3 )
|
||||
{
|
||||
m_circuitBreakerOpen = true;
|
||||
RiaLogging::warning( "OpenTelemetry circuit breaker opened due to consecutive failures" );
|
||||
}
|
||||
|
||||
if ( m_errorCallback )
|
||||
{
|
||||
m_errorCallback( error, context );
|
||||
}
|
||||
|
||||
RiaLogging::warning( QString( "OpenTelemetry error: %1" ).arg( context ) );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::attemptReconnection()
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if ( now - m_lastReconnectAttempt < std::chrono::minutes( 5 ) )
|
||||
{
|
||||
return; // Don't retry too frequently
|
||||
}
|
||||
|
||||
m_lastReconnectAttempt = now;
|
||||
|
||||
// Try to reinitialize connection
|
||||
if ( createExporter() )
|
||||
{
|
||||
resetCircuitBreaker();
|
||||
RiaLogging::info( "OpenTelemetry reconnection successful" );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RiaOpenTelemetryManager::isCircuitBreakerOpen() const
|
||||
{
|
||||
return m_circuitBreakerOpen.load();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::resetCircuitBreaker()
|
||||
{
|
||||
m_consecutiveFailures = 0;
|
||||
m_circuitBreakerOpen = false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::updateHealthMetrics( bool success )
|
||||
{
|
||||
if ( success )
|
||||
{
|
||||
m_healthMetrics.eventsSent++;
|
||||
m_healthMetrics.lastSuccessfulSend = std::chrono::steady_clock::now();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_healthMetrics.networkFailures++;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RiaOpenTelemetryManager::sendHealthSpan()
|
||||
{
|
||||
if ( !isEnabled() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto metrics = getHealthMetrics();
|
||||
std::map<std::string, std::string> attributes;
|
||||
attributes["health.events_queued"] = std::to_string( metrics.eventsQueued );
|
||||
attributes["health.events_sent"] = std::to_string( metrics.eventsSent );
|
||||
attributes["health.events_dropped"] = std::to_string( metrics.eventsDropped );
|
||||
attributes["health.network_failures"] = std::to_string( metrics.networkFailures );
|
||||
attributes["health.success_rate"] = std::to_string( metrics.getSuccessRate() );
|
||||
attributes["health.queue_size"] = std::to_string( getCurrentQueueSize() );
|
||||
|
||||
reportEventAsync( "health.status", attributes );
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2025- 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.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <stacktrace>
|
||||
#include <string>
|
||||
|
||||
class QString;
|
||||
class QNetworkAccessManager;
|
||||
class QTimer;
|
||||
|
||||
//==================================================================================================
|
||||
//
|
||||
// OpenTelemetry Manager for ResInsight
|
||||
// Handles async telemetry reporting with privacy filtering and resilience
|
||||
//
|
||||
//==================================================================================================
|
||||
class RiaOpenTelemetryManager : public QObject
|
||||
{
|
||||
public:
|
||||
// Error handling
|
||||
enum class TelemetryError
|
||||
{
|
||||
ConfigurationError,
|
||||
NetworkError,
|
||||
AuthenticationError,
|
||||
QuotaExceeded,
|
||||
PrivacyViolation,
|
||||
InternalError
|
||||
};
|
||||
using ErrorCallback = std::function<void( TelemetryError, const QString& details )>;
|
||||
|
||||
// Health monitoring
|
||||
struct HealthSnapshot
|
||||
{
|
||||
uint64_t eventsQueued{ 0 };
|
||||
uint64_t eventsSent{ 0 };
|
||||
uint64_t eventsDropped{ 0 };
|
||||
uint64_t networkFailures{ 0 };
|
||||
std::chrono::steady_clock::time_point lastSuccessfulSend;
|
||||
std::chrono::steady_clock::time_point systemStartTime;
|
||||
|
||||
double getSuccessRate() const;
|
||||
bool isHealthy() const;
|
||||
};
|
||||
|
||||
struct HealthMetrics
|
||||
{
|
||||
std::atomic<uint64_t> eventsQueued{ 0 };
|
||||
std::atomic<uint64_t> eventsSent{ 0 };
|
||||
std::atomic<uint64_t> eventsDropped{ 0 };
|
||||
std::atomic<uint64_t> networkFailures{ 0 };
|
||||
std::chrono::steady_clock::time_point lastSuccessfulSend;
|
||||
std::chrono::steady_clock::time_point systemStartTime;
|
||||
};
|
||||
|
||||
static RiaOpenTelemetryManager& instance();
|
||||
|
||||
bool initialize();
|
||||
void shutdown( std::chrono::seconds timeout = std::chrono::seconds( 30 ) );
|
||||
|
||||
// Event reporting
|
||||
void reportEventAsync( const std::string& eventName, const std::map<std::string, std::string>& attributes );
|
||||
void reportCrash( int signalCode, const std::stacktrace& trace );
|
||||
void reportTestCrash( const std::stacktrace& trace );
|
||||
|
||||
// Configuration
|
||||
bool isEnabled() const;
|
||||
bool isInitialized() const;
|
||||
void setErrorCallback( ErrorCallback callback );
|
||||
|
||||
// Performance and memory management
|
||||
void setMaxQueueSize( size_t maxEvents );
|
||||
void enableBackpressure( bool enable );
|
||||
void setMemoryThreshold( size_t maxMemoryMB );
|
||||
void setSamplingRate( double rate );
|
||||
size_t getCurrentQueueSize() const;
|
||||
|
||||
// Health monitoring
|
||||
HealthSnapshot getHealthMetrics() const;
|
||||
bool isHealthy() const;
|
||||
void enableHealthMonitoring( bool enable );
|
||||
|
||||
private:
|
||||
RiaOpenTelemetryManager();
|
||||
~RiaOpenTelemetryManager();
|
||||
|
||||
RiaOpenTelemetryManager( const RiaOpenTelemetryManager& ) = delete;
|
||||
RiaOpenTelemetryManager& operator=( const RiaOpenTelemetryManager& ) = delete;
|
||||
|
||||
struct Event
|
||||
{
|
||||
std::string name;
|
||||
std::map<std::string, std::string> attributes;
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
|
||||
Event( const std::string& eventName, const std::map<std::string, std::string>& attrs )
|
||||
: name( eventName )
|
||||
, attributes( attrs )
|
||||
, timestamp( std::chrono::system_clock::now() )
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Initialization
|
||||
bool initializeProvider();
|
||||
bool createExporter();
|
||||
void setupResourceAttributes();
|
||||
|
||||
// Event processing
|
||||
void processEvents();
|
||||
void processEvent( const Event& event );
|
||||
bool shouldSampleEvent() const;
|
||||
void flushPendingEvents();
|
||||
void onProcessEventTimer();
|
||||
void onNetworkReplyFinished();
|
||||
|
||||
// Circuit breaker and resilience
|
||||
void handleError( TelemetryError error, const QString& context );
|
||||
void attemptReconnection();
|
||||
bool isCircuitBreakerOpen() const;
|
||||
void resetCircuitBreaker();
|
||||
|
||||
// Health monitoring
|
||||
void updateHealthMetrics( bool success );
|
||||
void sendHealthSpan();
|
||||
|
||||
// Thread safety
|
||||
mutable std::mutex m_configMutex;
|
||||
mutable std::mutex m_queueMutex;
|
||||
std::queue<Event> m_eventQueue;
|
||||
|
||||
// State
|
||||
std::atomic<bool> m_initialized{ false };
|
||||
std::atomic<bool> m_enabled{ false };
|
||||
std::atomic<bool> m_isShuttingDown{ false };
|
||||
std::atomic<bool> m_circuitBreakerOpen{ false };
|
||||
|
||||
// Qt networking and timer
|
||||
QNetworkAccessManager* m_networkAccessManager{ nullptr };
|
||||
QTimer* m_processTimer{ nullptr };
|
||||
QTimer* m_healthTimer{ nullptr };
|
||||
|
||||
// Configuration
|
||||
size_t m_maxQueueSize{ 10000 };
|
||||
bool m_backpressureEnabled{ true };
|
||||
size_t m_memoryThresholdMB{ 50 };
|
||||
double m_samplingRate{ 1.0 };
|
||||
|
||||
// Error handling
|
||||
ErrorCallback m_errorCallback;
|
||||
std::atomic<int> m_consecutiveFailures{ 0 };
|
||||
std::chrono::steady_clock::time_point m_lastReconnectAttempt;
|
||||
|
||||
// Health monitoring
|
||||
mutable HealthMetrics m_healthMetrics;
|
||||
bool m_healthMonitoringEnabled{ true };
|
||||
};
|
||||
@@ -214,6 +214,28 @@ if(RESINSIGHT_FOUND_HDF5)
|
||||
endif() # MSVC
|
||||
endif()
|
||||
|
||||
#
|
||||
# OpenTelemetry
|
||||
#
|
||||
option(RESINSIGHT_ENABLE_OPENTELEMETRY "Enable OpenTelemetry integration" OFF)
|
||||
if(RESINSIGHT_ENABLE_OPENTELEMETRY)
|
||||
message(STATUS "OpenTelemetry support enabled")
|
||||
add_definitions(-DRESINSIGHT_OPENTELEMETRY_ENABLED)
|
||||
|
||||
# Create telemetry source files list
|
||||
set(OPENTELEMETRY_FILES
|
||||
Application/Tools/Telemetry/RiaOpenTelemetryManager.h
|
||||
Application/Tools/Telemetry/RiaOpenTelemetryManager.cpp
|
||||
)
|
||||
|
||||
list(APPEND CPP_SOURCES ${OPENTELEMETRY_FILES})
|
||||
endif()
|
||||
|
||||
# Always include OpenTelemetry preferences
|
||||
list(APPEND CPP_SOURCES Application/RiaPreferencesOpenTelemetry.h
|
||||
Application/RiaPreferencesOpenTelemetry.cpp
|
||||
)
|
||||
|
||||
qt_add_resources(QRC_FILES Application/Resources/ApplicationLibCode.qrc)
|
||||
|
||||
list(APPEND ALL_SOURCE_FILES ${CPP_SOURCES} ${MOC_SOURCE_FILES}
|
||||
@@ -353,6 +375,7 @@ target_include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Application/Tools
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Application/Tools/WellPathTools
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Application/Tools/KeyValueStore
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Application/Tools/Telemetry
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CommandFileInterface
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CommandFileInterface/Core
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/FileInterface
|
||||
|
||||
Reference in New Issue
Block a user