Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 9bb688d

Browse files
authored
Extension for initializing OpenCensus tracer into Azure Functions (#1010)
1 parent 29fd633 commit 9bb688d

File tree

6 files changed

+281
-0
lines changed

6 files changed

+281
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Updated `azure` module
66
([#886](https://github.com/census-instrumentation/opencensus-python/pull/886))
7+
- Updated `azure` module to enable Azure Functions integration
8+
([#1010](https://github.com/census-instrumentation/opencensus-python/pull/1010))
79
- PeriodicMetricTask flush on exit
810
([#943](https://github.com/census-instrumentation/opencensus-python/pull/943))
911
- Change blacklist to excludelist
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2021, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# TODO: Configure PYTHON_ENABLE_WORKER_EXTENSIONS = 1 function app setting.
16+
# Ensure opencensus-ext-azure, opencensus-ext-requests and azure-functions
17+
# are defined in your function app's requirements.txt and properly installed.
18+
#
19+
# For more information about getting started with Azure Functions, please visit
20+
# https://aka.ms/functions-python-vscode
21+
import json
22+
import logging
23+
24+
import requests
25+
26+
from opencensus.ext.azure.extension.azure_functions import OpenCensusExtension
27+
28+
OpenCensusExtension.configure(
29+
libraries=['requests'],
30+
connection_string='InstrumentationKey=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
31+
)
32+
33+
def main(req, context):
34+
logging.info('Executing HttpTrigger with OpenCensus extension')
35+
36+
with context.tracer.span("parent"):
37+
requests.get(url='http://example.com')
38+
39+
return json.dumps({
40+
'method': req.method,
41+
'ctx_func_name': context.function_name,
42+
'ctx_func_dir': context.function_directory,
43+
'ctx_invocation_id': context.invocation_id,
44+
'ctx_trace_context_Traceparent': context.trace_context.Traceparent,
45+
'ctx_trace_context_Tracestate': context.trace_context.Tracestate,
46+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2021, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright 2021, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from azure.functions import AppExtensionBase
16+
from opencensus.trace import config_integration
17+
from opencensus.trace.propagation.trace_context_http_header_format import (
18+
TraceContextPropagator,
19+
)
20+
from opencensus.trace.samplers import ProbabilitySampler
21+
from opencensus.trace.tracer import Tracer
22+
23+
from ..trace_exporter import AzureExporter
24+
25+
26+
class OpenCensusExtension(AppExtensionBase):
27+
"""Extension for Azure Functions integration to export traces into Azure
28+
Monitor. Ensure the following requirements are met:
29+
1. Azure Functions version is greater or equal to v3.0.15584
30+
2. App setting PYTHON_ENABLE_WORKER_EXTENSIONS is set to 1
31+
"""
32+
33+
@classmethod
34+
def init(cls):
35+
cls._exporter = None
36+
cls._trace_integrations = []
37+
38+
@classmethod
39+
def configure(cls,
40+
libraries,
41+
connection_string = None,
42+
*args,
43+
**kwargs):
44+
"""Configure libraries for integrating into OpenCensus extension.
45+
Initialize an Azure Exporter that will write traces to AppInsights.
46+
:type libraries: List[str]
47+
:param libraries: the libraries opencensus-ext-* that need to be
48+
integrated into OpenCensus tracer. (e.g. ['requests'])
49+
:type connection_string: Optional[str]
50+
:param connection_string: the connection string of azure exporter
51+
to write into. If this is set to None, the extension will use
52+
an instrumentation connection string from your app settings.
53+
"""
54+
cls._trace_integrations = config_integration.trace_integrations(
55+
libraries
56+
)
57+
58+
cls._exporter = AzureExporter(connection_string=connection_string)
59+
60+
@classmethod
61+
def pre_invocation_app_level(cls,
62+
logger,
63+
context,
64+
func_args = {},
65+
*args,
66+
**kwargs):
67+
"""An implementation of pre invocation hooks on Function App's level.
68+
The Python Worker Extension Interface is defined in
69+
https://github.com/Azure/azure-functions-python-library/
70+
blob/dev/azure/functions/extension/app_extension_base.py
71+
"""
72+
if not cls._exporter:
73+
logger.warning(
74+
'Please call OpenCensusExtension.configure() after the import '
75+
'statement to ensure AzureExporter is setup correctly.'
76+
)
77+
return
78+
79+
span_context = TraceContextPropagator().from_headers({
80+
"traceparent": context.trace_context.Traceparent,
81+
"tracestate": context.trace_context.Tracestate
82+
})
83+
84+
tracer = Tracer(
85+
span_context=span_context,
86+
exporter=cls._exporter,
87+
sampler=ProbabilitySampler(1.0)
88+
)
89+
90+
setattr(context, 'tracer', tracer)
91+
92+
@classmethod
93+
def post_invocation_app_level(cls,
94+
logger,
95+
context,
96+
func_args,
97+
func_ret,
98+
*args,
99+
**kwargs):
100+
"""An implementation of post invocation hooks on Function App's level.
101+
The Python Worker Extension Interface is defined in
102+
https://github.com/Azure/azure-functions-python-library/
103+
blob/dev/azure/functions/extension/app_extension_base.py
104+
"""
105+
if getattr(context, 'tracer', None):
106+
del context.tracer

contrib/opencensus-ext-azure/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
include_package_data=True,
4040
long_description=open('README.rst').read(),
4141
install_requires=[
42+
'azure-functions >= 1.7.0',
4243
'opencensus >= 0.8.dev0, < 1.0.0',
4344
'psutil >= 5.6.3',
4445
'requests >= 2.19.0',
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Copyright 2021, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import sys
17+
import unittest
18+
19+
import mock
20+
21+
from opencensus.ext.azure.extension.azure_functions import OpenCensusExtension
22+
23+
IS_SUPPORTED_PYTHON_VERSION = sys.version_info.major == 3
24+
25+
MOCK_APPINSIGHTS_KEY = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
26+
MOCK_AZURE_EXPORTER_CONNSTRING = (
27+
'InstrumentationKey=11111111-2222-3333-4444-555555555555;'
28+
'IngestionEndpoint=https://mock.in.applicationinsights.azure.com/'
29+
)
30+
31+
unittest.skipIf(
32+
not IS_SUPPORTED_PYTHON_VERSION,
33+
'Azure Functions only support Python 3.x'
34+
)
35+
class MockContext(object):
36+
class MockTraceContext(object):
37+
Tracestate = 'rojo=00f067aa0ba902b7'
38+
Traceparent = '00-4bf92f3577b34da6a3ce929d0e0e4736-5fd358d59f88ce45-01'
39+
40+
trace_context = MockTraceContext()
41+
42+
class TestAzureFunctionsExtension(unittest.TestCase):
43+
def setUp(self):
44+
self._instance = OpenCensusExtension
45+
OpenCensusExtension.init()
46+
os.environ['APPINSIGHTS_INSTRUMENTATIONKEY'] = MOCK_APPINSIGHTS_KEY
47+
48+
def tearDown(self):
49+
if 'APPINSIGHTS_INSTRUMENTATIONKEY' in os.environ:
50+
del os.environ['APPINSIGHTS_INSTRUMENTATIONKEY']
51+
52+
@mock.patch('opencensus.ext.azure.extension.azure_functions'
53+
'.config_integration')
54+
def test_configure_method_should_setup_trace_integration(self, cfg_mock):
55+
self._instance.configure(['requests'])
56+
cfg_mock.trace_integrations.assert_called_once_with(['requests'])
57+
58+
@mock.patch('opencensus.ext.azure.extension.azure_functions'
59+
'.AzureExporter')
60+
def test_configure_method_should_setup_azure_exporter(
61+
self,
62+
azure_exporter_mock
63+
):
64+
self._instance.configure(['requests'])
65+
azure_exporter_mock.assert_called_with(connection_string=None)
66+
67+
@mock.patch('opencensus.ext.azure.extension.azure_functions'
68+
'.AzureExporter')
69+
def test_configure_method_shouold_setup_azure_exporter_with_connstring(
70+
self,
71+
azure_exporter_mock
72+
):
73+
self._instance.configure(['request'], MOCK_AZURE_EXPORTER_CONNSTRING)
74+
azure_exporter_mock.assert_called_with(
75+
connection_string=MOCK_AZURE_EXPORTER_CONNSTRING
76+
)
77+
78+
def test_pre_invocation_should_warn_if_not_configured(self):
79+
mock_context = MockContext()
80+
mock_logger = mock.Mock()
81+
self._instance.pre_invocation_app_level(mock_logger, mock_context)
82+
mock_logger.warning.assert_called_once()
83+
84+
def test_pre_invocation_should_attach_tracer_to_context(self):
85+
# Attach a mock object to exporter
86+
self._instance._exporter = mock.Mock()
87+
88+
# Check if the tracer is attached to mock_context
89+
mock_context = MockContext()
90+
mock_logger = mock.Mock()
91+
self._instance.pre_invocation_app_level(mock_logger, mock_context)
92+
self.assertTrue(hasattr(mock_context, 'tracer'))
93+
94+
def test_post_invocation_should_ignore_tracer_deallocation_if_not_set(self):
95+
mock_context = MockContext()
96+
mock_logger = mock.Mock()
97+
mock_func_args = {}
98+
mock_func_ret = None
99+
self._instance.post_invocation_app_level(
100+
mock_logger, mock_context, mock_func_args, mock_func_ret
101+
)
102+
103+
def test_post_invocation_should_delete_tracer_from_context(self):
104+
mock_context = MockContext()
105+
mock_tracer = mock.Mock()
106+
setattr(mock_context, 'tracer', mock_tracer)
107+
mock_logger = mock.Mock()
108+
mock_func_args = {}
109+
mock_func_ret = None
110+
self._instance.post_invocation_app_level(
111+
mock_logger, mock_context, mock_func_args, mock_func_ret
112+
)
113+
self.assertFalse(hasattr(mock_context, 'tracer'))

0 commit comments

Comments
 (0)