Skip to content

Enable Custom Exporters #288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Enable custom exporters
([#288](https://github.com/microsoft/ApplicationInsights-Python/pull/288))
- Update samples
([#281](https://github.com/microsoft/ApplicationInsights-Python/pull/281))

Expand Down
6 changes: 3 additions & 3 deletions azure-monitor-opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ You can configure further with [OpenTelemetry environment variables][ot_env_vars
| Environment Variable | Description |
|-------------|----------------------|
| [OTEL_SERVICE_NAME][opentelemetry_spec_service_name], [OTEL_RESOURCE_ATTRIBUTES][opentelemetry_spec_resource_attributes] | Specifies the OpenTelemetry [resource][opentelemetry_spec_resource] associated with your application. |
| `OTEL_LOGS_EXPORTER` | If set to `None`, disables collection and export of logging telemetry. |
| `OTEL_METRICS_EXPORTER` | If set to `None`, disables collection and export of metric telemetry. |
| `OTEL_TRACES_EXPORTER` | If set to `None`, disables collection and export of distributed tracing telemetry. |
| `OTEL_LOGS_EXPORTER` | Specifies additional logs exporters by a comma separated list of entry point names. For example, to enable the ConsoleLogExporter in addition to the Azure Monitor OpenTelemetry Exporter, set `OTEL_LOGS_EXPORTER=console`. If set to `None`, disables collection and export of logging telemetry. |
| `OTEL_METRICS_EXPORTER` | Specifies additional metrics exporters by a comma separated list of entry point names. For example, to enable the ConsoleMetricExporter in addition to the Azure Monitor OpenTelemetry Exporter, set `OTEL_METRICS_EXPORTER=console`. If set to `None`, disables collection and export of metric telemetry. |
| `OTEL_TRACES_EXPORTER` | Specifies additional traces exporters by a comma separated list of entry point names. For example, to enable the ConsoleSpanExporter in addition to the Azure Monitor OpenTelemetry Exporter, set `OTEL_TRACES_EXPORTER=console`. If set to `None`, disables collection and export of distributed tracing telemetry. |
| `OTEL_BLRP_SCHEDULE_DELAY` | Specifies the logging export interval in milliseconds. Defaults to 5000. |
| `OTEL_BSP_SCHEDULE_DELAY` | Specifies the distributed tracing export interval in milliseconds. Defaults to 5000. |
| `OTEL_TRACES_SAMPLER_ARG` | 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. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# license information.
# --------------------------------------------------------------------------
from logging import getLogger
from os import getenv
from typing import Dict

from azure.monitor.opentelemetry._constants import (
Expand All @@ -22,6 +23,11 @@
)
from azure.monitor.opentelemetry.util.configurations import _get_configurations
from opentelemetry._logs import get_logger_provider, set_logger_provider
from opentelemetry.environment_variables import (
OTEL_LOGS_EXPORTER,
OTEL_METRICS_EXPORTER,
OTEL_TRACES_EXPORTER,
)
from opentelemetry.instrumentation.dependencies import (
get_dist_dependency_conflicts,
)
Expand Down Expand Up @@ -97,6 +103,11 @@ def _setup_tracing(configurations: Dict[str, ConfigurationValue]):
trace_exporter,
)
get_tracer_provider().add_span_processor(span_processor)
for traces_exporter in _get_extra_exporters(
"opentelemetry_traces_exporter", OTEL_TRACES_EXPORTER
):
span_processor = BatchSpanProcessor(traces_exporter)
get_tracer_provider().add_span_processor(span_processor)


def _setup_logging(configurations: Dict[str, ConfigurationValue]):
Expand All @@ -110,15 +121,30 @@ def _setup_logging(configurations: Dict[str, ConfigurationValue]):
schedule_delay_millis=logging_export_interval_ms,
)
get_logger_provider().add_log_record_processor(log_record_processor)
for logs_exporter in _get_extra_exporters(
"opentelemetry_logs_exporter", OTEL_LOGS_EXPORTER
):
log_record_processor = BatchLogRecordProcessor(
logs_exporter,
schedule_delay_millis=logging_export_interval_ms,
)
get_logger_provider().add_log_record_processor(log_record_processor)
handler = LoggingHandler(logger_provider=get_logger_provider())
getLogger().addHandler(handler)


def _setup_metrics(configurations: Dict[str, ConfigurationValue]):
metric_exporter = AzureMonitorMetricExporter(**configurations)
reader = PeriodicExportingMetricReader(metric_exporter)
metric_readers = [
PeriodicExportingMetricReader(
AzureMonitorMetricExporter(**configurations)
)
]
for metrics_exporter in _get_extra_exporters(
"opentelemetry_metrics_exporter", OTEL_METRICS_EXPORTER
):
metric_readers.append(PeriodicExportingMetricReader(metrics_exporter))
meter_provider = MeterProvider(
metric_readers=[reader],
metric_readers=metric_readers,
)
set_meter_provider(meter_provider)

Expand Down Expand Up @@ -149,3 +175,16 @@ def _setup_instrumentations():
lib_name,
exc_info=ex,
)


def _get_extra_exporters(entry_point_group, env_var):
exporter_entry_points = iter_entry_points(entry_point_group)
selected_exporter_names_env_var = getenv(env_var, "").lower().strip()
selected_exporter_names = selected_exporter_names_env_var.split(",")
exporters = []
for ep in exporter_entry_points:
if ep.name == "azure_monitor_opentelemetry_exporter":
continue
if ep.name in selected_exporter_names:
exporters.append(ep.load()())
return exporters
100 changes: 91 additions & 9 deletions azure-monitor-opentelemetry/tests/configuration/test_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
# limitations under the License.

import unittest
from unittest.mock import Mock, patch
from unittest.mock import Mock, call, patch

from azure.monitor.opentelemetry._configure import (
_SUPPORTED_INSTRUMENTED_LIBRARIES,
_get_extra_exporters,
_setup_instrumentations,
_setup_logging,
_setup_metrics,
Expand Down Expand Up @@ -162,6 +163,9 @@ def test_configure_azure_monitor_disable_metrics(
metrics_mock.assert_not_called()
instrumentation_mock.assert_called_once_with()

@patch(
"azure.monitor.opentelemetry._configure._get_extra_exporters",
)
@patch(
"azure.monitor.opentelemetry._configure.BatchSpanProcessor",
)
Expand Down Expand Up @@ -189,6 +193,7 @@ def test_setup_tracing(
get_tracer_provider_mock,
trace_exporter_mock,
bsp_mock,
extra_exporters_mock,
):
sampler_init_mock = Mock()
sampler_mock.return_value = sampler_init_mock
Expand All @@ -199,6 +204,12 @@ def test_setup_tracing(
trace_exporter_mock.return_value = trace_exp_init_mock
bsp_init_mock = Mock()
bsp_mock.return_value = bsp_init_mock
custom_exporter_mock1 = Mock()
custom_exporter_mock2 = Mock()
extra_exporters_mock.return_value = [
custom_exporter_mock1,
custom_exporter_mock2,
]

configurations = {
"connection_string": "test_cs",
Expand All @@ -212,9 +223,20 @@ def test_setup_tracing(
set_tracer_provider_mock.assert_called_once_with(tp_init_mock)
get_tracer_provider_mock.assert_called()
trace_exporter_mock.assert_called_once_with(**configurations)
bsp_mock.assert_called_once_with(trace_exp_init_mock)
tp_init_mock.add_span_processor.assert_called_once_with(bsp_init_mock)
bsp_mock.assert_has_calls(
[
call(trace_exp_init_mock),
call(custom_exporter_mock1),
call(custom_exporter_mock2),
]
)
tp_init_mock.add_span_processor.assert_has_calls(
[call(bsp_init_mock), call(bsp_init_mock), call(bsp_init_mock)]
)

@patch(
"azure.monitor.opentelemetry._configure._get_extra_exporters",
)
@patch(
"azure.monitor.opentelemetry._configure.getLogger",
)
Expand Down Expand Up @@ -246,6 +268,7 @@ def test_setup_logging(
blrp_mock,
logging_handler_mock,
get_logger_mock,
extra_exporters_mock,
):
lp_init_mock = Mock()
lp_mock.return_value = lp_init_mock
Expand All @@ -258,6 +281,12 @@ def test_setup_logging(
logging_handler_mock.return_value = logging_handler_init_mock
logger_mock = Mock()
get_logger_mock.return_value = logger_mock
custom_exporter_mock1 = Mock()
custom_exporter_mock2 = Mock()
extra_exporters_mock.return_value = [
custom_exporter_mock1,
custom_exporter_mock2,
]

configurations = {
"connection_string": "test_cs",
Expand All @@ -269,11 +298,15 @@ def test_setup_logging(
set_logger_provider_mock.assert_called_once_with(lp_init_mock)
get_logger_provider_mock.assert_called()
log_exporter_mock.assert_called_once_with(**configurations)
blrp_mock.assert_called_once_with(
log_exp_init_mock, schedule_delay_millis=10000
blrp_mock.assert_has_calls(
[
call(log_exp_init_mock, schedule_delay_millis=10000),
call(custom_exporter_mock1, schedule_delay_millis=10000),
call(custom_exporter_mock2, schedule_delay_millis=10000),
]
)
lp_init_mock.add_log_record_processor.assert_called_once_with(
blrp_init_mock
lp_init_mock.add_log_record_processor.assert_has_calls(
[call(blrp_init_mock), call(blrp_init_mock), call(blrp_init_mock)]
)
logging_handler_mock.assert_called_once_with(
logger_provider=lp_init_mock
Expand All @@ -283,6 +316,9 @@ def test_setup_logging(
logging_handler_init_mock
)

@patch(
"azure.monitor.opentelemetry._configure._get_extra_exporters",
)
@patch(
"azure.monitor.opentelemetry._configure.PeriodicExportingMetricReader",
)
Expand All @@ -302,24 +338,41 @@ def test_setup_metrics(
set_meter_provider_mock,
metric_exporter_mock,
reader_mock,
extra_exporters_mock,
):
mp_init_mock = Mock()
mp_mock.return_value = mp_init_mock
metric_exp_init_mock = Mock()
metric_exporter_mock.return_value = metric_exp_init_mock
reader_init_mock = Mock()
reader_mock.return_value = reader_init_mock
custom_exporter_mock1 = Mock()
custom_exporter_mock2 = Mock()
extra_exporters_mock.return_value = [
custom_exporter_mock1,
custom_exporter_mock2,
]

configurations = {
"connection_string": "test_cs",
}
_setup_metrics(configurations)
mp_mock.assert_called_once_with(
metric_readers=[reader_init_mock],
metric_readers=[
reader_init_mock,
reader_init_mock,
reader_init_mock,
],
)
set_meter_provider_mock.assert_called_once_with(mp_init_mock)
metric_exporter_mock.assert_called_once_with(**configurations)
reader_mock.assert_called_once_with(metric_exp_init_mock)
reader_mock.assert_has_calls(
[
call(metric_exp_init_mock),
call(custom_exporter_mock1),
call(custom_exporter_mock2),
]
)

@patch(
"azure.monitor.opentelemetry._configure.get_dist_dependency_conflicts"
Expand Down Expand Up @@ -395,3 +448,32 @@ def test_setup_instrumentations_exception(
ep_mock.load.assert_called_once()
instrumentor_mock.instrument.assert_not_called()
logger_mock.warning.assert_called_once()

@patch.dict(
"os.environ",
{
"EXPORTER_ENV_VAR": "custom_exporter1,azure_monitor_opentelemetry_exporter,custom_exporter2"
},
)
@patch("azure.monitor.opentelemetry._configure.iter_entry_points")
def test_extra_exporters(self, iter_mock):
ep_mock1 = Mock()
ep_mock1.name = "custom_exporter1"
exp_mock1 = Mock()
ep_mock1.load.return_value = exp_mock1
ep_mock_azmon = Mock()
ep_mock_azmon.name = "azure_monitor_opentelemetry_exporter"
exp_mock_azmon = Mock()
ep_mock_azmon.load.return_value = exp_mock_azmon
ep_mock2 = Mock()
ep_mock2.name = "custom_exporter2"
exp_mock2 = Mock()
ep_mock2.load.return_value = exp_mock2
iter_mock.return_value = (ep_mock_azmon, ep_mock2, ep_mock1)
exporter_entry_point_group = "exporter_entry_point_group"
self.assertEquals(
_get_extra_exporters(
exporter_entry_point_group, "EXPORTER_ENV_VAR"
),
[exp_mock2(), exp_mock1()],
)