Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
12 changes: 11 additions & 1 deletion azure_functions_worker/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,15 @@
# Base extension supported Python minor version
BASE_EXT_SUPPORTED_PY_MINOR_VERSION = 8

# Appsetting to turn on OpenTelemetry support/features
# Includes turning on Azure monitor distro to send telemetry to AppInsights
PYTHON_ENABLE_OPENTELEMETRY = "PYTHON_ENABLE_OPENTELEMETRY"
PYTHON_ENABLE_OPENTELEMETRY_DEFAULT = True
PYTHON_ENABLE_OPENTELEMETRY_DEFAULT = False

# Appsetting to specify root logger name of logger to collect telemetry for
# Used by Azure monitor distro
PYTHON_AZURE_MONITOR_LOGGER_NAME = "PYTHON_AZURE_MONITOR_LOGGER_NAME"
PYTHON_AZURE_MONITOR_LOGGER_NAME_DEFAULT = ""

# Appsetting to specify AppInsights connection string
APPLICATIONINSIGHTS_CONNECTION_STRING = "APPLICATIONINSIGHTS_CONNECTION_STRING"
43 changes: 41 additions & 2 deletions azure_functions_worker/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,46 @@ async def _dispatch_grpc_request(self, request):
resp = await request_handler(request)
self._grpc_resp_queue.put_nowait(resp)

def initialize_opentelemetry(self):
"""Initializes OpenTelemetry and Azure monitor distro
"""
self.update_opentelemetry_status()
try:
from azure.monitor.opentelemetry import configure_azure_monitor

# Set functions resource detector manually until officially
# include in Azure monitor distro
os.environ.setdefault(
"OTEL_EXPERIMENTAL_RESOURCE_DETECTORS",
"azure_functions",
)

configure_azure_monitor(
# Connection string can be explicitly specified in Appsetting
# If not set, defaults to env var
# APPLICATIONINSIGHTS_CONNECTION_STRING
connection_string=get_app_setting(
setting=APPLICATIONINSIGHTS_CONNECTION_STRING
),
logger_name=get_app_setting(
setting=PYTHON_AZURE_MONITOR_LOGGER_NAME,
default_value=PYTHON_AZURE_MONITOR_LOGGER_NAME_DEFAULT
),
)
self._otel_libs_available = True

logger.info("Successfully configured Azure monitor distro.")
except ImportError:
logger.exception(
"Cannot import Azure Monitor distro."
)
self._otel_libs_available = False
except Exception:
logger.exception(
"Error initializing Azure monitor distro."
)
self._otel_libs_available = False

def update_opentelemetry_status(self):
"""Check for OpenTelemetry library availability and
update the status attribute."""
Expand Down Expand Up @@ -334,10 +374,9 @@ async def _handle__worker_init_request(self, request):
constants.RPC_HTTP_TRIGGER_METADATA_REMOVED: _TRUE,
constants.SHARED_MEMORY_DATA_TRANSFER: _TRUE,
}

if get_app_setting(setting=PYTHON_ENABLE_OPENTELEMETRY,
default_value=PYTHON_ENABLE_OPENTELEMETRY_DEFAULT):
self.update_opentelemetry_status()
self.initialize_opentelemetry()

if self._otel_libs_available:
capabilities[constants.WORKER_OPEN_TELEMETRY_ENABLED] = _TRUE
Expand Down
45 changes: 42 additions & 3 deletions tests/unittests/test_opentelemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,37 @@ def test_update_opentelemetry_status_success(
self.assertTrue(self.dispatcher._otel_libs_available)

@patch('builtins.__import__')
def test_init_request_otel_capability_enabled(
self, mock_imports):
@patch("azure_functions_worker.dispatcher.Dispatcher.update_opentelemetry_status")
def test_initialize_opentelemetry_success(
self,
mock_update_ot,
mock_imports,
):
mock_imports.return_value = MagicMock()
self.dispatcher.initialize_opentelemetry()
mock_update_ot.assert_called_once()
self.assertTrue(self.dispatcher._otel_libs_available)

@patch("azure_functions_worker.dispatcher.Dispatcher.update_opentelemetry_status")
def test_initialize_opentelemetry_import_error(
self,
mock_update_ot,
):
with patch('builtins.__import__', side_effect=ImportError):
self.dispatcher.initialize_opentelemetry()
mock_update_ot.assert_called_once()
# Verify that otel_libs_available is set to False due to ImportError
self.assertFalse(self.dispatcher._otel_libs_available)

@patch("azure_functions_worker.dispatcher.get_app_setting")
@patch('builtins.__import__')
def test_init_request_otel_capability_enabled_app_setting(
self,
mock_imports,
mock_app_setting,
):
mock_imports.return_value = MagicMock()
mock_app_setting.return_value = True

init_request = protos.StreamingMessage(
worker_init_request=protos.WorkerInitRequest(
Expand All @@ -55,7 +83,14 @@ def test_init_request_otel_capability_enabled(
self.assertIn("WorkerOpenTelemetryEnabled", capabilities)
self.assertEqual(capabilities["WorkerOpenTelemetryEnabled"], "true")

def test_init_request_otel_capability_disabled(self):
@patch("azure_functions_worker.dispatcher.Dispatcher.initialize_opentelemetry")
@patch("azure_functions_worker.dispatcher.get_app_setting")
def test_init_request_otel_capability_disabled_app_setting(
self,
mock_app_setting,
mock_initialize_ot,
):
mock_app_setting.return_value = False

init_request = protos.StreamingMessage(
worker_init_request=protos.WorkerInitRequest(
Expand All @@ -70,5 +105,9 @@ def test_init_request_otel_capability_disabled(self):
self.assertEqual(init_response.worker_init_response.result.status,
protos.StatusResult.Success)

# OpenTelemetry initialized not called
mock_initialize_ot.assert_not_called()

# Verify that WorkerOpenTelemetryEnabled capability is not set
capabilities = init_response.worker_init_response.capabilities
self.assertNotIn("WorkerOpenTelemetryEnabled", capabilities)