338 lines
12 KiB
Elixir
338 lines
12 KiB
Elixir
|
defmodule OpentelemetryHTTPoisonTest do
|
||
|
alias OpentelemetryHTTPoison
|
||
|
alias OpenTelemetry.Tracer
|
||
|
use OpentelemetryHTTPoison.Case, async: false
|
||
|
|
||
|
doctest OpentelemetryHTTPoison
|
||
|
|
||
|
require OpenTelemetry.Tracer
|
||
|
require Record
|
||
|
|
||
|
for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do
|
||
|
Record.defrecord(name, spec)
|
||
|
end
|
||
|
|
||
|
setup do
|
||
|
flush_mailbox()
|
||
|
:otel_simple_processor.set_exporter(:otel_exporter_pid, self())
|
||
|
:ok
|
||
|
end
|
||
|
|
||
|
describe "OpentelemetryHTTPoison default attributes and headers" do
|
||
|
test "standard http client span attribute are set in span" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000")
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes_record, name: "GET")}
|
||
|
attributes = elem(attributes_record, 4)
|
||
|
|
||
|
assert ["http.method", "http.status_code", "http.url", "net.peer.name"] ==
|
||
|
attributes |> Map.keys() |> Enum.sort()
|
||
|
|
||
|
assert {"http.method", "GET"} in attributes
|
||
|
assert {"net.peer.name", "localhost"} in attributes
|
||
|
end
|
||
|
|
||
|
test "traceparent header is injected when no headers" do
|
||
|
%HTTPoison.Response{request: %{headers: headers}} =
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000")
|
||
|
|
||
|
assert "traceparent" in Enum.map(headers, &elem(&1, 0))
|
||
|
end
|
||
|
|
||
|
test "traceparent header is injected when list headers" do
|
||
|
%HTTPoison.Response{request: %{headers: headers}} =
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000", [{"Accept", "application/json"}])
|
||
|
|
||
|
assert "traceparent" in Enum.map(headers, &elem(&1, 0))
|
||
|
end
|
||
|
|
||
|
test "traceparent header is injected to user-supplied map headers" do
|
||
|
%HTTPoison.Response{request: %{headers: headers}} =
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000", %{"Accept" => "application/json"})
|
||
|
|
||
|
assert "traceparent" in Enum.map(headers, &elem(&1, 0))
|
||
|
end
|
||
|
|
||
|
test "traceparent header is injected to atom user-supplied map headers" do
|
||
|
%HTTPoison.Response{request: %{headers: headers}} =
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000", %{atom: "value"})
|
||
|
|
||
|
assert "atom" in Enum.map(headers, &elem(&1, 0))
|
||
|
end
|
||
|
|
||
|
test "http.url doesn't contain credentials" do
|
||
|
OpentelemetryHTTPoison.get!("http://user:pass@localhost:8000/user/edit/24")
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"http.url", "http://localhost:8000/user/edit/24"})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "OpentelemetryHTTPoison calls with additional options" do
|
||
|
test "additional span attributes can be passed to OpentelemetryHTTPoison invocation" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000", [],
|
||
|
ot_attributes: [{"app.callname", "mariorossi"}]
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"app.callname", "mariorossi"})
|
||
|
end
|
||
|
|
||
|
test "resource route can be explicitly passed to OpentelemetryHTTPoison invocation as a string" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: "/user/edit"
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"http.route", "/user/edit"})
|
||
|
end
|
||
|
|
||
|
test "resource route can be explicitly passed to OpentelemetryHTTPoison invocation as a function" do
|
||
|
infer_fn = fn request -> URI.parse(request.url).path end
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: infer_fn
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"http.route", "/user/edit/24"})
|
||
|
end
|
||
|
|
||
|
test "resource route inference can be explicitly ignored" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: :ignore
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
refute confirm_http_route_attribute(attributes)
|
||
|
end
|
||
|
|
||
|
test "resource route inference can be implicitly ignored" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24")
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
refute confirm_http_route_attribute(attributes)
|
||
|
end
|
||
|
|
||
|
test "resource route inference fails if an incorrect value is passed to the OpentelemetryHTTPoison invocation" do
|
||
|
assert_raise(ArgumentError, fn ->
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: nil
|
||
|
)
|
||
|
end)
|
||
|
|
||
|
assert_raise(ArgumentError, fn ->
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [], ot_resource_route: 1)
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
test "resource route and attributes can be passed to OpentelemetryHTTPoison as options together" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: "/user/edit",
|
||
|
ot_attributes: [{"app.callname", "mariorossi"}]
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"http.route", "/user/edit"})
|
||
|
assert confirm_attributes(attributes, {"app.callname", "mariorossi"})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "parent span is not affected" do
|
||
|
test "with a successful request" do
|
||
|
Tracer.with_span "parent" do
|
||
|
pre_request_ctx = Tracer.current_span_ctx()
|
||
|
OpentelemetryHTTPoison.get("http://localhost:8000")
|
||
|
|
||
|
post_request_ctx = Tracer.current_span_ctx()
|
||
|
assert post_request_ctx == pre_request_ctx
|
||
|
end
|
||
|
end
|
||
|
|
||
|
test "with an nxdomain request" do
|
||
|
Tracer.with_span "parent" do
|
||
|
pre_request_ctx = Tracer.current_span_ctx()
|
||
|
OpentelemetryHTTPoison.get("http://domain.invalid:8000")
|
||
|
|
||
|
post_request_ctx = Tracer.current_span_ctx()
|
||
|
assert post_request_ctx == pre_request_ctx
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "span_status is set to error for" do
|
||
|
test "status codes >= 400" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/status/400")
|
||
|
|
||
|
assert_receive {:span, span(status: {:status, :error, ""})}
|
||
|
end
|
||
|
|
||
|
test "HTTP econnrefused errors" do
|
||
|
{:error, %HTTPoison.Error{reason: expected_reason}} =
|
||
|
OpentelemetryHTTPoison.get("http://localhost:8001")
|
||
|
|
||
|
assert_receive {:span, span(status: {:status, :error, recorded_reason})}
|
||
|
assert inspect(expected_reason) == recorded_reason
|
||
|
end
|
||
|
|
||
|
test "HTTP nxdomain errors" do
|
||
|
{:error, %HTTPoison.Error{reason: expected_reason}} =
|
||
|
OpentelemetryHTTPoison.get("http://domain.invalid:8001")
|
||
|
|
||
|
assert_receive {:span, span(status: {:status, :error, recorded_reason})}
|
||
|
assert inspect(expected_reason) == recorded_reason
|
||
|
end
|
||
|
|
||
|
test "HTTP tls errors" do
|
||
|
{:error, %HTTPoison.Error{reason: expected_reason}} =
|
||
|
OpentelemetryHTTPoison.get("https://localhost:8000")
|
||
|
|
||
|
assert_receive {:span, span(status: {:status, :error, recorded_reason})}
|
||
|
assert inspect(expected_reason) == recorded_reason
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "OpentelemetryHTTPoison with additional configuration" do
|
||
|
test "default attributes can be set via a two element tuple list" do
|
||
|
set_env(:ot_attributes, [{"test_attribute", "test"}])
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24")
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"test_attribute", "test"})
|
||
|
end
|
||
|
|
||
|
test "default attributes that are not binary will be ignored" do
|
||
|
set_env(:ot_attributes, [
|
||
|
{"test_attribute", "test"},
|
||
|
{1, "ignored"},
|
||
|
{:ignored, "ignored_too"}
|
||
|
])
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24")
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"test_attribute", "test"})
|
||
|
end
|
||
|
|
||
|
test "default attributes can be overridden via a two element tuple list passed to the OpentelemetryHTTPoison invocation" do
|
||
|
set_env(:ot_attributes, [{"test_attribute", "test"}])
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_attributes: [{"test_attribute", "overridden"}]
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_attributes(attributes, {"test_attribute", "overridden"})
|
||
|
end
|
||
|
|
||
|
test "default attributes can be combined with attributes passed to the OpentelemetryHTTPoison invocation" do
|
||
|
set_env(:ot_attributes, [{"test_attribute", "test"}])
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_attributes: [
|
||
|
{"another_test_attribute", "another test"},
|
||
|
{"test_attribute_overridden", "overridden"}
|
||
|
]
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
|
||
|
assert confirm_attributes(attributes, {"test_attribute", "test"})
|
||
|
assert confirm_attributes(attributes, {"another_test_attribute", "another test"})
|
||
|
assert confirm_attributes(attributes, {"test_attribute_overridden", "overridden"})
|
||
|
end
|
||
|
|
||
|
test "resource route can be implicitly inferred by OpentelemetryHTTPoison invocation using a default function" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: :infer
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_http_route_attribute(attributes, "/user/:subpath")
|
||
|
end
|
||
|
|
||
|
test "resource route can be inferred by OpentelemetryHTTPoison invocation via a configured function" do
|
||
|
infer_fn = fn
|
||
|
%HTTPoison.Request{} = request -> URI.parse(request.url).path
|
||
|
end
|
||
|
|
||
|
set_env(:infer_route, infer_fn)
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: :infer
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_http_route_attribute(attributes, "/user/edit/24")
|
||
|
end
|
||
|
|
||
|
test "implicit resource route inference can be overridden with a function passed to the OpentelemetryHTTPoison invocation" do
|
||
|
infer_fn = fn
|
||
|
%HTTPoison.Request{} = request -> URI.parse(request.url).path
|
||
|
end
|
||
|
|
||
|
invocation_infer_fn = fn _ -> "test" end
|
||
|
|
||
|
set_env(:infer_route, infer_fn)
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: invocation_infer_fn
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_http_route_attribute(attributes, "test")
|
||
|
end
|
||
|
|
||
|
test "implicit resource route inference can be overridden with a string passed to the OpentelemetryHTTPoison invocation" do
|
||
|
infer_fn = fn
|
||
|
%HTTPoison.Request{} = request -> URI.parse(request.url).path
|
||
|
end
|
||
|
|
||
|
set_env(:infer_route, infer_fn)
|
||
|
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_resource_route: "test"
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
assert confirm_http_route_attribute(attributes, "test")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
test "OpentelemetryHTTPoison works if setup is not called" do
|
||
|
OpentelemetryHTTPoison.get!("http://localhost:8000/user/edit/24", [],
|
||
|
ot_attributes: [{"some_attribute", "some value"}]
|
||
|
)
|
||
|
|
||
|
assert_receive {:span, span(attributes: attributes)}, 1000
|
||
|
|
||
|
assert confirm_attributes(attributes, {"some_attribute", "some value"})
|
||
|
end
|
||
|
|
||
|
def flush_mailbox do
|
||
|
receive do
|
||
|
_ -> flush_mailbox()
|
||
|
after
|
||
|
10 -> :ok
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp confirm_attributes(attributes, attributes_to_confirm) do
|
||
|
attributes
|
||
|
|> Tuple.to_list()
|
||
|
|> Enum.filter(&is_map/1)
|
||
|
|> Enum.any?(fn map ->
|
||
|
attributes_to_confirm in map
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
defp confirm_http_route_attribute(attributes, value) do
|
||
|
confirm_attributes(attributes, {"http.route", value})
|
||
|
end
|
||
|
|
||
|
defp confirm_http_route_attribute(attributes) do
|
||
|
confirm_http_route_attribute(attributes, "")
|
||
|
end
|
||
|
end
|