Skip to content
Draft
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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.229.098"
VERSION = "0.229.099"


SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')
Expand Down
59 changes: 58 additions & 1 deletion application/single_app/functions_appinsights.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ def setup_appinsights_logging(settings):
"""
Set up Azure Monitor Application Insights using the modern OpenTelemetry approach.
This replaces the deprecated opencensus implementation.

Configures OpenTelemetry settings based on admin settings:
- OTEL_SERVICE_NAME: Service name for telemetry
- OTEL_TRACES_SAMPLER: Sampling strategy for traces
- OTEL_TRACES_SAMPLER_ARG: Sampling ratio (0.0 to 1.0)
- OTEL_PYTHON_FLASK_EXCLUDED_URLS: URLs to exclude from instrumentation
- OTEL_PYTHON_DISABLED_INSTRUMENTATIONS: Instrumentations to disable
- OTEL_LOGS_EXPORTER: Where to export logs
- OTEL_METRICS_EXPORTER: Where to export metrics
"""
global _appinsights_logger, _azure_monitor_configured

Expand All @@ -130,11 +139,59 @@ def setup_appinsights_logging(settings):
return

try:
# Apply OpenTelemetry configuration from settings to environment variables
# These must be set before calling configure_azure_monitor()

# Service Name - defaults to "simplechat"
otel_service_name = settings.get('otel_service_name', 'simplechat') if settings else 'simplechat'
if otel_service_name:
os.environ['OTEL_SERVICE_NAME'] = str(otel_service_name)
print(f"[Azure Monitor] OTEL_SERVICE_NAME set to: {otel_service_name}")

# Traces Sampler - defaults to "parentbased_always_on"
otel_traces_sampler = settings.get('otel_traces_sampler', 'parentbased_always_on') if settings else 'parentbased_always_on'
if otel_traces_sampler:
os.environ['OTEL_TRACES_SAMPLER'] = str(otel_traces_sampler)
print(f"[Azure Monitor] OTEL_TRACES_SAMPLER set to: {otel_traces_sampler}")

# Traces Sampler Argument - defaults to "1.0" (100%)
otel_traces_sampler_arg = settings.get('otel_traces_sampler_arg', '1.0') if settings else '1.0'
if otel_traces_sampler_arg:
os.environ['OTEL_TRACES_SAMPLER_ARG'] = str(otel_traces_sampler_arg)
print(f"[Azure Monitor] OTEL_TRACES_SAMPLER_ARG set to: {otel_traces_sampler_arg}")

# Flask Excluded URLs - defaults to health check endpoints
otel_flask_excluded_urls = settings.get('otel_flask_excluded_urls', 'healthcheck,/health,/external/health') if settings else 'healthcheck,/health,/external/health'
if otel_flask_excluded_urls:
os.environ['OTEL_PYTHON_FLASK_EXCLUDED_URLS'] = str(otel_flask_excluded_urls)
print(f"[Azure Monitor] OTEL_PYTHON_FLASK_EXCLUDED_URLS set to: {otel_flask_excluded_urls}")

# Disabled Instrumentations - defaults to empty (all enabled)
otel_disabled_instrumentations = settings.get('otel_disabled_instrumentations', '') if settings else ''
if otel_disabled_instrumentations:
os.environ['OTEL_PYTHON_DISABLED_INSTRUMENTATIONS'] = str(otel_disabled_instrumentations)
print(f"[Azure Monitor] OTEL_PYTHON_DISABLED_INSTRUMENTATIONS set to: {otel_disabled_instrumentations}")

# Logs Exporter - defaults to "console,otlp"
otel_logs_exporter = settings.get('otel_logs_exporter', 'console,otlp') if settings else 'console,otlp'
if otel_logs_exporter:
os.environ['OTEL_LOGS_EXPORTER'] = str(otel_logs_exporter)
print(f"[Azure Monitor] OTEL_LOGS_EXPORTER set to: {otel_logs_exporter}")

# Metrics Exporter - defaults to "otlp"
otel_metrics_exporter = settings.get('otel_metrics_exporter', 'otlp') if settings else 'otlp'
if otel_metrics_exporter:
os.environ['OTEL_METRICS_EXPORTER'] = str(otel_metrics_exporter)
print(f"[Azure Monitor] OTEL_METRICS_EXPORTER set to: {otel_metrics_exporter}")

# Enable Live Metrics - defaults to True
enable_live_metrics = settings.get('otel_enable_live_metrics', True) if settings else True

# Configure Azure Monitor with OpenTelemetry
# This automatically sets up logging, tracing, and metrics
configure_azure_monitor(
connection_string=connectionString,
enable_live_metrics=True, # Enable live metrics for real-time monitoring
enable_live_metrics=bool(enable_live_metrics),
disable_offline_storage=True, # Disable offline storage to prevent issues
)

Expand Down
47 changes: 47 additions & 0 deletions application/single_app/route_frontend_admin_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ def admin_settings():
if 'enable_debug_logging' not in settings:
settings['enable_debug_logging'] = False

# --- Add defaults for OpenTelemetry configuration ---
if 'otel_service_name' not in settings:
settings['otel_service_name'] = 'simplechat'
if 'otel_traces_sampler' not in settings:
settings['otel_traces_sampler'] = 'parentbased_always_on'
if 'otel_traces_sampler_arg' not in settings:
settings['otel_traces_sampler_arg'] = '1.0'
if 'otel_flask_excluded_urls' not in settings:
settings['otel_flask_excluded_urls'] = 'healthcheck,/health,/external/health'
if 'otel_disabled_instrumentations' not in settings:
settings['otel_disabled_instrumentations'] = ''
if 'otel_logs_exporter' not in settings:
settings['otel_logs_exporter'] = 'console,otlp'
if 'otel_metrics_exporter' not in settings:
settings['otel_metrics_exporter'] = 'otlp'
if 'otel_enable_live_metrics' not in settings:
settings['otel_enable_live_metrics'] = True

# --- Add default for semantic_kernel ---
if 'per_user_semantic_kernel' not in settings:
settings['per_user_semantic_kernel'] = False
Expand Down Expand Up @@ -458,6 +476,26 @@ def is_valid_url(url):
flash('Invalid Front Door URL format. Please provide a valid HTTP/HTTPS URL.', 'danger')
front_door_url = ''

# --- OpenTelemetry Configuration ---
otel_service_name = form_data.get('otel_service_name', 'simplechat').strip()
otel_traces_sampler = form_data.get('otel_traces_sampler', 'parentbased_always_on')
otel_traces_sampler_arg = form_data.get('otel_traces_sampler_arg', '1.0').strip()
otel_flask_excluded_urls = form_data.get('otel_flask_excluded_urls', 'healthcheck,/health,/external/health').strip()
otel_disabled_instrumentations = form_data.get('otel_disabled_instrumentations', '').strip()
otel_logs_exporter = form_data.get('otel_logs_exporter', 'console,otlp')
otel_metrics_exporter = form_data.get('otel_metrics_exporter', 'otlp')
otel_enable_live_metrics = form_data.get('otel_enable_live_metrics') == 'on'

# Validate OTEL_TRACES_SAMPLER_ARG is a valid float between 0.0 and 1.0
try:
sampler_arg_float = float(otel_traces_sampler_arg)
if sampler_arg_float < 0.0 or sampler_arg_float > 1.0:
flash('OTEL Traces Sampler Argument must be between 0.0 and 1.0. Reset to 1.0.', 'warning')
otel_traces_sampler_arg = '1.0'
except ValueError:
flash('Invalid OTEL Traces Sampler Argument. Must be a number between 0.0 and 1.0. Reset to 1.0.', 'warning')
otel_traces_sampler_arg = '1.0'

# --- Construct new_settings Dictionary ---
new_settings = {
# Logging
Expand All @@ -467,6 +505,15 @@ def is_valid_url(url):
'debug_timer_value': debug_timer_value,
'debug_timer_unit': debug_timer_unit,
'debug_logging_turnoff_time': debug_logging_turnoff_time_str,
# OpenTelemetry Configuration
'otel_service_name': otel_service_name,
'otel_traces_sampler': otel_traces_sampler,
'otel_traces_sampler_arg': otel_traces_sampler_arg,
'otel_flask_excluded_urls': otel_flask_excluded_urls,
'otel_disabled_instrumentations': otel_disabled_instrumentations,
'otel_logs_exporter': otel_logs_exporter,
'otel_metrics_exporter': otel_metrics_exporter,
'otel_enable_live_metrics': otel_enable_live_metrics,
# General
'app_title': app_title,
'show_logo': form_data.get('show_logo') == 'on',
Expand Down
86 changes: 86 additions & 0 deletions application/single_app/templates/admin_settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,92 @@ <h5>
</div>
</div>

<div class="card p-3 mb-3" id="opentelemetry-section">
<h5>
<i class="bi bi-sliders me-2"></i>OpenTelemetry Configuration
</h5>
<p class="text-muted">Fine-tune telemetry collection, sampling, and instrumentation for Azure Monitor Application Insights.</p>

<!-- Service Name -->
<div class="mb-3">
<label for="otel_service_name" class="form-label fw-bold">Service Name</label>
<input type="text" class="form-control" id="otel_service_name" name="otel_service_name" value="{{ settings.otel_service_name }}" placeholder="simplechat">
<small class="form-text text-muted">Logical service name for telemetry data. Used to distinguish between multiple deployments (e.g., "simplechat-production").</small>
</div>

<!-- Traces Sampler -->
<div class="mb-3">
<label for="otel_traces_sampler" class="form-label fw-bold">Traces Sampler</label>
<select class="form-select" id="otel_traces_sampler" name="otel_traces_sampler">
<option value="always_on" {% if settings.otel_traces_sampler == 'always_on' %}selected{% endif %}>Always On (100% sampling)</option>
<option value="always_off" {% if settings.otel_traces_sampler == 'always_off' %}selected{% endif %}>Always Off (0% sampling)</option>
<option value="traceidratio" {% if settings.otel_traces_sampler == 'traceidratio' %}selected{% endif %}>Trace ID Ratio (percentage-based)</option>
<option value="parentbased_always_on" {% if settings.otel_traces_sampler == 'parentbased_always_on' %}selected{% endif %}>Parent-based Always On (default)</option>
<option value="parentbased_always_off" {% if settings.otel_traces_sampler == 'parentbased_always_off' %}selected{% endif %}>Parent-based Always Off</option>
<option value="parentbased_traceidratio" {% if settings.otel_traces_sampler == 'parentbased_traceidratio' %}selected{% endif %}>Parent-based Trace ID Ratio</option>
</select>
<small class="form-text text-muted">Controls what percentage of traces are collected. Use ratio-based samplers to reduce costs in high-traffic environments.</small>
</div>

<!-- Traces Sampler Argument -->
<div class="mb-3">
<label for="otel_traces_sampler_arg" class="form-label fw-bold">Traces Sampler Argument (Ratio)</label>
<input type="text" class="form-control" id="otel_traces_sampler_arg" name="otel_traces_sampler_arg" value="{{ settings.otel_traces_sampler_arg }}" placeholder="1.0">
<small class="form-text text-muted">Sampling ratio (0.0 to 1.0). For example, 0.1 = 10% sampling, 1.0 = 100% sampling. Used with ratio-based samplers.</small>
</div>

<!-- Flask Excluded URLs -->
<div class="mb-3">
<label for="otel_flask_excluded_urls" class="form-label fw-bold">Flask Excluded URLs</label>
<input type="text" class="form-control" id="otel_flask_excluded_urls" name="otel_flask_excluded_urls" value="{{ settings.otel_flask_excluded_urls }}" placeholder="healthcheck,/health,/external/health">
<small class="form-text text-muted">Comma-separated regex patterns for URLs to exclude from instrumentation. Reduces noise and costs by excluding health checks and internal endpoints.</small>
</div>

<!-- Disabled Instrumentations -->
<div class="mb-3">
<label for="otel_disabled_instrumentations" class="form-label fw-bold">Disabled Instrumentations</label>
<input type="text" class="form-control" id="otel_disabled_instrumentations" name="otel_disabled_instrumentations" value="{{ settings.otel_disabled_instrumentations }}" placeholder="Leave empty to enable all">
<small class="form-text text-muted">Comma-separated list of instrumentations to disable (e.g., "flask,requests"). Leave empty to enable all instrumentations.</small>
</div>

<!-- Logs Exporter -->
<div class="mb-3">
<label for="otel_logs_exporter" class="form-label fw-bold">Logs Exporter</label>
<select class="form-select" id="otel_logs_exporter" name="otel_logs_exporter">
<option value="console" {% if settings.otel_logs_exporter == 'console' %}selected{% endif %}>Console</option>
<option value="otlp" {% if settings.otel_logs_exporter == 'otlp' %}selected{% endif %}>OTLP (Azure Monitor)</option>
<option value="console,otlp" {% if settings.otel_logs_exporter == 'console,otlp' %}selected{% endif %}>Console and OTLP (default)</option>
<option value="none" {% if settings.otel_logs_exporter == 'none' %}selected{% endif %}>None</option>
</select>
<small class="form-text text-muted">Where to export OpenTelemetry logs. Choose "OTLP" for production or "Console" for development.</small>
</div>

<!-- Metrics Exporter -->
<div class="mb-3">
<label for="otel_metrics_exporter" class="form-label fw-bold">Metrics Exporter</label>
<select class="form-select" id="otel_metrics_exporter" name="otel_metrics_exporter">
<option value="console" {% if settings.otel_metrics_exporter == 'console' %}selected{% endif %}>Console</option>
<option value="otlp" {% if settings.otel_metrics_exporter == 'otlp' %}selected{% endif %}>OTLP (Azure Monitor, default)</option>
<option value="console,otlp" {% if settings.otel_metrics_exporter == 'console,otlp' %}selected{% endif %}>Console and OTLP</option>
<option value="none" {% if settings.otel_metrics_exporter == 'none' %}selected{% endif %}>None</option>
</select>
<small class="form-text text-muted">Where to export OpenTelemetry metrics. Choose "None" if using external metrics platforms like Prometheus.</small>
</div>

<!-- Enable Live Metrics -->
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="otel_enable_live_metrics" name="otel_enable_live_metrics" {% if settings.otel_enable_live_metrics %}checked{% endif %}>
<label class="form-check-label ms-2" for="otel_enable_live_metrics">
Enable Live Metrics
</label>
<i class="bi bi-info-circle ms-2" data-bs-toggle="tooltip" title="Enables Azure Monitor Live Metrics stream for real-time monitoring. Maintains a persistent connection."></i>
</div>

<div class="alert alert-warning mt-3" role="alert">
<strong>Note:</strong> Changing OpenTelemetry settings requires an application restart to take effect. See <a href="https://github.com/microsoft/simplechat/blob/main/docs/features/OPENTELEMETRY_CONFIGURATION.md" target="_blank">documentation</a> for detailed use cases and recommendations.
</div>
</div>

<div class="card p-3 mb-3" id="debug-logging-section">
<h5>
<i class="bi bi-bug me-2"></i>Debug Logging
Expand Down
Loading