From 78af228058c582e21ad227393d328c9ee346be00 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Wed, 1 Feb 2023 11:59:27 -0800 Subject: [PATCH 1/6] test --- .../tests/configuration/test_util.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py b/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py index eb8a8308..71486d29 100644 --- a/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py +++ b/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py @@ -23,6 +23,7 @@ def test_get_configurations(self): connection_string="test_cs", disable_logging="test_disable_logging", disable_tracing="test_disable_tracing", + instrumentations=["test_instrumentation"], logging_level="test_logging_level", logger_name="test_logger_name", service_name="test_service_name", @@ -31,6 +32,7 @@ def test_get_configurations(self): sampling_ratio="test_sample_ratio", tracing_export_interval="test_tracing_interval", logging_export_interval="test_logging_interval", + views=("test_view"), ) self.assertEqual(configurations["connection_string"], "test_cs") @@ -40,6 +42,7 @@ def test_get_configurations(self): self.assertEqual( configurations["disable_tracing"], "test_disable_tracing" ) + self.assertEqual(configurations["instrumentations"], ["test_instrumentation"]) self.assertEqual(configurations["logging_level"], "test_logging_level") self.assertEqual(configurations["logger_name"], "test_logger_name") self.assertEqual(configurations["service_name"], "test_service_name") @@ -52,3 +55,4 @@ def test_get_configurations(self): self.assertEqual( configurations["logging_export_interval"], "test_logging_interval" ) + self.assertEqual(configurations["views"], ("test_view")) From 9ff6bc891e60d07c58eb8edf81be1fdb2bddb813 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 2 Feb 2023 09:35:51 -0800 Subject: [PATCH 2/6] instr --- azure-monitor-opentelemetry-distro/README.md | 34 +++++++++++++++++-- .../monitor/opentelemetry/distro/__init__.py | 13 ++++++- .../samples/tracing/client.py | 3 ++ .../tracing/django/sample/example/views.py | 2 +- .../samples/tracing/server_flask.py | 7 ++++ 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/azure-monitor-opentelemetry-distro/README.md b/azure-monitor-opentelemetry-distro/README.md index 7abc6231..a568cb93 100644 --- a/azure-monitor-opentelemetry-distro/README.md +++ b/azure-monitor-opentelemetry-distro/README.md @@ -39,7 +39,7 @@ pip install azure-monitor-opentelemetry-distro --pre ### Usage -You can use `configure_azure_monitor` to set up instrumentation for your app to Azure Monitor. `configure_azure_monitor` supports the following optional arguments: +You can use `configure_azure_monitor` to set up instrumentation for your app to Azure Monitor. `configure_azure_monitor()` supports the following optional arguments: * connection_string - The [connection string][connection_string_doc] for your Application Insights resource. The connection string will be automatically populated from the `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable if not explicitly passed in. * instrumentations - Specifies the libraries with [instrumentations][ot_instrumentations] that you would like to use. Accepts a comma separated list. e.g. `["requests", "flask"]` @@ -56,9 +56,37 @@ You can use `configure_azure_monitor` to set up instrumentation for your app to * sampling_ratio - Specifies the ratio of distributed tracing telemetry to be [sampled][application_insights_sampling]. Accepted values are in the range [0,1]. Defaults to 1.0, meaning no telemetry is sampled out. * tracing_export_interval_millis - Specifies the distributed tracing export interval in milliseconds. Defaults to 30,000. -See additional [configuration related to exporting here][exporter_configuration_docs]. +#### Exporter configurations -### Example code +You can pass exporter configuration parameters directly into `configure_azure_monitor()`. See additional [configuration related to exporting here][exporter_configuration_docs]. + +```python +... +configure_azure_monitor( + connection_string="", + disable_offline_storage=True, +) +... +``` + +#### Instrumentation configurations + +You can pass in instrumentation specific configuration into `configure_azure_monitor()` with the key `_config` and value as a dictionary representing `kwargs` for the corresponding instrumentation. Note the instrumented library must also be enabled through the `instrumentations` configuration. + +```python +... +configure_azure_monitor( + connection_string="", + instrumentations=["flask", "requests"], + flask_config={"excluded_urls": "http://localhost:8080/ignore"}, + requests_config={"excluded_urls": "http://example.com"}, +) +... +``` + +Take a look at the specific [instrumenation][ot_instrumentations] documentation for available configurations. + +### Samples Samples are available [here][samples] to demonstrate how to utilize the above configuration options. diff --git a/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py b/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py index 2bc29d82..f18c1396 100644 --- a/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py +++ b/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py @@ -33,6 +33,7 @@ _logger = getLogger(__name__) +_INSTRUMENTATION_CONFIG_SUFFIX = "_config" _SUPPORTED_INSTRUMENTED_LIBRARIES = { "django", "flask", @@ -141,6 +142,15 @@ def _setup_metrics(resource: Resource, configurations: Dict[str, Any]): def _setup_instrumentations(configurations: Dict[str, Any]): instrumentations = configurations.get("instrumentations", []) + instrumentation_configs = {} + + # Instrumentation specific configs + # Format is {"": {"":}} + for k, v in configurations.items(): + if k.endswith(_INSTRUMENTATION_CONFIG_SUFFIX): + lib_name = k.partition(_INSTRUMENTATION_CONFIG_SUFFIX)[0] + instrumentation_configs[lib_name] = v + for lib_name in instrumentations: if lib_name in _SUPPORTED_INSTRUMENTED_LIBRARIES: try: @@ -158,7 +168,8 @@ def _setup_instrumentations(configurations: Dict[str, Any]): lib_name.capitalize() ) class_ = getattr(module, instrumentor_name) - class_().instrument() + config = instrumentation_configs.get(lib_name, {}) + class_().instrument(**config) except ImportError: _logger.warning( "Unable to import %s. Please make sure it is installed.", diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/client.py b/azure-monitor-opentelemetry-distro/samples/tracing/client.py index fd56a788..0a7a97a8 100644 --- a/azure-monitor-opentelemetry-distro/samples/tracing/client.py +++ b/azure-monitor-opentelemetry-distro/samples/tracing/client.py @@ -18,6 +18,7 @@ disable_logging=True, disable_metrics=True, instrumentations=["requests"], + requests_config={"excluded_urls": "http://example.com"}, tracing_export_interval_millis=15000, ) @@ -26,6 +27,8 @@ try: # Requests made using the requests library will be automatically captured response = requests.get("https://azure.microsoft.com/", timeout=5) + # This request will not be tracked due to the excluded_urls configuration + response = requests.get("http://example.com", timeout=5) logger.warning("Request sent") except Exception as ex: # If an exception occurs, this can be manually recorded on the parent span diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py index 618e3f23..876f17c0 100644 --- a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py @@ -9,7 +9,7 @@ # Configure Azure monitor collection telemetry pipeline configure_azure_monitor( - # connection_string="", + connection_string="", service_name="django_service_name", instrumentations=["django"], disable_logging=True, diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py index 6fef59b9..ae3c40ee 100644 --- a/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py +++ b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py @@ -13,6 +13,7 @@ disable_logging=True, disable_metrics=True, instrumentations=["flask"], + flask_config={"excluded_urls": "http://localhost:8080/ignore"}, tracing_export_interval_millis=15000, ) @@ -30,5 +31,11 @@ def exception(): raise Exception("Hit an exception") +# Requests sent to this endpoint will not be tracked due to +# flask_config configuration +@app.route("/ignore") +def ignore(): + return "Request received but not tracked." + if __name__ == "__main__": app.run(host="localhost", port=8080) From 302d48667f6a73e8474e873d95ed0d09d414d512 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 2 Feb 2023 09:49:42 -0800 Subject: [PATCH 3/6] test --- .../monitor/opentelemetry/distro/__init__.py | 4 +- .../tests/configuration/test_configure.py | 70 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py b/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py index f18c1396..118b8c0e 100644 --- a/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py +++ b/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py @@ -34,12 +34,12 @@ _INSTRUMENTATION_CONFIG_SUFFIX = "_config" -_SUPPORTED_INSTRUMENTED_LIBRARIES = { +_SUPPORTED_INSTRUMENTED_LIBRARIES = ( "django", "flask", "psycopg2", "requests", -} +) def configure_azure_monitor(**kwargs): diff --git a/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py b/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py index da7cceec..fbb936c4 100644 --- a/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py +++ b/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py @@ -505,3 +505,73 @@ def test_setup_instrumentations_failed_general( ) instrumentor_mock.assert_not_called() instrument_mock.instrument.assert_not_called() + + @patch("azure.monitor.opentelemetry.distro.getattr") + def test_setup_instrumentations_custom_configuration( + self, + getattr_mock, + ): + for lib_name in _SUPPORTED_INSTRUMENTED_LIBRARIES: + with patch("importlib.import_module") as import_module_mock: + configurations = { + "instrumentations": [lib_name], + lib_name + "_config": {"test_key": "test_value"} + } + instrument_mock = Mock() + instrumentor_mock = Mock() + instrumentor_mock.return_value = instrument_mock + getattr_mock.return_value = instrumentor_mock + _setup_instrumentations(configurations) + self.assertEqual(import_module_mock.call_count, 2) + instr_lib_name = "opentelemetry.instrumentation." + lib_name + import_module_mock.assert_has_calls( + [call(lib_name), call(instr_lib_name)] + ) + instrumentor_mock.assert_called_once() + instrument_mock.instrument.assert_called_once_with( + **{"test_key": "test_value"} + ) + + @patch("azure.monitor.opentelemetry.distro.getattr") + def test_setup_instrumentations_custom_configuration_not_enabled( + self, + getattr_mock, + ): + with patch("importlib.import_module") as import_module_mock: + lib_name = list(_SUPPORTED_INSTRUMENTED_LIBRARIES)[0] + configurations = { + "instrumentations": [], + lib_name + "_config": {"test_key": "test_value"} + } + instrument_mock = Mock() + instrumentor_mock = Mock() + instrumentor_mock.return_value = instrument_mock + getattr_mock.return_value = instrumentor_mock + _setup_instrumentations(configurations) + import_module_mock.assert_not_called() + instrumentor_mock.assert_not_called() + instrument_mock.instrument.assert_not_called() + + @patch("azure.monitor.opentelemetry.distro.getattr") + def test_setup_instrumentations_custom_configuration_incorrect( + self, + getattr_mock, + ): + with patch("importlib.import_module") as import_module_mock: + lib_name = list(_SUPPORTED_INSTRUMENTED_LIBRARIES)[0] + configurations = { + "instrumentations": [lib_name], + lib_name + "error_config": {"test_key": "test_value"} + } + instrument_mock = Mock() + instrumentor_mock = Mock() + instrumentor_mock.return_value = instrument_mock + getattr_mock.return_value = instrumentor_mock + _setup_instrumentations(configurations) + self.assertEqual(import_module_mock.call_count, 2) + instr_lib_name = "opentelemetry.instrumentation." + lib_name + import_module_mock.assert_has_calls( + [call(lib_name), call(instr_lib_name)] + ) + instrumentor_mock.assert_called_once() + instrument_mock.instrument.assert_called_once_with(**{}) From 38484ddd2f5f85133efe4b9bda72b9b9ef1fbd71 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 2 Feb 2023 09:50:43 -0800 Subject: [PATCH 4/6] lint --- .../samples/tracing/server_flask.py | 1 + .../tests/configuration/test_configure.py | 6 +++--- .../tests/configuration/test_util.py | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py index ae3c40ee..dd9f54d3 100644 --- a/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py +++ b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py @@ -37,5 +37,6 @@ def exception(): def ignore(): return "Request received but not tracked." + if __name__ == "__main__": app.run(host="localhost", port=8080) diff --git a/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py b/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py index fbb936c4..786aa8fc 100644 --- a/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py +++ b/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py @@ -515,7 +515,7 @@ def test_setup_instrumentations_custom_configuration( with patch("importlib.import_module") as import_module_mock: configurations = { "instrumentations": [lib_name], - lib_name + "_config": {"test_key": "test_value"} + lib_name + "_config": {"test_key": "test_value"}, } instrument_mock = Mock() instrumentor_mock = Mock() @@ -541,7 +541,7 @@ def test_setup_instrumentations_custom_configuration_not_enabled( lib_name = list(_SUPPORTED_INSTRUMENTED_LIBRARIES)[0] configurations = { "instrumentations": [], - lib_name + "_config": {"test_key": "test_value"} + lib_name + "_config": {"test_key": "test_value"}, } instrument_mock = Mock() instrumentor_mock = Mock() @@ -561,7 +561,7 @@ def test_setup_instrumentations_custom_configuration_incorrect( lib_name = list(_SUPPORTED_INSTRUMENTED_LIBRARIES)[0] configurations = { "instrumentations": [lib_name], - lib_name + "error_config": {"test_key": "test_value"} + lib_name + "error_config": {"test_key": "test_value"}, } instrument_mock = Mock() instrumentor_mock = Mock() diff --git a/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py b/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py index 71486d29..8c70dcf0 100644 --- a/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py +++ b/azure-monitor-opentelemetry-distro/tests/configuration/test_util.py @@ -42,7 +42,9 @@ def test_get_configurations(self): self.assertEqual( configurations["disable_tracing"], "test_disable_tracing" ) - self.assertEqual(configurations["instrumentations"], ["test_instrumentation"]) + self.assertEqual( + configurations["instrumentations"], ["test_instrumentation"] + ) self.assertEqual(configurations["logging_level"], "test_logging_level") self.assertEqual(configurations["logger_name"], "test_logger_name") self.assertEqual(configurations["service_name"], "test_service_name") From a26fc3c94d15207dc2e8da897077cb83a20c6806 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 2 Feb 2023 09:58:33 -0800 Subject: [PATCH 5/6] lint --- CHANGELOG.md | 2 ++ azure-monitor-opentelemetry-distro/README.md | 6 +++--- .../samples/metrics/instruments.py | 5 ++--- .../samples/tracing/django/sample/example/views.py | 1 + .../samples/tracing/server_flask.py | 1 + 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4f9471..64e6af0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ ([#227](https://github.com/microsoft/ApplicationInsights-Python/pull/227)) - Add metric configuration to distro api ([#232](https://github.com/microsoft/ApplicationInsights-Python/pull/232)) +- Add ability to pass custom configuration into instrumentations + ([#235](https://github.com/microsoft/ApplicationInsights-Python/pull/235)) ## [1.0.0b8](https://github.com/microsoft/ApplicationInsights-Python/releases/tag/v1.0.0b8) - 2022-09-26 diff --git a/azure-monitor-opentelemetry-distro/README.md b/azure-monitor-opentelemetry-distro/README.md index a568cb93..8d630f9d 100644 --- a/azure-monitor-opentelemetry-distro/README.md +++ b/azure-monitor-opentelemetry-distro/README.md @@ -39,7 +39,7 @@ pip install azure-monitor-opentelemetry-distro --pre ### Usage -You can use `configure_azure_monitor` to set up instrumentation for your app to Azure Monitor. `configure_azure_monitor()` supports the following optional arguments: +You can use `configure_azure_monitor` to set up instrumentation for your app to Azure Monitor. `configure_azure_monitor` supports the following optional arguments: * connection_string - The [connection string][connection_string_doc] for your Application Insights resource. The connection string will be automatically populated from the `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable if not explicitly passed in. * instrumentations - Specifies the libraries with [instrumentations][ot_instrumentations] that you would like to use. Accepts a comma separated list. e.g. `["requests", "flask"]` @@ -58,7 +58,7 @@ You can use `configure_azure_monitor` to set up instrumentation for your app to #### Exporter configurations -You can pass exporter configuration parameters directly into `configure_azure_monitor()`. See additional [configuration related to exporting here][exporter_configuration_docs]. +You can pass exporter configuration parameters directly into `configure_azure_monitor`. See additional [configuration related to exporting here][exporter_configuration_docs]. ```python ... @@ -71,7 +71,7 @@ configure_azure_monitor( #### Instrumentation configurations -You can pass in instrumentation specific configuration into `configure_azure_monitor()` with the key `_config` and value as a dictionary representing `kwargs` for the corresponding instrumentation. Note the instrumented library must also be enabled through the `instrumentations` configuration. +You can pass in instrumentation specific configuration into `configure_azure_monitor` with the key `_config` and value as a dictionary representing `kwargs` for the corresponding instrumentation. Note the instrumented library must also be enabled through the `instrumentations` configuration. ```python ... diff --git a/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py b/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py index d9b6f1b8..27f7374f 100644 --- a/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py +++ b/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py @@ -14,9 +14,6 @@ disable_tracing=True, ) -# Create a namespaced meter -meter = metrics.get_meter_provider().get_meter("sample") - # Callback functions for observable instruments def observable_counter_func(options: CallbackOptions) -> Iterable[Observation]: yield Observation(1, {}) @@ -31,6 +28,8 @@ def observable_up_down_counter_func( def observable_gauge_func(options: CallbackOptions) -> Iterable[Observation]: yield Observation(9, {}) +# Create a namespaced meter +meter = metrics.get_meter_provider().get_meter("sample") # Counter counter = meter.create_counter("counter") diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py index 876f17c0..cf279665 100644 --- a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py @@ -17,6 +17,7 @@ tracing_export_interval_millis=15000, ) + # Requests sent to the django application will be automatically captured def index(request): return HttpResponse("Hello, world.") diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py index dd9f54d3..c1c09de3 100644 --- a/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py +++ b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py @@ -19,6 +19,7 @@ app = flask.Flask(__name__) + # Requests sent to the flask application will be automatically captured @app.route("/") def test(): From 990be3c616da2d8541aa198e617022fbc0f1b515 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Thu, 2 Feb 2023 10:02:49 -0800 Subject: [PATCH 6/6] Update instruments.py --- .../samples/metrics/instruments.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py b/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py index 27f7374f..33caa6b4 100644 --- a/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py +++ b/azure-monitor-opentelemetry-distro/samples/metrics/instruments.py @@ -14,6 +14,7 @@ disable_tracing=True, ) + # Callback functions for observable instruments def observable_counter_func(options: CallbackOptions) -> Iterable[Observation]: yield Observation(1, {}) @@ -28,6 +29,7 @@ def observable_up_down_counter_func( def observable_gauge_func(options: CallbackOptions) -> Iterable[Observation]: yield Observation(9, {}) + # Create a namespaced meter meter = metrics.get_meter_provider().get_meter("sample")