Current section

Files

Jump to
plug_accesslog lib plug accesslog formatter.ex
Raw

lib/plug/accesslog/formatter.ex

defmodule Plug.AccessLog.Formatter do
@moduledoc """
Log message formatter.
"""
use Timex
import Plug.Conn
@format_agent "%{User-Agent}i"
@format_clf "%h %l %u %t \"%r\" %>s %b"
@format_clf_vhost "%v " <> @format_clf
@format_combined "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
@format_combined_vhost "%v " <> @format_combined
@format_referer "%{Referer}i -> %U"
@doc """
Formats a log message.
The `:default` format is `:clf`.
The following formatting directives are available:
- `%b` - Size of response in bytes
- `%h` - Remote hostname
- `%{VARNAME}i` - Header line sent by the client
- `%l` - Remote logname
- `%r` - First line of HTTP request
- `%>s` - Response status code
- `%t` - Time the request was received in the format `[10/Jan/2015:14:46:18 +0100]`
- `%u` - Remote user
- `%U` - URL path requested (without query string)
- `%v` - Server name
**Note for %b**: To determine the size of the response the "Content-Length"
(exact case match required for now!) will be inspected and, if available,
returned unverified. If the header is not present the response body will be
inspected using `byte_size/1`.
**Note for %h**: The hostname will always be the ip of the client.
**Note for %l**: Always a dash ("-").
**Note for %r**: For now the http version is always logged as "HTTP/1.1",
regardless of the true http version.
"""
@spec format(format :: atom | String.t, conn :: Plug.Conn.t) :: String.t
def format(nil, conn), do: format(:clf, conn)
def format(:default, conn), do: format(:clf, conn)
def format(:agent, conn), do: format(@format_agent, conn)
def format(:clf, conn), do: format(@format_clf, conn)
def format(:clf_vhost, conn), do: format(@format_clf_vhost, conn)
def format(:combined, conn), do: format(@format_combined, conn)
def format(:combined_vhost, conn), do: format(@format_combined_vhost, conn)
def format(:referer, conn), do: format(@format_referer, conn)
def format(format, conn) when is_binary(format) do
format(format, conn, "")
end
# Internal construction methods
defp format(<< "%b", rest :: binary >>, conn, message) do
content_length = case get_resp_header(conn, "Content-Length") do
[ length ] -> length
_ -> (conn.resp_body || "") |> byte_size()
end
if 0 == content_length do
content_length = "-"
end
format(rest, conn, message <> to_string(content_length))
end
defp format(<< "%h", rest :: binary >>, conn, message) do
remote_ip = conn.remote_ip |> :inet_parse.ntoa() |> to_string()
format(rest, conn, message <> remote_ip)
end
defp format(<< "%l", rest :: binary >>, conn, message) do
format(rest, conn, message <> "-")
end
defp format(<< "%r", rest :: binary >>, conn, message) do
request = conn.method <> " " <> full_path(conn) <> " HTTP/1.1"
format(rest, conn, message <> request)
end
defp format(<< "%>s", rest :: binary >>, conn, message) do
status = conn.status |> to_string()
format(rest, conn, message <> status)
end
defp format(<< "%t", rest :: binary >>, conn, message) do
request_date = conn.private[:plug_accesslog] |> Date.from(:local)
format_string = "[%d/%b/%Y:%H:%M:%S %z]"
request_time = DateFormat.format!(request_date, format_string, :strftime)
format(rest, conn, message <> request_time)
end
defp format(<< "%u", rest :: binary >>, conn, message) do
username = case get_req_header(conn, "Authorization") do
[<< "Basic ", credentials :: binary >>] -> get_user(credentials)
_ -> "-"
end
format(rest, conn, message <> username)
end
defp format(<< "%U", rest :: binary >>, conn, message) do
format(rest, conn, message <> full_path(conn))
end
defp format(<< "%v", rest :: binary >>, conn, message) do
format(rest, conn, message <> conn.host)
end
defp format(<< "%{", rest :: binary >>, conn, message) do
[ varname, rest ] = rest |> String.split("}", parts: 2)
<< vartype :: binary-1, rest :: binary >> = rest
varvalue = case vartype do
"i" ->
case get_req_header(conn, varname) do
[ value ] -> value
_ -> "-"
end
_ -> "-"
end
format(rest, conn, message <> varvalue)
end
defp format(<< char, rest :: binary >>, conn, message) do
format(rest, conn, message <> << char >>)
end
defp format("", _conn, message), do: message
# Internal helper methods
defp get_user(credentials) do
try do
case parse_credentials(credentials) do
[ user, _pass ] -> user
_ -> "-"
end
rescue
_ -> "-"
end
end
defp parse_credentials(credentials) do
credentials
|> Base.decode64!()
|> String.split(":")
end
end