[opentelemetry-tesla] add custom span name override as middleware opt (#105)
* fix remove extra bracket in mix.exs * use capture log for less verbose test output * add span_name opt for overriding span name * add moduledoc * allow function for span_name opt
This commit is contained in:
parent
7a4c33ef7c
commit
c69a3c7b49
|
@ -1,9 +1,24 @@
|
|||
defmodule Tesla.Middleware.OpenTelemetry do
|
||||
@moduledoc """
|
||||
Creates OpenTelemetry spans and injects tracing headers into HTTP requests
|
||||
|
||||
When used with `Tesla.Middleware.PathParams`, the span name will be created
|
||||
based on the provided path. Without it, the span name follow OpenTelemetry
|
||||
standards and use just the method name, if not being overriden by opts.
|
||||
|
||||
NOTE: This middleware needs to come before `Tesla.Middleware.PathParams`
|
||||
|
||||
## Options
|
||||
|
||||
- `:span_name` - override span name. Can be a `String` for a static span name,
|
||||
or a function that takes the `Tesla.Env` and returns a `String`
|
||||
|
||||
"""
|
||||
require OpenTelemetry.Tracer
|
||||
@behaviour Tesla.Middleware
|
||||
|
||||
def call(env, next, _options) do
|
||||
span_name = get_span_name(env)
|
||||
def call(env, next, opts) do
|
||||
span_name = get_span_name(env, Keyword.get(opts, :span_name))
|
||||
|
||||
OpenTelemetry.Tracer.with_span span_name, %{kind: :client} do
|
||||
env
|
||||
|
@ -14,7 +29,15 @@ defmodule Tesla.Middleware.OpenTelemetry do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_span_name(env) do
|
||||
defp get_span_name(_env, span_name) when is_binary(span_name) do
|
||||
span_name
|
||||
end
|
||||
|
||||
defp get_span_name(env, span_name_fun) when is_function(span_name_fun, 1) do
|
||||
span_name_fun.(env)
|
||||
end
|
||||
|
||||
defp get_span_name(env, _) do
|
||||
case env.opts[:path_params] do
|
||||
nil -> "HTTP #{http_method(env.method)}"
|
||||
_ -> URI.parse(env.url).path
|
||||
|
|
|
@ -25,38 +25,141 @@ defmodule Tesla.Middleware.OpenTelemetryTest do
|
|||
{:ok, bypass: bypass}
|
||||
end
|
||||
|
||||
test "it records a generic span name if opentelemetry middleware is configured before path params middleware",
|
||||
%{
|
||||
bypass: bypass
|
||||
} do
|
||||
defmodule TestClient do
|
||||
def get(client) do
|
||||
params = [id: '3']
|
||||
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])
|
||||
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
|
||||
|
||||
def client(url) do
|
||||
middleware = [
|
||||
{Tesla.Middleware.BaseUrl, url},
|
||||
Tesla.Middleware.OpenTelemetry,
|
||||
Tesla.Middleware.PathParams
|
||||
]
|
||||
Bypass.expect_once(bypass, "GET", "/users/3", fn conn ->
|
||||
Plug.Conn.resp(conn, 204, "")
|
||||
end)
|
||||
|
||||
Tesla.client(middleware)
|
||||
end
|
||||
bypass.port
|
||||
|> endpoint_url()
|
||||
|> TestClient.client()
|
||||
|> TestClient.get()
|
||||
|
||||
assert_receive {:span, span(name: "/users/:id", attributes: _attributes)}
|
||||
end
|
||||
|
||||
Bypass.expect_once(bypass, "GET", "/users/3", fn conn ->
|
||||
Plug.Conn.resp(conn, 204, "")
|
||||
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
|
||||
|
||||
bypass.port
|
||||
|> endpoint_url()
|
||||
|> TestClient.client()
|
||||
|> TestClient.get()
|
||||
def client(url) do
|
||||
middleware = [
|
||||
{Tesla.Middleware.BaseUrl, url},
|
||||
Tesla.Middleware.OpenTelemetry
|
||||
]
|
||||
|
||||
assert_receive {:span, span(name: "/users/:id", attributes: _attributes)}
|
||||
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
|
||||
|
@ -311,9 +414,9 @@ defmodule Tesla.Middleware.OpenTelemetryTest do
|
|||
]
|
||||
}} =
|
||||
Tesla.Middleware.OpenTelemetry.call(
|
||||
%Tesla.Env{url: ""},
|
||||
[],
|
||||
"http://example.com"
|
||||
_env = %Tesla.Env{url: ""},
|
||||
_next = [],
|
||||
_opts = []
|
||||
)
|
||||
|
||||
assert is_binary(traceparent)
|
||||
|
|
|
@ -1 +1 @@
|
|||
ExUnit.start()
|
||||
ExUnit.start(capture_log: true)
|
||||
|
|
Loading…
Reference in New Issue