request_id.ex (2382B)
1 defmodule Plug.RequestId do 2 @moduledoc """ 3 A plug for generating a unique request id for each request. 4 5 The generated request id will be in the format "uq8hs30oafhj5vve8ji5pmp7mtopc08f". 6 7 If a request id already exists as the "x-request-id" HTTP request header, 8 then that value will be used assuming it is between 20 and 200 characters. 9 If it is not, a new request id will be generated. 10 11 The request id is added to the Logger metadata as `:request_id` and the response as 12 the "x-request-id" HTTP header. To see the request id in your log output, 13 configure your logger backends to include the `:request_id` metadata: 14 15 config :logger, :console, metadata: [:request_id] 16 17 It is recommended to include this metadata configuration in your production 18 configuration file. 19 20 You can also access the `request_id` programmatically by calling 21 `Logger.metadata[:request_id]`. Do not access it via the request header, as 22 the request header value has not been validated and it may not always be 23 present. 24 25 To use this plug, just plug it into the desired module: 26 27 plug Plug.RequestId 28 29 ## Options 30 31 * `:http_header` - The name of the HTTP *request* header to check for 32 existing request ids. This is also the HTTP *response* header that will be 33 set with the request id. Default value is "x-request-id" 34 35 plug Plug.RequestId, http_header: "custom-request-id" 36 37 """ 38 39 require Logger 40 alias Plug.Conn 41 @behaviour Plug 42 43 @impl true 44 def init(opts) do 45 Keyword.get(opts, :http_header, "x-request-id") 46 end 47 48 @impl true 49 def call(conn, req_id_header) do 50 conn 51 |> get_request_id(req_id_header) 52 |> set_request_id(req_id_header) 53 end 54 55 defp get_request_id(conn, header) do 56 case Conn.get_req_header(conn, header) do 57 [] -> {conn, generate_request_id()} 58 [val | _] -> if valid_request_id?(val), do: {conn, val}, else: {conn, generate_request_id()} 59 end 60 end 61 62 defp set_request_id({conn, request_id}, header) do 63 Logger.metadata(request_id: request_id) 64 Conn.put_resp_header(conn, header, request_id) 65 end 66 67 defp generate_request_id do 68 binary = << 69 System.system_time(:nanosecond)::64, 70 :erlang.phash2({node(), self()}, 16_777_216)::24, 71 :erlang.unique_integer()::32 72 >> 73 74 Base.url_encode64(binary) 75 end 76 77 defp valid_request_id?(s), do: byte_size(s) in 20..200 78 end