Logging in Dash

Hi all!

I’ve been implementing logging capabilities in Dash for the last weeks and mostly doing so base don my prior experience with the logging library of python. However, I have some questions regarding this being a case for a web app, since my past experience has been in embedded systems were the instances running is just one and there are no “client” and “server” side.

  1. How to differentiate between clients in the logs? specifically the disk file logs. So far I havent been able to think of a good solution to it
  2. Are there any good practices (Dash or Flask specific) in terms of log configuration?

all help in kindly appreciated.

1 Like

I typically assign a session ID to each client. If you just need to distinguish between client in terms of uniqueness, you could log the session ID. Alternatively, you could log a user name or similar if available

2 Likes

Yes this is my approach as well, however the session id get assigned after I’ve configured my logger already and it is (the session_id) only inside the layout.

How do you suggest accessing the s-id? or how to post-session initialization configure the logger?

The details depend on how you want the logs to look, but the basic structure could be something like,

import logging
import secrets

from dash_extensions.enrich import DashProxy, Input, Output, html
from flask import session


def _get_session_id():
    session_key = "session_id"
    if not session.get(session_key):
        session[session_key] = secrets.token_urlsafe(16)
    return session.get(session_key)


def _record_factory(*args, **kwargs):
    record = old_factory(*args, **kwargs)
    try:
        record.session_id = _get_session_id()
    except RuntimeError:
        record.session_id = "NO_ACTIVE_SESSION"
    return record


# Create logger.
logging.basicConfig(format="%(session_id)s - %(message)s")
old_factory = logging.getLogRecordFactory()
logging.setLogRecordFactory(_record_factory)
# Create small demo app.
app = DashProxy()
app.layout = html.Div([
    html.Button("Click me!", id="btn"),
    html.Div(id="log"),
])


@app.callback(Input("btn", "n_clicks"), Output("log", "children"), prevent_initial_call=True)
def log_session_id(n_clicks):
    logging.warning(f"This session has {n_clicks} clicks")
    return f"You clicked {n_clicks} times"


if __name__ == "__main__":
    app.run_server()

Clicking the button in different browser sessions yields logs like,

bCifbl-rZIbnv6at312qgg - This session has 1 clicks
NO_ACTIVE_SESSION - 127.0.0.1 - - [11/Jul/2022 14:50:45] "POST /_dash-update-component HTTP/1.1" 200 -
bCifbl-rZIbnv6at312qgg - This session has 2 clicks
NO_ACTIVE_SESSION - 127.0.0.1 - - [11/Jul/2022 14:50:46] "POST /_dash-update-component HTTP/1.1" 200 -
bCifbl-rZIbnv6at312qgg - This session has 3 clicks
[CHANGE BROWSER]
wgY5fGggshWrLmD4AiosMQ - This session has 1 clicks
NO_ACTIVE_SESSION - 127.0.0.1 - - [11/Jul/2022 14:50:52] "POST /_dash-update-component HTTP/1.1" 200 -
wgY5fGggshWrLmD4AiosMQ - This session has 2 clicks
NO_ACTIVE_SESSION - 127.0.0.1 - - [11/Jul/2022 14:50:53] "POST /_dash-update-component HTTP/1.1" 200 -
wgY5fGggshWrLmD4AiosMQ - This session has 3 clicks

which I guess is more-or-less what you are after? :slight_smile:

4 Likes

@Emil this looks like exactly what I’m after - but I’m struggling to replicate your results.

When I run the below (very similar to your code, though not quite identical)

import logging
import secrets

from dash import Dash, Input, Output, html
from flask import session


def _get_session_id():
    session_key = "session_id"
    if not session.get(session_key):
        session[session_key] = secrets.token_urlsafe(16)
    return session.get(session_key)


def _record_factory(*args, **kwargs):
    record = old_factory(*args, **kwargs)
    try:
        record.session_id = _get_session_id()
    except RuntimeError:
        record.session_id = "NO_ACTIVE_SESSION"
    return record


# Create logger.
logging.basicConfig(format="%(session_id)s - %(message)s")
old_factory = logging.getLogRecordFactory()
logging.setLogRecordFactory(_record_factory)

# Create small demo app.
app = Dash()
app.layout = html.Div([
    html.Button("Click me!", id="btn"),
    html.Div(id="log"),
])


@app.callback(Output("log", "children"), Input("btn", "n_clicks"), prevent_initial_call=True)
def log_session_id(n_clicks):
    logging.warning(f"This session has {n_clicks} clicks")
    return f"You clicked {n_clicks} times"


if __name__ == "__main__":
    app.run_server()

I get the following, which has no session IDs in the log messages:

Am I doing something wrong?

It looks like you forgot to use dash-extensions?

Ah I see, thanks Emil. I originally thought that dash-extensions was an optional install to Dash, rather than another project, and that replacing DashProxy with Dash would be equivalent. Makes a lot more sense now that I’ve looked through the dash-extensions docs!