diff --git a/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex b/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex index ff378746..279de14e 100644 --- a/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex +++ b/instrumentation/opentelemetry_bandit/lib/opentelemetry_bandit.ex @@ -468,10 +468,30 @@ defmodule OpentelemetryBandit do |> Tracer.set_attributes() end + maybe_update_span_name(conn) + Tracer.end_span() Ctx.clear() end + defp maybe_update_span_name(conn) do + case conn.private do + %{plug_route: {route, _fun}} -> + method = sanitize_method(conn.method) + Tracer.update_name("#{method} #{route}") + Tracer.set_attribute(HTTPAttributes.http_route(), route) + + _ -> + :ok + end + end + + defp sanitize_method(method) do + if parse_method(method) == HTTPAttributes.http_request_method_values().other, + do: "HTTP", + else: method + end + @doc false def handle_request_exception(meta, config) do Tracer.set_status(OpenTelemetry.status(:error, "")) diff --git a/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs b/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs index 86b0f002..eb768be9 100644 --- a/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs +++ b/instrumentation/opentelemetry_bandit/test/bandit/opentelemetry_bandit_test.exs @@ -488,6 +488,63 @@ defmodule OpentelemetryBanditTest do assert [] = :otel_events.list(events) end + test "updates span name and sets http.route from conn.private[:plug_route]" do + OpentelemetryBandit.setup() + port = start_server() + + Req.get("http://localhost:#{port}/with_route") + + assert_receive {:span, + span( + name: "GET /items/:id", + kind: :server, + attributes: span_attrs + )} + + assert Map.get(:otel_attributes.map(span_attrs), HTTPAttributes.http_route()) == + "/items/:id" + end + + test "updates span name with POST method and route" do + OpentelemetryBandit.setup() + port = start_server() + + Req.post("http://localhost:#{port}/post_with_route", body: "") + + assert_receive {:span, + span( + name: "POST /items", + kind: :server, + attributes: span_attrs + )} + + attrs = :otel_attributes.map(span_attrs) + assert Map.get(attrs, HTTPAttributes.http_route()) == "/items" + assert Map.get(attrs, HTTPAttributes.http_response_status_code()) == 201 + end + + test "updates span name with route on 5xx response" do + OpentelemetryBandit.setup() + port = start_server() + + Req.get("http://localhost:#{port}/error_with_route", retry: false) + + expected_status = OpenTelemetry.status(:error, "") + + assert_receive {:span, + span( + name: "GET /items/:id", + kind: :server, + attributes: span_attrs, + status: ^expected_status + )} + + attrs = :otel_attributes.map(span_attrs) + assert Map.get(attrs, HTTPAttributes.http_route()) == "/items/:id" + assert Map.get(attrs, HTTPAttributes.http_response_status_code()) == 500 + assert Map.get(attrs, ErrorAttributes.error_type()) == "500" + end + test "with halted request" do OpentelemetryBandit.setup() port = start_server() @@ -522,6 +579,25 @@ defmodule OpentelemetryBanditTest do conn |> send_resp(200, "OK") end + def with_route(conn) do + conn + |> put_private(:plug_route, {"/items/:id", fn -> nil end}) + |> send_resp(200, "OK") + end + + def post_with_route(conn) do + conn + |> put_private(:plug_route, {"/items", fn -> nil end}) + |> send_resp(201, "Created") + end + + def error_with_route(conn) do + conn + |> put_private(:plug_route, {"/items/:id", fn -> nil end}) + |> send_resp(500, "Internal Server Error") + |> halt() + end + def with_body(conn) do conn |> put_resp_content_type("application/json")