Skip to content

Introduce register_log_level/reset_log_levels - #835

Draft
sirosen wants to merge 1 commit into
hynek:mainfrom
sirosen:register-log-levels
Draft

Introduce register_log_level/reset_log_levels#835
sirosen wants to merge 1 commit into
hynek:mainfrom
sirosen:register-log-levels

Conversation

@sirosen

@sirosen sirosen commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This is open as a draft for initial feedback. I want to confirm whether or not the overall approach is acceptable before working on fleshing out documentation and refining the work further.

This PR is a potential solution for #47.

These two methods, housed in a dedicated module, call out to register custom log levels across various other components. Registering a log level does the following:

  • confirm the level and name are good
  • add the level and name to mappings
  • add a relevant named method to structlog-provided loggers
  • add the log level to level-based filtering bound loggers

The reset method inverts these steps, removing from these same registered locations in reverse order.

Names which are already used by structlog are forbidden, so no special checking or "undo rules" are needed for the reset.

Pull Request Checklist

  • I acknowledge this project's AI policy.
  • This pull request is not from my main branch.
  • There's tests for all new and changed code.
  • New APIs are added to our typing tests in api.py.
  • Updated documentation for changed code.
    • New functions/classes have to be added to docs/api.rst by hand.
    • Changed/added classes/methods/functions have appropriate versionadded, versionchanged, or deprecated directives.
      • The next version is the second number in the current release + 1. The first number represents the current year. So if the current version on PyPI is 26.1.0, the next version is gonna be 26.2.0. If the next version is the first in the new year, it'll be 27.1.0.
  • Documentation in .rst and .md files is written using semantic newlines.
  • Changes (and possible deprecations) are documented in the changelog.

These two methods, housed in a dedicated module, call out to register
custom log levels across various other components. Registering a log
level does the following:

- confirm the level and name are good
- add the level and name to mappings
- add a relevant named method to structlog-provided loggers
- add the log level to level-based filtering bound loggers

The reset method inverts these steps, removing from these same registered
locations in reverse order.

Names which are already used by `structlog` are forbidden, so no special
checking or "undo rules" are needed for the reset.
@codspeed-hq

codspeed-hq Bot commented Aug 1, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 10 untouched benchmarks


Comparing sirosen:register-log-levels (b4ea53e) with main (bf3cfd0)

Open in CodSpeed

@sirosen sirosen left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done a self-review to note my high-level questions and concerns.


while CUSTOM_LOG_LEVELS:
level = CUSTOM_LOG_LEVELS.pop()
del LEVEL_TO_NAME[level]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a new module the right place for all of this? stdlib didn't feel right to me.

# NOTSET is a special 0-valued level, but not a method name
"notset",
)
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these names are to be forbidden, I think statically listing them is more legible than trying to be fancy/dynamic. But there's a risk of drift...
Plus, forbidding the names means that adding a new method to this list could be a breaking change! Yuck!

This is one of the things I winced at.

Comment thread src/structlog/_native.py
meths: dict[str, Callable[..., Any]] = {"log": log, "alog": alog}
for lvl, name in LEVEL_TO_NAME.items():
meths[name], meths[f"a{name}"] = make_method(lvl)
meths[name], meths[f"a{name}"] = _make_method(min_level, lvl)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff is illegible, but all I did is pop out make_method, add min_level to its args, and dedent it. I needed that for the usage below.

Comment thread src/structlog/_native.py
for bound_logger in LEVEL_TO_FILTERING_LOGGER.values():
delattr(bound_logger, level_name)
delattr(bound_logger, f"a{level_name}")
del LEVEL_TO_FILTERING_LOGGER[level]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, these methods could fully rebuild the LEVEL_TO_FILTERING_LOGGER table each time? But that's a lot of repeat work each time a log level gets registered.

Comment thread src/structlog/_output.py
def remove_custom_level_method(level_name: str, /) -> None:
"""Remove all registered log level methods."""
for logger_class in (BytesLogger, PrintLogger, WriteLogger):
delattr(logger_class, level_name)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it's slightly more manageable if some of the responsibility here is passed to the component modules. You can see a similar bit of logic added to _native, but this is more clearly isolated.

As above though, we have to patch with setattr/delattr and there's a real feeling here that it would be very easy to make a mistake. e.g., Add a new logger class but not add it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant