1defmodule Plug.RequestId do 2 @moduledoc """ 3 A plug for generating a unique request id for each request. A generated 4 request id will in the format "uq8hs30oafhj5vve8ji5pmp7mtopc08f". 5 6 If a request id already exists as the "x-request-id" HTTP request header, 7 then that value will be used assuming it is between 20 and 200 characters. 8 If it is not, a new request id will be generated. 9 10 The request id is added to the Logger metadata as `:request_id` and the response as 11 the "x-request-id" HTTP header. To see the request id in your log output, 12 configure your logger backends to include the `:request_id` metadata: 13 14 config :logger, :console, metadata: [:request_id] 15 16 It is recommended to include this metadata configuration in your production 17 configuration file. 18 19 To use it, just plug it into the desired module: 20 21 plug Plug.RequestId 22 23 ## Options 24 25 * `:http_header` - The name of the HTTP *request* header to check for 26 existing request ids. This is also the HTTP *response* header that will be 27 set with the request id. Default value is "x-request-id" 28 29 plug Plug.RequestId, http_header: "custom-request-id" 30 """ 31 32 require Logger 33 alias Plug.Conn 34 @behaviour Plug 35 36 def init(opts) do 37 Keyword.get(opts, :http_header, "x-request-id") 38 end 39 40 def call(conn, req_id_header) do 41 conn 42 |> get_request_id(req_id_header) 43 |> set_request_id(req_id_header) 44 end 45 46 defp get_request_id(conn, header) do 47 case Conn.get_req_header(conn, header) do 48 [] -> {conn, generate_request_id()} 49 [val|_] -> if valid_request_id?(val), do: {conn, val}, else: {conn, generate_request_id()} 50 end 51 end 52 53 defp set_request_id({conn, request_id}, header) do 54 Logger.metadata(request_id: request_id) 55 Conn.put_resp_header(conn, header, request_id) 56 end 57 58 defp generate_request_id do 59 Base.hex_encode32(:crypto.strong_rand_bytes(20), case: :lower) 60 end 61 62 defp valid_request_id?(s), do: byte_size(s) in 20..200 63end 64