Skip to content

Commit 7aac7b0

Browse files
authored
feat: Add env var to control sending telemetry to appinsights in functions worker (#1621)
* str * change * otel * lint * test * changes * Update pyproject.toml * fix test
1 parent 95a4793 commit 7aac7b0

File tree

5 files changed

+151
-30
lines changed

5 files changed

+151
-30
lines changed

azure_functions_worker/constants.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,18 @@
8282
BASE_EXT_SUPPORTED_PY_MINOR_VERSION = 8
8383

8484
# Appsetting to turn on OpenTelemetry support/features
85-
# Includes turning on Azure monitor distro to send telemetry to AppInsights
85+
# A value of "true" enables the setting
8686
PYTHON_ENABLE_OPENTELEMETRY = "PYTHON_ENABLE_OPENTELEMETRY"
87-
PYTHON_ENABLE_OPENTELEMETRY_DEFAULT = False
87+
88+
# Appsetting to turn on ApplicationInsights support/features
89+
# A value of "true" enables the setting
90+
PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY = \
91+
"PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY"
8892

8993
# Appsetting to specify root logger name of logger to collect telemetry for
90-
# Used by Azure monitor distro
91-
PYTHON_AZURE_MONITOR_LOGGER_NAME = "PYTHON_AZURE_MONITOR_LOGGER_NAME"
92-
PYTHON_AZURE_MONITOR_LOGGER_NAME_DEFAULT = ""
94+
# Used by Azure monitor distro (Application Insights)
95+
PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME = "PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME"
96+
PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME_DEFAULT = ""
9397

9498
# Appsetting to specify AppInsights connection string
9599
APPLICATIONINSIGHTS_CONNECTION_STRING = "APPLICATIONINSIGHTS_CONNECTION_STRING"

azure_functions_worker/dispatcher.py

+23-18
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
APPLICATIONINSIGHTS_CONNECTION_STRING,
2727
HTTP_URI,
2828
METADATA_PROPERTIES_WORKER_INDEXED,
29-
PYTHON_AZURE_MONITOR_LOGGER_NAME,
30-
PYTHON_AZURE_MONITOR_LOGGER_NAME_DEFAULT,
29+
PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME,
30+
PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME_DEFAULT,
3131
PYTHON_ENABLE_DEBUG_LOGGING,
3232
PYTHON_ENABLE_INIT_INDEXING,
33+
PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY,
3334
PYTHON_ENABLE_OPENTELEMETRY,
34-
PYTHON_ENABLE_OPENTELEMETRY_DEFAULT,
3535
PYTHON_LANGUAGE_RUNTIME,
3636
PYTHON_ROLLBACK_CWD_PATH,
3737
PYTHON_SCRIPT_FILE_NAME,
@@ -103,8 +103,10 @@ def __init__(self, loop: BaseEventLoop, host: str, port: int,
103103
self._function_metadata_result = None
104104
self._function_metadata_exception = None
105105

106-
# Used for checking if open telemetry is enabled
106+
# Used for checking if appinsights is enabled
107107
self._azure_monitor_available = False
108+
# Used for checking if open telemetry is enabled
109+
self._otel_libs_available = False
108110
self._context_api = None
109111
self._trace_context_propagator = None
110112

@@ -318,8 +320,8 @@ def initialize_azure_monitor(self):
318320
setting=APPLICATIONINSIGHTS_CONNECTION_STRING
319321
),
320322
logger_name=get_app_setting(
321-
setting=PYTHON_AZURE_MONITOR_LOGGER_NAME,
322-
default_value=PYTHON_AZURE_MONITOR_LOGGER_NAME_DEFAULT
323+
setting=PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME,
324+
default_value=PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME_DEFAULT
323325
),
324326
)
325327
self._azure_monitor_available = True
@@ -381,12 +383,15 @@ async def _handle__worker_init_request(self, request):
381383
constants.RPC_HTTP_TRIGGER_METADATA_REMOVED: _TRUE,
382384
constants.SHARED_MEMORY_DATA_TRANSFER: _TRUE,
383385
}
384-
if get_app_setting(setting=PYTHON_ENABLE_OPENTELEMETRY,
385-
default_value=PYTHON_ENABLE_OPENTELEMETRY_DEFAULT):
386+
387+
if is_envvar_true(PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY):
386388
self.initialize_azure_monitor()
387389

388-
if self._azure_monitor_available:
389-
capabilities[constants.WORKER_OPEN_TELEMETRY_ENABLED] = _TRUE
390+
if is_envvar_true(PYTHON_ENABLE_OPENTELEMETRY):
391+
self._otel_libs_available = True
392+
393+
if self._azure_monitor_available or self._otel_libs_available:
394+
capabilities[constants.WORKER_OPEN_TELEMETRY_ENABLED] = _TRUE
390395

391396
if DependencyManager.should_load_cx_dependencies():
392397
DependencyManager.prioritize_customer_dependencies()
@@ -662,7 +667,7 @@ async def _handle__invocation_request(self, request):
662667
args[name] = bindings.Out()
663668

664669
if fi.is_async:
665-
if self._azure_monitor_available:
670+
if self._azure_monitor_available or self._otel_libs_available:
666671
self.configure_opentelemetry(fi_context)
667672

668673
call_result = \
@@ -779,14 +784,14 @@ async def _handle__function_environment_reload_request(self, request):
779784
bindings.load_binding_registry()
780785

781786
capabilities = {}
782-
if get_app_setting(
783-
setting=PYTHON_ENABLE_OPENTELEMETRY,
784-
default_value=PYTHON_ENABLE_OPENTELEMETRY_DEFAULT):
787+
if is_envvar_true(PYTHON_ENABLE_OPENTELEMETRY):
788+
self._otel_libs_available = True
789+
if is_envvar_true(PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY):
785790
self.initialize_azure_monitor()
786791

787-
if self._azure_monitor_available:
788-
capabilities[constants.WORKER_OPEN_TELEMETRY_ENABLED] = (
789-
_TRUE)
792+
if self._azure_monitor_available or self._otel_libs_available:
793+
capabilities[constants.WORKER_OPEN_TELEMETRY_ENABLED] = (
794+
_TRUE)
790795

791796
if is_envvar_true(PYTHON_ENABLE_INIT_INDEXING):
792797
try:
@@ -996,7 +1001,7 @@ def _run_sync_func(self, invocation_id, context, func, params):
9961001
# invocation_id from ThreadPoolExecutor's threads.
9971002
context.thread_local_storage.invocation_id = invocation_id
9981003
try:
999-
if self._azure_monitor_available:
1004+
if self._azure_monitor_available or self._otel_libs_available:
10001005
self.configure_opentelemetry(context)
10011006
return ExtensionManager.get_sync_invocation_wrapper(context,
10021007
func)(params)

azure_functions_worker/utils/app_setting_manager.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
FUNCTIONS_WORKER_SHARED_MEMORY_DATA_TRANSFER_ENABLED,
88
PYTHON_ENABLE_DEBUG_LOGGING,
99
PYTHON_ENABLE_INIT_INDEXING,
10+
PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY,
1011
PYTHON_ENABLE_OPENTELEMETRY,
1112
PYTHON_ENABLE_WORKER_EXTENSIONS,
1213
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT,
@@ -29,7 +30,8 @@ def get_python_appsetting_state():
2930
FUNCTIONS_WORKER_SHARED_MEMORY_DATA_TRANSFER_ENABLED,
3031
PYTHON_SCRIPT_FILE_NAME,
3132
PYTHON_ENABLE_INIT_INDEXING,
32-
PYTHON_ENABLE_OPENTELEMETRY]
33+
PYTHON_ENABLE_OPENTELEMETRY,
34+
PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY]
3335

3436
app_setting_states = "".join(
3537
f"{app_setting}: {current_vars[app_setting]} | "

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Repository = "https://github.com/Azure/azure-functions-python-worker"
4545
dev = [
4646
"azure-eventhub", # Used for EventHub E2E tests
4747
"azure-functions-durable", # Used for Durable E2E tests
48+
"azure-monitor-opentelemetry", # Used for Azure Monitor unit tests
4849
"flask",
4950
"fastapi~=0.103.2",
5051
"pydantic",
@@ -55,6 +56,7 @@ dev = [
5556
"requests==2.*",
5657
"coverage",
5758
"pytest-sugar",
59+
"opentelemetry-api", # Used for OpenTelemetry unit tests
5860
"pytest-cov",
5961
"pytest-xdist",
6062
"pytest-randomly",

tests/unittests/test_opentelemetry.py

+114-6
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ def test_update_opentelemetry_status_import_error(self):
2323
# Patch the built-in import mechanism
2424
with patch('builtins.__import__', side_effect=ImportError):
2525
self.dispatcher.update_opentelemetry_status()
26-
# Verify that otel_libs_available is set to False due to ImportError
27-
self.assertFalse(self.dispatcher._azure_monitor_available)
26+
# Verify that context variables are None due to ImportError
27+
self.assertIsNone(self.dispatcher._context_api)
28+
self.assertIsNone(self.dispatcher._trace_context_propagator)
2829

2930
@patch('builtins.__import__')
3031
def test_update_opentelemetry_status_success(
@@ -54,12 +55,12 @@ def test_initialize_azure_monitor_import_error(
5455
with patch('builtins.__import__', side_effect=ImportError):
5556
self.dispatcher.initialize_azure_monitor()
5657
mock_update_ot.assert_called_once()
57-
# Verify that otel_libs_available is set to False due to ImportError
58+
# Verify that azure_monitor_available is set to False due to ImportError
5859
self.assertFalse(self.dispatcher._azure_monitor_available)
5960

60-
@patch.dict(os.environ, {'PYTHON_ENABLE_OPENTELEMETRY': 'true'})
61+
@patch.dict(os.environ, {'PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY': 'true'})
6162
@patch('builtins.__import__')
62-
def test_init_request_otel_capability_enabled_app_setting(
63+
def test_init_request_initialize_azure_monitor_enabled_app_setting(
6364
self,
6465
mock_imports,
6566
):
@@ -78,13 +79,15 @@ def test_init_request_otel_capability_enabled_app_setting(
7879
self.assertEqual(init_response.worker_init_response.result.status,
7980
protos.StatusResult.Success)
8081

82+
# Verify azure_monitor_available is set to True
83+
self.assertTrue(self.dispatcher._azure_monitor_available)
8184
# Verify that WorkerOpenTelemetryEnabled capability is set to _TRUE
8285
capabilities = init_response.worker_init_response.capabilities
8386
self.assertIn("WorkerOpenTelemetryEnabled", capabilities)
8487
self.assertEqual(capabilities["WorkerOpenTelemetryEnabled"], "true")
8588

8689
@patch("azure_functions_worker.dispatcher.Dispatcher.initialize_azure_monitor")
87-
def test_init_request_otel_capability_disabled_app_setting(
90+
def test_init_request_initialize_azure_monitor_default_app_setting(
8891
self,
8992
mock_initialize_azmon,
9093
):
@@ -103,8 +106,113 @@ def test_init_request_otel_capability_disabled_app_setting(
103106
protos.StatusResult.Success)
104107

105108
# Azure monitor initialized not called
109+
# Since default behavior is not enabled
106110
mock_initialize_azmon.assert_not_called()
107111

112+
# Verify azure_monitor_available is set to False
113+
self.assertFalse(self.dispatcher._azure_monitor_available)
114+
# Verify that WorkerOpenTelemetryEnabled capability is not set
115+
capabilities = init_response.worker_init_response.capabilities
116+
self.assertNotIn("WorkerOpenTelemetryEnabled", capabilities)
117+
118+
@patch.dict(os.environ, {'PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY': 'false'})
119+
@patch("azure_functions_worker.dispatcher.Dispatcher.initialize_azure_monitor")
120+
def test_init_request_initialize_azure_monitor_disabled_app_setting(
121+
self,
122+
mock_initialize_azmon,
123+
):
124+
125+
init_request = protos.StreamingMessage(
126+
worker_init_request=protos.WorkerInitRequest(
127+
host_version="2.3.4",
128+
function_app_directory=str(FUNCTION_APP_DIRECTORY)
129+
)
130+
)
131+
132+
init_response = self.loop.run_until_complete(
133+
self.dispatcher._handle__worker_init_request(init_request))
134+
135+
self.assertEqual(init_response.worker_init_response.result.status,
136+
protos.StatusResult.Success)
137+
138+
# Azure monitor initialized not called
139+
mock_initialize_azmon.assert_not_called()
140+
141+
# Verify azure_monitor_available is set to False
142+
self.assertFalse(self.dispatcher._azure_monitor_available)
143+
# Verify that WorkerOpenTelemetryEnabled capability is not set
144+
capabilities = init_response.worker_init_response.capabilities
145+
self.assertNotIn("WorkerOpenTelemetryEnabled", capabilities)
146+
147+
@patch.dict(os.environ, {'PYTHON_ENABLE_OPENTELEMETRY': 'true'})
148+
def test_init_request_enable_opentelemetry_enabled_app_setting(
149+
self,
150+
):
151+
152+
init_request = protos.StreamingMessage(
153+
worker_init_request=protos.WorkerInitRequest(
154+
host_version="2.3.4",
155+
function_app_directory=str(FUNCTION_APP_DIRECTORY)
156+
)
157+
)
158+
159+
init_response = self.loop.run_until_complete(
160+
self.dispatcher._handle__worker_init_request(init_request))
161+
162+
self.assertEqual(init_response.worker_init_response.result.status,
163+
protos.StatusResult.Success)
164+
165+
# Verify otel_libs_available is set to True
166+
self.assertTrue(self.dispatcher._otel_libs_available)
167+
# Verify that WorkerOpenTelemetryEnabled capability is set to _TRUE
168+
capabilities = init_response.worker_init_response.capabilities
169+
self.assertIn("WorkerOpenTelemetryEnabled", capabilities)
170+
self.assertEqual(capabilities["WorkerOpenTelemetryEnabled"], "true")
171+
172+
@patch.dict(os.environ, {'PYTHON_ENABLE_OPENTELEMETRY': 'false'})
173+
def test_init_request_enable_opentelemetry_default_app_setting(
174+
self,
175+
):
176+
177+
init_request = protos.StreamingMessage(
178+
worker_init_request=protos.WorkerInitRequest(
179+
host_version="2.3.4",
180+
function_app_directory=str(FUNCTION_APP_DIRECTORY)
181+
)
182+
)
183+
184+
init_response = self.loop.run_until_complete(
185+
self.dispatcher._handle__worker_init_request(init_request))
186+
187+
self.assertEqual(init_response.worker_init_response.result.status,
188+
protos.StatusResult.Success)
189+
190+
# Verify otel_libs_available is set to False by default
191+
self.assertFalse(self.dispatcher._otel_libs_available)
192+
# Verify that WorkerOpenTelemetryEnabled capability is not set
193+
capabilities = init_response.worker_init_response.capabilities
194+
self.assertNotIn("WorkerOpenTelemetryEnabled", capabilities)
195+
196+
@patch.dict(os.environ, {'PYTHON_APPLICATIONINSIGHTS_ENABLE_TELEMETRY': 'false'})
197+
def test_init_request_enable_azure_monitor_disabled_app_setting(
198+
self,
199+
):
200+
201+
init_request = protos.StreamingMessage(
202+
worker_init_request=protos.WorkerInitRequest(
203+
host_version="2.3.4",
204+
function_app_directory=str(FUNCTION_APP_DIRECTORY)
205+
)
206+
)
207+
208+
init_response = self.loop.run_until_complete(
209+
self.dispatcher._handle__worker_init_request(init_request))
210+
211+
self.assertEqual(init_response.worker_init_response.result.status,
212+
protos.StatusResult.Success)
213+
214+
# Verify otel_libs_available is set to False by default
215+
self.assertFalse(self.dispatcher._otel_libs_available)
108216
# Verify that WorkerOpenTelemetryEnabled capability is not set
109217
capabilities = init_response.worker_init_response.capabilities
110218
self.assertNotIn("WorkerOpenTelemetryEnabled", capabilities)

0 commit comments

Comments
 (0)