Skip to content
Closed
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/

## [Unreleased](https://github.com/hynek/structlog/compare/25.5.0...HEAD)

### Added

- `structlog.remove_file_from_lock()` to release the write lock that is implicitly registered for a file by `PrintLogger`, `WriteLogger`, and `BytesLogger`.
Useful when the underlying file is being closed.


### Removed

- Python 3.8 and 3.9 support.
Expand Down
2 changes: 2 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ API Reference

.. autoclass:: BytesLoggerFactory

.. autofunction:: remove_file_from_lock

.. autoexception:: DropEvent

.. autoclass:: BoundLoggerBase
Expand Down
2 changes: 2 additions & 0 deletions src/structlog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
PrintLoggerFactory,
WriteLogger,
WriteLoggerFactory,
remove_file_from_lock,
)
from structlog.exceptions import DropEvent
from structlog.testing import ReturnLogger, ReturnLoggerFactory
Expand Down Expand Up @@ -79,6 +80,7 @@
"is_configured",
"make_filtering_bound_logger",
"processors",
"remove_file_from_lock",
"reset_defaults",
"stdlib",
"testing",
Expand Down
13 changes: 13 additions & 0 deletions src/structlog/_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ def _get_lock_for_file(file: IO[Any]) -> threading.Lock:
return lock


def remove_file_from_lock(file: IO[Any]) -> None:
"""
Remove the write lock associated with *file* from the registry.

Useful when *file* is being closed and you want to release the lock that
was implicitly created for it by `PrintLogger`, `WriteLogger`, or
`BytesLogger`. A no-op if no lock is registered for *file*.

.. versionadded:: 26.1.0
"""
WRITE_LOCKS.pop(file, None)


class PrintLogger:
"""
Print events into a file.
Expand Down
12 changes: 12 additions & 0 deletions tests/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
PrintLoggerFactory,
WriteLogger,
WriteLoggerFactory,
remove_file_from_lock,
)
from structlog._output import WRITE_LOCKS, stderr, stdout

Expand Down Expand Up @@ -68,6 +69,17 @@ def test_lock(self, logger_cls, sio):

assert sio in WRITE_LOCKS

def test_remove_file_from_lock(self, logger_cls, sio):
"""
remove_file_from_lock removes the lock entry for the given file.
"""
logger_cls(sio)
assert sio in WRITE_LOCKS

remove_file_from_lock(sio)

assert sio not in WRITE_LOCKS

@pytest.mark.parametrize("method", stdlib_log_methods)
def test_stdlib_methods_support(self, logger_cls, method, sio):
"""
Expand Down
5 changes: 5 additions & 0 deletions tests/typing/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from __future__ import annotations

import io
import logging
import logging.config

Expand Down Expand Up @@ -412,3 +413,7 @@ def f() -> None:

cr.repr_native_str
cr.repr_native_str = True

_f = io.StringIO()
structlog.PrintLogger(_f)
structlog.remove_file_from_lock(_f)