Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
With it, you do not need to add `structlog.stdlib.PositionalArgumentsFormatter` processor to format positional arguments from *structlog* loggers.
[#668](https://github.com/hynek/structlog/pull/668)

- Native loggers now have `is_enabled_for()` and `get_effective_level()` methods that mirror the behavior of the standard library's `logging.Logger.isEnabledFor()` and `logging.Logger.getEffectiveLevel()`.
[#689](https://github.com/hynek/structlog/pull/689)


## Changed

Expand Down
4 changes: 4 additions & 0 deletions src/structlog/_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ async def alog(
meths["msg"] = meths["info"]
meths["amsg"] = meths["ainfo"]

# Introspection
meths["is_enabled_for"] = lambda self, level: level >= min_level
meths["get_effective_level"] = lambda self: min_level

return type(
f"BoundLoggerFilteringAt{LEVEL_TO_NAME.get(min_level, 'Notset').capitalize()}",
(BoundLoggerBase,),
Expand Down
14 changes: 14 additions & 0 deletions src/structlog/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ def new(self, **new_values: Any) -> FilteringBoundLogger:
.. versionadded:: 22.1.0
"""

def is_enabled_for(self, level: int) -> bool:
"""
Check whether the logger is enabled for *level*.

.. versionadded:: 25.1.0
"""

def get_effective_level(self) -> int:
"""
Return the effective level of the logger.

.. versionadded:: 25.1.0
"""

def debug(self, event: str, *args: Any, **kw: Any) -> Any:
"""
Log ``event % args`` with **kw** at **debug** level.
Expand Down
18 changes: 17 additions & 1 deletion tests/test_log_levels.py → tests/test_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,23 @@ def _bl(cl):
return make_filtering_bound_logger(logging.INFO)(cl, [], {})


class TestFilteringLogger:
class TestNativeFilteringLogger:
def test_is_enabled_for(self, bl):
"""
is_enabled_for returns True if the log level is enabled.
"""
assert bl.is_enabled_for(20)
assert bl.is_enabled_for(logging.INFO)

assert not bl.is_enabled_for(19)
assert not bl.is_enabled_for(logging.DEBUG)

def test_get_effective_level(self, bl):
"""
get_effective_level returns the log level.
"""
assert 20 == logging.INFO == bl.get_effective_level()

def test_exact_level(self, bl, cl):
"""
if log level is exactly the min_level, log.
Expand Down
15 changes: 7 additions & 8 deletions tests/typing/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,10 @@ def bytes_dumps(
processors=[structlog.processors.JSONRenderer(serializer=bytes_dumps)]
)

structlog.configure(
processors=[
structlog.stdlib.render_to_log_args_and_kwargs,
],
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)

structlog.configure(
processors=[
structlog.stdlib.render_to_log_args_and_kwargs,
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
Expand Down Expand Up @@ -359,3 +352,9 @@ def typecheck_bound_logger_return() -> None:

fbl: FilteringBoundLogger = structlog.get_logger()
fbl.info("Hello %s! The answer is %d.", "World", 42, x=1)


# Introspection
level: int = fbl.get_effective_level()
is_active: bool = fbl.is_enabled_for(logging.INFO)
is_active = fbl.is_enabled_for(20)
Loading