Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
LoggerJSON Formatters
defmodule Web.LoggerPlugFormatter do
@moduledoc """
Formats connection into Logger metadata:
* `connection.type` - type of connection (Sent or Chunked);
* `connection.method` - HTTP request method;
* `connection.request_path` - HTTP request path;
* `connection.request_id` - value of `X-Request-ID` response header (see `Plug.RequestId`);
* `connection.status` - HTTP status code sent to a client;
* `connection.params` - HTTP filtered params;
* `client.user_agent` - value of `User-Agent` header;
* `client.ip' - value of `X-Forwarded-For` header if present, otherwise - remote IP of a connected client;
* `client.api_version' - version of API that was requested by a client;
* `node.hostname` - system hostname;
* `node.pid` - Erlang VM process identifier;
* `phoenix.controller` - Phoenix controller that processed the request;
* `phoenix.action` - Phoenix action that processed the request;
* `latency_μs` - time in microseconds taken to process the request.
"""
import Jason.Helpers, only: [json_map: 1]
@doc false
def build_metadata(conn, latency, client_version_header) do
# credo:disable-for-next-line
latency_μs = System.convert_time_unit(latency, :native, :microsecond)
user_agent = LoggerJSON.Plug.get_header(conn, "user-agent")
ip = remote_ip(conn)
api_version = LoggerJSON.Plug.get_header(conn, client_version_header)
{hostname, vm_pid} = node_metadata()
phoenix_metadata(conn) ++
[
connection:
json_map(
type: connection_type(conn),
method: conn.method,
request_path: conn.request_path,
status: conn.status,
params: fetch_params(conn.params)
),
client:
json_map(
user_agent: user_agent,
ip: ip,
api_version: api_version
),
node: json_map(hostname: to_string(hostname), vm_pid: vm_pid),
latency_μs: latency_μs
]
end
defp connection_type(%{state: :set_chunked}), do: "chunked"
defp connection_type(_), do: "sent"
defp remote_ip(conn) do
LoggerJSON.Plug.get_header(conn, "x-forwarded-for") ||
to_string(:inet_parse.ntoa(conn.remote_ip))
end
defp phoenix_metadata(%{private: %{phoenix_controller: controller, phoenix_action: action}}) do
[phoenix: %{controller: controller, action: action}]
end
defp phoenix_metadata(_conn) do
[]
end
defp node_metadata do
{:ok, hostname} = :inet.gethostname()
vm_pid =
case Integer.parse(System.get_pid()) do
{pid, _units} -> pid
_ -> nil
end
{hostname, vm_pid}
end
defp fetch_params(%Plug.Conn.Unfetched{aspect: :params}), do: %{}
defp fetch_params(params), do: discard_values(params, ["password"])
# https://github.com/phoenixframework/phoenix/blob/v1.4.16/lib/phoenix/logger.ex#L73-L91
defp discard_values(%{__struct__: mod} = struct, _params) when is_atom(mod) do
struct
end
defp discard_values(%{} = map, params) do
Enum.into(map, %{}, fn {k, v} ->
if is_binary(k) and String.contains?(k, params) do
{k, "[FILTERED]"}
else
{k, discard_values(v, params)}
end
end)
end
defp discard_values([_ | _] = list, params) do
Enum.map(list, &discard_values(&1, params))
end
defp discard_values(other, _params), do: other
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment