telemetry.ex (2934B)
1 defmodule Plug.Telemetry do 2 @moduledoc """ 3 A plug to instrument the pipeline with `:telemetry` events. 4 5 When plugged, the event prefix is a required option: 6 7 plug Plug.Telemetry, event_prefix: [:my, :plug] 8 9 In the example above, two events will be emitted: 10 11 * `[:my, :plug, :start]` - emitted when the plug is invoked. 12 The event carries the `system_time` as measurement. The metadata 13 is the whole `Plug.Conn` under the `:conn` key and any leftover 14 options given to the plug under `:options`. 15 16 * `[:my, :plug, :stop]` - emitted right before the request is sent. 17 The event carries a single measurement, `:duration`, which is the 18 monotonic time difference between the stop and start events. 19 It has the same metadata as the start event, except the connection 20 has been updated. 21 22 Note this plug measures the time between its invocation until a response 23 is sent. The `:stop` event is not guaranteed to be emitted in all error 24 cases, so this Plug cannot be used as a Telemetry span. 25 26 ## Time unit 27 28 The `:duration` measurements are presented in the `:native` time unit. 29 You can read more about it in the docs for `System.convert_time_unit/3`. 30 31 ## Example 32 33 defmodule InstrumentedPlug do 34 use Plug.Router 35 36 plug :match 37 plug Plug.Telemetry, event_prefix: [:my, :plug] 38 plug Plug.Parsers, parsers: [:urlencoded, :multipart] 39 plug :dispatch 40 41 get "/" do 42 send_resp(conn, 200, "Hello, world!") 43 end 44 end 45 46 In this example, the stop event's `duration` includes the time 47 it takes to parse the request, dispatch it to the correct handler, 48 and execute the handler. The events are not emitted for requests 49 not matching any handlers, since the plug is placed after the match plug. 50 """ 51 52 @behaviour Plug 53 54 @impl true 55 def init(opts) do 56 {event_prefix, opts} = Keyword.pop(opts, :event_prefix) 57 58 unless event_prefix do 59 raise ArgumentError, ":event_prefix is required" 60 end 61 62 ensure_valid_event_prefix!(event_prefix) 63 start_event = event_prefix ++ [:start] 64 stop_event = event_prefix ++ [:stop] 65 {start_event, stop_event, opts} 66 end 67 68 @impl true 69 def call(conn, {start_event, stop_event, opts}) do 70 start_time = System.monotonic_time() 71 metadata = %{conn: conn, options: opts} 72 :telemetry.execute(start_event, %{system_time: System.system_time()}, metadata) 73 74 Plug.Conn.register_before_send(conn, fn conn -> 75 duration = System.monotonic_time() - start_time 76 :telemetry.execute(stop_event, %{duration: duration}, %{conn: conn, options: opts}) 77 conn 78 end) 79 end 80 81 defp ensure_valid_event_prefix!(event_prefix) do 82 if is_list(event_prefix) && Enum.all?(event_prefix, &is_atom/1) do 83 :ok 84 else 85 raise ArgumentError, 86 "expected :event_prefix to be a list of atoms, got: #{inspect(event_prefix)}" 87 end 88 end 89 end