From c103756e9ade4b04c5c220b595302ea6671a11af Mon Sep 17 00:00:00 2001 From: Ben Schultzer Date: Tue, 6 Jan 2026 16:06:37 -0500 Subject: [PATCH] expose generate_id function and make it configurable in request id --- lib/plug/request_id.ex | 22 +++++++++++++--------- test/plug/request_id_test.exs | 13 +++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/plug/request_id.ex b/lib/plug/request_id.ex index 96c437da..245eaac6 100644 --- a/lib/plug/request_id.ex +++ b/lib/plug/request_id.ex @@ -54,6 +54,9 @@ defmodule Plug.RequestId do plug Plug.RequestId, logger_metadata_key: :my_request_id + * `:generate_request_id` - The function used to generate the request ID, defaults to `generate_request_id/0`. + + plug Plug.RequestId, generate_request_id: fn -> "myapp-" <> Plug.RequestId.generate_request_id() end """ alias Plug.Conn @@ -64,13 +67,14 @@ defmodule Plug.RequestId do { Keyword.get(opts, :http_header, "x-request-id"), Keyword.get(opts, :assign_as), - Keyword.get(opts, :logger_metadata_key, :request_id) + Keyword.get(opts, :logger_metadata_key, :request_id), + Keyword.get(opts, :generate_request_id, &__MODULE__.generate_request_id/0) } end @impl true - def call(conn, {header, assign_as, logger_metadata_key}) do - request_id = get_request_id(conn, header) + def call(conn, {header, assign_as, logger_metadata_key, generate_request_id}) do + request_id = get_request_id(conn, header, generate_request_id) Logger.metadata([{logger_metadata_key, request_id}]) conn = if assign_as, do: Conn.assign(conn, assign_as, request_id), else: conn @@ -78,14 +82,16 @@ defmodule Plug.RequestId do Conn.put_resp_header(conn, header, request_id) end - defp get_request_id(conn, header) do + defp get_request_id(conn, header, generate_request_id) do case Conn.get_req_header(conn, header) do - [] -> generate_request_id() - [val | _] -> if valid_request_id?(val), do: val, else: generate_request_id() + [val | _] when byte_size(val) in 20..200 -> val + _ -> generate_request_id.() end end - defp generate_request_id do + @doc "Generates Base64 encoded request ID." + @spec generate_request_id :: binary() + def generate_request_id do binary = << System.system_time(:nanosecond)::64, :erlang.phash2({node(), self()}, 16_777_216)::24, @@ -94,6 +100,4 @@ defmodule Plug.RequestId do Base.url_encode64(binary) end - - defp valid_request_id?(s), do: byte_size(s) in 20..200 end diff --git a/test/plug/request_id_test.exs b/test/plug/request_id_test.exs index d535fdd8..7ab9c144 100644 --- a/test/plug/request_id_test.exs +++ b/test/plug/request_id_test.exs @@ -7,6 +7,19 @@ defmodule Plug.RequestIdTest do Plug.RequestId.call(conn, Plug.RequestId.init(opts)) end + test "generates new request id with generate_request_id" do + conn = + call(conn(:get, "/"), + generate_request_id: fn -> "myapp-" <> Plug.RequestId.generate_request_id() end + ) + + [res_request_id] = get_resp_header(conn, "x-request-id") + meta_request_id = Logger.metadata()[:request_id] + assert generated_request_id?(res_request_id) + assert res_request_id == meta_request_id + assert String.starts_with?(res_request_id, "myapp-") + end + test "generates new request id if none exists" do conn = call(conn(:get, "/"), []) [res_request_id] = get_resp_header(conn, "x-request-id")