'Redirect print and/or logging to Panel
I have made a small application that uses a Rich to show a Live view in several Panels.
Is there a way to put standard (or rich) print
statements into a particular panel?
Being able to show logging output in its own dedicated panel would also work.
I feel like this would be a very common use case but I haven't found any documentation. I think the answer could be in using the Console.capture()
method but I can't figure it out.
Solution 1:[1]
I figure out how you can manage to that. First, we need to intercept the stdout process of Rich logger. We start with a class:
from collections import deque
class Logger():
_instance = None
def __init__(self):
self.messages = deque(["sa"])
self.size = 10
def __new__(class_, *args, **kwargs):
if not isinstance(class_._instance, class_):
class_._instance = object.__new__(class_, *args, **kwargs)
return class_._instance
def write(self, message):
self.messages.extend(message.splitlines())
while len(self.messages) > self.size:
self.messages.popleft()
def flush(self):
pass
which is a Singleton class. We need to pass this class into console API as
from rich.console import Console
c = Console(file=Logger(), width=150)
with some width. Then, we create a logging handler
from rich.logging import RichHandler
r = RichHandler(console=c)
This will be our logging handler as
import logging
logging.basicConfig(
level="NOTSET", format=FORMAT, datefmt="[%X]", handlers=[r]
)
logger = logging.getLogger("rich")
Later on, we need to use our Logger class called somewhere you manage your layout. For me, it is inside a Dashboard class.
class Dashboard:
def __init__(self):
self.log_std = Logger()
def update(self, new_parameters):
self.layout["logs"].update(Panel(Text(
"\n".join(self.log_std.messages), justify="right"), padding=(1, 2),
title="[b red]Logs",
))
Each time I call update
method, it updates my layout.
My layout is more complex, self.layout["logs"]
where I show the logs.
Solution 2:[2]
So I took @Mete Yildirim's answer as inspiration and came up with a slight variation that uses an existing logging
handler instead of creating a new logger.
The logging.handlers module has a BufferingHandler() that I used for prototyping my solution. By passing the handler into rich
, I can snoop its contents and print them into my rich
Panel without mods to logging
.
LOG_BUFFER_MAX_MSGS = 20
# Set up the main/root logger
main_logger = logging.getLogger()
main_logger.setLevel(logging.DEBUG)
# Instantiate the buffering handler we will watch from within Rich
buffering_handler = BufferingHandler(capacity=LOG_BUFFER_MAX_MSGS)
main_logger.addHandler(buffering_handler)
# Local logger
log = logging.getLogger("rich")
# Create a basic Rich layout
layout = Layout(name="root")
def get_log():
"""
We call this method from within Rich to snoop the messages
within the BufferingHandler and put them in a form Rich
can display.
Check the BufferingHandler() source to see how we can access
its data.
"""
log_messages = []
for li in buffering_handler.buffer:
log_messages.append(li.msg)
return Panel("\n".join(log_messages))
# Run Rich, displaying each log message to the screen
with Live(layout, refresh_per_second=4) as live:
while True:
layout["root"].update(get_log())
time.sleep(0.25)
To test the above, we can generate some log messages in the background:
def create_log_messages():
msgs = (
"Test message 1",
"Test message 2",
"Test message 3",
)
for li in msgs:
log.info(li)
time.sleep(2)
threading.Thread(target=create_log_messages).start()
Shortcomings:
The BufferingHandler() will clear it's buffer when the number of lines exceeds the value passed into its capacity
argument.
I would prefer that it delete old messages instead, but that will require either overloading the existing BufferHandler() implementation or writing a new Handler. The BufferHandler() code is short, so simply writing a new one shouldn't be too much effort.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Mete Yıldırım |
Solution 2 | JS. |