Packages
appsignal
2.15.8
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.0
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.1
2.14.0
2.13.4
2.13.3
2.13.2
2.13.1
2.13.0
2.12.3
2.12.2
2.12.1
2.12.0
2.11.0
2.10.2
2.10.1
2.10.0
2.9.2
2.9.1
2.9.0
2.8.3
2.8.2
2.8.1
2.8.0
2.7.13
2.7.12
2.7.11
2.7.10
2.7.9
2.7.8
2.7.7
2.7.6
2.7.5
2.7.4
2.7.3
2.7.2
2.7.1
2.7.0
2.7.0-beta.1
2.6.1
2.6.0
2.5.3
2.5.2
2.5.1
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.1
2.3.0
2.2.19
2.2.18
2.2.17
2.2.16
2.2.15
2.2.14
2.2.13
2.2.12
retired
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.15
2.1.14
2.1.13
2.1.12
2.1.11
2.1.10
2.1.9
2.1.8
2.1.7
2.1.7-beta.1
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.8
2.0.7
2.0.6
2.0.6-beta.1
2.0.5
retired
2.0.4
retired
2.0.3
retired
2.0.2
retired
2.0.1
retired
2.0.0
retired
2.0.0-beta.11
2.0.0-beta.10
2.0.0-beta.9
2.0.0-beta.8
2.0.0-beta.7
2.0.0-beta.6
2.0.0-beta.5
2.0.0-beta.4
2.0.0-beta.3
2.0.0-beta.2
2.0.0-beta.1
2.0.0-alpha.7
2.0.0-alpha.6
2.0.0-alpha.5
2.0.0-alpha.4
2.0.0-alpha.3
2.0.0-alpha.2
2.0.0-alpha.1
1.13.5
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.13.0-beta.2
1.13.0-beta.1
1.12.1
1.12.0
1.12.0-beta.3
1.12.0-beta.2
1.12.0-beta.1
1.11.8
1.11.7
1.11.6
1.11.5
1.11.4
1.11.3
1.11.2
1.11.1
1.11.0
1.11.0-beta.1
1.10.13
1.10.12
1.10.11
1.10.10
1.10.9
1.10.8
1.10.7
1.10.6
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.4
1.9.3
1.9.2
1.9.1
1.9.1-beta.1
1.9.0
1.9.0-beta.1
1.9.0-alpha.1
1.8.2
1.8.1
1.8.1-beta.2
1.8.1-beta.1
1.8.0
1.7.2
1.7.1
1.7.0
1.7.0-beta.2
1.7.0-beta.1
1.7.0-alpha.4
1.7.0-alpha.3
1.7.0-alpha.2
1.7.0-alpha.1
1.6.7
1.6.6
1.6.6-beta.2
1.6.6-beta.1
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-beta.1
1.6.0-alpha.1
1.5.1
1.5.0
1.5.0-beta.2
1.5.0-beta.1
1.4.11-beta.1
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-alpha.1
1.3.6
1.3.6-beta.1
1.3.5
1.3.4
1.3.4-beta.1
1.3.3
1.3.2
1.3.1
1.3.0
1.3.0-beta.3
1.3.0-beta.2
1.3.0-beta.1
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.1
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.2
0.9.1
0.9.0
0.8.0
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Collects error and performance data from your Elixir applications and sends it to AppSignal
Current section
Files
Jump to
Current section
Files
lib/appsignal/tesla.ex
defmodule Appsignal.Tesla do
require Logger
@tracer Application.compile_env(:appsignal, :appsignal_tracer, Appsignal.Tracer)
@span Application.compile_env(:appsignal, :appsignal_span, Appsignal.Span)
@moduledoc false
def attach do
handlers = %{
[:tesla, :request, :start] => &__MODULE__.tesla_request_start/4,
[:tesla, :request, :stop] => &__MODULE__.tesla_request_stop/4,
[:tesla, :request, :exception] => &__MODULE__.tesla_request_stop/4
}
for {event, fun} <- handlers do
case :telemetry.attach({__MODULE__, event}, event, fun, :ok) do
:ok ->
_ = Appsignal.IntegrationLogger.debug("Appsignal.Tesla attached to #{inspect(event)}")
:ok
{:error, _} = error ->
Logger.warning("Appsignal.Tesla not attached to #{inspect(event)}: #{inspect(error)}")
error
end
end
end
def tesla_request_start(
_event,
_measurements,
%{env: env},
_config
) do
do_tesla_request_start(@tracer.current_span(), env)
end
defp do_tesla_request_start(nil, _env), do: nil
defp do_tesla_request_start(parent, env) do
%{method: method, url: url} = env
middlewares = env_middlewares(env)
base_url = middleware_base_url(middlewares)
use_full_path = path_params?(middlewares)
sanitised_url = sanitise_url(url, base_url, use_full_path)
upcased_method = String.upcase(Atom.to_string(method))
name = "#{upcased_method} #{sanitised_url}"
"http_request"
|> @tracer.create_span(parent)
|> @span.set_name(name)
|> @span.set_attribute("appsignal:category", "request.tesla")
end
# Sanitises an URL by keeping only its scheme, host and port.
#
# The `use_full_path` parameter is set to true for enhanced grouping
# when `Tesla.Middleware.PathParams` is present.
#
# If the URL does not contain a host, and a base URL is provided by
# `Tesla.Middleware.BaseUrl`, then the base URL's scheme, host,
# port and path are used instead.
def sanitise_url(url, base_url, use_full_path) do
do_sanitise_url(URI.parse(url || ""), URI.parse(base_url || ""), use_full_path)
end
# Users can specify a host in the request's URL, which overrides the
# base URL in the BaseURL middleware. Only use the base URL if the URL
# in the telemetry event does not already have a host.
#
# When using the base URL, its path fragment will be used, regardless
# of the value of `use_full_path`.
#
# If `use_full_path` is true, then the path fragments of the base URL
# and the URL are concatenated together.
defp do_sanitise_url(%URI{host: nil, path: path}, base_uri, true) do
do_sanitise_url(base_uri, "#{base_uri.path}#{path}")
end
defp do_sanitise_url(%URI{host: nil}, base_uri, _) do
do_sanitise_url(base_uri, base_uri.path)
end
defp do_sanitise_url(uri, _, true) do
do_sanitise_url(uri, uri.path)
end
defp do_sanitise_url(uri, _, _) do
do_sanitise_url(uri, nil)
end
defp do_sanitise_url(%URI{scheme: scheme, host: host, port: port}, path) do
URI.to_string(%URI{scheme: scheme, host: host, port: port, path: path})
end
def env_middlewares(env) do
# Use the same middleware order implemented in `Tesla.prepare`, but ignore
# `post` client middlewares, which are not relevant to our use case:
# https://github.com/elixir-tesla/tesla/blob/9fbf221/lib/tesla.ex#L116-L120
normalize_middlewares(client_pre_middlewares(env) ++ module_middlewares(env))
end
defp module_middlewares(env) do
module = Map.get(env, :__module__)
if module != nil and function_exported?(module, :__middleware__, 0) do
module.__middleware__()
end || []
end
defp client_pre_middlewares(env) do
client = Map.get(env, :__client__)
if client != nil do
Map.get(client, :pre, [])
end || []
end
defp normalize_middlewares(middlewares) do
# The `Tesla.Env.runtime()` type obtained from the `__middleware__`
# property in the module and the `pre` property in the client can
# contain middlewares and adapters of different shapes. Keep only
# module-based middlewares and their arguments, if any, using an
# unified value shape.
middlewares
|> Enum.map(fn
{:fn, _} -> nil
{middleware, _, opts} -> {middleware, opts}
{middleware, _} -> {middleware, nil}
_ -> nil
end)
|> Enum.reject(&is_nil/1)
end
defp middleware_base_url(normalized_middlewares) do
Enum.find_value(normalized_middlewares, fn {middleware, opts} ->
if middleware == Tesla.Middleware.BaseUrl, do: List.first(opts)
end)
end
def path_params?(normalized_middlewares) do
path_params_position =
middleware_position(normalized_middlewares, Tesla.Middleware.PathParams)
telemetry_position = middleware_position(normalized_middlewares, Tesla.Middleware.Telemetry)
# If the PathParams middleware appears before the Telemetry middleware,
# the URL in the telemetry event will already have the parameter values
# interpolated into it, and therefore it cannot be used for grouping.
path_params_position && telemetry_position < path_params_position
end
defp middleware_position(normalized_middlewares, expected_middleware) do
Enum.find_index(normalized_middlewares, fn {middleware, _opts} ->
middleware == expected_middleware
end)
end
def tesla_request_stop(_event, _measurements, _metadata, _config) do
@tracer.close_span(@tracer.current_span())
end
end