I'm developing a simple website in Elixir with Phoenix. I'd like to add some custom middleware that runs after a response has been generated. For example, in order to log the total number of bytes in each response I'd like to have a Plug like this
defmodule HelloWeb.Plugs.ByteLogger do
import Plug.Conn
require Logger
def init(default), do: default
def call(conn, default) do
log("bytes sent: #{String.length(conn.resp_body)}")
end
end
Trying to use this plug in one of the Phoenix pipelines in the router won't work though, they are all run before the response is rendered. Instead it causes a FunctionClauseError
since conn.resp_body
is nil
. I'm not sure how to use this plug so it can run after the response is rendered.
I think you are looking for register_before_send/2.
This allows to register callbacks that will be called before resp_body
gets set to nil as explained here.
Should look like:
defmodule HelloWeb.Plugs.ByteLogger do
import Plug.Conn
require Logger
def init(default), do: default
def call(conn, default) do
register_before_send(conn, fn conn ->
log("bytes sent: #{String.length(conn.resp_body)}")
conn
end)
end
end
Edit: I don't think you should be using String.length
for the byte size:
resp_body
is not necessarily a string, can be an I/O-listbyte_size/1
should be used to count bytes, String.length/1
returns UTF-8 grapheme countThe following could do the job, but with a significant performance impact due to the need of concatenating the body:
conn.resp_body |> to_string() |> byte_size()
:erlang.iolist_size/1 seems to work well and I suppose is much better performance-wise.