How to log client IP addresses?

How is this done within a Dash app? I tried and failed to get it working using a combination of app.server.route('/') and flask.request.remote_addr. Thanks!

You can access the IP address a request originates from within the corresponding callback like so:

from flask import request

@app.callback(Output('target', 'children'), [Input('input', 'value')])
def get_ip(value):
    return html.Div(request.remote_addr)
6 Likes

Thanks for the quick reply @nedned. I knew my lack of flask experience had me dancing around the answer.

No worries :slight_smile:

I think the main trick is that you only have access to the request global in an actual request context, which for Dash is in a callback.

1 Like
import socket
print (socket.gethostbyname("www.goole.com"))

More on…ip address

Sorry to dig this up but it’s basically the only hit when looking for the topic…

How does that solution work? What is input or the value, respectively? They’re never used. When is the callback triggered?

I couldn’t get it to work in a minimal setting that should just print the ip address.

accessing flask.request.remote_addr will work anywhere you’re inside a Flask Request Context. In Dash, this means you can access it anywhere you’re inside a callback function. And that’s it, nothing about the input or output values of the callback matter. It’s up to you what you want to trigger the callback and do with the IP address.

If you want to create a minimal proof of concept, just make something simple like clicking a button returning the IP address to a placeholder div.

1 Like

So it’s not possible to have this be part of the layout function already?
It has to be within a callback?

I’ll come back with an M(non)WE once I’m back at my machine.

EDIT: This somewhat does it:

from dash import Dash
from dash_bootstrap_components.themes import BOOTSTRAP

from dash import Dash, html, callback, Output, Input
from flask import request

@callback(
    Output('IP', 'children'),
    Input('btn-1', 'n_clicks')
)
def ip(_: int) -> html.Div:
    req: dict = request.__dict__.copy()
    return html.Div(
        children=[
            html.Div(f"{key}: {val}") for key, val in req.items()
        ]
    )

def create_layout(app: Dash) -> html.Div:
    return html.Div(
        className="app-div",  # type: ignore
        children=[
            html.H1(app.title),
            html.Hr(),
            html.Button('Button 1', id='btn-1'),
            html.Div(id="IP"),
        ],
    )

app = Dash(
    __name__,
    suppress_callback_exceptions=True,
    #use_pages=True,
    external_stylesheets=[BOOTSTRAP],
)
app.title = "TEST"
app.layout = create_layout(app)


def main(app: Dash) -> None:
    app.run(debug=True, dev_tools_ui=True, dev_tools_hot_reload=True)


if (__name__ == "__main__"):
    main(app)

However, this requires an option to be present that triggers the callback - a button in this example.
Can this be done without such an option right at call time but not after?
Calling the function in the layout, i.e., html.Div(id="IP", children=ip(0)), breaks the request context (why even? we usually can call functions that generate a layout, can’t we?).

EDIT:
Can a callback have no input? somewhat deals with the same issue which makes it cleaner looking than my button solution:

from typing import Any

@callback(
    Output('IP', 'children'),
    Input('none', 'children')
)
def ip(_: Any) -> html.Div:
    ...

and in the layout:

...
html.Hr(),
            html.Div(
                id='none',
                children=[],
                style={'display': 'none'},
            ),
            html.Div(id="IP"),
...

But it still should be able to get this done without a callback shouldn’t it?

Hello @gothicVI,

You can use something like a div as the input and output:

@app.callback(
Output('id','id'),
Input('id','id')
)
def pull_IP(n):
    ### code
    return dasn.no_update

This in theory should only run once in your app, or when this div is displayed. :slight_smile:

2 Likes