I’m usually interested in understanding the usage of my apps, and I use logging for that. I like to know:
- Are people using Dropdown A or B? Or are they mostly using Slider C?
- What are the most selected option(s) from those components?
- How long does it take to actually execute my callbacks?
- When we moved the Datepicker to the left, did people use it more/less?
This is the first version of a function decorator that does that for any callback (or any other Python function). It takes a timestamp before/after running the function, it gets the module name, function name, and the args with which it was called, and logs them to a file:
def analyze(func):
def wrapper(*args, **kwargs):
t0 = datetime.datetime.now()
result = func(*args, **kwargs)
t1 = datetime.datetime.now()
logging.debug(msg=f'{func.__name__}: [{t0} {t1}] Ran with: {args}')
return result
return wrapper
After defining this function and setting some logging settings, all you have to do is add it as a decorator to the callbacks you want to analyze/track:
@app.callback(Output('single_output', 'children'),
Input('single_dropdown', 'value'))
@analyze # <-- that's it
def display_single_day(day):
return f"You selected {day}"
Running the below sample app and playing with its components a few times produces the following example logs:
[2020-11-25 16:59:14,543] DEBUG in app.display_single_day: [2020-11-25 16:59:14.543916 2020-11-25 16:59:14.543923] Ran with: ('Wed',)
[2020-11-25 16:59:14,545] DEBUG in app.display_multi_color: [2020-11-25 16:59:14.545222 2020-11-25 16:59:14.545227] Ran with: (['blue'],)
[2020-11-25 16:59:20,634] DEBUG in app.display_single_day: [2020-11-25 16:59:20.634769 2020-11-25 16:59:20.634775] Ran with: ('Sun',)
[2020-11-25 16:59:23,890] DEBUG in app.display_single_day: [2020-11-25 16:59:23.890013 2020-11-25 16:59:23.890019] Ran with: ('Tue',)
[2020-11-25 16:59:31,189] DEBUG in app.display_multi_color: [2020-11-25 16:59:31.189847 2020-11-25 16:59:31.189857] Ran with: (['blue', 'green'],)
[2020-11-25 16:59:33,099] DEBUG in app.display_multi_color: [2020-11-25 16:59:33.099130 2020-11-25 16:59:33.099137] Ran with: (['blue', 'green', 'red'],)
[2020-11-25 16:59:34,679] DEBUG in app.display_multi_color: [2020-11-25 16:59:34.679461 2020-11-25 16:59:34.679468] Ran with: (['green', 'red'],)
[2020-11-25 16:59:35,214] DEBUG in app.display_multi_color: [2020-11-25 16:59:35.214155 2020-11-25 16:59:35.214161] Ran with: (['red'],)
[2020-11-25 16:59:37,236] DEBUG in app.display_multi_color: [2020-11-25 16:59:37.236363 2020-11-25 16:59:37.236370] Ran with: (['red', 'yellow'],)
[2020-11-25 16:59:45,214] DEBUG in app.display_single_day: [2020-11-25 16:59:45.214144 2020-11-25 16:59:45.214150] Ran with: ('Sun',)
Since we format those logs the way we want, we can easily extract the relevant information with a regex:
import re
regex = re.compile('\[.*?\] ([A-Z]+) in (.*?)\.(.*?): \[(.*?) (.*?)\] Ran with: (.*)')
log_lines = []
with open('analytics.log') as file:
for line in file:
if regex.findall(line):
log_lines.append(regex.findall(line)[0])
After putting them in a list, we make a DataFrame summarizing the user interactions (args can definitely be imporoved):
logs_df = pd.DataFrame(log_lines, columns=['level', 'module', 'function', 'start', 'end', 'args'])
logs_df
level | module | function | start | end | args |
---|---|---|---|---|---|
DEBUG | app | display_single_day | 2020-11-25 16:59:14.543916 | 2020-11-25 16:59:14.543923 | (‘Wed’,) |
DEBUG | app | display_multi_color | 2020-11-25 16:59:14.545222 | 2020-11-25 16:59:14.545227 | ([‘blue’],) |
DEBUG | app | display_single_day | 2020-11-25 16:59:20.634769 | 2020-11-25 16:59:20.634775 | (‘Sun’,) |
DEBUG | app | display_single_day | 2020-11-25 16:59:23.890013 | 2020-11-25 16:59:23.890019 | (‘Tue’,) |
DEBUG | app | display_multi_color | 2020-11-25 16:59:31.189847 | 2020-11-25 16:59:31.189857 | ([‘blue’, ‘green’],) |
DEBUG | app | display_multi_color | 2020-11-25 16:59:33.099130 | 2020-11-25 16:59:33.099137 | ([‘blue’, ‘green’, ‘red’],) |
DEBUG | app | display_multi_color | 2020-11-25 16:59:34.679461 | 2020-11-25 16:59:34.679468 | ([‘green’, ‘red’],) |
DEBUG | app | display_multi_color | 2020-11-25 16:59:35.214155 | 2020-11-25 16:59:35.214161 | ([‘red’],) |
DEBUG | app | display_multi_color | 2020-11-25 16:59:37.236363 | 2020-11-25 16:59:37.236370 | ([‘red’, ‘yellow’],) |
DEBUG | app | display_single_day | 2020-11-25 16:59:45.214144 | 2020-11-25 16:59:45.214150 | (‘Sun’,) |
Below is a full example:
# app.py
import datetime
import logging
import re
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Output, Input
import pandas as pd
logging.basicConfig(level=logging.DEBUG,
filename='analytics.log',
format='[%(asctime)s] %(levelname)s in %(module)s.%(message)s')
def analyze(func):
def wrapper(*args, **kwargs):
t0 = datetime.datetime.now()
result = func(*args, **kwargs)
t1 = datetime.datetime.now()
logging.debug(msg=f'{func.__name__}: [{t0} {t1}] Ran with: {args}')
return result
return wrapper
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Dropdown(id='single_dropdown',
value='Wed',
options=[{'label': day, 'value': day}
for day in ['Sat', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']]),
html.Div(id='single_output'),
dcc.Dropdown(id='multi_dropdown',
multi=True,
value=['blue'],
options=[{'label': color, 'value': color}
for color in ['blue', 'green', 'yellow', 'orange', 'red']]),
html.Div(id='multi_output'),
])
@app.callback(Output('single_output', 'children'),
Input('single_dropdown', 'value'))
@analyze
def display_single_day(day):
return f"You selected {day}"
@app.callback(Output('multi_output', 'children'),
Input('multi_dropdown', 'value'))
@analyze
def display_multi_color(colors):
return f"You selected these colors: {', '.join(colors)}"
if __name__ == '__main__':
app.run_server(debug=True)
Happy to get feedback or suggestions.
Thanks.