Streamlit Debugging Tricks: A handy guide to building a sticky debug div for debug.print calls

Hi all!

As not-a-developer, I often find myself inserting debugging statements like st.write(some_variable) throughout my code. This can be suboptimal, so I created a proof-of-concept for a “debug.print” command that allows me to directly print debug information in my Streamlit app :slight_smile:

Idea: create a div that which will stick to the bottom and contain debugging info. Include time & line of code from which debug command was triggered. Save history and be able to scroll.

Example: https://stdebug.streamlit.app/
Github: GitHub - TomJohnH/streamlit-debug

Code elements:

  1. Very simple external module st_debug.py
  2. External css style debug.css

Usage

  1. Create python file st_debug.py (or use the function directely)
import streamlit as st
import datetime
from inspect import getframeinfo, stack

def debug(input):
    if "debug_string" not in st.session_state:
        st.session_state["debug_string"] = "<b>Debug window ☝️</b>"
    now = datetime.datetime.now()
    st.session_state["debug_string"] = (
        "<div style='border-bottom: dotted; border-width: thin;border-color: #cccccc;'>"
        + str(now.hour)
        + ":"
        + str(now.minute)
        + ":"
        + str(now.second)
        + " Debug.print["
        + str(getframeinfo(stack()[1][0]).lineno)
        + "] "
        + str(input)
        + "</div>"
        + st.session_state["debug_string"]
    )
  1. Create external css file:
.debug {
    padding-left: 10px;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    max-height: 200px;
    background-color: #333;
    color: #ccc;
    width: 100%;
    overflow: auto;
    font-family: monospace;
}
  1. Import module:
import st_debug as d
  1. Load css:
def local_css(file_name):
    with open(file_name) as f:
        st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)

local_css("debug.css")
  1. At the bottom of your code make additional div:
if "debug_string" in st.session_state:
    st.markdown(
        f'<div class="debug">{ st.session_state["debug_string"]}</div>',
        unsafe_allow_html=True,
    )

Write debugging commands:

a = np.matrix("1 2; 3 4")
d.debug("this presents state of the matrix " + str(a))

or

if "counter" not in st.session_state:
    st.session_state["counter"] = 1

d.debug("counter " + str(st.session_state["counter"]))
  1. That’s all :slight_smile: Above method is easily reusable in other apps (in fact you have to only import external file, load css and you are ready to go)



BONUS ROUND:

Let’s say that you like the div but you would like to toggle it’s visibility with CTRL + Q shortcut.

  1. Expand st_debug.py file with function storing js code that will be responsible for div toggle and reaction to key presses:
def js_code():
    return """
    <script>
    function myFunction() {
    
    var x = window.parent.document.getElementById("debug");
    if (x.style.display === "none") {
        x.style.display = "block";
    } else {
        x.style.display = "none";
    }
    }

    function KeyPress(e) {
        var evtobj = window.event? event : e
        if (evtobj.keyCode == 81 && evtobj.ctrlKey) myFunction();
    }

    const doc = window.parent.document;
    doc.onkeydown = KeyPress;

    </script>
    """
  1. import streamlit.components.v1 in your main app file
import streamlit.components.v1 as components
  1. Below the “debug div” add js script using components.html
components.html(
    d.js_code(),
    height=0,
    width=0,
)

Now you can toggle the debug div with CTRL+Q keys combination.

  1. Happy streamliting!
7 Likes

Looks super cool! We’ve been thinking about having something like this built-in for a long time, maybe with some additional features like a profiler or a view on what’s in your session state and cache.

1 Like

Hi @jrieke, thank you for the comment. Having a build in “watcher” would be very usefull! :slight_smile:

For anyone interested: as my example is somewhat quick and dirty PoC, direct d.debug(st.session.state) would not look good (session state saves value of "debug_string" variable that is used to print out debugg info). If you need to print out session state info you can create dictionary without "debug_string"

session_state_without_debug = {}
for key in st.session_state:
    if key != "debug_string":
        session_state_without_debug[key] = st.session_state[key]

d.debug(str(session_state_without_debug))