Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ exclude =
CVS
.venv*/
venv*/
*/samples/*
target
__pycache__
*/build/lib/*
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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

Expand Down
4 changes: 3 additions & 1 deletion azure-monitor-opentelemetry-distro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -24,6 +26,16 @@
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):
"""
Expand All @@ -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
)
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -7,11 +13,7 @@
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"
Expand Down
34 changes: 34 additions & 0 deletions azure-monitor-opentelemetry-distro/samples/tracing/client.py
Original file line number Diff line number Diff line change
@@ -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="<your-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()
Original file line number Diff line number Diff line change
@@ -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="<your-connection-string>",
service_name="psycopg2_service_name",
disable_logging=True,
instrumentations=["psycopg2"],
tracing_export_interval_millis=15000,
)

cnx = psycopg2.connect(database="test", user="<user>", password="<password>")
cursor = cnx.cursor()
cursor.execute("INSERT INTO test_tables (test_field) VALUES (123)")
cursor.close()
cnx.close()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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"),
]
Original file line number Diff line number Diff line change
@@ -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="<your-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.")
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Loading