Saving session state to for debugging

I’m developing a pretty complex Streamlit app. It has many fragments. Development has been going well. But after adding several new features the app is crashing without error messages. Casual debugging with print statements has not been helpful.

Today I realized that a great debug strategy would be to run the function to be debugged independently of the rest of the app … if I could save the session state. I thought that should be easy, but I can’t do it. Not with pickle. Checking the discussion pages, I saw cloudpickle and joblib recommended as alternatives. They don’t work either. I get the same basic error:

streamlit.errors.StreamlitAPIException: _getstate_() is not a valid Streamlit command.

Based on other comments I tried a number of things, including turning the session state into a dictionary, saving dict items separately, and trying to enforce serialization. Nothing helped. Either I got this error or I got an empty dump file.

This shouldn’t be so hard! Any suggestions?

What data do you have in Session State? If everything is serializable you can just write your own function to export and import the data to something like a csv. (Some loop like for key in st.session_stateto loop through and grab all your keys, values, and types so that you can reconstruct them from saved text.)

1 Like

I do think everything is serializable! It’s text, numpy arrays (images), and maybe some floats. They are stored in dictionaries and lists. CSVs wouldn’t be easy for the arrays! It seems like you’re telling me that binary objects can’t be saved. Is that right?

You may try to use pickle or dill to serialize. And maybe convert numpy to list and so on.

I think this is reasonably easy. st.session_state functions like a dictionary. Streamlit logs are downloadable. So simply dump your session state to your log file:

session_state_dump = {key: value for key, value in st.session_state.items()}
logger.info(f"Session snapshot: {session_state_dump}")

If you’d like to prettify it:

logger.info(f"JSON snapshot: {json.dumps(session_state_dump, default=str, indent=4)}")

Here’s a fully working implementation of an app.py file you can run to see how it works.

import streamlit as st
import logging
from datetime import datetime
import json

def configure_logger() -> logging.Logger:
    logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s ] %(message)s", handlers=[logging.FileHandler("debug.log", mode="a"), logging.StreamHandler()])
    return logging.getLogger(__name__)

logger = configure_logger()
logger.info("Starting Streamlit session")

def main():
    st.title("Debugging Streamlit Session State")
    if "start_time" not in st.session_state:    
        st.session_state["start_time"] = datetime.now()

    st.write(f"Elapsed: {datetime.now() - st.session_state.start_time}")
    st.session_state['sample_list'] = [1, 2, 3, 4, 5]
    st.session_state['sample_dict'] = {'a': 1, 'b': 2, 'c': 3}
    st.session_state['sample_tuple'] = (1, 2, 3, 4, 5)
    st.session_state['sample_set'] = {1, 2, 3, 4, 5}
    st.session_state['sample_string'] = "Hello, World!"

    session_state_dump = {key: value for key, value in st.session_state.items()}
    logger.info(f"Session snapshot: {session_state_dump}")
    # To make it pretty:
    logger.info(f"JSON snapshot: {json.dumps(session_state_dump, default=str, indent=4)}")

    reset_session_state = st.button("Reset Session State")
    
    if reset_session_state:
        st.session_state.clear()

if __name__ == "__main__":
    main()