Log client IP from X-Forwarded-For in request logs

Add RemoteIp plug that extracts real client IP from X-Forwarded-For
header (set by nginx/dokku proxy) and adds it to Logger metadata.
This commit is contained in:
Graham McIntire 2026-03-31 12:05:05 -05:00
parent c709fb5c32
commit 45fb9736fa
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
3 changed files with 50 additions and 1 deletions

View file

@ -20,7 +20,7 @@ config :esbuild,
# Configure Elixir's Logger
config :logger, :default_formatter,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
metadata: [:request_id, :remote_ip]
# Configure the mailer
#

View file

@ -41,6 +41,7 @@ defmodule MicrowavepropWeb.Endpoint do
cookie_key: "request_logger"
plug Plug.RequestId
plug MicrowavepropWeb.Plugs.RemoteIp
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
plug Plug.Parsers,

View file

@ -0,0 +1,48 @@
defmodule MicrowavepropWeb.Plugs.RemoteIp do
@moduledoc """
Extracts the client IP from X-Forwarded-For (set by nginx/dokku proxy)
and assigns it to conn.remote_ip and Logger metadata.
"""
@behaviour Plug
@impl true
def init(opts), do: opts
@impl true
def call(conn, _opts) do
ip =
case Plug.Conn.get_req_header(conn, "x-forwarded-for") do
[forwarded | _] ->
forwarded
|> String.split(",")
|> hd()
|> String.trim()
|> parse_ip()
_ ->
nil
end
conn =
if ip do
%{conn | remote_ip: ip}
else
conn
end
Logger.metadata(remote_ip: format_ip(conn.remote_ip))
conn
end
defp parse_ip(str) do
case :inet.parse_address(String.to_charlist(str)) do
{:ok, ip} -> ip
_ -> nil
end
end
defp format_ip({a, b, c, d}), do: "#{a}.#{b}.#{c}.#{d}"
defp format_ip({a, b, c, d, e, f, g, h}), do: "#{a}:#{b}:#{c}:#{d}:#{e}:#{f}:#{g}:#{h}"
defp format_ip(_), do: "unknown"
end