Packages
phoenix
1.4.9
1.8.9
1.8.8
1.8.7
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.8.0-rc.4
1.8.0-rc.3
1.8.0-rc.2
1.8.0-rc.1
1.8.0-rc.0
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.7.0-rc.3
1.7.0-rc.2
1.7.0-rc.1
1.7.0-rc.0
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.15
1.5.14
1.5.13
1.5.12
1.5.11
1.5.10
1.5.9
1.5.8
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.0
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.3
1.4.0-rc.2
1.4.0-rc.1
1.4.0-rc.0
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.3.0-rc.3
1.3.0-rc.2
1.3.0-rc.1
1.3.0-rc.0
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.1
1.2.0-rc.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.1
0.4.0
0.3.1
0.3.0
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Productive. Reliable. Fast. A productive web framework that does not compromise speed or maintainability.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/phoenix/endpoint/instrument.ex
defmodule Phoenix.Endpoint.Instrument do
@moduledoc false
# This is the arity that event callbacks in the instrumenter modules must
# have.
@event_callback_arity 3
@doc false
def definstrument(otp_app, endpoint) do
app_instrumenters = app_instrumenters(otp_app, endpoint)
quote bind_quoted: [app_instrumenters: app_instrumenters] do
require Logger
@doc """
Instruments the given function.
`event` is the event identifier (usually an atom) that specifies which
instrumenting function to call in the instrumenter modules. `runtime` is
metadata to be associated with the event at runtime (e.g., the query being
issued if the event to instrument is a DB query).
## Examples
instrument :render_view, %{view: "index.html"}, fn ->
render(conn, "index.html")
end
"""
defmacro instrument(event, runtime \\ Macro.escape(%{}), fun) do
compile = Macro.escape(Phoenix.Endpoint.Instrument.strip_caller(__CALLER__))
quote do
unquote(__MODULE__).instrument(
unquote(event),
unquote(compile),
unquote(runtime),
unquote(fun)
)
end
end
# For each event in any of the instrumenters, we must generate a
# clause of the `instrument/4` function. It'll look like this:
#
# def instrument(:my_event, compile, runtime, fun) do
# res0 = Inst0.my_event(:start, compile, runtime)
# ...
#
# start = :erlang.monotonic_time
# try do
# fun.()
# after
# diff = ...
# Inst0.my_event(:stop, diff, res0)
# ...
# end
# end
#
@doc false
def instrument(event, compile, runtime, fun)
for {event, instrumenters} <- app_instrumenters do
def instrument(unquote(event), var!(compile), var!(runtime), fun)
when is_map(var!(compile)) and is_map(var!(runtime)) and is_function(fun, 0) do
unquote(Phoenix.Endpoint.Instrument.compile_start_callbacks(event, instrumenters))
start = :erlang.monotonic_time
try do
fun.()
after
var!(diff) = :erlang.monotonic_time - start
unquote(Phoenix.Endpoint.Instrument.compile_stop_callbacks(event, instrumenters))
end
end
end
# Catch-all clause
def instrument(event, compile, runtime, fun)
when is_atom(event) and is_map(compile) and is_map(runtime) and is_function(fun, 0) do
fun.()
end
end
end
# Reads a list of the instrumenters from the config of `otp_app` and finds all
# events in those instrumenters. The return value is a list of `{event,
# instrumenters}` tuples, one for each event defined by any instrumenters
# (with no duplicated events); `instrumenters` is the list of instrumenters
# interested in `event`.
defp app_instrumenters(otp_app, endpoint) do
config = Application.get_env(otp_app, endpoint, [])
instrumenters = config[:instrumenters] || []
unless is_list(instrumenters) and Enum.all?(instrumenters, &is_atom/1) do
raise ":instrumenters must be a list of instrumenter modules"
end
events_to_instrumenters(instrumenters)
end
# Strips a `Macro.Env` struct, leaving only interesting compile-time metadata.
@doc false
@spec strip_caller(Macro.Env.t) :: %{}
def strip_caller(%Macro.Env{module: mod, function: fun, file: file, line: line}) do
caller = %{module: mod, function: form_fa(fun), file: file, line: line}
if app = Application.get_env(:logger, :compile_time_application) do
Map.put(caller, :application, app)
else
caller
end
end
defp form_fa({name, arity}), do: Atom.to_string(name) <> "/" <> Integer.to_string(arity)
defp form_fa(nil), do: nil
# called by Phoenix.Endpoint.instrument/4, see docs there
@doc false
@spec extract_endpoint(Plug.Conn.t | Phoenix.Socket.t | module) :: module | nil
def extract_endpoint(endpoint_or_conn_or_socket) do
case endpoint_or_conn_or_socket do
%Plug.Conn{private: %{phoenix_endpoint: endpoint}} -> endpoint
%Phoenix.Socket{endpoint: endpoint} -> endpoint
%{__struct__: struct} when struct in [Plug.Conn, Phoenix.Socket] -> nil
endpoint -> endpoint
end
end
# Returns the AST for all the calls to the "start event" callbacks in the given
# list of `instrumenters`.
# Each function call looks like this:
#
# res0 = Instr0.my_event(:start, compile, runtime)
#
@doc false
@spec compile_start_callbacks(term, [module]) :: Macro.t
def compile_start_callbacks(event, instrumenters) do
Enum.map Enum.with_index(instrumenters), fn {inst, index} ->
error_prefix = "Instrumenter #{inspect inst}.#{event}/3 failed.\n"
quote do
unquote(build_result_variable(index)) =
try do
unquote(inst).unquote(event)(:start, var!(compile), var!(runtime))
catch
kind, error ->
Logger.error [unquote(error_prefix), Exception.format(kind, error, System.stacktrace())]
end
end
end
end
# Returns the AST for all the calls to the "stop event" callbacks in the given
# list of `instrumenters`.
# Each function call looks like this:
#
# Instr0.my_event(:stop, diff, res0)
#
@doc false
@spec compile_stop_callbacks(term, [module]) :: Macro.t
def compile_stop_callbacks(event, instrumenters) do
Enum.map Enum.with_index(instrumenters), fn {inst, index} ->
error_prefix = "Instrumenter #{inspect inst}.#{event}/3 failed.\n"
quote do
try do
unquote(inst).unquote(event)(:stop, var!(diff), unquote(build_result_variable(index)))
catch
kind, error ->
Logger.error unquote(error_prefix) <> Exception.format(kind, error)
end
end
end
end
# Takes a list of instrumenter modules and returns a list of `{event,
# instrumenters}` tuples where each tuple represents an event and all the
# modules interested in that event.
defp events_to_instrumenters(instrumenters) do
instrumenters # [Ins1, Ins2, ...]
|> instrumenters_and_events() # [{Ins1, e1}, {Ins2, e1}, ...]
|> Enum.group_by(fn {_inst, e} -> e end) # %{e1 => [{Ins1, e1}, ...], ...}
|> Enum.map(fn {e, insts} -> {e, strip_events(insts)} end) # [{e1, [Ins1, Ins2]}, ...]
end
defp instrumenters_and_events(instrumenters) do
# We're only interested in functions (events) with the given arity.
for inst <- instrumenters,
{event, @event_callback_arity} <- inst.__info__(:functions),
do: {inst, event}
end
defp strip_events(instrumenters) do
for {inst, _evt} <- instrumenters, do: inst
end
defp build_result_variable(index) when is_integer(index) do
"res#{index}" |> String.to_atom() |> Macro.var(nil)
end
end