[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
|
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
|
require OpenTelemetry.Tracer
|
||||||
@behaviour Tesla.Middleware
|
@behaviour Tesla.Middleware
|
||||||
|
|
||||||
def call(env, next, _options) do
|
def call(env, next, opts) do
|
||||||
span_name = get_span_name(env)
|
span_name = get_span_name(env, Keyword.get(opts, :span_name))
|
||||||
|
|
||||||
OpenTelemetry.Tracer.with_span span_name, %{kind: :client} do
|
OpenTelemetry.Tracer.with_span span_name, %{kind: :client} do
|
||||||
env
|
env
|
||||||
|
@ -14,7 +29,15 @@ defmodule Tesla.Middleware.OpenTelemetry do
|
||||||
end
|
end
|
||||||
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
|
case env.opts[:path_params] do
|
||||||
nil -> "HTTP #{http_method(env.method)}"
|
nil -> "HTTP #{http_method(env.method)}"
|
||||||
_ -> URI.parse(env.url).path
|
_ -> URI.parse(env.url).path
|
||||||
|
|
|
@ -25,38 +25,141 @@ defmodule Tesla.Middleware.OpenTelemetryTest do
|
||||||
{:ok, bypass: bypass}
|
{:ok, bypass: bypass}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it records a generic span name if opentelemetry middleware is configured before path params middleware",
|
describe "span name" do
|
||||||
%{
|
test "uses generic route name when opentelemetry middleware is configured before path params middleware",
|
||||||
bypass: bypass
|
%{
|
||||||
} do
|
bypass: bypass
|
||||||
defmodule TestClient do
|
} do
|
||||||
def get(client) do
|
defmodule TestClient do
|
||||||
params = [id: '3']
|
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
|
end
|
||||||
|
|
||||||
def client(url) do
|
Bypass.expect_once(bypass, "GET", "/users/3", fn conn ->
|
||||||
middleware = [
|
Plug.Conn.resp(conn, 204, "")
|
||||||
{Tesla.Middleware.BaseUrl, url},
|
end)
|
||||||
Tesla.Middleware.OpenTelemetry,
|
|
||||||
Tesla.Middleware.PathParams
|
|
||||||
]
|
|
||||||
|
|
||||||
Tesla.client(middleware)
|
bypass.port
|
||||||
end
|
|> endpoint_url()
|
||||||
|
|> TestClient.client()
|
||||||
|
|> TestClient.get()
|
||||||
|
|
||||||
|
assert_receive {:span, span(name: "/users/:id", attributes: _attributes)}
|
||||||
end
|
end
|
||||||
|
|
||||||
Bypass.expect_once(bypass, "GET", "/users/3", fn conn ->
|
test "uses low-cardinality method name when path params middleware is not used",
|
||||||
Plug.Conn.resp(conn, 204, "")
|
%{
|
||||||
end)
|
bypass: bypass
|
||||||
|
} do
|
||||||
|
defmodule TestClient do
|
||||||
|
def get(client) do
|
||||||
|
Tesla.get(client, "/users/")
|
||||||
|
end
|
||||||
|
|
||||||
bypass.port
|
def client(url) do
|
||||||
|> endpoint_url()
|
middleware = [
|
||||||
|> TestClient.client()
|
{Tesla.Middleware.BaseUrl, url},
|
||||||
|> TestClient.get()
|
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
|
end
|
||||||
|
|
||||||
test "Records spans for Tesla HTTP client", %{bypass: bypass} do
|
test "Records spans for Tesla HTTP client", %{bypass: bypass} do
|
||||||
|
@ -311,9 +414,9 @@ defmodule Tesla.Middleware.OpenTelemetryTest do
|
||||||
]
|
]
|
||||||
}} =
|
}} =
|
||||||
Tesla.Middleware.OpenTelemetry.call(
|
Tesla.Middleware.OpenTelemetry.call(
|
||||||
%Tesla.Env{url: ""},
|
_env = %Tesla.Env{url: ""},
|
||||||
[],
|
_next = [],
|
||||||
"http://example.com"
|
_opts = []
|
||||||
)
|
)
|
||||||
|
|
||||||
assert is_binary(traceparent)
|
assert is_binary(traceparent)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
ExUnit.start()
|
ExUnit.start(capture_log: true)
|
||||||
|
|
Loading…
Reference in New Issue