opentelemetry-erlang-contrib/instrumentation/opentelemetry_tesla/test/middleware/opentelemetry_tesla_middlew...

428 lines
11 KiB
Elixir

defmodule Tesla.Middleware.OpenTelemetryTest do
use ExUnit.Case
require Record
for {name, spec} <- Record.extract_all(from_lib: "opentelemetry/include/otel_span.hrl") do
Record.defrecord(name, spec)
end
for {name, spec} <- Record.extract_all(from_lib: "opentelemetry_api/include/opentelemetry.hrl") do
Record.defrecord(name, spec)
end
setup do
bypass = Bypass.open()
:application.stop(:opentelemetry)
:application.set_env(:opentelemetry, :tracer, :otel_tracer_default)
:application.set_env(:opentelemetry, :processors, [
{:otel_batch_processor, %{scheduled_delay_ms: 1, exporter: {:otel_exporter_pid, self()}}}
])
:application.start(:opentelemetry)
{:ok, bypass: bypass}
end
describe "span name" do
test "uses generic route name when opentelemetry middleware is configured before path params middleware",
%{
bypass: bypass
} do
defmodule TestClient do
def get(client) do
params = [id: '3']
Tesla.get(client, "/users/:id", opts: [path_params: params])
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry,
Tesla.Middleware.PathParams
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users/3", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get()
assert_receive {:span, span(name: "/users/:id", attributes: _attributes)}
end
test "uses low-cardinality method name when path params middleware is not used",
%{
bypass: bypass
} do
defmodule TestClient do
def get(client) do
Tesla.get(client, "/users/")
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users/", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get()
assert_receive {:span, span(name: "HTTP GET", attributes: _attributes)}
end
test "uses custom span name when passed in middleware opts",
%{
bypass: bypass
} do
defmodule TestClient do
def get(client) do
params = [id: '3']
Tesla.get(client, "/users/:id", opts: [path_params: params])
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
{Tesla.Middleware.OpenTelemetry, span_name: "POST :my-high-cardinality-url"},
Tesla.Middleware.PathParams
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users/3", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get()
assert_receive {:span, span(name: "POST :my-high-cardinality-url", attributes: _attributes)}
end
test "uses custom span name function when passed in middleware opts",
%{
bypass: bypass
} do
defmodule TestClient do
def get(client) do
params = [id: '3']
Tesla.get(client, "/users/:id", opts: [path_params: params])
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
{Tesla.Middleware.OpenTelemetry,
span_name: fn env ->
"#{String.upcase(to_string(env.method))} potato"
end},
Tesla.Middleware.PathParams
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users/3", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get()
assert_receive {:span, span(name: "GET potato", attributes: _attributes)}
end
end
test "Records spans for Tesla HTTP client", %{bypass: bypass} do
defmodule TestClient do
def get(client) do
Tesla.get(client, "/users/")
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get()
assert_receive {:span, span(name: "HTTP GET", attributes: _attributes)}
end
test "Marks Span status as :error when HTTP request fails", %{bypass: bypass} do
defmodule TestClient do
def get(client) do
Tesla.get(client, "/users/")
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users", fn conn ->
Plug.Conn.resp(conn, 500, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get()
assert_receive {:span, span(status: {:status, :error, ""})}
end
test "Marks Span status as :errors when max redirects are exceeded", %{bypass: bypass} do
defmodule TestClient do
def get(client) do
Tesla.get(client, "/users/")
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry,
{Tesla.Middleware.FollowRedirects, max_redirects: 1}
]
Tesla.client(middleware)
end
end
Bypass.expect(bypass, "GET", "/users", fn conn ->
conn
|> Plug.Conn.put_resp_header("Location", "/users/1")
|> Plug.Conn.resp(301, "")
end)
Bypass.expect(bypass, "GET", "/users/1", fn conn ->
conn
|> Plug.Conn.put_resp_header("Location", "/users/2")
|> Plug.Conn.resp(301, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get()
assert_receive {:span, span(status: {:status, :error, ""})}
end
test "Appends query string parameters to http.url attribute", %{bypass: bypass} do
defmodule TestClient do
def get(client, id) do
params = [id: id]
Tesla.get(client, "/users/:id", opts: [path_params: params])
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry,
Tesla.Middleware.PathParams,
{Tesla.Middleware.Query, [token: "some-token", array: ["foo", "bar"]]}
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users/2", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get("2")
assert_receive {:span, span(name: _name, attributes: attributes)}
mapped_attributes = :otel_attributes.map(attributes)
assert mapped_attributes[:"http.url"] ==
"http://localhost:#{bypass.port}/users/2?token=some-token&array%5B%5D=foo&array%5B%5D=bar"
end
test "http.url attribute is correct when request doesn't contain query string parameters", %{
bypass: bypass
} do
defmodule TestClient do
def get(client, id) do
params = [id: id]
Tesla.get(client, "/users/:id", opts: [path_params: params])
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry,
Tesla.Middleware.PathParams,
{Tesla.Middleware.Query, []}
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users/2", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get("2")
assert_receive {:span, span(name: _name, attributes: attributes)}
mapped_attributes = :otel_attributes.map(attributes)
assert mapped_attributes[:"http.url"] ==
"http://localhost:#{bypass.port}/users/2"
end
test "Handles url path arguments correctly", %{bypass: bypass} do
defmodule TestClient do
def get(client, id) do
params = [id: id]
Tesla.get(client, "/users/:id", opts: [path_params: params])
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry,
Tesla.Middleware.PathParams,
{Tesla.Middleware.Query, [token: "some-token"]}
]
Tesla.client(middleware)
end
end
Bypass.expect_once(bypass, "GET", "/users/2", fn conn ->
Plug.Conn.resp(conn, 204, "")
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get("2")
assert_receive {:span, span(name: _name, attributes: attributes)}
assert %{"http.target": "/users/2"} = :otel_attributes.map(attributes)
end
test "Records http.response_content_length param into the span", %{bypass: bypass} do
defmodule TestClient do
def get(client, id) do
params = [id: id]
Tesla.get(client, "/users/:id", opts: [path_params: params])
end
def client(url) do
middleware = [
{Tesla.Middleware.BaseUrl, url},
Tesla.Middleware.OpenTelemetry,
Tesla.Middleware.PathParams,
{Tesla.Middleware.Query, [token: "some-token"]}
]
Tesla.client(middleware)
end
end
response = "HELLO 👋"
Bypass.expect_once(bypass, "GET", "/users/2", fn conn ->
Plug.Conn.resp(conn, 200, response)
end)
bypass.port
|> endpoint_url()
|> TestClient.client()
|> TestClient.get("2")
assert_receive {:span, span(name: _name, attributes: attributes)}
mapped_attributes = :otel_attributes.map(attributes)
{response_size, _} = Integer.parse(mapped_attributes[:"http.response_content_length"])
assert response_size == byte_size(response)
end
test "Injects distributed tracing headers" do
OpentelemetryTelemetry.start_telemetry_span(
"tracer_id",
"my_label",
%{},
%{kind: :client}
)
assert {:ok,
%Tesla.Env{
headers: [
{"traceparent", traceparent}
]
}} =
Tesla.Middleware.OpenTelemetry.call(
_env = %Tesla.Env{url: ""},
_next = [],
_opts = []
)
assert is_binary(traceparent)
end
defp endpoint_url(port), do: "http://localhost:#{port}/"
end