Question

Jupyter notebook. How to direct the output to a specific cell?

Is there a way to specify the output cell where a function should print its output?

In my specific case, I have some threads running, each with a logger. The logger output is printed on any running cell, interfering with that cell's intended output. Is there a way I can force the logger to print only on cell #1, for example?

 5  213  5
1 Jan 1970

Solution

 6

You could use the following approach:

  • Redirect all log messages in the root logger (which you will get by calling getLogger()) to a QueueHandler to accumulate the log messages in a queue.Queue.
  • In the intended output cell, start a QueueListener that wraps a StreamHandler. The QueueListener, as its name implies, will listen to new items on the logging queue. It will pass new items to the StreamHandler, which will actually print them.

Assuming we want to print below cell 1, this could look as follows:

# Cell 1
import logging, queue, threading, time
from logging.handlers import QueueHandler, QueueListener

log_queue = queue.Queue(-1)

logging.getLogger().addHandler(QueueHandler(log_queue))

listener = QueueListener(log_queue, logging.StreamHandler())
listener.start()

In cell 2, we will simulate some activity:

# Cell 2
def log_activity_1():
    while True:
        logging.getLogger().warning("Activity 1")
        time.sleep(1)

threading.Thread(target=log_activity_1, daemon=True).start()

And likewise, in cell 3:

# Cell 3
def log_activity_2():
    while True:
        logging.getLogger().warning("Activity 2")
        time.sleep(2)

threading.Thread(target=log_activity_2, daemon=True).start()

The output will happen, basically in real time, under the cell that contains the listener.start() call, thus under cell 1 (and only there) in our case. It will look as expected: For each logged "Activity 2", we will see "Activity 1" logged in alternation and approximately twice as often, as we sleep 2 seconds in the former, and 1 second in the latter: jupyter notebook with proposed cells and described output

Once processing has finished, we can stop the QueueListener (either programmatically or manually) via listener.stop() – or rather, we should stop the listener this way, following its documentation: if you don’t call [stop()] before your application exits, there may be some records still left on the queue, which won’t be processed.

2024-07-08
simon

Solution

 3

You can create a wrapper that will redirect anything printed to another cell.

# Cell 1

import logging
from io import StringIO
from IPython.display import display, HTML
import sys

# Set up a StringIO object to capture output
output_capture = StringIO()
ch = logging.StreamHandler(output_capture)
ch.setLevel(logging.INFO)

# Configure the logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(ch)

# Create a display area
display_id = display(HTML(''), display_id=True)

# Function to update the display
def update_display():
    output_contents = output_capture.getvalue()
    display_id.update(HTML(f"<pre>{output_contents}</pre>"))

def capture_output(func):
    def wrapper(*args, **kwargs):
        # Redirect stdout to our StringIO object
        old_stdout = sys.stdout
        sys.stdout = output_capture
        
        try:
            # Call the original function
            result = func(*args, **kwargs)
        finally:
            # Restore stdout
            sys.stdout = old_stdout
            
        # Update the display
        update_display()
        
        return result
    return wrapper

In a subsequent cell:

# Cell 2
@capture_output
def my_function():
    print('Hello world!')

# Now when you call my_function(), anything printed within the function will be displayed in the first cell
my_function()
2024-07-09
Jeremiah Herberg