diff --git a/.flake8 b/.flake8 index 6180ee0..9ec8d18 100644 --- a/.flake8 +++ b/.flake8 @@ -21,6 +21,7 @@ exclude = CVS .venv*/ venv*/ + */samples/* target __pycache__ */build/lib/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 335f5fc..4269351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ ([#217](https://github.com/microsoft/ApplicationInsights-Python/pull/217)) - Add Logging configuration to Distro API ([#218](https://github.com/microsoft/ApplicationInsights-Python/pull/218)) +- Add instrumentation selection config + ([#228](https://github.com/microsoft/ApplicationInsights-Python/pull/228)) ## [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 2e47ae7..e70037e 100644 --- a/azure-monitor-opentelemetry-distro/README.md +++ b/azure-monitor-opentelemetry-distro/README.md @@ -33,7 +33,8 @@ pip install azure-monitor-opentelemetry-distro --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. -* service_name = Specifies the [service][service_semantic_convention_doc] name. +* instrumentations = Specifies the libraries with [instrumentations][ot_instrumentations] that you would like to use. Accepts a comma separated list. e.g. `["requests", "flask"]` +* service_name = Specifies the [service][service_semantic_convention_doc] name. * service_namespace = Specifies the [service][service_semantic_convention_doc] namespace. * service_instance_id = Specifies the [service][service_semantic_convention_doc] instance id. * disable_logging = If set to `True`, disables collection and export of logging telemetry. @@ -66,6 +67,7 @@ To use this package, you must have: [connection_string_doc]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/sdk-connection-string [exporter_configuration_docs]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/monitor/azure-monitor-opentelemetry-exporter#configuration [logging_level]: https://docs.python.org/3/library/logging.html#levels +[ot_instrumentations]: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation [ot_python_docs]: https://opentelemetry.io/docs/instrumentation/python/ [ot_sdk_python]: https://github.com/open-telemetry/opentelemetry-python [opentelemetry_instrumentation_requests]: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-requests 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 3154d18..dccff7b 100644 --- a/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py +++ b/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/__init__.py @@ -3,7 +3,9 @@ # Licensed under the MIT License. See License in the project root for # license information. # -------------------------------------------------------------------------- +import importlib from logging import NOTSET, getLogger +from typing import Any, Dict from azure.monitor.opentelemetry.distro.util import get_configurations from azure.monitor.opentelemetry.exporter import ( @@ -24,6 +26,16 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.semconv.resource import ResourceAttributes from opentelemetry.trace import get_tracer_provider, set_tracer_provider +_logger = getLogger(__name__) + + +_SUPPORTED_INSTRUMENTED_LIBRARIES = { + "django", + "flask", + "psycopg2", + "requests", +} + def configure_azure_monitor(**kwargs): """ @@ -33,51 +45,110 @@ def configure_azure_monitor(**kwargs): """ configurations = get_configurations(**kwargs) + + disable_tracing = configurations.get("disable_tracing", False) + disable_logging = configurations.get("disable_logging", False) + + resource = None + if not disable_logging or not disable_tracing: + resource = _get_resource(configurations) + + # Setup tracing pipeline + if not disable_tracing: + _setup_tracing(resource, configurations) + + # Setup logging pipeline + if not disable_logging: + _setup_logging(resource, configurations) + + # Setup instrumentations + # Instrumentations need to be setup last so to use the global providers + # instanstiated in the other setup steps + _setup_instrumentations(configurations) + + +def _get_resource(configurations: Dict[str, Any]) -> Resource: service_name = configurations.get("service_name", "") service_namespace = configurations.get("service_namespace", "") service_instance_id = configurations.get("service_instance_id", "") - disable_logging = configurations.get("disable_logging", False) - logging_level = configurations.get("logging_level", NOTSET) - logging_export_interval_millis = configurations.get( - "logging_export_interval_millis", 30000 + return Resource.create( + { + ResourceAttributes.SERVICE_NAME: service_name, + ResourceAttributes.SERVICE_NAMESPACE: service_namespace, + ResourceAttributes.SERVICE_INSTANCE_ID: service_instance_id, + } ) - disable_tracing = configurations.get("disable_tracing", False) + + +def _setup_tracing(resource: Resource, configurations: Dict[str, Any]): sampling_ratio = configurations.get("sampling_ratio", 1.0) tracing_export_interval_millis = configurations.get( "tracing_export_interval_millis", 30000 ) + tracer_provider = TracerProvider( + sampler=ApplicationInsightsSampler(sampling_ratio=sampling_ratio), + resource=resource, + ) + set_tracer_provider(tracer_provider) + trace_exporter = AzureMonitorTraceExporter(**configurations) + span_processor = BatchSpanProcessor( + trace_exporter, + export_timeout_millis=tracing_export_interval_millis, + ) + get_tracer_provider().add_span_processor(span_processor) - resource = None - if not disable_logging or not disable_tracing: - resource = Resource.create( - { - ResourceAttributes.SERVICE_NAME: service_name, - ResourceAttributes.SERVICE_NAMESPACE: service_namespace, - ResourceAttributes.SERVICE_INSTANCE_ID: service_instance_id, - } - ) - if not disable_logging: - logger_provider = LoggerProvider(resource=resource) - set_logger_provider(logger_provider) - log_exporter = AzureMonitorLogExporter(**kwargs) - log_record_processor = BatchLogRecordProcessor( - log_exporter, - export_timeout_millis=logging_export_interval_millis, - ) - get_logger_provider().add_log_record_processor(log_record_processor) - handler = LoggingHandler( - level=logging_level, logger_provider=get_logger_provider() - ) - getLogger().addHandler(handler) - if not disable_tracing: - tracer_provider = TracerProvider( - sampler=ApplicationInsightsSampler(sampling_ratio=sampling_ratio), - resource=resource, - ) - set_tracer_provider(tracer_provider) - trace_exporter = AzureMonitorTraceExporter(**kwargs) - span_processor = BatchSpanProcessor( - trace_exporter, - export_timeout_millis=tracing_export_interval_millis, - ) - get_tracer_provider().add_span_processor(span_processor) + +def _setup_logging(resource: Resource, configurations: Dict[str, Any]): + logging_level = configurations.get("logging_level", NOTSET) + logging_export_interval_millis = configurations.get( + "logging_export_interval_millis", 30000 + ) + logger_provider = LoggerProvider(resource=resource) + set_logger_provider(logger_provider) + log_exporter = AzureMonitorLogExporter(**configurations) + log_record_processor = BatchLogRecordProcessor( + log_exporter, + export_timeout_millis=logging_export_interval_millis, + ) + get_logger_provider().add_log_record_processor(log_record_processor) + handler = LoggingHandler( + level=logging_level, logger_provider=get_logger_provider() + ) + getLogger().addHandler(handler) + + +def _setup_instrumentations(configurations: Dict[str, Any]): + instrumentations = configurations.get("instrumentations", []) + for lib_name in instrumentations: + if lib_name in _SUPPORTED_INSTRUMENTED_LIBRARIES: + try: + importlib.import_module(lib_name) + except ImportError: + _logger.warning( + "Unable to import %s. Please make sure it is installed.", + lib_name, + ) + continue + instr_lib_name = "opentelemetry.instrumentation." + lib_name + try: + module = importlib.import_module(instr_lib_name) + instrumentor_name = "{}Instrumentor".format( + lib_name.capitalize() + ) + class_ = getattr(module, instrumentor_name) + class_().instrument() + except ImportError: + _logger.warning( + "Unable to import %s. Please make sure it is installed.", + instr_lib_name, + ) + except Exception as ex: + _logger.warning( + "Exception occured when instrumenting: %s.", + lib_name, + exc_info=ex, + ) + else: + _logger.warning( + "Instrumentation not supported for library: %s.", lib_name + ) diff --git a/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/_constants.py b/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/_constants.py index 810c500..51c4f55 100644 --- a/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/_constants.py +++ b/azure-monitor-opentelemetry-distro/azure/monitor/opentelemetry/distro/_constants.py @@ -1,3 +1,9 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- + import logging import platform from os import environ @@ -7,11 +13,7 @@ from azure.monitor.opentelemetry.exporter._connection_string_parser import ( ConnectionStringParser, ) -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License in the project root for -# license information. -# -------------------------------------------------------------------------- +# --------------------Diagnostic/status logging------------------------------ _LOG_PATH_LINUX = "/var/log/applicationinsights" _LOG_PATH_WINDOWS = "\\LogFiles\\ApplicationInsights" diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/client.py b/azure-monitor-opentelemetry-distro/samples/tracing/client.py new file mode 100644 index 0000000..5c6730e --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/client.py @@ -0,0 +1,34 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- +import logging + +import requests +from azure.monitor.opentelemetry.distro import configure_azure_monitor +from opentelemetry import trace + +logger = logging.getLogger(__name__) + +# Configure Azure monitor collection telemetry pipeline +configure_azure_monitor( + connection_string="", + service_name="client_service_name", + disable_logging=True, + instrumentations=["requests"], + tracing_export_interval_millis=15000, +) + +tracer = trace.get_tracer(__name__) +with tracer.start_as_current_span("Request parent span") as span: + try: + # Requests made using the requests library will be automatically captured + response = requests.get("https://azure.microsoft.com/", timeout=5) + logger.warning("Request sent") + except Exception as ex: + # If an exception occurs, this can be manually recorded on the parent span + span.set_attribute("status", "exception") + span.record_exception(ex) + +input() diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/db_psycopg2.py b/azure-monitor-opentelemetry-distro/samples/tracing/db_psycopg2.py new file mode 100644 index 0000000..79a3c5b --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/db_psycopg2.py @@ -0,0 +1,22 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- +import psycopg2 +from azure.monitor.opentelemetry.distro import configure_azure_monitor + +# Configure Azure monitor collection telemetry pipeline +configure_azure_monitor( + connection_string="", + service_name="psycopg2_service_name", + disable_logging=True, + instrumentations=["psycopg2"], + tracing_export_interval_millis=15000, +) + +cnx = psycopg2.connect(database="test", user="", password="") +cursor = cnx.cursor() +cursor.execute("INSERT INTO test_tables (test_field) VALUES (123)") +cursor.close() +cnx.close() diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/__init__.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/__init__.py new file mode 100644 index 0000000..5b7f7a9 --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/admin.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/admin.py new file mode 100644 index 0000000..3f8b9ca --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/admin.py @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +from django.contrib import admin + +# Register your models here. diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/apps.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/apps.py new file mode 100644 index 0000000..cfad605 --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/apps.py @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +from django.apps import AppConfig + + +class ExampleConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "example" diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/migrations/__init__.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/migrations/__init__.py new file mode 100644 index 0000000..5b7f7a9 --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/migrations/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/models.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/models.py new file mode 100644 index 0000000..526ea7f --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/models.py @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +from django.db import models + +# Create your models here. diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/tests.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/tests.py new file mode 100644 index 0000000..7705f14 --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/tests.py @@ -0,0 +1,5 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +from django.test import TestCase + +# Create your tests here. diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/urls.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/urls.py new file mode 100644 index 0000000..27499cb --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/urls.py @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.index, name="index"), + path("exception", views.exception, name="exception"), +] 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 new file mode 100644 index 0000000..05da83b --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/example/views.py @@ -0,0 +1,26 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- + +from azure.monitor.opentelemetry.distro import configure_azure_monitor +from django.http import HttpResponse + +# Configure Azure monitor collection telemetry pipeline +configure_azure_monitor( + # connection_string="", + service_name="django_service_name", + instrumentations=["django"], + disable_logging=True, + tracing_export_interval_millis=15000, +) + +# Requests sent to the django application will be automatically captured +def index(request): + return HttpResponse("Hello, world.") + + +# Exceptions that are raised within the request are automatically captured +def exception(request): + raise Exception("Exception was raised.") diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/manage.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/manage.py new file mode 100644 index 0000000..5d854eb --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/manage.py @@ -0,0 +1,25 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample.settings") + + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/__init__.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/__init__.py new file mode 100644 index 0000000..5b7f7a9 --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/asgi.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/asgi.py new file mode 100644 index 0000000..9002011 --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/asgi.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# cSpell:disable +""" +ASGI config for sample project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample.settings") + +application = get_asgi_application() + +# cSpell:enable diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/settings.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/settings.py new file mode 100644 index 0000000..27456fe --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/settings.py @@ -0,0 +1,133 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +""" +Django settings for sample project. + +Generated by 'django-admin startproject' using Django 3.2.7. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +# cSpell:disable + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = ( + "django-insecure--9p!az#-flphjtvtl#c_ep6x#1lo+0@nzci#-(!-3c$!o0lyjk" +) + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "sample.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "sample.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = "/static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# cSpell:enable diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/urls.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/urls.py new file mode 100644 index 0000000..851a91e --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/urls.py @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +"""sample URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.urls import include, path + +urlpatterns = [ + path("", include("example.urls")), +] diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/wsgi.py b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/wsgi.py new file mode 100644 index 0000000..1d47339 --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/django/sample/sample/wsgi.py @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +""" +WSGI config for sample project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample.settings") + +application = get_wsgi_application() diff --git a/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py new file mode 100644 index 0000000..5ff0c5d --- /dev/null +++ b/azure-monitor-opentelemetry-distro/samples/tracing/server_flask.py @@ -0,0 +1,33 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License in the project root for +# license information. +# -------------------------------------------------------------------------- +import flask +from azure.monitor.opentelemetry.distro import configure_azure_monitor + +# Configure Azure monitor collection telemetry pipeline +configure_azure_monitor( + connection_string="", + service_name="flask_service_name", + disable_logging=True, + instrumentations=["flask"], + tracing_export_interval_millis=15000, +) + +app = flask.Flask(__name__) + +# Requests sent to the flask application will be automatically captured +@app.route("/") +def test(): + return "Test flask request" + + +# Exceptions that are raised within the request are automatically captured +@app.route("/exception") +def exception(): + raise Exception("Hit an exception") + + +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 8bbfca2..ec7c301 100644 --- a/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py +++ b/azure-monitor-opentelemetry-distro/tests/configuration/test_configure.py @@ -13,321 +13,147 @@ # limitations under the License. import unittest -from unittest.mock import Mock, patch +from unittest.mock import Mock, call, patch -from azure.monitor.opentelemetry.distro import configure_azure_monitor +from azure.monitor.opentelemetry.distro import ( + _SUPPORTED_INSTRUMENTED_LIBRARIES, + _get_resource, + _setup_instrumentations, + _setup_logging, + _setup_tracing, + configure_azure_monitor, +) from opentelemetry.semconv.resource import ResourceAttributes class TestConfigure(unittest.TestCase): @patch( - "azure.monitor.opentelemetry.distro.BatchSpanProcessor", + "azure.monitor.opentelemetry.distro._setup_instrumentations", ) @patch( - "azure.monitor.opentelemetry.distro.AzureMonitorTraceExporter", + "azure.monitor.opentelemetry.distro._setup_logging", ) @patch( - "azure.monitor.opentelemetry.distro.get_tracer_provider", + "azure.monitor.opentelemetry.distro._setup_tracing", ) @patch( - "azure.monitor.opentelemetry.distro.set_tracer_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.TracerProvider", - autospec=True, - ) - @patch( - "azure.monitor.opentelemetry.distro.ApplicationInsightsSampler", - ) - @patch( - "azure.monitor.opentelemetry.distro.getLogger", - ) - @patch( - "azure.monitor.opentelemetry.distro.LoggingHandler", - ) - @patch( - "azure.monitor.opentelemetry.distro.BatchLogRecordProcessor", - ) - @patch( - "azure.monitor.opentelemetry.distro.AzureMonitorLogExporter", - ) - @patch( - "azure.monitor.opentelemetry.distro.get_logger_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.set_logger_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.LoggerProvider", - autospec=True, - ) - @patch( - "azure.monitor.opentelemetry.distro.Resource", + "azure.monitor.opentelemetry.distro._get_resource", ) def test_configure_azure_monitor( self, resource_mock, - lp_mock, - set_logger_provider_mock, - get_logger_provider_mock, - log_exporter_mock, - blrp_mock, - logging_handler_mock, - get_logger_mock, - sampler_mock, - tp_mock, - set_tracer_provider_mock, - get_tracer_provider_mock, - trace_exporter_mock, - bsp_mock, + tracing_mock, + logging_mock, + instrumentation_mock, ): + kwargs = { + "connection_string": "test_cs", + "disable_logging": False, + "disable_tracing": False, + "logging_export_interval_millis": 10000, + "logging_level": "test_logging_level", + "service_name": "test_service_name", + "service_namespace": "test_namespace", + "service_instance_id": "test_id", + "sampling_ratio": 0.5, + "tracing_export_interval_millis": 15000, + } resource_init_mock = Mock() - resource_mock.create.return_value = resource_init_mock - - lp_init_mock = Mock() - lp_mock.return_value = lp_init_mock - get_logger_provider_mock.return_value = lp_init_mock - log_exp_init_mock = Mock() - log_exporter_mock.return_value = log_exp_init_mock - blrp_init_mock = Mock() - blrp_mock.return_value = blrp_init_mock - logging_handler_init_mock = Mock() - logging_handler_mock.return_value = logging_handler_init_mock - logger_mock = Mock() - get_logger_mock.return_value = logger_mock - - sampler_init_mock = Mock() - sampler_mock.return_value = sampler_init_mock - tp_init_mock = Mock() - tp_mock.return_value = tp_init_mock - get_tracer_provider_mock.return_value = tp_init_mock - trace_exp_init_mock = Mock() - trace_exporter_mock.return_value = trace_exp_init_mock - bsp_init_mock = Mock() - bsp_mock.return_value = bsp_init_mock - - configure_azure_monitor( - connection_string="test_cs", - console_exporting=False, - disable_logging=False, - disable_tracing=False, - logging_export_interval_millis=10000, - logging_level="test_logging_level", - service_name="test_service_name", - service_namespace="test_namespace", - service_instance_id="test_id", - sampling_ratio=0.5, - tracing_export_interval_millis=15000, - ) - resource_mock.create.assert_called_once_with( - { - ResourceAttributes.SERVICE_NAME: "test_service_name", - ResourceAttributes.SERVICE_NAMESPACE: "test_namespace", - ResourceAttributes.SERVICE_INSTANCE_ID: "test_id", - } - ) - - lp_mock.assert_called_once_with(resource=resource_init_mock) - set_logger_provider_mock.assert_called_once_with(lp_init_mock) - get_logger_provider_mock.assert_called() - log_exporter_mock.assert_called_once() - blrp_mock.assert_called_once_with( - log_exp_init_mock, export_timeout_millis=10000 - ) - lp_init_mock.add_log_record_processor.assert_called_once_with( - blrp_init_mock - ) - logging_handler_mock.assert_called_once_with( - level="test_logging_level", logger_provider=lp_init_mock - ) - get_logger_mock.assert_called_once_with() - logger_mock.addHandler.assert_called_once_with( - logging_handler_init_mock - ) - - sampler_mock.assert_called_once_with(sampling_ratio=0.5) - tp_mock.assert_called_once_with( - resource=resource_init_mock, - sampler=sampler_init_mock, - ) - set_tracer_provider_mock.assert_called_once_with(tp_init_mock) - get_tracer_provider_mock.assert_called() - trace_exporter_mock.assert_called_once() - bsp_mock.assert_called_once_with( - trace_exp_init_mock, export_timeout_millis=15000 - ) - tp_init_mock.add_span_processor(bsp_init_mock) + resource_mock.return_value = resource_init_mock + configure_azure_monitor(**kwargs) + resource_mock.assert_called_once_with(kwargs) + tracing_mock.assert_called_once_with(resource_init_mock, kwargs) + logging_mock.assert_called_once_with(resource_init_mock, kwargs) + instrumentation_mock.assert_called_once_with(kwargs) @patch( - "azure.monitor.opentelemetry.distro.BatchSpanProcessor", + "azure.monitor.opentelemetry.distro._setup_instrumentations", ) @patch( - "azure.monitor.opentelemetry.distro.AzureMonitorTraceExporter", + "azure.monitor.opentelemetry.distro._setup_logging", ) @patch( - "azure.monitor.opentelemetry.distro.get_tracer_provider", + "azure.monitor.opentelemetry.distro._setup_tracing", ) @patch( - "azure.monitor.opentelemetry.distro.set_tracer_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.TracerProvider", - autospec=True, - ) - @patch( - "azure.monitor.opentelemetry.distro.ApplicationInsightsSampler", - ) - @patch( - "azure.monitor.opentelemetry.distro.getLogger", - ) - @patch( - "azure.monitor.opentelemetry.distro.LoggingHandler", - ) - @patch( - "azure.monitor.opentelemetry.distro.BatchLogRecordProcessor", - ) - @patch( - "azure.monitor.opentelemetry.distro.AzureMonitorLogExporter", - ) - @patch( - "azure.monitor.opentelemetry.distro.get_logger_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.set_logger_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.LoggerProvider", - autospec=True, - ) - @patch( - "azure.monitor.opentelemetry.distro.Resource", - ) - def test_configure_azure_monitor_disable_tracing_and_logging( - self, - resource_mock, - lp_mock, - set_logger_provider_mock, - get_logger_provider_mock, - log_exporter_mock, - blrp_mock, - logging_handler_mock, - get_logger_mock, - sampler_mock, - tp_mock, - set_tracer_provider_mock, - get_tracer_provider_mock, - trace_exporter_mock, - bsp_mock, - ): - configure_azure_monitor( - connection_string="test_cs", - disable_logging=True, - disable_tracing=True, - ) - resource_mock.assert_not_called() - - lp_mock.assert_not_called() - set_logger_provider_mock.assert_not_called() - get_logger_provider_mock.assert_not_called() - log_exporter_mock.assert_not_called() - blrp_mock.assert_not_called() - logging_handler_mock.assert_not_called() - get_logger_mock.assert_not_called() - - sampler_mock.assert_not_called() - tp_mock.assert_not_called() - set_tracer_provider_mock.assert_not_called() - get_tracer_provider_mock.assert_not_called() - trace_exporter_mock.assert_not_called() - bsp_mock.assert_not_called() - - @patch( - "azure.monitor.opentelemetry.distro.BatchSpanProcessor", - ) - @patch( - "azure.monitor.opentelemetry.distro.AzureMonitorTraceExporter", - ) - @patch( - "azure.monitor.opentelemetry.distro.get_tracer_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.set_tracer_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.TracerProvider", - autospec=True, - ) - @patch( - "azure.monitor.opentelemetry.distro.ApplicationInsightsSampler", - ) - @patch( - "azure.monitor.opentelemetry.distro.getLogger", - ) - @patch( - "azure.monitor.opentelemetry.distro.LoggingHandler", - ) - @patch( - "azure.monitor.opentelemetry.distro.BatchLogRecordProcessor", - ) - @patch( - "azure.monitor.opentelemetry.distro.AzureMonitorLogExporter", - ) - @patch( - "azure.monitor.opentelemetry.distro.get_logger_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.set_logger_provider", - ) - @patch( - "azure.monitor.opentelemetry.distro.LoggerProvider", - autospec=True, - ) - @patch( - "azure.monitor.opentelemetry.distro.Resource", + "azure.monitor.opentelemetry.distro._get_resource", ) def test_configure_azure_monitor_disable_tracing( self, resource_mock, - lp_mock, - set_logger_provider_mock, - get_logger_provider_mock, - log_exporter_mock, - blrp_mock, - logging_handler_mock, - get_logger_mock, - sampler_mock, - tp_mock, - set_tracer_provider_mock, - get_tracer_provider_mock, - trace_exporter_mock, - bsp_mock, + tracing_mock, + logging_mock, + instrumentation_mock, ): + kwargs = { + "connection_string": "test_cs", + "disable_logging": False, + "disable_tracing": True, + "logging_export_interval_millis": 10000, + "logging_level": "test_logging_level", + "service_name": "test_service_name", + "service_namespace": "test_namespace", + "service_instance_id": "test_id", + "sampling_ratio": 0.5, + "tracing_export_interval_millis": 15000, + } resource_init_mock = Mock() - resource_mock.create.return_value = resource_init_mock + resource_mock.return_value = resource_init_mock + configure_azure_monitor(**kwargs) + resource_mock.assert_called_once_with(kwargs) + tracing_mock.assert_not_called() + logging_mock.assert_called_once_with(resource_init_mock, kwargs) + instrumentation_mock.assert_called_once_with(kwargs) - lp_init_mock = Mock() - lp_mock.return_value = lp_init_mock - get_logger_provider_mock.return_value = lp_init_mock - log_exp_init_mock = Mock() - log_exporter_mock.return_value = log_exp_init_mock - blrp_init_mock = Mock() - blrp_mock.return_value = blrp_init_mock - logging_handler_init_mock = Mock() - logging_handler_mock.return_value = logging_handler_init_mock - logger_mock = Mock() - get_logger_mock.return_value = logger_mock + @patch( + "azure.monitor.opentelemetry.distro._setup_instrumentations", + ) + @patch( + "azure.monitor.opentelemetry.distro._setup_logging", + ) + @patch( + "azure.monitor.opentelemetry.distro._setup_tracing", + ) + @patch( + "azure.monitor.opentelemetry.distro._get_resource", + ) + def test_configure_azure_monitor_disable_logging( + self, + resource_mock, + tracing_mock, + logging_mock, + instrumentation_mock, + ): + kwargs = { + "connection_string": "test_cs", + "disable_logging": True, + "disable_tracing": False, + "logging_export_interval_millis": 10000, + "logging_level": "test_logging_level", + "service_name": "test_service_name", + "service_namespace": "test_namespace", + "service_instance_id": "test_id", + "sampling_ratio": 0.5, + "tracing_export_interval_millis": 15000, + } + resource_init_mock = Mock() + resource_mock.return_value = resource_init_mock + configure_azure_monitor(**kwargs) + resource_mock.assert_called_once_with(kwargs) + tracing_mock.assert_called_once_with(resource_init_mock, kwargs) + logging_mock.assert_not_called() + instrumentation_mock.assert_called_once_with(kwargs) - configure_azure_monitor( - connection_string="test_cs", - console_exporting=False, - disable_logging=False, - disable_tracing=True, - logging_export_interval_millis=10000, - logging_level="test_logging_level", - service_name="test_service_name", - service_namespace="test_namespace", - service_instance_id="test_id", - ) + @patch( + "azure.monitor.opentelemetry.distro.Resource", + ) + def test_get_resource(self, resource_mock): + configuration = { + "service_name": "test_service_name", + "service_namespace": "test_namespace", + "service_instance_id": "test_id", + } + _get_resource(configuration) resource_mock.create.assert_called_once_with( { ResourceAttributes.SERVICE_NAME: "test_service_name", @@ -336,31 +162,6 @@ class TestConfigure(unittest.TestCase): } ) - lp_mock.assert_called_once_with(resource=resource_init_mock) - set_logger_provider_mock.assert_called_once_with(lp_init_mock) - get_logger_provider_mock.assert_called() - log_exporter_mock.assert_called_once() - blrp_mock.assert_called_once_with( - log_exp_init_mock, export_timeout_millis=10000 - ) - lp_init_mock.add_log_record_processor.assert_called_once_with( - blrp_init_mock - ) - logging_handler_mock.assert_called_once_with( - level="test_logging_level", logger_provider=lp_init_mock - ) - get_logger_mock.assert_called_once_with() - logger_mock.addHandler.assert_called_once_with( - logging_handler_init_mock - ) - - sampler_mock.assert_not_called() - tp_mock.assert_not_called() - set_tracer_provider_mock.assert_not_called() - get_tracer_provider_mock.assert_not_called() - trace_exporter_mock.assert_not_called() - bsp_mock.assert_not_called() - @patch( "azure.monitor.opentelemetry.distro.BatchSpanProcessor", ) @@ -380,6 +181,46 @@ class TestConfigure(unittest.TestCase): @patch( "azure.monitor.opentelemetry.distro.ApplicationInsightsSampler", ) + def test_setup_tracing( + self, + sampler_mock, + tp_mock, + set_tracer_provider_mock, + get_tracer_provider_mock, + trace_exporter_mock, + bsp_mock, + ): + resource_mock = Mock() + sampler_init_mock = Mock() + sampler_mock.return_value = sampler_init_mock + tp_init_mock = Mock() + tp_mock.return_value = tp_init_mock + get_tracer_provider_mock.return_value = tp_init_mock + trace_exp_init_mock = Mock() + trace_exporter_mock.return_value = trace_exp_init_mock + bsp_init_mock = Mock() + bsp_mock.return_value = bsp_init_mock + + configurations = { + "connection_string": "test_cs", + "disable_tracing": False, + "sampling_ratio": 0.5, + "tracing_export_interval_millis": 15000, + } + _setup_tracing(resource_mock, configurations) + sampler_mock.assert_called_once_with(sampling_ratio=0.5) + tp_mock.assert_called_once_with( + resource=resource_mock, + sampler=sampler_init_mock, + ) + set_tracer_provider_mock.assert_called_once_with(tp_init_mock) + get_tracer_provider_mock.assert_called() + trace_exporter_mock.assert_called_once() + bsp_mock.assert_called_once_with( + trace_exp_init_mock, export_timeout_millis=15000 + ) + tp_init_mock.add_span_processor(bsp_init_mock) + @patch( "azure.monitor.opentelemetry.distro.getLogger", ) @@ -402,12 +243,8 @@ class TestConfigure(unittest.TestCase): "azure.monitor.opentelemetry.distro.LoggerProvider", autospec=True, ) - @patch( - "azure.monitor.opentelemetry.distro.Resource", - ) - def test_configure_azure_monitor_disable_logging( + def test_setup_logging( self, - resource_mock, lp_mock, set_logger_provider_mock, get_logger_provider_mock, @@ -415,63 +252,141 @@ class TestConfigure(unittest.TestCase): blrp_mock, logging_handler_mock, get_logger_mock, - sampler_mock, - tp_mock, - set_tracer_provider_mock, - get_tracer_provider_mock, - trace_exporter_mock, - bsp_mock, ): - resource_init_mock = Mock() - resource_mock.create.return_value = resource_init_mock + resource_mock = Mock() - sampler_init_mock = Mock() - sampler_mock.return_value = sampler_init_mock - tp_init_mock = Mock() - tp_mock.return_value = tp_init_mock - get_tracer_provider_mock.return_value = tp_init_mock - trace_exp_init_mock = Mock() - trace_exporter_mock.return_value = trace_exp_init_mock - bsp_init_mock = Mock() - bsp_mock.return_value = bsp_init_mock + lp_init_mock = Mock() + lp_mock.return_value = lp_init_mock + get_logger_provider_mock.return_value = lp_init_mock + log_exp_init_mock = Mock() + log_exporter_mock.return_value = log_exp_init_mock + blrp_init_mock = Mock() + blrp_mock.return_value = blrp_init_mock + logging_handler_init_mock = Mock() + logging_handler_mock.return_value = logging_handler_init_mock + logger_mock = Mock() + get_logger_mock.return_value = logger_mock - configure_azure_monitor( - connection_string="test_cs", - console_exporting=False, - disable_logging=True, - disable_tracing=False, - logging_level="test_logging_level", - service_name="test_service_name", - service_namespace="test_namespace", - service_instance_id="test_id", - sampling_ratio=0.5, - tracing_export_interval_millis=15000, + configurations = { + "connection_string": "test_cs", + "disable_logging": False, + "logging_export_interval_millis": 10000, + "logging_level": "test_logging_level", + } + _setup_logging(resource_mock, configurations) + + lp_mock.assert_called_once_with(resource=resource_mock) + set_logger_provider_mock.assert_called_once_with(lp_init_mock) + get_logger_provider_mock.assert_called() + log_exporter_mock.assert_called_once() + blrp_mock.assert_called_once_with( + log_exp_init_mock, export_timeout_millis=10000 ) - resource_mock.create.assert_called_once_with( - { - ResourceAttributes.SERVICE_NAME: "test_service_name", - ResourceAttributes.SERVICE_NAMESPACE: "test_namespace", - ResourceAttributes.SERVICE_INSTANCE_ID: "test_id", - } + lp_init_mock.add_log_record_processor.assert_called_once_with( + blrp_init_mock + ) + logging_handler_mock.assert_called_once_with( + level="test_logging_level", logger_provider=lp_init_mock + ) + get_logger_mock.assert_called_once_with() + logger_mock.addHandler.assert_called_once_with( + logging_handler_init_mock ) - lp_mock.assert_not_called() - set_logger_provider_mock.assert_not_called() - get_logger_provider_mock.assert_not_called() - log_exporter_mock.assert_not_called() - blrp_mock.assert_not_called() - logging_handler_mock.assert_not_called() - get_logger_mock.assert_not_called() + @patch("azure.monitor.opentelemetry.distro.getattr") + def test_setup_instrumentations( + self, + getattr_mock, + ): + for lib_name in _SUPPORTED_INSTRUMENTED_LIBRARIES: + with patch("importlib.import_module") as import_module_mock: + configurations = {"instrumentations": [lib_name]} + 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() - sampler_mock.assert_called_once_with(sampling_ratio=0.5) - tp_mock.assert_called_once_with( - resource=resource_init_mock, - sampler=sampler_init_mock, - ) - set_tracer_provider_mock.assert_called_once_with(tp_init_mock) - get_tracer_provider_mock.assert_called() - trace_exporter_mock.assert_called_once() - bsp_mock.assert_called_once_with( - trace_exp_init_mock, export_timeout_millis=15000 - ) - tp_init_mock.add_span_processor(bsp_init_mock) + @patch("azure.monitor.opentelemetry.distro.getattr") + def test_setup_instrumentations_lib_not_found( + self, + getattr_mock, + ): + with patch("importlib.import_module") as import_module_mock: + configurations = {"instrumentations": ["non_supported_lib"]} + 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_import_lib_failed( + self, + getattr_mock, + ): + for lib_name in _SUPPORTED_INSTRUMENTED_LIBRARIES: + with patch( + "importlib.import_module", side_effect=ImportError() + ) as import_module_mock: + configurations = {"instrumentations": [lib_name]} + 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_called_once() + instrumentor_mock.assert_not_called() + instrument_mock.instrument.assert_not_called() + + @patch("azure.monitor.opentelemetry.distro.getattr") + def test_setup_instrumentations_import_instr_failed( + self, + getattr_mock, + ): + for lib_name in _SUPPORTED_INSTRUMENTED_LIBRARIES: + with patch("importlib.import_module") as import_module_mock: + configurations = {"instrumentations": [lib_name]} + instrument_mock = Mock() + instrumentor_mock = Mock() + instrumentor_mock.return_value = instrument_mock + getattr_mock.return_value = instrumentor_mock + import_module_mock.side_effect = [None, ImportError()] + _setup_instrumentations(configurations) + instr_lib_name = "opentelemetry.instrumentation." + lib_name + import_module_mock.assert_has_calls( + [call(lib_name), call(instr_lib_name)] + ) + instrumentor_mock.assert_not_called() + instrument_mock.instrument.assert_not_called() + + @patch("azure.monitor.opentelemetry.distro.getattr") + def test_setup_instrumentations_failed_general( + self, + getattr_mock, + ): + for lib_name in _SUPPORTED_INSTRUMENTED_LIBRARIES: + with patch("importlib.import_module") as import_module_mock: + configurations = {"instrumentations": [lib_name]} + instrument_mock = Mock() + instrumentor_mock = Mock() + instrumentor_mock.return_value = instrument_mock + getattr_mock.side_effect = Exception() + _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_not_called() + instrument_mock.instrument.assert_not_called()