Logging handler (#2151)

* logging handler

* changelog

* remove logging experiment

* handler

* fix

* docs for logging handler
This commit is contained in:
Will McGugan
2023-03-28 11:50:28 +01:00
committed by GitHub
parent d787f61090
commit 73f065bbbd
8 changed files with 86 additions and 4 deletions

View File

@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `Tree`: `clear`, `reset` - `Tree`: `clear`, `reset`
- Screens with alpha in their background color will now blend with the background. https://github.com/Textualize/textual/pull/2139 - Screens with alpha in their background color will now blend with the background. https://github.com/Textualize/textual/pull/2139
- Added "thick" border style. https://github.com/Textualize/textual/pull/2139 - Added "thick" border style. https://github.com/Textualize/textual/pull/2139
- message_pump.app will now set the active app if it is not already set.
### Added ### Added
@@ -36,7 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added scroll_end switch to TextLog.write https://github.com/Textualize/textual/pull/2127 - Added scroll_end switch to TextLog.write https://github.com/Textualize/textual/pull/2127
- Added Screen.ModalScreen which prevents App from handling bindings. https://github.com/Textualize/textual/pull/2139 - Added Screen.ModalScreen which prevents App from handling bindings. https://github.com/Textualize/textual/pull/2139
- Added TEXTUAL_LOG env var which should be a path that Textual will write verbose logs to (textual devtools is generally preferred) https://github.com/Textualize/textual/pull/2148 - Added TEXTUAL_LOG env var which should be a path that Textual will write verbose logs to (textual devtools is generally preferred) https://github.com/Textualize/textual/pull/2148
- Added textual.logging.TextualHandler logging handler
## [0.16.0] - 2023-03-22 ## [0.16.0] - 2023-03-22

1
docs/api/logging.md Normal file
View File

@@ -0,0 +1 @@
::: textual.logging

View File

@@ -120,5 +120,37 @@ class LogApp(App):
if __name__ == "__main__": if __name__ == "__main__":
LogApp().run() LogApp().run()
```
### Logging handler
Textual has a [logging handler][textual.logging.TextualHandler] which will write anything logged via the builtin logging library to the devtools.
This may be useful if you have a third-party library that uses the logging module, and you want to see those logs with Textual logs.
!!! note
The logging library works with strings only, so you won't be able to log Rich renderables such as `self.tree` with the logging handler.
Here's an example of configuring logging to use the `TextualHandler`.
```python
import logging
from textual.app import App
from textual.logging import TextualHandler
logging.basicConfig(
level="NOTSET",
handlers=[TextualHandler()],
)
class LogApp(App):
"""Using logging with Textual."""
def on_mount(self) -> None:
logging.debug("Logged via TextualHandler")
if __name__ == "__main__":
LogApp().run()
``` ```

View File

@@ -169,6 +169,7 @@ nav:
- "api/list_item.md" - "api/list_item.md"
- "api/list_view.md" - "api/list_view.md"
- "api/loading_indicator.md" - "api/loading_indicator.md"
- "api/logging.md"
- "api/markdown_viewer.md" - "api/markdown_viewer.md"
- "api/markdown.md" - "api/markdown.md"
- "api/message_pump.md" - "api/message_pump.md"

View File

@@ -142,6 +142,11 @@ class Logger:
"""Logs system information.""" """Logs system information."""
return Logger(self._log, LogGroup.SYSTEM) return Logger(self._log, LogGroup.SYSTEM)
@property
def logging(self) -> Logger:
"""Logs from stdlib logging module."""
return Logger(self._log, LogGroup.LOGGING)
log = Logger(None) log = Logger(None)

View File

@@ -12,6 +12,7 @@ class LogGroup(Enum):
ERROR = 5 ERROR = 5
PRINT = 6 PRINT = 6
SYSTEM = 7 SYSTEM = 7
LOGGING = 8
class LogVerbosity(Enum): class LogVerbosity(Enum):

32
src/textual/logging.py Normal file
View File

@@ -0,0 +1,32 @@
import sys
from logging import Handler, LogRecord
from ._context import active_app
class TextualHandler(Handler):
"""A Logging handler for Textual apps."""
def __init__(self, stderr: bool = True, stdout: bool = False) -> None:
"""Initialize a Textual logging handler.
Args:
stderr: Log to stderr when there is no active app.
stdout: Log to stdout when there is not active app.
"""
super().__init__()
self._stderr = stderr
self._stdout = stdout
def emit(self, record: LogRecord) -> None:
"""Invoked by logging."""
message = self.format(record)
try:
app = active_app.get()
except LookupError:
if self._stderr:
print(message, file=sys.stderr)
elif self._stdout:
print(message, file=sys.stdout)
else:
app.log.logging(message)

View File

@@ -156,17 +156,26 @@ class MessagePump(metaclass=MessagePumpMeta):
try: try:
return active_app.get() return active_app.get()
except LookupError: except LookupError:
raise NoActiveAppError() from .app import App
node: MessagePump | None = self
while not isinstance(node, App):
if node is None:
raise NoActiveAppError()
node = node._parent
active_app.set(node)
return node
@property @property
def is_parent_active(self) -> bool: def is_parent_active(self) -> bool:
"""Is the parent active?"""
return bool( return bool(
self._parent and not self._parent._closed and not self._parent._closing self._parent and not self._parent._closed and not self._parent._closing
) )
@property @property
def is_running(self) -> bool: def is_running(self) -> bool:
"""bool: Is the message pump running (potentially processing messages).""" """Is the message pump running (potentially processing messages)."""
return self._running return self._running
@property @property