diff --git a/CHANGELOG.md b/CHANGELOG.md index b40782b1..959aacab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,8 @@ ([#247](https://github.com/microsoft/ApplicationInsights-Python/pull/247)) - Updating documents for new namespace ([#249](https://github.com/microsoft/ApplicationInsights-Python/pull/249)) +- Configuration via env vars and argument validation. + ([#262](https://github.com/microsoft/ApplicationInsights-Python/pull/262)) ## [1.0.0b8](https://github.com/microsoft/ApplicationInsights-Python/releases/tag/v1.0.0b8) - 2022-09-26 diff --git a/azure-monitor-opentelemetry/README.md b/azure-monitor-opentelemetry/README.md index 3e1e3d4d..e8e2c87f 100644 --- a/azure-monitor-opentelemetry/README.md +++ b/azure-monitor-opentelemetry/README.md @@ -11,7 +11,7 @@ This distro automatically installs the following libraries: OpenTelemetry instrumentations allow automatic collection of requests sent from underlying instrumented libraries. The following is a list of OpenTelemetry instrumentations that come bundled in with the Azure monitor distro. If you would like to add support for another OpenTelemetry instrumentation, please submit a feature [request][distro_feature_request]. In the meantime, you can use the OpenTelemetry instrumentation manually via it's own APIs (i.e. `instrument()`) in your code. See [this][samples_manual] for an example. -| Instrumentation | Supported library | Supported versions | +| Instrumentation | Supported library | Supported versions | | ------------------------------------- | ----------------- | ------------------ | | [OpenTelemetry Django Instrumentation][ot_instrumentation_django] | [django][pypi_django] | [link][ot_instrumentation_django_version] | [OpenTelemetry FastApi Instrumentation][ot_instrumentation_fastapi] | [fastapi][pypi_fastapi] | [link][ot_instrumentation_fastapi_version] @@ -52,14 +52,24 @@ pip install azure-monitor-opentelemetry --pre 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. -* disable_logging - If set to `True`, disables collection and export of logging telemetry. Defaults to `False`. -* disable_metrics - If set to `True`, disables collection and export of metric telemetry. Defaults to `False`. -* disable_tracing - If set to `True`, disables collection and export of distributed tracing telemetry. Defaults to `False`. -* exclude_instrumentations - By default, all supported [instrumentations](#officially-supported-instrumentations) are enabled to collect telemetry. Specify instrumentations you do not want to enable to collect telemetry by passing in a comma separated list of instrumented library names. e.g. `["requests", "flask"]` -* instrumentation_config - Specifies a dictionary of kwargs that will be applied to instrumentation configuration. You can specify which instrumentation you want to configure by name in the key field and value as a dictionary representing `kwargs` for the corresponding instrumentation. - Refer to the `Supported Library` section [above](#officially-supported-instrumentations) for the list of suppoprted library names. - +| Parameter | Description | Environment Variable | +|-------------------|----------------------------------------------------|----------------------| +| `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. | `APPLICATIONINSIGHTS_CONNECTION_STRING` | +| `exclude_instrumentations` | By default, all supported [instrumentations](#officially-supported-instrumentations) are enabled to collect telemetry. Specify instrumentations you do not want to enable to collect telemetry by passing in a comma separated list of instrumented library names. e.g. `["requests", "flask"]` | | +| `resource` | Specifies the OpenTelemetry [resource][opentelemetry_spec_resource] associated with your application. See [this][ot_sdk_python_resource] for default behavior. | [OTEL_SERVICE_NAME][opentelemetry_spec_service_name], [OTEL_RESOURCE_ATTRIBUTES][opentelemetry_spec_resource_attributes] | +| `disable_logging` | If set to `True`, disables collection and export of logging telemetry. Defaults to `False`. | | +| `disable_metrics` | If set to `True`, disables collection and export of metric telemetry. Defaults to `False`. | | +| `disable_tracing` | If set to `True`, disables collection and export of distributed tracing telemetry. Defaults to `False`. | | +| `logging_level` | Specifies the [logging level][logging_level] of the logs you would like to collect for your logging pipeline. Defaults to 0 which is `logging.NOTSET`. | | +| `logger_name` | Specifies the [logger name][logger_name_hierarchy_doc] under which logging will be instrumented. Defaults to "" which corresponds to the root logger. | | +| `logging_export_interval_ms`| Specifies the logging export interval in milliseconds. Defaults to 5000. | `OTEL_BLRP_SCHEDULE_DELAY` | +| `metric_readers` | Specifies the [metric readers][ot_metric_reader] that you would like to use for your metric pipeline. Accepts a list of [metric readers][ot_sdk_python_metric_reader]. | | +| `views` | Specifies the list of [views][opentelemetry_spec_view] to configure for the metric pipeline. See [here][ot_sdk_python_view_examples] for example usage. | | +| `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. | `OTEL_TRACES_SAMPLER_ARG` | +| `tracing_export_interval_ms`| Specifies the distributed tracing export interval in milliseconds. Defaults to 5000. | `OTEL_BSP_SCHEDULE_DELAY` | +| `instrumentation_config` | Specifies a dictionary of kwargs that will be applied to instrumentation configuration. You can specify which instrumentation you want to configure by name in the key field and value as a dictionary representing `kwargs` for the corresponding instrumentation. Refer to the `Supported Library` section [above](#officially-supported-instrumentations) for the list of supported library names. | | + +Example for use of `instrumentation_config`: ```python ... configure_azure_monitor( @@ -78,15 +88,6 @@ configure_azure_monitor( Take a look at the specific [instrumenation][ot_instrumentations] documentation for available configurations. -* resource - Specified the OpenTelemetry [resource][opentelemetry_spec_resource] associated with your application. See [this][ot_sdk_python_resource] for default behavior. -* logging_level - Specifies the [logging level][logging_level] of the logs you would like to collect for your logging pipeline. Defaults to logging.NOTSET. -* logger_name = Specifies the [logger name][logger_name_hierarchy_doc] under which logging will be instrumented. Defaults to "" which corresponds to the root logger. -* logging_export_interval_ms - Specifies the logging export interval in milliseconds. Defaults to 5000. -* metric_readers - Specifies the [metric readers][ot_metric_reader] that you would like to use for your metric pipeline. Accepts a list of [metric readers][ot_sdk_python_metric_reader]. -* views - Specifies the list of [views][opentelemetry_spec_view] to configure for the metric pipeline. See [here][ot_sdk_python_view_examples] for example usage. -* 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_ms - Specifies the distributed tracing export interval in milliseconds. Defaults to 5000. - #### Azure monitor OpenTelemetry Exporter configurations You can pass Azure monitor OpenTelemetry exporter configuration parameters directly into `configure_azure_monitor`. See additional [configuration related to exporting here][exporter_configuration_docs]. @@ -143,6 +144,8 @@ Samples are available [here][samples] to demonstrate how to utilize the above co [ot_instrumentation_urllib3]: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-urllib3 [ot_instrumentation_urllib3_version]: https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/package.py#L16 [opentelemetry_spec_resource]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#resource-sdk +[opentelemetry_spec_resource_attributes]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable +[opentelemetry_spec_service_name]: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions#semantic-attributes-with-sdk-provided-default-value [opentelemetry_spec_view]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view [pip]: https://pypi.org/project/pip/ [pypi_django]: https://pypi.org/project/Django/ diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py index dc4c92ec..fed4b639 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_configure.py @@ -3,9 +3,24 @@ # Licensed under the MIT License. See License in the project root for # license information. # -------------------------------------------------------------------------- -from logging import NOTSET, getLogger +from logging import getLogger from typing import Any, Dict +from azure.monitor.opentelemetry._constants import ( + DISABLE_LOGGING_ARG, + DISABLE_METRICS_ARG, + DISABLE_TRACING_ARG, + EXCLUDE_INSTRUMENTATIONS_ARG, + INSTRUMENTATION_CONFIG_ARG, + LOGGER_NAME_ARG, + LOGGING_EXPORT_INTERVAL_MS_ARG, + LOGGING_LEVEL_ARG, + METRIC_READERS_ARG, + RESOURCE_ARG, + SAMPLING_RATIO_ARG, + TRACING_EXPORT_INTERVAL_MS_ARG, + VIEWS_ARG, +) from azure.monitor.opentelemetry._types import ConfigurationValue from azure.monitor.opentelemetry.exporter import ( ApplicationInsightsSampler, @@ -53,7 +68,7 @@ def configure_azure_monitor(**kwargs) -> None: end user to configure OpenTelemetry and Azure monitor components. The configuration can be done via arguments passed to this function. :keyword str connection_string: Connection string for your Application Insights resource. - :keyword Sequence[str] connection_string: Specifies the libraries with instrumentations to be enabled. + :keyword Sequence[str] exclude_instrumentations: Specifies instrumentations you want to disable. :keyword Resource resource: Specified the OpenTelemetry [resource][opentelemetry_spec_resource] associated with your application. :keyword bool disable_logging: If set to `True`, disables collection and export of logging telemetry. Defaults to `False`. :keyword bool disable_metrics: If set to `True`, disables collection and export of metric telemetry. Defaults to `False`. @@ -75,9 +90,9 @@ def configure_azure_monitor(**kwargs) -> None: configurations = _get_configurations(**kwargs) - disable_tracing = configurations.get("disable_tracing", False) - disable_logging = configurations.get("disable_logging", False) - disable_metrics = configurations.get("disable_metrics", False) + disable_tracing = configurations[DISABLE_TRACING_ARG] + disable_logging = configurations[DISABLE_LOGGING_ARG] + disable_metrics = configurations[DISABLE_METRICS_ARG] resource = None if not disable_logging or not disable_tracing or not disable_metrics: @@ -102,16 +117,14 @@ def configure_azure_monitor(**kwargs) -> None: def _get_resource(configurations: Dict[str, ConfigurationValue]) -> Resource: - return configurations.get("resource", Resource.create()) + return configurations.get(RESOURCE_ARG, Resource.create()) def _setup_tracing( resource: Resource, configurations: Dict[str, ConfigurationValue] ): - sampling_ratio = configurations.get("sampling_ratio", 1.0) - tracing_export_interval_ms = configurations.get( - "tracing_export_interval_ms", 5000 - ) + sampling_ratio = configurations[SAMPLING_RATIO_ARG] + tracing_export_interval_ms = configurations[TRACING_EXPORT_INTERVAL_MS_ARG] tracer_provider = TracerProvider( sampler=ApplicationInsightsSampler(sampling_ratio=sampling_ratio), resource=resource, @@ -128,11 +141,9 @@ def _setup_tracing( def _setup_logging( resource: Resource, configurations: Dict[str, ConfigurationValue] ): - logger_name = configurations.get("logger_name", "") - logging_level = configurations.get("logging_level", NOTSET) - logging_export_interval_ms = configurations.get( - "logging_export_interval_ms", 5000 - ) + logger_name = configurations[LOGGER_NAME_ARG] + logging_level = configurations[LOGGING_LEVEL_ARG] + logging_export_interval_ms = configurations[LOGGING_EXPORT_INTERVAL_MS_ARG] logger_provider = LoggerProvider(resource=resource) set_logger_provider(logger_provider) log_exporter = AzureMonitorLogExporter(**configurations) @@ -150,8 +161,8 @@ def _setup_logging( def _setup_metrics( resource: Resource, configurations: Dict[str, ConfigurationValue] ): - views = configurations.get("views", ()) - metric_readers = configurations.get("metric_readers", []) + views = configurations[VIEWS_ARG] + metric_readers = configurations[METRIC_READERS_ARG] metric_exporter = AzureMonitorMetricExporter(**configurations) reader = PeriodicExportingMetricReader(metric_exporter) meter_provider = MeterProvider( @@ -163,10 +174,8 @@ def _setup_metrics( def _setup_instrumentations(configurations: Dict[str, ConfigurationValue]): - exclude_instrumentations = configurations.get( - "exclude_instrumentations", [] - ) - instrumentation_configs = configurations.get("instrumentation_config", {}) + exclude_instrumentations = configurations[EXCLUDE_INSTRUMENTATIONS_ARG] + instrumentation_configs = configurations[INSTRUMENTATION_CONFIG_ARG] # use pkg_resources for now until https://github.com/open-telemetry/opentelemetry-python/pull/3168 is merged for entry_point in iter_entry_points("opentelemetry_instrumentor"): diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py index 51c4f558..2489b772 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py @@ -13,6 +13,25 @@ ConnectionStringParser, ) +# --------------------Configuration------------------------------------------ + +CONNECTION_STRING_ARG = "connection_string" +EXCLUDE_INSTRUMENTATIONS_ARG = "exclude_instrumentations" +RESOURCE_ARG = "resource" +DISABLE_LOGGING_ARG = "disable_logging" +DISABLE_METRICS_ARG = "disable_metrics" +DISABLE_TRACING_ARG = "disable_tracing" +# TODO: Consider Log Level to match env var +LOGGING_LEVEL_ARG = "logging_level" +LOGGER_NAME_ARG = "logger_name" +LOGGING_EXPORT_INTERVAL_MS_ARG = "logging_export_interval_ms" +METRIC_READERS_ARG = "metric_readers" +VIEWS_ARG = "views" +SAMPLING_RATIO_ARG = "sampling_ratio" +TRACING_EXPORT_INTERVAL_MS_ARG = "tracing_export_interval_ms" +INSTRUMENTATION_CONFIG_ARG = "instrumentation_config" + + # --------------------Diagnostic/status logging------------------------------ _LOG_PATH_LINUX = "/var/log/applicationinsights" diff --git a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/util/configurations.py b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/util/configurations.py index 44d2c5d0..1df8c387 100644 --- a/azure-monitor-opentelemetry/azure/monitor/opentelemetry/util/configurations.py +++ b/azure-monitor-opentelemetry/azure/monitor/opentelemetry/util/configurations.py @@ -4,9 +4,37 @@ # license information. # -------------------------------------------------------------------------- +from logging import NOTSET, getLogger +from os import environ from typing import Dict +from azure.monitor.opentelemetry._constants import ( + DISABLE_LOGGING_ARG, + DISABLE_METRICS_ARG, + DISABLE_TRACING_ARG, + EXCLUDE_INSTRUMENTATIONS_ARG, + INSTRUMENTATION_CONFIG_ARG, + LOGGER_NAME_ARG, + LOGGING_EXPORT_INTERVAL_MS_ARG, + LOGGING_LEVEL_ARG, + METRIC_READERS_ARG, + SAMPLING_RATIO_ARG, + TRACING_EXPORT_INTERVAL_MS_ARG, + VIEWS_ARG, +) from azure.monitor.opentelemetry._types import ConfigurationValue +from opentelemetry.sdk.environment_variables import OTEL_TRACES_SAMPLER_ARG + +_INVALID_FLOAT_MESSAGE = "Value of %s must be a float. Defaulting to %s: %s" + + +# Speced out but unused by OTel SDK as of 1.15.0 +LOGGING_EXPORT_INTERVAL_MS_ENV_VAR = "OTEL_BLRP_SCHEDULE_DELAY" +# TODO: remove when sampler uses env var instead +SAMPLING_RATIO_ENV_VAR = OTEL_TRACES_SAMPLER_ARG + + +_logger = getLogger(__name__) def _get_configurations(**kwargs) -> Dict[str, ConfigurationValue]: @@ -15,7 +43,93 @@ def _get_configurations(**kwargs) -> Dict[str, ConfigurationValue]: for key, val in kwargs.items(): configurations[key] = val + _default_exclude_instrumentations(configurations) + _default_disable_logging(configurations) + _default_disable_metrics(configurations) + _default_disable_tracing(configurations) + _default_logging_level(configurations) + _default_logger_name(configurations) + _default_logging_export_interval_ms(configurations) + _default_metric_readers(configurations) + _default_views(configurations) + _default_sampling_ratio(configurations) + _default_tracing_export_interval_ms(configurations) + _default_instrumentation_config(configurations) + + # TODO: remove when validation added to BLRP + if configurations[LOGGING_EXPORT_INTERVAL_MS_ARG] <= 0: + raise ValueError( + "%s must be positive." % LOGGING_EXPORT_INTERVAL_MS_ARG + ) + return configurations -# TODO: Add env var configuration +def _default_exclude_instrumentations(configurations): + if EXCLUDE_INSTRUMENTATIONS_ARG not in configurations: + configurations[EXCLUDE_INSTRUMENTATIONS_ARG] = [] + + +def _default_disable_logging(configurations): + if DISABLE_LOGGING_ARG not in configurations: + configurations[DISABLE_LOGGING_ARG] = False + + +def _default_disable_metrics(configurations): + if DISABLE_METRICS_ARG not in configurations: + configurations[DISABLE_METRICS_ARG] = False + + +def _default_disable_tracing(configurations): + if DISABLE_TRACING_ARG not in configurations: + configurations[DISABLE_TRACING_ARG] = False + + +def _default_logging_level(configurations): + if LOGGING_LEVEL_ARG not in configurations: + configurations[LOGGING_LEVEL_ARG] = NOTSET + + +def _default_logger_name(configurations): + if LOGGER_NAME_ARG not in configurations: + configurations[LOGGER_NAME_ARG] = "" + + +def _default_logging_export_interval_ms(configurations): + if LOGGING_EXPORT_INTERVAL_MS_ARG not in configurations: + configurations[LOGGING_EXPORT_INTERVAL_MS_ARG] = 5000 + + +def _default_metric_readers(configurations): + if METRIC_READERS_ARG not in configurations: + configurations[METRIC_READERS_ARG] = [] + + +def _default_views(configurations): + if VIEWS_ARG not in configurations: + configurations[VIEWS_ARG] = () + + +# TODO: remove when sampler uses env var instead +def _default_sampling_ratio(configurations): + if SAMPLING_RATIO_ARG not in configurations: + default = 1.0 + if SAMPLING_RATIO_ENV_VAR in environ: + try: + default = float(environ[SAMPLING_RATIO_ENV_VAR]) + except ValueError as e: + _logger.error( + _INVALID_FLOAT_MESSAGE + % (SAMPLING_RATIO_ENV_VAR, default, e) + ) + configurations[SAMPLING_RATIO_ARG] = default + + +def _default_tracing_export_interval_ms(configurations): + if TRACING_EXPORT_INTERVAL_MS_ARG not in configurations: + configurations[TRACING_EXPORT_INTERVAL_MS_ARG] = None + + +def _default_instrumentation_config(configurations): + if INSTRUMENTATION_CONFIG_ARG not in configurations: + configurations[INSTRUMENTATION_CONFIG_ARG] = {} diff --git a/azure-monitor-opentelemetry/samples/metrics/views.py b/azure-monitor-opentelemetry/samples/metrics/views.py index 2eb3925c..b81b1186 100644 --- a/azure-monitor-opentelemetry/samples/metrics/views.py +++ b/azure-monitor-opentelemetry/samples/metrics/views.py @@ -8,7 +8,6 @@ # Create a view matching the counter instrument `my.counter` # and configure the new name `my.counter.total` for the result metrics stream change_metric_name_view = View( - connection_string="", instrument_type=Counter, instrument_name="my.counter", name="my.counter.total", diff --git a/azure-monitor-opentelemetry/tests/configuration/test_configure.py b/azure-monitor-opentelemetry/tests/configuration/test_configure.py index be264574..667e89ad 100644 --- a/azure-monitor-opentelemetry/tests/configuration/test_configure.py +++ b/azure-monitor-opentelemetry/tests/configuration/test_configure.py @@ -52,19 +52,19 @@ def test_configure_azure_monitor( ): kwargs = { "connection_string": "test_cs", + "exclude_instrumentations": [], "disable_tracing": False, "disable_logging": False, "disable_metrics": False, "logging_export_interval_ms": 10000, "logging_level": "test_logging_level", "logger_name": "test_logger_name", - "metric_readers": "test_metric_readers", - "service_name": "test_service_name", - "service_namespace": "test_namespace", - "service_instance_id": "test_id", + "resource": "test_resource", "sampling_ratio": 0.5, "tracing_export_interval_ms": 15000, + "metric_readers": "test_metric_readers", "views": "test_views", + "instrumentation_config": [], } resource_init_mock = Mock() resource_mock.return_value = resource_init_mock @@ -100,18 +100,19 @@ def test_configure_azure_monitor_disable_tracing( ): kwargs = { "connection_string": "test_cs", + "exclude_instrumentations": [], "disable_tracing": True, "disable_logging": False, "disable_metrics": False, "logging_export_interval_ms": 10000, "logging_level": "test_logging_level", "logger_name": "test_logger_name", - "service_name": "test_service_name", - "service_namespace": "test_namespace", - "service_instance_id": "test_id", + "resource": "test_resource", "sampling_ratio": 0.5, "tracing_export_interval_ms": 15000, + "metric_readers": [], "views": "test_views", + "instrumentation_config": [], } resource_init_mock = Mock() resource_mock.return_value = resource_init_mock @@ -147,18 +148,19 @@ def test_configure_azure_monitor_disable_logging( ): kwargs = { "connection_string": "test_cs", + "exclude_instrumentations": [], "disable_tracing": False, "disable_logging": True, "disable_metrics": False, "logging_export_interval_ms": 10000, "logging_level": "test_logging_level", "logger_name": "test_logger_name", - "service_name": "test_service_name", - "service_namespace": "test_namespace", - "service_instance_id": "test_id", + "resource": "test_resource", "sampling_ratio": 0.5, "tracing_export_interval_ms": 15000, + "metric_readers": [], "views": "test_views", + "instrumentation_config": [], } resource_init_mock = Mock() resource_mock.return_value = resource_init_mock @@ -194,17 +196,19 @@ def test_configure_azure_monitor_disable_metrics( ): kwargs = { "connection_string": "test_cs", + "exclude_instrumentations": [], "disable_tracing": False, "disable_logging": False, "disable_metrics": True, "logging_export_interval_ms": 10000, "logging_level": "test_logging_level", - "service_name": "test_service_name", - "service_namespace": "test_namespace", - "service_instance_id": "test_id", + "logger_name": "test_logger_name", + "resource": "test_resource", "sampling_ratio": 0.5, "tracing_export_interval_ms": 15000, + "metric_readers": [], "views": "test_views", + "instrumentation_config": [], } resource_init_mock = Mock() resource_mock.return_value = resource_init_mock @@ -413,7 +417,10 @@ def test_setup_instrumentations( iter_mock, dep_mock, ): - configurations = {} + configurations = { + "exclude_instrumentations": [], + "instrumentation_config": {}, + } ep_mock = Mock() iter_mock.return_value = [ep_mock] instrumentor_mock = Mock() @@ -437,7 +444,10 @@ def test_setup_instrumentations_lib_not_supported( iter_mock, dep_mock, ): - configurations = {} + configurations = { + "exclude_instrumentations": [], + "instrumentation_config": {}, + } ep_mock = Mock() ep2_mock = Mock() iter_mock.return_value = (ep_mock, ep2_mock) @@ -464,7 +474,10 @@ def test_setup_instrumentations_lib_excluded( dep_mock, ): instr_exclude = _SUPPORTED_INSTRUMENTED_LIBRARIES[0] - configurations = {"exclude_instrumentations": [instr_exclude]} + configurations = { + "exclude_instrumentations": [instr_exclude], + "instrumentation_config": {}, + } ep_mock = Mock() ep2_mock = Mock() iter_mock.return_value = (ep_mock, ep2_mock) @@ -492,7 +505,10 @@ def test_setup_instrumentations_conflict( dep_mock, logger_mock, ): - configurations = {} + configurations = { + "exclude_instrumentations": [], + "instrumentation_config": {}, + } ep_mock = Mock() iter_mock.return_value = (ep_mock,) instrumentor_mock = Mock() @@ -518,7 +534,10 @@ def test_setup_instrumentations_exception( dep_mock, logger_mock, ): - configurations = {} + configurations = { + "exclude_instrumentations": [], + "instrumentation_config": {}, + } ep_mock = Mock() iter_mock.return_value = (ep_mock,) instrumentor_mock = Mock() @@ -544,6 +563,7 @@ def test_setup_instrumentations_custom_configuration( ): libr_name = _SUPPORTED_INSTRUMENTED_LIBRARIES[0] configurations = { + "exclude_instrumentations": [], "instrumentation_config": { libr_name: { "test_key": "test_value", diff --git a/azure-monitor-opentelemetry/tests/configuration/test_util.py b/azure-monitor-opentelemetry/tests/configuration/test_util.py index 111d5e6f..afff2205 100644 --- a/azure-monitor-opentelemetry/tests/configuration/test_util.py +++ b/azure-monitor-opentelemetry/tests/configuration/test_util.py @@ -12,51 +12,141 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest +from logging import NOTSET +from unittest import TestCase +from unittest.mock import patch -from azure.monitor.opentelemetry.util.configurations import _get_configurations +from azure.monitor.opentelemetry.util.configurations import ( + LOGGING_EXPORT_INTERVAL_MS_ENV_VAR, + SAMPLING_RATIO_ENV_VAR, + _get_configurations, +) -class TestUtil(unittest.TestCase): +class TestUtil(TestCase): def test_get_configurations(self): configurations = _get_configurations( connection_string="test_cs", + exclude_instrumentations="test_exclude_instrumentations", disable_logging="test_disable_logging", + disable_metrics="test_disable_metrics", disable_tracing="test_disable_tracing", instrumentations=["test_instrumentation"], logging_level="test_logging_level", logger_name="test_logger_name", - service_name="test_service_name", - service_namespace="test_namespace", - service_instance_id="test_id", + resource="test_resource", sampling_ratio="test_sample_ratio", - tracing_export_interval="test_tracing_interval", - logging_export_interval="test_logging_interval", + tracing_export_interval_ms=10000, + logging_export_interval_ms=10000, metric_readers=("test_readers"), views=("test_view"), + instrumentation_config="test_instrumentation_config", + credential="test_credential", ) self.assertEqual(configurations["connection_string"], "test_cs") + self.assertEqual( + configurations["exclude_instrumentations"], + "test_exclude_instrumentations", + ) self.assertEqual( configurations["disable_logging"], "test_disable_logging" ) self.assertEqual( - configurations["disable_tracing"], "test_disable_tracing" + configurations["disable_metrics"], "test_disable_metrics" ) self.assertEqual( - configurations["instrumentations"], ["test_instrumentation"] + configurations["disable_tracing"], "test_disable_tracing" ) self.assertEqual(configurations["logging_level"], "test_logging_level") self.assertEqual(configurations["logger_name"], "test_logger_name") - self.assertEqual(configurations["service_name"], "test_service_name") - self.assertEqual(configurations["service_namespace"], "test_namespace") - self.assertEqual(configurations["service_instance_id"], "test_id") + self.assertEqual(configurations["resource"], "test_resource") self.assertEqual(configurations["sampling_ratio"], "test_sample_ratio") + self.assertEqual(configurations["tracing_export_interval_ms"], 10000) + self.assertEqual(configurations["logging_export_interval_ms"], 10000) + self.assertEqual(configurations["metric_readers"], ("test_readers")) + self.assertEqual(configurations["views"], ("test_view")) self.assertEqual( - configurations["tracing_export_interval"], "test_tracing_interval" + configurations["instrumentation_config"], + ("test_instrumentation_config"), ) - self.assertEqual( - configurations["logging_export_interval"], "test_logging_interval" + self.assertEqual(configurations["credential"], ("test_credential")) + + @patch.dict("os.environ", {}, clear=True) + def test_get_configurations_defaults(self): + configurations = _get_configurations() + + self.assertTrue("connection_string" not in configurations) + self.assertEqual(configurations["exclude_instrumentations"], []) + self.assertEqual(configurations["disable_logging"], False) + self.assertEqual(configurations["disable_metrics"], False) + self.assertEqual(configurations["disable_tracing"], False) + self.assertEqual(configurations["logging_level"], NOTSET) + self.assertEqual(configurations["logger_name"], "") + self.assertTrue("resource" not in configurations) + self.assertEqual(configurations["sampling_ratio"], 1.0) + self.assertEqual(configurations["tracing_export_interval_ms"], None) + self.assertEqual(configurations["logging_export_interval_ms"], 5000) + self.assertEqual(configurations["metric_readers"], []) + self.assertEqual(configurations["views"], ()) + self.assertEqual(configurations["instrumentation_config"], {}) + + def test_get_configurations_validation(self): + self.assertRaises( + ValueError, _get_configurations, logging_export_interval_ms=-0.5 ) - self.assertEqual(configurations["metric_readers"], ("test_readers")) - self.assertEqual(configurations["views"], ("test_view")) + self.assertRaises( + ValueError, _get_configurations, logging_export_interval_ms=-1 + ) + + @patch.dict( + "os.environ", + { + LOGGING_EXPORT_INTERVAL_MS_ENV_VAR: "10000", + SAMPLING_RATIO_ENV_VAR: "0.5", + }, + clear=True, + ) + def test_get_configurations_env_vars(self): + configurations = _get_configurations() + + self.assertTrue("connection_string" not in configurations) + self.assertEqual(configurations["exclude_instrumentations"], []) + self.assertEqual(configurations["disable_logging"], False) + self.assertEqual(configurations["disable_metrics"], False) + self.assertEqual(configurations["disable_tracing"], False) + self.assertEqual(configurations["logging_level"], NOTSET) + self.assertEqual(configurations["logger_name"], "") + self.assertTrue("resource" not in configurations) + self.assertEqual(configurations["sampling_ratio"], 0.5) + self.assertEqual(configurations["tracing_export_interval_ms"], None) + self.assertEqual(configurations["logging_export_interval_ms"], 5000) + self.assertEqual(configurations["metric_readers"], []) + self.assertEqual(configurations["views"], ()) + self.assertEqual(configurations["instrumentation_config"], {}) + + @patch.dict( + "os.environ", + { + LOGGING_EXPORT_INTERVAL_MS_ENV_VAR: "Ten Thousand", + SAMPLING_RATIO_ENV_VAR: "Half", + }, + clear=True, + ) + def test_get_configurations_env_vars_validation(self): + configurations = _get_configurations() + + self.assertTrue("connection_string" not in configurations) + self.assertEqual(configurations["exclude_instrumentations"], []) + self.assertEqual(configurations["disable_logging"], False) + self.assertEqual(configurations["disable_metrics"], False) + self.assertEqual(configurations["disable_tracing"], False) + self.assertEqual(configurations["logging_level"], NOTSET) + self.assertEqual(configurations["logger_name"], "") + self.assertTrue("resource" not in configurations) + self.assertEqual(configurations["sampling_ratio"], 1.0) + self.assertEqual(configurations["tracing_export_interval_ms"], None) + self.assertEqual(configurations["logging_export_interval_ms"], 5000) + self.assertEqual(configurations["metric_readers"], []) + self.assertEqual(configurations["views"], ()) + self.assertEqual(configurations["instrumentation_config"], {})