diff --git a/contrib/opencensus-ext-azure/CHANGELOG.md b/contrib/opencensus-ext-azure/CHANGELOG.md index 53175aa45..1a5688f47 100644 --- a/contrib/opencensus-ext-azure/CHANGELOG.md +++ b/contrib/opencensus-ext-azure/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## Unreleased +- Implement connection strings + ([#767](https://github.com/census-instrumentation/opencensus-python/pull/767)) - Standard metrics incoming requests per second ([#758](https://github.com/census-instrumentation/opencensus-python/pull/758)) - Standard metrics incoming requests average execution rate diff --git a/contrib/opencensus-ext-azure/README.rst b/contrib/opencensus-ext-azure/README.rst index cdc5c955c..1d844e153 100644 --- a/contrib/opencensus-ext-azure/README.rst +++ b/contrib/opencensus-ext-azure/README.rst @@ -24,8 +24,8 @@ The **Azure Monitor Log Handler** allows you to export Python logs to `Azure Mon This example shows how to send a warning level log to Azure Monitor. * Create an Azure Monitor resource and get the instrumentation key, more information can be found `here `_. -* Put the instrumentation key in ``APPINSIGHTS_INSTRUMENTATIONKEY`` environment variable. -* You can also specify the instrumentation key explicitly in the code, which will take priority over a set environment variable. +* Place your instrumentation key in a `connection string` and directly into your code. +* Alternatively, you can specify your `connection string` in an environment variable ``APPLICATIONINSIGHTS_CONNECTION_STRING``. .. code:: python @@ -34,15 +34,16 @@ This example shows how to send a warning level log to Azure Monitor. from opencensus.ext.azure.log_exporter import AzureLogHandler logger = logging.getLogger(__name__) - logger.addHandler(AzureLogHandler()) + logger.addHandler(AzureLogHandler(connection_string='InstrumentationKey=')) logger.warning('Hello, World!') +* Alternatively, you can specify your `connection string` in an environment variable ``APPLICATIONINSIGHTS_CONNECTION_STRING``. + You can enrich the logs with trace IDs and span IDs by using the `logging integration <../opencensus-ext-logging>`_. * Create an Azure Monitor resource and get the instrumentation key, more information can be found `here `_. -* Install the `logging integration package <../opencensus-ext-logging>`_ using ``pip install opencensus-ext-logging``. -* Put the instrumentation key in ``APPINSIGHTS_INSTRUMENTATIONKEY`` environment variable. -* You can also specify the instrumentation key explicitly in the code, which will take priority over a set environment variable. +* Place your instrumentation key in a `connection string` and directly into your code. +* Alternatively, you can specify your `connection string` in an environment variable ``APPLICATIONINSIGHTS_CONNECTION_STRING``. .. code:: python @@ -58,16 +59,19 @@ You can enrich the logs with trace IDs and span IDs by using the `logging integr logger = logging.getLogger(__name__) - handler = AzureLogHandler() + handler = AzureLogHandler(connection_string='InstrumentationKey=') handler.setFormatter(logging.Formatter('%(traceId)s %(spanId)s %(message)s')) logger.addHandler(handler) - tracer = Tracer(exporter=AzureExporter(), sampler=ProbabilitySampler(1.0)) + tracer = Tracer( + exporter=AzureExporter(connection_string='InstrumentationKey='), + sampler=ProbabilitySampler(1.0) + ) logger.warning('Before the span') with tracer.span(name='test'): logger.warning('In the span') - logger.warning('After the span') + logger.warning('After the span')s Metrics ~~~~~~~ @@ -75,8 +79,8 @@ Metrics The **Azure Monitor Metrics Exporter** allows you to export metrics to `Azure Monitor`_. * Create an Azure Monitor resource and get the instrumentation key, more information can be found `here `_. -* Put the instrumentation key in ``APPINSIGHTS_INSTRUMENTATIONKEY`` environment variable. -* You can also specify the instrumentation key explicitly in the code, which will take priority over a set environment variable. +* Place your instrumentation key in a `connection string` and directly into your code. +* Alternatively, you can specify your `connection string` in an environment variable ``APPLICATIONINSIGHTS_CONNECTION_STRING``. .. code:: python @@ -105,7 +109,7 @@ The **Azure Monitor Metrics Exporter** allows you to export metrics to `Azure Mo def main(): # Enable metrics # Set the interval in seconds in which you want to send metrics - exporter = metrics_exporter.new_metrics_exporter() + exporter = metrics_exporter.new_metrics_exporter(connection_string='InstrumentationKey=') view_manager.register_exporter(exporter) view_manager.register_view(CARROTS_VIEW) @@ -138,7 +142,7 @@ The exporter also includes a set of standard metrics that are exported to Azure # All you need is the next line. You can disable standard metrics by # passing in enable_standard_metrics=False into the constructor of # new_metrics_exporter() - _exporter = metrics_exporter.new_metrics_exporter() + _exporter = metrics_exporter.new_metrics_exporter(connection_string='InstrumentationKey=') for i in range(100): print(psutil.virtual_memory()) @@ -167,26 +171,31 @@ The **Azure Monitor Trace Exporter** allows you to export `OpenCensus`_ traces t This example shows how to send a span "hello" to Azure Monitor. * Create an Azure Monitor resource and get the instrumentation key, more information can be found `here `_. -* Put the instrumentation key in ``APPINSIGHTS_INSTRUMENTATIONKEY`` environment variable. -* You can also specify the instrumentation key explicitly in the code, which will take priority over a set environment variable. +* Place your instrumentation key in a `connection string` and directly into your code. +* Alternatively, you can specify your `connection string` in an environment variable ``APPLICATIONINSIGHTS_CONNECTION_STRING``. -.. code:: python + .. code:: python from opencensus.ext.azure.trace_exporter import AzureExporter from opencensus.trace.samplers import ProbabilitySampler from opencensus.trace.tracer import Tracer - tracer = Tracer(exporter=AzureExporter(), sampler=ProbabilitySampler(1.0)) + tracer = Tracer( + exporter=AzureExporter(connection_string='InstrumentationKey='), + sampler=ProbabilitySampler(1.0) + ) with tracer.span(name='hello'): print('Hello, World!') -You can also specify the instrumentation key explicitly in the code. +OpenCensus also supports several [integrations](https://github.com/census-instrumentation/opencensus-python#integration) which allows OpenCensus to integrate with third party libraries. + +This example shows how to integrate with the [requests](https://2.python-requests.org/en/master/) library. * Create an Azure Monitor resource and get the instrumentation key, more information can be found `here `_. * Install the `requests integration package <../opencensus-ext-requests>`_ using ``pip install opencensus-ext-requests``. -* Put the instrumentation key in ``APPINSIGHTS_INSTRUMENTATIONKEY`` environment variable. -* You can also specify the instrumentation key explicitly in the code, which will take priority over a set environment variable. +* Place your instrumentation key in a `connection string` and directly into your code. +* Alternatively, you can specify your `connection string` in an environment variable ``APPLICATIONINSIGHTS_CONNECTION_STRING``. .. code:: python diff --git a/contrib/opencensus-ext-azure/examples/logs/correlated.py b/contrib/opencensus-ext-azure/examples/logs/correlated.py index 205a46bc1..69445e997 100644 --- a/contrib/opencensus-ext-azure/examples/logs/correlated.py +++ b/contrib/opencensus-ext-azure/examples/logs/correlated.py @@ -24,8 +24,9 @@ logger = logging.getLogger(__name__) -# TODO: you need to specify the instrumentation key in the -# APPINSIGHTS_INSTRUMENTATIONKEY environment variable. +# TODO: you need to specify the instrumentation key in a connection string +# and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING +# environment variable. handler = AzureLogHandler() logger.addHandler(handler) diff --git a/contrib/opencensus-ext-azure/examples/logs/error.py b/contrib/opencensus-ext-azure/examples/logs/error.py index 4f801342a..772861cf1 100644 --- a/contrib/opencensus-ext-azure/examples/logs/error.py +++ b/contrib/opencensus-ext-azure/examples/logs/error.py @@ -17,8 +17,9 @@ from opencensus.ext.azure.log_exporter import AzureLogHandler logger = logging.getLogger(__name__) -# TODO: you need to specify the instrumentation key in the -# APPINSIGHTS_INSTRUMENTATIONKEY environment variable. +# TODO: you need to specify the instrumentation key in a connection string +# and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING +# environment variable. logger.addHandler(AzureLogHandler()) diff --git a/contrib/opencensus-ext-azure/examples/logs/simple.py b/contrib/opencensus-ext-azure/examples/logs/simple.py index cdda2b688..1fba3d668 100644 --- a/contrib/opencensus-ext-azure/examples/logs/simple.py +++ b/contrib/opencensus-ext-azure/examples/logs/simple.py @@ -17,7 +17,8 @@ from opencensus.ext.azure.log_exporter import AzureLogHandler logger = logging.getLogger(__name__) -# TODO: you need to specify the instrumentation key in the -# APPINSIGHTS_INSTRUMENTATIONKEY environment variable. +# TODO: you need to specify the instrumentation key in a connection string +# and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING +# environment variable. logger.addHandler(AzureLogHandler()) logger.warning('Hello, World!') diff --git a/contrib/opencensus-ext-azure/examples/metrics/simple.py b/contrib/opencensus-ext-azure/examples/metrics/simple.py index 2409f21ad..67115f6bd 100644 --- a/contrib/opencensus-ext-azure/examples/metrics/simple.py +++ b/contrib/opencensus-ext-azure/examples/metrics/simple.py @@ -38,6 +38,9 @@ def main(): # Enable metrics # Set the interval in seconds in which you want to send metrics + # TODO: you need to specify the instrumentation key in a connection string + # and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING + # environment variable. exporter = metrics_exporter.new_metrics_exporter() view_manager.register_exporter(exporter) diff --git a/contrib/opencensus-ext-azure/examples/metrics/standard.py b/contrib/opencensus-ext-azure/examples/metrics/standard.py index 7951c8aa9..3c4627765 100644 --- a/contrib/opencensus-ext-azure/examples/metrics/standard.py +++ b/contrib/opencensus-ext-azure/examples/metrics/standard.py @@ -19,6 +19,9 @@ def main(): + # TODO: you need to specify the instrumentation key in a connection string + # and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING + # environment variable. # All you need is the next line. You can disable standard metrics by # passing in enable_standard_metrics=False into the constructor of # new_metrics_exporter() diff --git a/contrib/opencensus-ext-azure/examples/metrics/sum.py b/contrib/opencensus-ext-azure/examples/metrics/sum.py index e92a5e406..355c72954 100644 --- a/contrib/opencensus-ext-azure/examples/metrics/sum.py +++ b/contrib/opencensus-ext-azure/examples/metrics/sum.py @@ -38,6 +38,9 @@ def main(): # Enable metrics # Set the interval in seconds in which you want to send metrics + # TODO: you need to specify the instrumentation key in a connection string + # and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING + # environment variable. exporter = metrics_exporter.new_metrics_exporter() view_manager.register_exporter(exporter) diff --git a/contrib/opencensus-ext-azure/examples/traces/client.py b/contrib/opencensus-ext-azure/examples/traces/client.py index eb8480089..004c79ab2 100644 --- a/contrib/opencensus-ext-azure/examples/traces/client.py +++ b/contrib/opencensus-ext-azure/examples/traces/client.py @@ -20,8 +20,9 @@ from opencensus.trace.tracer import Tracer config_integration.trace_integrations(['requests']) -# TODO: you need to specify the instrumentation key in the -# APPINSIGHTS_INSTRUMENTATIONKEY environment variable. +# TODO: you need to specify the instrumentation key in a connection string +# and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING +# environment variable. tracer = Tracer(exporter=AzureExporter(), sampler=ProbabilitySampler(1.0)) with tracer.span(name='parent'): with tracer.span(name='child'): diff --git a/contrib/opencensus-ext-azure/examples/traces/config.py b/contrib/opencensus-ext-azure/examples/traces/config.py index 9c4c7fd0f..c5a9e025a 100644 --- a/contrib/opencensus-ext-azure/examples/traces/config.py +++ b/contrib/opencensus-ext-azure/examples/traces/config.py @@ -19,7 +19,8 @@ tracer = Tracer( exporter=AzureExporter( # TODO: replace the all-zero GUID with your instrumentation key. - instrumentation_key='00000000-0000-0000-0000-000000000000', + connection_string='InstrumentationKey= \ + 00000000-0000-0000-0000-000000000000', ), sampler=ProbabilitySampler(rate=1.0), ) diff --git a/contrib/opencensus-ext-azure/examples/traces/custom.py b/contrib/opencensus-ext-azure/examples/traces/custom.py index edcb187c5..5b23b10a7 100644 --- a/contrib/opencensus-ext-azure/examples/traces/custom.py +++ b/contrib/opencensus-ext-azure/examples/traces/custom.py @@ -21,7 +21,8 @@ 'TRACE': { 'SAMPLER': 'opencensus.trace.samplers.ProbabilitySampler(rate=1.0)', 'EXPORTER': '''opencensus.ext.azure.trace_exporter.AzureExporter( - instrumentation_key='00000000-0000-0000-0000-000000000000', + connection_string= + 'InstrumentationKey=00000000-0000-0000-0000-000000000000', )''', }, } diff --git a/contrib/opencensus-ext-azure/examples/traces/server.py b/contrib/opencensus-ext-azure/examples/traces/server.py index a76e5d3a2..3702e7ef3 100644 --- a/contrib/opencensus-ext-azure/examples/traces/server.py +++ b/contrib/opencensus-ext-azure/examples/traces/server.py @@ -20,8 +20,9 @@ from opencensus.trace import config_integration from opencensus.trace.samplers import ProbabilitySampler -# TODO: you need to specify the instrumentation key in the -# APPINSIGHTS_INSTRUMENTATIONKEY environment variable. +# TODO: you need to specify the instrumentation key in a connection string +# and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING +# environment variable. app = Flask(__name__) middleware = FlaskMiddleware( app, diff --git a/contrib/opencensus-ext-azure/examples/traces/simple.py b/contrib/opencensus-ext-azure/examples/traces/simple.py index 49c69d6e1..b0008f464 100644 --- a/contrib/opencensus-ext-azure/examples/traces/simple.py +++ b/contrib/opencensus-ext-azure/examples/traces/simple.py @@ -16,8 +16,9 @@ from opencensus.trace.samplers import ProbabilitySampler from opencensus.trace.tracer import Tracer -# TODO: you need to specify the instrumentation key in the -# APPINSIGHTS_INSTRUMENTATIONKEY environment variable. +# TODO: you need to specify the instrumentation key in a connection string +# and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING +# environment variable. tracer = Tracer(exporter=AzureExporter(), sampler=ProbabilitySampler(1.0)) with tracer.span(name='foo'): diff --git a/contrib/opencensus-ext-azure/opencensus/ext/azure/common/__init__.py b/contrib/opencensus-ext-azure/opencensus/ext/azure/common/__init__.py index 15687db17..754e2a27c 100644 --- a/contrib/opencensus-ext-azure/opencensus/ext/azure/common/__init__.py +++ b/contrib/opencensus-ext-azure/opencensus/ext/azure/common/__init__.py @@ -17,14 +17,81 @@ from opencensus.ext.azure.common.protocol import BaseObject +INGESTION_ENDPOINT = 'ingestionendpoint' +INSTRUMENTATION_KEY = 'instrumentationkey' + + +def process_options(options): + code_cs = parse_connection_string(options.connection_string) + code_ikey = options.instrumentation_key + env_cs = parse_connection_string( + os.getenv('APPLICATIONINSIGHTS_CONNECTION_STRING')) + env_ikey = os.getenv('APPINSIGHTS_INSTRUMENTATIONKEY') + + # The priority of which value takes on the instrumentation key is: + # 1. Key from explicitly passed in connection string + # 2. Key from explicitly passed in instrumentation key + # 3. Key from connection string in environment variable + # 4. Key from instrumentation key in environment variable + options.instrumentation_key = code_cs.get(INSTRUMENTATION_KEY) \ + or code_ikey \ + or env_cs.get(INSTRUMENTATION_KEY) \ + or env_ikey + # The priority of the ingestion endpoint is as follows: + # 1. The endpoint explicitly passed in connection string + # 2. The endpoint from the connection string in environment variable + # 3. The default breeze endpoint + endpoint = code_cs.get(INGESTION_ENDPOINT) \ + or env_cs.get(INGESTION_ENDPOINT) \ + or 'https://dc.services.visualstudio.com' + options.endpoint = endpoint + '/v2/track' + + +def parse_connection_string(connection_string): + if connection_string is None: + return {} + try: + pairs = connection_string.split(';') + result = dict(s.split('=') for s in pairs) + # Convert keys to lower-case due to case type-insensitive checking + result = {key.lower(): value for key, value in result.items()} + except Exception: + raise ValueError('Invalid connection string') + # Validate authorization + auth = result.get('authorization') + if auth is not None and auth.lower() != 'ikey': + raise ValueError('Invalid authorization mechanism') + # Construct the ingestion endpoint if not passed in explicitly + if result.get(INGESTION_ENDPOINT) is None: + endpoint_suffix = '' + location_prefix = '' + suffix = result.get('endpointsuffix') + if suffix is not None: + endpoint_suffix = suffix + # Get regional information if provided + prefix = result.get('location') + if prefix is not None: + location_prefix = prefix + '.' + endpoint = 'https://' + location_prefix + 'dc.' + endpoint_suffix + result[INGESTION_ENDPOINT] = endpoint + else: + # Default to None if cannot construct + result[INGESTION_ENDPOINT] = None + return result + class Options(BaseObject): + def __init__(self, *args, **kwargs): + super(Options, self).__init__(*args, **kwargs) + process_options(self) + _default = BaseObject( + connection_string=None, enable_standard_metrics=True, endpoint='https://dc.services.visualstudio.com/v2/track', export_interval=15.0, grace_period=5.0, - instrumentation_key=os.getenv('APPINSIGHTS_INSTRUMENTATIONKEY', None), + instrumentation_key=None, max_batch_size=100, minimum_retry_interval=60, # minimum retry interval in seconds proxy=None, diff --git a/contrib/opencensus-ext-azure/tests/test_options.py b/contrib/opencensus-ext-azure/tests/test_options.py new file mode 100644 index 000000000..5c16d9c6c --- /dev/null +++ b/contrib/opencensus-ext-azure/tests/test_options.py @@ -0,0 +1,138 @@ +# Copyright 2019, OpenCensus Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +from opencensus.ext.azure import common + + +class TestOptions(unittest.TestCase): + def setUp(self): + os.environ.clear() + + def test_process_options_ikey_code_cs(self): + options = common.Options() + options.connection_string = 'Authorization=ikey;InstrumentationKey=123' + options.instrumentation_key = '456' + os.environ['APPLICATIONINSIGHTS_CONNECTION_STRING'] = \ + 'Authorization=ikey;InstrumentationKey=789' + os.environ['APPINSIGHTS_INSTRUMENTATIONKEY'] = '101112' + common.process_options(options) + + self.assertEqual(options.instrumentation_key, '123') + + def test_process_options_ikey_code_ikey(self): + options = common.Options() + options.connection_string = None + options.instrumentation_key = '456' + os.environ['APPLICATIONINSIGHTS_CONNECTION_STRING'] = \ + 'Authorization=ikey;InstrumentationKey=789' + os.environ['APPINSIGHTS_INSTRUMENTATIONKEY'] = '101112' + common.process_options(options) + + self.assertEqual(options.instrumentation_key, '456') + + def test_process_options_ikey_env_cs(self): + options = common.Options() + options.connection_string = None + options.instrumentation_key = None + os.environ['APPLICATIONINSIGHTS_CONNECTION_STRING'] = \ + 'Authorization=ikey;InstrumentationKey=789' + os.environ['APPINSIGHTS_INSTRUMENTATIONKEY'] = '101112' + common.process_options(options) + + self.assertEqual(options.instrumentation_key, '789') + + def test_process_options_ikey_env_ikey(self): + options = common.Options() + options.connection_string = None + options.instrumentation_key = None + os.environ['APPINSIGHTS_INSTRUMENTATIONKEY'] = '101112' + common.process_options(options) + + self.assertEqual(options.instrumentation_key, '101112') + + def test_process_options_endpoint_code_cs(self): + options = common.Options() + options.connection_string = 'Authorization=ikey;IngestionEndpoint=123' + os.environ['APPLICATIONINSIGHTS_CONNECTION_STRING'] = \ + 'Authorization=ikey;IngestionEndpoint=456' + common.process_options(options) + + self.assertEqual(options.endpoint, '123/v2/track') + + def test_process_options_endpoint_env_cs(self): + options = common.Options() + options.connection_string = None + os.environ['APPLICATIONINSIGHTS_CONNECTION_STRING'] = \ + 'Authorization=ikey;IngestionEndpoint=456' + common.process_options(options) + + self.assertEqual(options.endpoint, '456/v2/track') + + def test_process_options_endpoint_default(self): + options = common.Options() + options.connection_string = None + common.process_options(options) + + self.assertEqual(options.endpoint, + 'https://dc.services.visualstudio.com/v2/track') + + def test_parse_connection_string_none(self): + cs = None + result = common.parse_connection_string(cs) + + self.assertEqual(result, {}) + + def test_parse_connection_string_invalid(self): + cs = 'asd' + self.assertRaises(ValueError, + lambda: common.parse_connection_string(cs)) + + def test_parse_connection_string_default_auth(self): + cs = 'InstrumentationKey=123' + result = common.parse_connection_string(cs) + self.assertEqual(result['instrumentationkey'], '123') + + def test_parse_connection_string_invalid_auth(self): + cs = 'Authorization=asd' + self.assertRaises(ValueError, + lambda: common.parse_connection_string(cs)) + + def test_parse_connection_string_explicit_endpoint(self): + cs = 'Authorization=ikey;IngestionEndpoint=123;' \ + 'Location=us;EndpointSuffix=suffix' + result = common.parse_connection_string(cs) + + self.assertEqual(result['ingestionendpoint'], '123') + + def test_parse_connection_string_default(self): + cs = 'Authorization=ikey;Location=us' + result = common.parse_connection_string(cs) + + self.assertEqual(result['ingestionendpoint'], + None) + + def test_parse_connection_string_no_location(self): + cs = 'Authorization=ikey;EndpointSuffix=suffix' + result = common.parse_connection_string(cs) + + self.assertEqual(result['ingestionendpoint'], 'https://dc.suffix') + + def test_parse_connection_string_location(self): + cs = 'Authorization=ikey;EndpointSuffix=suffix;Location=us' + result = common.parse_connection_string(cs) + + self.assertEqual(result['ingestionendpoint'], 'https://us.dc.suffix')