Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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 sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added
Comment thread
rads-1996 marked this conversation as resolved.

- Added custom log record processors configuration option
([#43713](https://github.com/Azure/azure-sdk-for-python/pull/43713))
- Performance Counters
([#43262](https://github.com/Azure/azure-sdk-for-python/pull/43262))
- Adding more diagnostic log message IDs
Expand Down
2 changes: 2 additions & 0 deletions sdk/monitor/azure-monitor-opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ You can use `configure_azure_monitor` to set up instrumentation for your app to
| `instrumentation_options` | A nested dictionary that determines which instrumentations to enable or disable. Instrumentations are referred to by their [Library Names](#officially-supported-instrumentations). For example, `{"azure_sdk": {"enabled": False}, "flask": {"enabled": False}, "django": {"enabled": True}}` will disable Azure Core Tracing and the Flask instrumentation but leave Django and the other default instrumentations enabled. The `OTEL_PYTHON_DISABLED_INSTRUMENTATIONS` environment variable explained below can also be used to disable instrumentations. | `N/A` |
| `resource` | Specifies the OpenTelemetry [Resource][ot_spec_resource] associated with your application. Passed in [Resource Attributes][ot_spec_resource_attributes] take priority over default attributes and those from [Resource Detectors][ot_python_resource_detectors]. | [OTEL_SERVICE_NAME][ot_spec_service_name], [OTEL_RESOURCE_ATTRIBUTES][ot_spec_resource_attributes], [OTEL_EXPERIMENTAL_RESOURCE_DETECTORS][ot_python_resource_detectors] |
| `span_processors` | A list of [span processors][ot_span_processor] that will perform processing on each of your spans before they are exported. Useful for filtering/modifying telemetry. | `N/A` |
| `log_record_processors` | A list of [log record processors][ot_log_record_processor] that will add custom attributes to the logs before they are exported. Useful for modifying telemetry. | `N/A` |
Comment thread
rads-1996 marked this conversation as resolved.
Outdated
| `views` | A list of [views][ot_view] that will be used to customize metrics exported by the SDK. | `N/A` |
| `traces_per_second` | Configures the Rate Limited sampler by specifying the maximum number of traces to sample per second. When set, this automatically enables the rate-limited sampler. Alternatively, you can configure sampling using the `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG` environment variables as described in the table below. Please note that the sampling configuration via environment variables will have precedence over the sampling exporter/distro options. | `N/A`

Expand Down Expand Up @@ -231,6 +232,7 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
[ot_sdk_python]: https://github.com/open-telemetry/opentelemetry-python
[ot_sdk_python_metric_reader]: https://opentelemetry-python.readthedocs.io/en/latest/sdk/metrics.export.html#opentelemetry.sdk.metrics.export.MetricReader
[ot_span_processor]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#span-processor
[ot_log_record_processor]: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/logs/sdk/md#log-record-processor
Comment thread
rads-1996 marked this conversation as resolved.
Outdated
[ot_view]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view
[ot_sdk_python_view_examples]: https://github.com/open-telemetry/opentelemetry-python/tree/main/docs/examples/metrics/views
[ot_instrumentation_django]: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-django
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
SAMPLING_TRACES_PER_SECOND_ARG,
SPAN_PROCESSORS_ARG,
VIEWS_ARG,
LOG_RECORD_PROCESSORS_ARG,
)
from azure.monitor.opentelemetry._types import ConfigurationValue
from azure.monitor.opentelemetry.exporter._quickpulse import ( # pylint: disable=import-error,no-name-in-module
Expand Down Expand Up @@ -101,6 +102,8 @@ def configure_azure_monitor(**kwargs) -> None: # pylint: disable=C4758
Attributes take priority over default attributes and those from Resource Detectors.
:keyword list[~opentelemetry.sdk.trace.SpanProcessor] span_processors: List of `SpanProcessor` objects
to process every span prior to exporting. Will be run sequentially.
:keyword list[~opentelemetry.sdk._logs.LogRecordProcessor] log_record_processors: List of `LogRecordProcessor` objects
to process every log record prior to exporting. Will be run sequentially.
:keyword bool enable_live_metrics: Boolean value to determine whether to enable live metrics feature.
Defaults to `False`.
:keyword bool enable_performance_counters: Boolean value to determine whether to enable performance counters.
Expand Down Expand Up @@ -212,6 +215,8 @@ def _setup_logging(configurations: Dict[str, ConfigurationValue]):
resource: Resource = configurations[RESOURCE_ARG] # type: ignore
enable_performance_counters_config = configurations[ENABLE_PERFORMANCE_COUNTERS_ARG]
logger_provider = LoggerProvider(resource=resource)
for custom_log_record_processor in configurations[LOG_RECORD_PROCESSORS_ARG]: # type: ignore
logger_provider.add_log_record_processor(custom_log_record_processor) # type: ignore
if configurations.get(ENABLE_LIVE_METRICS_ARG):
qlp = _QuickpulseLogRecordProcessor()
logger_provider.add_log_record_processor(qlp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
RATE_LIMITED_SAMPLER = "microsoft.rate_limited"
FIXED_PERCENTAGE_SAMPLER = "microsoft.fixed.percentage"
SAMPLING_TRACES_PER_SECOND_ARG = "traces_per_second"
LOG_RECORD_PROCESSORS_ARG = "log_record_processors"

# --------------------Autoinstrumentation Configuration------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
VIEWS_ARG,
RATE_LIMITED_SAMPLER,
FIXED_PERCENTAGE_SAMPLER,
LOG_RECORD_PROCESSORS_ARG,
)
from azure.monitor.opentelemetry._types import ConfigurationValue
from azure.monitor.opentelemetry._version import VERSION
Expand Down Expand Up @@ -76,6 +77,7 @@ def _get_configurations(**kwargs) -> Dict[str, ConfigurationValue]:
_default_sampling_ratio(configurations)
_default_instrumentation_options(configurations)
_default_span_processors(configurations)
_default_log_record_processors(configurations)
_default_enable_live_metrics(configurations)
_default_enable_performance_counters(configurations)
_default_views(configurations)
Expand Down Expand Up @@ -217,6 +219,8 @@ def _default_instrumentation_options(configurations):
def _default_span_processors(configurations):
configurations.setdefault(SPAN_PROCESSORS_ARG, [])

def _default_log_record_processors(configurations):
configurations.setdefault(LOG_RECORD_PROCESSORS_ARG, [])

def _default_enable_live_metrics(configurations):
configurations.setdefault(ENABLE_LIVE_METRICS_ARG, False)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License in the project root for
# license information.
# --------------------------------------------------------------------------

import logging
from logging import getLogger
from azure.monitor.opentelemetry import configure_azure_monitor
from opentelemetry import trace
from opentelemetry.sdk._logs import LogRecordProcessor, LogData
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys

logger = getLogger(__name__)
logger.setLevel(logging.INFO)



class LogRecordEnrichingProcessor(LogRecordProcessor):
"""
A log record processor that enriches log records with operation name
Comment thread
rads-1996 marked this conversation as resolved.
Outdated
from the current span context.
"""

def force_flush(self, timeout_millis: int = 30000) -> bool:
"""Force flush any pending log records."""
return True

def shutdown(self) -> None:
"""Shutdown the processor."""
pass

def on_emit(self, log_data: LogData) -> None:
"""
Enrich the log record with operation name from the current span.

Args:
log_data: The log data containing log record and context information
"""
# Get the current span from the current context (not from trace_context which might be None)
current_span = trace.get_current_span()

if current_span and hasattr(current_span, 'name') and current_span.name:
# Add the operation name to log record attributes
if log_data.log_record.attributes is None:
log_data.log_record.attributes = {}
log_data.log_record.attributes[ContextTagKeys.AI_OPERATION_NAME] = current_span.name


# Create the log record enriching processor
log_enriching_processor = LogRecordEnrichingProcessor()

# Configure Azure Monitor with the custom log record processor
configure_azure_monitor(
log_record_processors=[log_enriching_processor]
)

# Your application code here
tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("span-name-here"):
logger.info("This log will be enriched with operation name")

input()
15 changes: 11 additions & 4 deletions sdk/monitor/azure-monitor-opentelemetry/tests/test_configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ def test_setup_logging(self, get_logger_mock, pclp_mock):
logging_handler_mock = Mock()
elp_mock = Mock()
set_elp_mock = Mock()
clrp_mock = Mock()

lp_init_mock = Mock()
lp_mock.return_value = lp_init_mock
Expand All @@ -523,19 +524,23 @@ def test_setup_logging(self, get_logger_mock, pclp_mock):
logging_handler_init_mock = Mock()
logging_handler_mock.return_value = logging_handler_init_mock
logger_mock = Mock()
custom_lrp = Mock()
logger_mock.handlers = []
get_logger_mock.return_value = logger_mock
formatter_init_mock = Mock()
elp_init_mock = Mock()
elp_mock.return_value = elp_init_mock
clrp_init_mock = Mock()
clrp_mock.return_value = clrp_init_mock
pclp_init_mock = Mock()
pclp_mock.return_value = pclp_init_mock
configurations = {
"connection_string": "test_cs",
"enable_performance_counters": True,
"logger_name": "test",
"resource": TEST_RESOURCE,
"logging_formatter": formatter_init_mock
"logging_formatter": formatter_init_mock,
"log_record_processors": [custom_lrp],
}

# Patch all the necessary modules and imports
Expand All @@ -557,8 +562,8 @@ def test_setup_logging(self, get_logger_mock, pclp_mock):
set_logger_provider_mock.assert_called_once_with(lp_init_mock)
log_exporter_mock.assert_called_once_with(**configurations)
blrp_mock.assert_called_once_with(log_exp_init_mock)
self.assertEqual(lp_init_mock.add_log_record_processor.call_count, 2)
lp_init_mock.add_log_record_processor.assert_has_calls([call(pclp_init_mock), call(blrp_init_mock)])
self.assertEqual(lp_init_mock.add_log_record_processor.call_count, 3)
lp_init_mock.add_log_record_processor.assert_has_calls([call(custom_lrp), call(pclp_init_mock), call(blrp_init_mock)])
logging_handler_mock.assert_called_once_with(logger_provider=lp_init_mock)
logging_handler_init_mock.setFormatter.assert_called_once_with(formatter_init_mock)
get_logger_mock.assert_called_once_with("test")
Expand Down Expand Up @@ -606,6 +611,7 @@ def test_setup_logging_duplicate_logger(self, get_logger_mock, instance_mock, pc
"logger_name": "test",
"resource": TEST_RESOURCE,
"logging_formatter": None,
"log_record_processors": [],
}

# Patch all the necessary modules and imports
Expand Down Expand Up @@ -666,7 +672,8 @@ def test_setup_logging_disable_performance_counters(self, get_logger_mock, pclp_
"enable_performance_counters": False,
"logger_name": "test",
"resource": TEST_RESOURCE,
"logging_formatter": formatter_init_mock
"logging_formatter": formatter_init_mock,
"log_record_processors": [],
}

# Patch all the necessary modules and imports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def test_get_configurations(self, resource_create_mock):
views=["test_view"],
logger_name="test_logger",
span_processors=["test_processor"],
log_record_processors=["test_log_record_processor"],
)

self.assertEqual(configurations["connection_string"], "test_cs")
Expand Down Expand Up @@ -107,6 +108,7 @@ def test_get_configurations(self, resource_create_mock):
self.assertEqual(configurations["views"], ["test_view"])
self.assertEqual(configurations["logger_name"], "test_logger")
self.assertEqual(configurations["span_processors"], ["test_processor"])
self.assertEqual(configurations["log_record_processors"], ["test_log_record_processor"])

@patch.dict("os.environ", {}, clear=True)
@patch("opentelemetry.sdk.resources.Resource.create", return_value=TEST_DEFAULT_RESOURCE)
Expand Down Expand Up @@ -140,6 +142,7 @@ def test_get_configurations_defaults(self, resource_create_mock):
self.assertEqual(configurations["enable_performance_counters"], True)
self.assertEqual(configurations["logger_name"], "")
self.assertEqual(configurations["span_processors"], [])
self.assertEqual(configurations["log_record_processors"], [])
self.assertEqual(configurations["views"], [])

@patch.dict(
Expand Down
Loading