mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Logging handler (#2151)
* logging handler * changelog * remove logging experiment * handler * fix * docs for logging handler
This commit is contained in:
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- `Tree`: `clear`, `reset`
|
||||
- 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
|
||||
- message_pump.app will now set the active app if it is not already set.
|
||||
|
||||
### 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 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.logging.TextualHandler logging handler
|
||||
|
||||
## [0.16.0] - 2023-03-22
|
||||
|
||||
|
||||
1
docs/api/logging.md
Normal file
1
docs/api/logging.md
Normal file
@@ -0,0 +1 @@
|
||||
::: textual.logging
|
||||
@@ -120,5 +120,37 @@ class LogApp(App):
|
||||
|
||||
if __name__ == "__main__":
|
||||
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()
|
||||
```
|
||||
|
||||
@@ -169,6 +169,7 @@ nav:
|
||||
- "api/list_item.md"
|
||||
- "api/list_view.md"
|
||||
- "api/loading_indicator.md"
|
||||
- "api/logging.md"
|
||||
- "api/markdown_viewer.md"
|
||||
- "api/markdown.md"
|
||||
- "api/message_pump.md"
|
||||
|
||||
@@ -142,6 +142,11 @@ class Logger:
|
||||
"""Logs system information."""
|
||||
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)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class LogGroup(Enum):
|
||||
ERROR = 5
|
||||
PRINT = 6
|
||||
SYSTEM = 7
|
||||
LOGGING = 8
|
||||
|
||||
|
||||
class LogVerbosity(Enum):
|
||||
|
||||
32
src/textual/logging.py
Normal file
32
src/textual/logging.py
Normal 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)
|
||||
@@ -156,17 +156,26 @@ class MessagePump(metaclass=MessagePumpMeta):
|
||||
try:
|
||||
return active_app.get()
|
||||
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
|
||||
def is_parent_active(self) -> bool:
|
||||
"""Is the parent active?"""
|
||||
return bool(
|
||||
self._parent and not self._parent._closed and not self._parent._closing
|
||||
)
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user