57 lines
1.5 KiB
Elixir
57 lines
1.5 KiB
Elixir
defmodule ToweropsWeb.Plugs.RemoteIpLogger do
|
|
@moduledoc """
|
|
Plug that extracts the remote IP address and adds it to Logger metadata.
|
|
|
|
When behind a proxy/load balancer (like Traefik in Kubernetes), the real client IP
|
|
is in the X-Forwarded-For or X-Real-IP headers. This plug extracts the IP and
|
|
adds it to Logger metadata so it appears in all logs for the request.
|
|
"""
|
|
|
|
import Plug.Conn
|
|
|
|
require Logger
|
|
|
|
def init(opts), do: opts
|
|
|
|
def call(conn, _opts) do
|
|
remote_ip = get_remote_ip(conn)
|
|
|
|
# Add to Logger metadata so it appears in all logs for this request
|
|
Logger.metadata(remote_ip: remote_ip)
|
|
|
|
conn
|
|
end
|
|
|
|
defp get_remote_ip(conn) do
|
|
# Try X-Forwarded-For first (set by Traefik and other proxies)
|
|
case get_req_header(conn, "x-forwarded-for") do
|
|
[forwarded | _] ->
|
|
# X-Forwarded-For can be a comma-separated list; take the first (original client)
|
|
forwarded
|
|
|> String.split(",")
|
|
|> List.first()
|
|
|> String.trim()
|
|
|
|
[] ->
|
|
get_fallback_ip(conn)
|
|
end
|
|
end
|
|
|
|
defp get_fallback_ip(conn) do
|
|
# Try X-Real-IP header
|
|
case get_req_header(conn, "x-real-ip") do
|
|
[real_ip | _] ->
|
|
real_ip
|
|
|
|
[] ->
|
|
format_remote_ip(conn.remote_ip)
|
|
end
|
|
end
|
|
|
|
defp format_remote_ip({a, b, c, d}), do: "#{a}.#{b}.#{c}.#{d}"
|
|
defp format_remote_ip({a, b, c, d, e, f, g, h}), do: format_ipv6({a, b, c, d, e, f, g, h})
|
|
|
|
defp format_ipv6({a, b, c, d, e, f, g, h}) do
|
|
Enum.map_join([a, b, c, d, e, f, g, h], ":", &Integer.to_string(&1, 16))
|
|
end
|
|
end
|