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/5380.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `enabled()` support to the Logger API, SDK, and `LogRecordProcessor` to let instrumentation skip expensive work when logging is disabled
39 changes: 39 additions & 0 deletions opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,23 @@ def emit(
) -> None:
"""Emits a :class:`LogRecord` representing a log to the processing pipeline."""

def enabled( # pylint: disable=no-self-use
self,
*,
context: Context | None = None,
severity_number: SeverityNumber | None = None,
event_name: str | None = None,
) -> bool:
"""Returns whether the logger is enabled for the given arguments.

Instrumentation should call this before performing expensive work to
construct a log record, and skip that work if ``False`` is returned.

The returned value may change over time and should be checked each time
before emitting a log record.
"""
return True


class NoOpLogger(Logger):
"""The default Logger used when no Logger implementation is available.
Expand Down Expand Up @@ -219,6 +236,15 @@ def emit(
) -> None:
pass

def enabled(
self,
*,
context: Context | None = None,
severity_number: SeverityNumber | None = None,
event_name: str | None = None,
) -> bool:
return False


class ProxyLogger(Logger):
def __init__( # pylint: disable=super-init-not-called
Expand Down Expand Up @@ -300,6 +326,19 @@ def emit(
exception=exception,
)

def enabled(
self,
*,
context: Context | None = None,
severity_number: SeverityNumber | None = None,
event_name: str | None = None,
) -> bool:
return self._logger.enabled(
context=context,
severity_number=severity_number,
event_name=event_name,
)


class LoggerProvider(ABC):
"""
Expand Down
30 changes: 30 additions & 0 deletions opentelemetry-api/tests/logs/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,33 @@ def test_proxy_logger_forwards_record_with_exception(self):
logger.emit(record)

logger._real_logger.emit.assert_called_once_with(record)

def test_proxy_logger_enabled_delegates_to_real_logger(self):
logger = _logs_internal.ProxyLogger("proxy-test")
real_logger = Mock(spec=LoggerTest("proxy-test"))
real_logger.enabled.return_value = True
logger._real_logger = real_logger

result = logger.enabled(
severity_number=_logs.SeverityNumber.INFO, event_name="test"
)

self.assertTrue(result)
real_logger.enabled.assert_called_once_with(
context=None,
severity_number=_logs.SeverityNumber.INFO,
event_name="test",
)

def test_proxy_logger_enabled_falls_back_to_noop(self):
logger = _logs_internal.ProxyLogger("proxy-test")
self.assertFalse(logger.enabled())

def test_noop_logger_enabled_returns_false(self):
logger = _logs.NoOpLogger("noop-test")
self.assertFalse(logger.enabled())
self.assertFalse(
logger.enabled(
severity_number=_logs.SeverityNumber.ERROR, event_name="e"
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,21 @@ def on_emit(self, log_record: ReadWriteLogRecord) -> None:
on error handling expectations.
"""

def enabled( # pylint: disable=no-self-use
self,
*,
context: Context | None = None,
instrumentation_scope: InstrumentationScope | None = None,
severity_number: SeverityNumber | None = None,
event_name: str | None = None,
) -> bool:
"""Returns whether this processor would emit a record for the given arguments.

Processors that support filtering MAY override this method. The default
implementation returns ``True``.
"""
return True # pylint: disable=unused-argument

@abc.abstractmethod
def shutdown(self) -> None:
"""Called when a :class:`opentelemetry.sdk._logs.Logger` is shutdown"""
Expand Down Expand Up @@ -392,6 +407,27 @@ def on_emit(self, log_record: ReadWriteLogRecord) -> None:
for lp in self._log_record_processors:
lp.on_emit(log_record)

def enabled(
self,
*,
context: Context | None = None,
instrumentation_scope: InstrumentationScope | None = None,
severity_number: SeverityNumber | None = None,
event_name: str | None = None,
) -> bool:
processors = self._log_record_processors
if not processors:
return False
return any(
lp.enabled(
context=context,
instrumentation_scope=instrumentation_scope,
severity_number=severity_number,
event_name=event_name,
)
for lp in processors
)

def shutdown(self) -> None:
"""Shutdown the log processors one by one"""
for lp in self._log_record_processors:
Expand Down Expand Up @@ -466,6 +502,27 @@ def _submit_and_wait(
def on_emit(self, log_record: ReadWriteLogRecord) -> None:
self._submit_and_wait(lambda lp: lp.on_emit, log_record)

def enabled(
self,
*,
context: Context | None = None,
instrumentation_scope: InstrumentationScope | None = None,
severity_number: SeverityNumber | None = None,
event_name: str | None = None,
) -> bool:
processors = self._log_record_processors
if not processors:
return False
return any(
lp.enabled(
context=context,
instrumentation_scope=instrumentation_scope,
severity_number=severity_number,
event_name=event_name,
)
for lp in processors
)

def shutdown(self) -> None:
self._submit_and_wait(lambda lp: lp.shutdown)

Expand Down Expand Up @@ -687,6 +744,22 @@ def __init__(
def _is_enabled(self) -> bool:
return self._logger_config.is_enabled

def enabled(
self,
*,
context: Context | None = None,
severity_number: SeverityNumber | None = None,
event_name: str | None = None,
) -> bool:
if not self._is_enabled():
return False
return self._multi_log_record_processor.enabled(
context=context,
instrumentation_scope=self._instrumentation_scope,
severity_number=severity_number,
event_name=event_name,
)

def _set_logger_config(self, logger_config: _LoggerConfig) -> None:
self._logger_config = logger_config

Expand Down
59 changes: 59 additions & 0 deletions opentelemetry-sdk/tests/logs/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,62 @@ def test_emit_readwrite_logrecord_uses_exception(self):
self.assertEqual(
attributes[exception_attributes.EXCEPTION_TYPE], "RuntimeError"
)

def test_enabled_with_no_processors_returns_false(self):
provider = LoggerProvider()
logger = provider.get_logger("test")
self.assertFalse(logger.enabled())

def test_enabled_with_processor_returns_true(self):
provider = LoggerProvider()
provider.add_log_record_processor(Mock())
logger = provider.get_logger("test")
self.assertTrue(logger.enabled())

def test_enabled_disabled_logger_returns_false(self):
provider = LoggerProvider(
_logger_configurator=_disable_logger_configurator
)
provider.add_log_record_processor(Mock())
logger = provider.get_logger("test")
self.assertFalse(logger.enabled())

def test_enabled_passes_args_to_processor(self): # pylint: disable=no-self-use
provider = LoggerProvider()
processor_mock = Mock()
processor_mock.enabled.return_value = True
provider.add_log_record_processor(processor_mock)
logger = provider.get_logger("test")

logger.enabled(
severity_number=SeverityNumber.INFO, event_name="my.event"
)

processor_mock.enabled.assert_called_once_with(
context=None,
instrumentation_scope=logger._instrumentation_scope,
severity_number=SeverityNumber.INFO,
event_name="my.event",
)

def test_enabled_all_processors_disabled_returns_false(self):
provider = LoggerProvider()
p1 = Mock()
p1.enabled.return_value = False
p2 = Mock()
p2.enabled.return_value = False
provider.add_log_record_processor(p1)
provider.add_log_record_processor(p2)
logger = provider.get_logger("test")
self.assertFalse(logger.enabled())

def test_enabled_one_processor_enabled_returns_true(self):
provider = LoggerProvider()
p1 = Mock()
p1.enabled.return_value = False
p2 = Mock()
p2.enabled.return_value = True
provider.add_log_record_processor(p1)
provider.add_log_record_processor(p2)
logger = provider.get_logger("test")
self.assertTrue(logger.enabled())
Loading