Skip to content

Commit 90991d2

Browse files
authored
Add configuration of logging format and logger name via env var in distro (#42035)
1 parent f4a0fb0 commit 90991d2

File tree

5 files changed

+142
-8
lines changed

5 files changed

+142
-8
lines changed

sdk/monitor/azure-monitor-opentelemetry/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
### Features Added
66

7+
- Add configuring of logging format and logger name via environment variables
8+
([#42035](https://github.com/Azure/azure-sdk-for-python/pull/42035))
9+
710
### Breaking Changes
811

912
### Bugs Fixed

sdk/monitor/azure-monitor-opentelemetry/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ You can use `configure_azure_monitor` to set up instrumentation for your app to
6161
|-------------------|----------------------------------------------------|----------------------|
6262
| `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` |
6363
| `enable_live_metrics` | Enable [live metrics][application_insights_live_metrics] feature. Defaults to `False`. | `N/A` |
64-
| `logging_formatter` | A Python logging [formatter][python_logging_formatter] that will be used to format collected logs. | `N/A` |
65-
| `logger_name` | The name of the [Python logger][python_logger] under which telemetry is collected. Setting this value is imperative so logs created from the SDK itself are not tracked. | `N/A` |
64+
| `logging_formatter` | A Python logging [formatter][python_logging_formatter] that will be used to format collected logs. | `PYTHON_APPLICATIONINSIGHTS_LOGGING_FORMAT` - accepts a STRING field used for formatting, not a [formatter][python_logging_formatter] |
65+
| `logger_name` | The name of the [Python logger][python_logger] under which telemetry is collected. Setting this value is imperative so logs created from the SDK itself are not tracked. | `PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME` |
6666
| `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` |
6767
| `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] |
6868
| `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` |

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
SAMPLER_ARG = "sampler"
3434
TRACE_EXPORTER_NAMES_ARG = "trace_exporter_names"
3535

36+
LOGGER_NAME_ENV_ARG = "PYTHON_APPLICATIONINSIGHTS_LOGGER_NAME"
37+
LOGGING_FORMAT_ENV_ARG = "PYTHON_APPLICATIONINSIGHTS_LOGGING_FORMAT"
38+
3639

3740
# --------------------Diagnostic/status logging------------------------------
3841

sdk/monitor/azure-monitor-opentelemetry/azure/monitor/opentelemetry/_utils/configurations.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
ENABLE_LIVE_METRICS_ARG,
3535
INSTRUMENTATION_OPTIONS_ARG,
3636
LOGGER_NAME_ARG,
37+
LOGGER_NAME_ENV_ARG,
3738
LOGGING_FORMATTER_ARG,
39+
LOGGING_FORMAT_ENV_ARG,
3840
RESOURCE_ARG,
3941
SAMPLING_RATIO_ARG,
4042
SPAN_PROCESSORS_ARG,
@@ -103,14 +105,29 @@ def _default_disable_tracing(configurations):
103105

104106

105107
def _default_logger_name(configurations):
106-
configurations.setdefault(LOGGER_NAME_ARG, "")
107-
108+
if LOGGER_NAME_ARG in configurations:
109+
return
110+
if LOGGER_NAME_ENV_ARG in environ:
111+
configurations[LOGGER_NAME_ARG] = environ[LOGGER_NAME_ENV_ARG]
112+
else:
113+
configurations.setdefault(LOGGER_NAME_ARG, "")
108114

109115
def _default_logging_formatter(configurations):
110116
formatter = configurations.get(LOGGING_FORMATTER_ARG)
111-
if not isinstance(formatter, Formatter):
112-
configurations[LOGGING_FORMATTER_ARG] = None
113-
117+
if formatter:
118+
if not isinstance(formatter, Formatter):
119+
configurations[LOGGING_FORMATTER_ARG] = None
120+
return
121+
elif LOGGING_FORMAT_ENV_ARG in environ:
122+
try:
123+
configurations[LOGGING_FORMATTER_ARG] = Formatter(environ[LOGGING_FORMAT_ENV_ARG])
124+
except Exception as ex: # pylint: disable=broad-exception-caught
125+
_logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug
126+
"Exception occurred when creating logging Formatter from format: %s, %s.",
127+
environ[LOGGING_FORMAT_ENV_ARG],
128+
ex,
129+
)
130+
configurations[LOGGING_FORMATTER_ARG] = None
114131

115132
def _default_resource(configurations):
116133
environ.setdefault(OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, ",".join(_SUPPORTED_RESOURCE_DETECTORS))

sdk/monitor/azure-monitor-opentelemetry/tests/utils/test_configurations.py

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from os import environ
1616
from unittest import TestCase
1717
from unittest.mock import patch
18+
from logging import Formatter
1819

1920
from opentelemetry.instrumentation.environment_variables import (
2021
OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,
@@ -23,13 +24,14 @@
2324
SAMPLING_RATIO_ENV_VAR,
2425
_get_configurations,
2526
)
27+
from azure.monitor.opentelemetry._constants import LOGGER_NAME_ENV_ARG, LOGGING_FORMAT_ENV_ARG
2628
from opentelemetry.environment_variables import (
2729
OTEL_LOGS_EXPORTER,
2830
OTEL_METRICS_EXPORTER,
2931
OTEL_TRACES_EXPORTER,
3032
)
3133
from opentelemetry.sdk.environment_variables import OTEL_EXPERIMENTAL_RESOURCE_DETECTORS
32-
from opentelemetry.sdk.resources import Resource, Attributes
34+
from opentelemetry.sdk.resources import Resource
3335

3436
from azure.monitor.opentelemetry._version import VERSION
3537

@@ -260,3 +262,112 @@ def test_merge_instrumentation_options_extra_args(self, resource_create_mock):
260262
"urllib3": {"enabled": True},
261263
},
262264
)
265+
266+
@patch.dict(
267+
"os.environ",
268+
{
269+
LOGGER_NAME_ENV_ARG: "test_env_logger",
270+
},
271+
clear=True,
272+
)
273+
def test_get_configurations_logger_name_env_var(self):
274+
configurations = _get_configurations()
275+
276+
self.assertEqual(configurations["logger_name"], "test_env_logger")
277+
278+
@patch.dict(
279+
"os.environ",
280+
{
281+
LOGGER_NAME_ENV_ARG: "test_env_logger",
282+
},
283+
clear=True,
284+
)
285+
def test_get_configurations_logger_name_param_overrides_env_var(self):
286+
configurations = _get_configurations(logger_name="test_param_logger")
287+
288+
self.assertEqual(configurations["logger_name"], "test_param_logger")
289+
290+
@patch.dict(
291+
"os.environ",
292+
{
293+
LOGGING_FORMAT_ENV_ARG: "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
294+
},
295+
clear=True,
296+
)
297+
def test_get_configurations_logging_format_env_var(self):
298+
configurations = _get_configurations()
299+
300+
formatter = configurations["logging_formatter"]
301+
self.assertIsNotNone(formatter)
302+
self.assertIsInstance(formatter, Formatter)
303+
# Test that the formatter works correctly with a sample log record
304+
import logging
305+
record = logging.LogRecord(
306+
name="test_logger",
307+
level=logging.INFO,
308+
pathname="test.py",
309+
lineno=1,
310+
msg="test message",
311+
args=(),
312+
exc_info=None,
313+
)
314+
# Type assertion for mypy
315+
assert isinstance(formatter, Formatter)
316+
formatted = formatter.format(record)
317+
self.assertIn("test_logger", formatted)
318+
self.assertIn("INFO", formatted)
319+
self.assertIn("test message", formatted)
320+
321+
@patch.dict(
322+
"os.environ",
323+
{
324+
LOGGING_FORMAT_ENV_ARG: "invalid format %(nonexistent)z",
325+
},
326+
clear=True,
327+
)
328+
@patch("azure.monitor.opentelemetry._utils.configurations._logger")
329+
def test_get_configurations_logging_format_env_var_invalid_format(self, mock_logger):
330+
configurations = _get_configurations()
331+
332+
# Should be None when format is invalid
333+
self.assertIsNone(configurations["logging_formatter"])
334+
# Should log a warning
335+
mock_logger.warning.assert_called_once()
336+
call_args = mock_logger.warning.call_args[0]
337+
self.assertIn("Exception occurred when creating logging Formatter", call_args[0])
338+
self.assertIn("invalid format %(nonexistent)z", call_args[1])
339+
340+
@patch.dict(
341+
"os.environ",
342+
{
343+
LOGGING_FORMAT_ENV_ARG: "%(asctime)s - %(message)s",
344+
},
345+
clear=True,
346+
)
347+
def test_get_configurations_logging_format_param_overrides_env_var(self):
348+
from logging import Formatter
349+
custom_formatter = Formatter("%(levelname)s: %(message)s")
350+
configurations = _get_configurations(logging_formatter=custom_formatter)
351+
352+
# Parameter should override environment variable
353+
self.assertEqual(configurations["logging_formatter"], custom_formatter)
354+
355+
@patch.dict(
356+
"os.environ",
357+
{
358+
LOGGING_FORMAT_ENV_ARG: "%(asctime)s - %(message)s",
359+
},
360+
clear=True,
361+
)
362+
def test_get_configurations_logging_format_invalid_param_uses_env_var(self):
363+
configurations = _get_configurations(logging_formatter="not_a_formatter")
364+
365+
# Invalid parameter should be set to None, but env var should still be used
366+
self.assertIsNone(configurations["logging_formatter"])
367+
368+
@patch.dict("os.environ", {}, clear=True)
369+
def test_get_configurations_logging_format_no_env_var(self):
370+
configurations = _get_configurations()
371+
372+
# Should not have logging_formatter key when no env var is set
373+
self.assertNotIn("logging_formatter", configurations)

0 commit comments

Comments
 (0)