I want to invoke this task from my app.py and display the log messages as the task runs. I found a few examples that redirected stdout but I cannot get them to work, for example
import logging
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
logger = st.logger.get_logger(__name__)
@contextmanager
def stdout_capture(output_func):
with StringIO() as stdout, redirect_stdout(stdout):
old_write = stdout.write
def new_write(string):
ret = old_write(string)
output_func(stdout.getvalue())
return ret
stdout.write = new_write
yield
output = st.empty()
with stdout_capture(output.code):
print("test")
logger.info("test 2")
tasks.run()
This correctly test to output.code but not test 2 or messages from the task module, theses log messages are still appearing in my console and not the output widget.
Iâve considered writing my own log handler but Iâm wondering if thereâs anything obvious Iâve done wrong with my current approach and is this possible / what approaches could I try?
If not Iâll probably use subprocess.Popen and a file log.
From what youâve described, it seems like your main objective is to display logs from task.py in your app as theyâre generated. What youâve tried involves redirecting stdout, which is a common strategy for capturing print statements. But it wonât work for capturing log messages directly, as Pythonâs logging module doesnât use stdout by default for its output.
Since Streamlitâs loggers donât write to stdout (the loggers are set up with a StreamHandler that writes to the console) and do not propagate their messages to the root logger (which was configured to redirect to stdout), log messages were not captured by your stdout_capture context manager.
As youâve suggested, writing your own log handler might be the way to go. You could extend Streamlitâs logger with a custom handler that routes logs to st.empty().code for display, like so:
# app.py
import logging
import task
import streamlit as st
from streamlit.logger import get_logger
class StreamlitLogHandler(logging.Handler):
def __init__(self, widget_update_func):
super().__init__()
self.widget_update_func = widget_update_func
def emit(self, record):
msg = self.format(record)
self.widget_update_func(msg)
logger = get_logger(task.__name__)
handler = StreamlitLogHandler(st.empty().code)
logger.addHandler(handler)
# Run your task
task.run()
# task.py
import logging
logger = logging.getLogger(__name__)
import time
import streamlit as st
def run(*args, **kwargs):
logger.info("đ Running task ")
time.sleep(2)
logger.info("đ„” Still running task")
time.sleep(2)
logger.info("â Finished task")
Breakdown:
Use get_logger from streamlit.logger, which retrieves a Streamlit-configured logger. Itâs already set up with Streamlitâs logging config such as format and level
Define StreamlitLogHandler â a custom logging handler that overrides the emit method. This method is called whenever a log message is sent to the handler
In the emit method, the logs are formatted into a message string, and then passed to the widget_update_func, which in this case is st.empty().code
By adding StreamlitLogHandler to the Streamlit logger (logger.addHandler(handler)), youâre ensuring that any logs processed by this logger are also passed to the custom handler. And since the custom handler is designed to update a Streamlit element, you see the logs in your app
Awesome thanks @snehankekre that works perfectly! I was about to resort to subprocess.Popen (which Iâve done for another process) but donât have to now.
Not sure if itâs required but I added
logger.handlers.clear()
Before adding the handler to ensure I didnât get duplicates (I read about this issue in other posts and other solutions are to use a cache decorator around a function to setup the logger, or add the logger to session state).
PS How is the logging module writing to the console without using stdout (or stderr)? Iâd always assumed that was the only mechanism to write to the console?
Youâre right. Clearing the log handlers is probably a good idea to avoid duplicates due to script reruns.
PS How is the logging module writing to the console without using stdout (or stderr)? Iâd always assumed that was the only mechanism to write to the console?
Itâs writing to stderr. The StreamHandler in Pythonâs logging module sends logs to streams like stderr or stdout or even to files, if configured to do so. When you create a logger and donât specifically configure its handlers, it uses stderr. This is why you see log messages in the console, even though youâre not explicitly writing to stdout. Hereâs where that happens in Streamlitâs logging module:
By default, the StreamHandler in Python logging writes to stderr and not stdout.
This means that even though the log messages are displayed in the console, they are being sent via stderr.
In Streamlitâs case, each logger is configured with this StreamHandler and a specific format, directing its output to stderr. This is why your initial approach with redirecting stdout did not capture these log messages, as they were not being routed through stdout. Additionally, Streamlit loggers do not propagate their log messages to the root logger (logger.propagate = False). This means that any configuration done on the root logger (like redirecting stdout) does not affect Streamlitâs loggers.
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking âAccept allâ, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.