Skip to content
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
1 change: 1 addition & 0 deletions .changelog/5724.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-sdk`: make `Logger.enabled()` respect the configured minimum severity and trace-based filtering
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import logging

from opentelemetry._logs import SeverityNumber as SDKSeverityNumber
from opentelemetry._logs import set_logger_provider
from opentelemetry.configuration._common import (
_map_compression,
Expand Down Expand Up @@ -239,24 +240,19 @@ def _create_log_record_processor(
def _to_logger_config(config: LoggerConfigConfig | None) -> _LoggerConfig:
"""Map an experimental per-logger config to an SDK ``_LoggerConfig``.

Only ``enabled`` is honored. ``minimum_severity`` and ``trace_based`` are
accepted by the config schema but not supported by the Python SDK
``_LoggerConfig``; the ones that are set are ignored with a warning naming
them. An absent ``enabled`` leaves the logger enabled.
Absent fields use the defaults defined by the Logs SDK specification.
"""
if config is None:
return _LoggerConfig.default()
unsupported_fields = [
field_name for field_name in ("minimum_severity", "trace_based") if getattr(config, field_name) is not None
]
if unsupported_fields:
_logger.warning(
"Ignoring logger_configurator fields that are not supported by the Python SDK LoggerProvider: %s",
", ".join(unsupported_fields),
)
if config.enabled is None:
return _LoggerConfig.default()
return _LoggerConfig(is_enabled=config.enabled)
return _LoggerConfig(
is_enabled=config.enabled if config.enabled is not None else True,
minimum_severity=(
SDKSeverityNumber[config.minimum_severity.name.upper()]
if config.minimum_severity is not None
else SDKSeverityNumber.UNSPECIFIED
),
trace_based=config.trace_based if config.trace_based is not None else False,
)


def _create_logger_configurator(
Expand Down
38 changes: 11 additions & 27 deletions opentelemetry-configuration/tests/test_logger_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import unittest
from unittest.mock import MagicMock, patch

from opentelemetry._logs import SeverityNumber as SDKSeverityNumber
from opentelemetry._logs import get_logger_provider
from opentelemetry.configuration._logger_provider import (
_DEFAULT_EXPORT_TIMEOUT_MILLIS,
Expand Down Expand Up @@ -608,7 +609,7 @@ def test_absent_enabled_defaults_to_enabled(self):
provider = create_logger_provider(config)
self.assertTrue(self._enabled(provider, "any.scope"))

def test_unsupported_minimum_severity_logs_warning(self):
def test_minimum_severity_is_applied(self):
config = LoggerProviderConfig(
processors=[],
logger_configurator_development=LoggerConfiguratorConfig(
Expand All @@ -618,21 +619,14 @@ def test_unsupported_minimum_severity_logs_warning(self):
),
),
)
with self.assertLogs(
"opentelemetry.configuration._logger_provider",
level="WARNING",
) as cm:
create_logger_provider(config)
self.assertTrue(
any("minimum_severity" in msg for msg in cm.output),
"Expected warning about unsupported minimum_severity",
)
self.assertFalse(
any("trace_based" in msg for msg in cm.output),
"Warning must not name trace_based when it is not set",
provider = create_logger_provider(config)

self.assertEqual(
provider._apply_logger_configurator(InstrumentationScope("any.scope")).minimum_severity,
SDKSeverityNumber.WARN,
)

def test_unsupported_trace_based_logs_warning(self):
def test_trace_based_is_applied(self):
config = LoggerProviderConfig(
processors=[],
logger_configurator_development=LoggerConfiguratorConfig(
Expand All @@ -642,19 +636,9 @@ def test_unsupported_trace_based_logs_warning(self):
),
),
)
with self.assertLogs(
"opentelemetry.configuration._logger_provider",
level="WARNING",
) as cm:
create_logger_provider(config)
self.assertTrue(
any("trace_based" in msg for msg in cm.output),
"Expected warning about unsupported trace_based",
)
self.assertFalse(
any("minimum_severity" in msg for msg in cm.output),
"Warning must not name minimum_severity when it is not set",
)
provider = create_logger_provider(config)

self.assertTrue(provider._apply_logger_configurator(InstrumentationScope("any.scope")).trace_based)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
from opentelemetry.trace import (
format_span_id,
format_trace_id,
get_current_span,
)
from opentelemetry.util.types import AnyValue, Attributes

Expand Down Expand Up @@ -728,6 +729,8 @@ def flush(self) -> None:
@dataclass
class _LoggerConfig:
is_enabled: bool = True
minimum_severity: SeverityNumber = SeverityNumber.UNSPECIFIED
trace_based: bool = False

@classmethod
def default(cls) -> _LoggerConfig:
Expand Down Expand Up @@ -770,6 +773,16 @@ def enabled(
) -> bool:
if not self._is_enabled():
return False
if (
severity_number is not None
and severity_number is not SeverityNumber.UNSPECIFIED
and severity_number.value < self._logger_config.minimum_severity.value
):
return False
if self._logger_config.trace_based:
span_context = get_current_span(context).get_span_context()
if span_context.is_valid and not span_context.trace_flags.sampled:
return False
return self._multi_log_record_processor.enabled(
context=context,
instrumentation_scope=self._instrumentation_scope,
Expand Down
70 changes: 70 additions & 0 deletions opentelemetry-sdk/tests/logs/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
_scope_name_matches_glob,
)
from opentelemetry.semconv.attributes import exception_attributes
from opentelemetry.trace import (
NonRecordingSpan,
SpanContext,
TraceFlags,
set_span_in_context,
)


class TestLoggerProvider(unittest.TestCase):
Expand Down Expand Up @@ -503,6 +509,70 @@ def test_enabled_disabled_logger_returns_false(self):
logger = provider.get_logger("test")
self.assertFalse(logger.enabled())

def test_enabled_below_minimum_severity_returns_false(self):
logger, processor = self._get_logger()
logger._set_logger_config(_LoggerConfig(minimum_severity=SeverityNumber.WARN))

self.assertFalse(logger.enabled(severity_number=SeverityNumber.INFO4))
processor.enabled.assert_not_called()

def test_enabled_at_or_above_minimum_severity_returns_true(self):
logger, processor = self._get_logger()
logger._set_logger_config(_LoggerConfig(minimum_severity=SeverityNumber.WARN))
processor.enabled.return_value = True

self.assertTrue(logger.enabled(severity_number=SeverityNumber.WARN))
self.assertTrue(logger.enabled(severity_number=SeverityNumber.ERROR))

def test_enabled_unspecified_severity_bypasses_minimum_severity(self):
logger, processor = self._get_logger()
logger._set_logger_config(_LoggerConfig(minimum_severity=SeverityNumber.WARN))
processor.enabled.return_value = True

self.assertTrue(logger.enabled())
self.assertTrue(logger.enabled(severity_number=SeverityNumber.UNSPECIFIED))

def test_enabled_trace_based_unsampled_trace_returns_false(self):
logger, processor = self._get_logger()
logger._set_logger_config(_LoggerConfig(trace_based=True))
context = set_span_in_context(
NonRecordingSpan(
SpanContext(
trace_id=1,
span_id=1,
is_remote=False,
trace_flags=TraceFlags.get_default(),
)
)
)

self.assertFalse(logger.enabled(context=context))
processor.enabled.assert_not_called()

def test_enabled_trace_based_sampled_trace_returns_true(self):
logger, processor = self._get_logger()
logger._set_logger_config(_LoggerConfig(trace_based=True))
processor.enabled.return_value = True
context = set_span_in_context(
NonRecordingSpan(
SpanContext(
trace_id=1,
span_id=1,
is_remote=False,
trace_flags=TraceFlags(TraceFlags.SAMPLED),
)
)
)

self.assertTrue(logger.enabled(context=context))

def test_enabled_trace_based_without_trace_returns_true(self):
logger, processor = self._get_logger()
logger._set_logger_config(_LoggerConfig(trace_based=True))
processor.enabled.return_value = True

self.assertTrue(logger.enabled(context=get_current()))

def test_enabled_passes_args_to_processor(self): # pylint: disable=no-self-use
provider = LoggerProvider()
processor_mock = Mock()
Expand Down
Loading