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`
|
- `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
1
docs/api/logging.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
::: textual.logging
|
||||||
@@ -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()
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
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:
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user