Current section
Files
Jump to
Current section
Files
lib/plug/accesslog/formatter.ex
defmodule Plug.AccessLog.Formatter do
@moduledoc """
Log message formatter.
"""
alias Plug.AccessLog.Formatter
@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:
- `%%` - Percentage sign
- `%B` - Size of response in bytes. Outputs "0" when no bytes are sent.
- `%b` - Size of response in bytes. Outputs "-" when no bytes are sent.
- `%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 and %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
log("", conn, format)
end
# Internal construction methods
defp log(message, _conn, ""), do: message
defp log(message, conn, << "%%", rest :: binary >>) do
message <> "%"
|> log(conn, rest)
end
defp log(message, conn, << "%B", rest :: binary >>) do
message
|> Formatter.ResponseBytes.append(conn, "0")
|> log(conn, rest)
end
defp log(message, conn, << "%b", rest :: binary >>) do
message
|> Formatter.ResponseBytes.append(conn, "-")
|> log(conn, rest)
end
defp log(message, conn, << "%h", rest :: binary >>) do
message
|> Formatter.RemoteHostname.append(conn)
|> log(conn, rest)
end
defp log(message, conn, << "%l", rest :: binary >>) do
message <> "-"
|> log(conn, rest)
end
defp log(message, conn, << "%r", rest :: binary >>) do
message
|> Formatter.RequestLine.append(conn)
|> log(conn, rest)
end
defp log(message, conn, << "%>s", rest :: binary >>) do
message <> to_string(conn.status)
|> log(conn, rest)
end
defp log(message, conn, << "%t", rest :: binary >>) do
message
|> Formatter.RequestTime.append(conn)
|> log(conn, rest)
end
defp log(message, conn, << "%u", rest :: binary >>) do
message
|> Formatter.RemoteUser.append(conn)
|> log(conn, rest)
end
defp log(message, conn, << "%U", rest :: binary >>) do
message
|> Formatter.RequestPath.append(conn)
|> log(conn, rest)
end
defp log(message, conn, << "%v", rest :: binary >>) do
message <> conn.host
|> log(conn, rest)
end
defp log(message, conn, << "%{", rest :: binary >>) do
[ varname, rest ] = rest |> String.split("}", parts: 2)
<< vartype :: binary-1, rest :: binary >> = rest
message = case vartype do
"i" -> Formatter.RequestHeader.append(message, conn, varname)
_ -> message <> "-"
end
log(message, conn, rest)
end
defp log(message, conn, << char, rest :: binary >>) do
message <> << char >>
|> log(conn, rest)
end
end