Page MenuHomePhorge

No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/middleware/telemetry.ex b/lib/tesla/middleware/telemetry.ex
index e3dc643..9f452fa 100644
--- a/lib/tesla/middleware/telemetry.ex
+++ b/lib/tesla/middleware/telemetry.ex
@@ -1,33 +1,118 @@
if Code.ensure_loaded?(:telemetry) do
defmodule Tesla.Middleware.Telemetry do
@moduledoc """
- Send the request time and meta-information through telemetry.
+ Emits events using the `:telemetry` library to expose instrumentation.
## Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.Telemetry
end
- :telemetry.attach("my-tesla-telemetry", [:tesla, :request], fn event, time, meta, config ->
+ :telemetry.attach("my-tesla-telemetry", [:tesla, :request, :stop], fn event, measurements, meta, config ->
# Do something with the event
end)
```
+ ## Telemetry Events
+
+ * `[:tesla, :request, :start]` - emitted at the beginning of the request.
+ * Measurement: `%{system_time: System.system_time()}`
+ * Metadata: `%{env: Tesla.Env.t()}`
+
+ * `[:tesla, :request, :stop]` - emitted at the end of the request.
+ * Measurement: `%{duration: native_time}`
+ * Metadata: `%{env: Tesla.Env.t()} | %{env: Tesla.Env.t(), error: term()}`
+
+ * `[:tesla, :request, :exception]` - emitted when an exception has been raised.
+ * Measurement: `%{duration: native_time}`
+ * Metadata: `%{kind: Exception.kind(), reason: term(), stacktrace: Exception.stacktrace()}`
+
+ ## Legacy Telemetry Events
+
+ * `[:tesla, :request]` - This event is emitted for backwards compatibility only and should be considered deprecated.
+ This event can be disabled by setting `config :tesla, Tesla.Middleware.Telemetry, disable_legacy_event: true` in your config. Be sure to run `mix deps.compile --force tesla` after changing this setting to ensure the change is picked up.
+
Please check the [telemetry](https://hexdocs.pm/telemetry/) for the further usage.
"""
+ @disable_legacy_event Application.get_env(:tesla, Tesla.Middleware.Telemetry,
+ disable_legacy_event: false
+ )[:disable_legacy_event]
+
@behaviour Tesla.Middleware
@impl Tesla.Middleware
def call(env, next, _opts) do
- {time, res} = :timer.tc(Tesla, :run, [env, next])
- :telemetry.execute([:tesla, :request], %{request_time: time}, %{result: res})
- res
+ start_time = System.monotonic_time()
+
+ emit_start(%{env: env})
+
+ try do
+ Tesla.run(env, next)
+ catch
+ kind, reason ->
+ stacktrace = System.stacktrace()
+ duration = System.monotonic_time() - start_time
+
+ emit_exception(duration, %{kind: kind, reason: reason, stacktrace: stacktrace})
+
+ :erlang.raise(kind, reason, stacktrace)
+ else
+ {:ok, env} = result ->
+ duration = System.monotonic_time() - start_time
+
+ emit_stop(duration, %{env: env})
+ emit_legacy_event(duration, result)
+
+ result
+
+ {:error, reason} = result ->
+ duration = System.monotonic_time() - start_time
+
+ emit_stop(duration, %{env: env, error: reason})
+ emit_legacy_event(duration, result)
+
+ result
+ end
+ end
+
+ defp emit_start(metadata) do
+ :telemetry.execute(
+ [:tesla, :request, :start],
+ %{system_time: System.system_time()},
+ metadata
+ )
+ end
+
+ defp emit_stop(duration, metadata) do
+ :telemetry.execute(
+ [:tesla, :request, :stop],
+ %{duration: duration},
+ metadata
+ )
+ end
+
+ defp emit_legacy_event(duration, result) do
+ if !@disable_legacy_event do
+ :telemetry.execute(
+ [:tesla, :request],
+ %{request_time: duration},
+ %{result: result}
+ )
+ end
+ end
+
+ defp emit_exception(duration, metadata) do
+ :telemetry.execute(
+ [:tesla, :request, :exception],
+ %{duration: duration},
+ metadata
+ )
end
end
end
diff --git a/mix.exs b/mix.exs
index 78059e7..c77b796 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,134 +1,134 @@
defmodule Tesla.Mixfile do
use Mix.Project
@version "1.3.2"
def project do
[
app: :tesla,
version: @version,
description: description(),
package: package(),
source_url: "https://github.com/teamon/tesla",
elixir: "~> 1.5",
elixirc_paths: elixirc_paths(Mix.env()),
deps: deps(),
lockfile: lockfile(System.get_env("LOCKFILE")),
test_coverage: [tool: ExCoveralls],
dialyzer: [
plt_add_apps: [:inets],
plt_add_deps: :project
],
docs: docs()
]
end
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[applications: applications(Mix.env())]
end
def applications(:test), do: applications(:dev) ++ [:httparrot, :hackney, :ibrowse, :gun]
def applications(_), do: [:logger, :ssl, :inets]
defp description do
"HTTP client library, with support for middleware and multiple adapters."
end
defp package do
[
maintainers: ["Tymon Tobolski"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/teamon/tesla"}
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp lockfile(nil), do: "mix.lock"
defp lockfile(lockfile), do: "test/lockfiles/#{lockfile}.lock"
defp deps do
[
{:mime, "~> 1.0"},
# http clients
{:ibrowse, "~> 4.4.0", optional: true},
{:hackney, "~> 1.6", optional: true},
{:gun, "~> 1.3", optional: true},
{:castore, "~> 0.1", optional: true},
{:mint, "~> 1.0", optional: true},
# json parsers
{:jason, ">= 1.0.0", optional: true},
{:poison, ">= 1.0.0", optional: true},
{:exjsx, ">= 3.0.0", optional: true},
# other
{:fuse, "~> 2.4", optional: true},
- {:telemetry, "~> 0.3", optional: true},
+ {:telemetry, "~> 0.4", optional: true},
# testing & docs
{:excoveralls, "~> 0.8", only: :test},
{:httparrot, "~> 1.2", only: :test},
{:ex_doc, "~> 0.21", only: :dev},
{:mix_test_watch, "~> 1.0", only: :dev},
{:dialyxir, "~> 1.0.0-rc.3", only: [:dev, :test]},
{:inch_ex, "~> 0.5.6", only: :docs}
]
end
defp docs do
[
main: "readme",
source_ref: "v#{@version}",
extras: ["README.md"],
groups_for_modules: [
Behaviours: [
Tesla.Adapter,
Tesla.Middleware
],
Adapters: [
Tesla.Adapter.Gun,
Tesla.Adapter.Hackney,
Tesla.Adapter.Httpc,
Tesla.Adapter.Ibrowse,
Tesla.Adapter.Mint
],
Middlewares: [
Tesla.Middleware.BaseUrl,
Tesla.Middleware.BasicAuth,
Tesla.Middleware.Compression,
Tesla.Middleware.CompressRequest,
Tesla.Middleware.DecodeJson,
Tesla.Middleware.DecodeRels,
Tesla.Middleware.DecompressResponse,
Tesla.Middleware.DigestAuth,
Tesla.Middleware.EncodeJson,
Tesla.Middleware.FollowRedirects,
Tesla.Middleware.FormUrlencoded,
Tesla.Middleware.Fuse,
Tesla.Middleware.Headers,
Tesla.Middleware.JSON,
Tesla.Middleware.KeepRequest,
Tesla.Middleware.Logger,
Tesla.Middleware.MethodOverride,
Tesla.Middleware.Opts,
Tesla.Middleware.PathParams,
Tesla.Middleware.Query,
Tesla.Middleware.Retry,
Tesla.Middleware.Telemetry,
Tesla.Middleware.Timeout
]
],
nest_modules_by_prefix: [
Tesla.Adapter,
Tesla.Middleware
]
]
end
end
diff --git a/test/tesla/middleware/telemetry_test.exs b/test/tesla/middleware/telemetry_test.exs
index a4417dd..6986333 100644
--- a/test/tesla/middleware/telemetry_test.exs
+++ b/test/tesla/middleware/telemetry_test.exs
@@ -1,52 +1,75 @@
defmodule Tesla.Middleware.TelemetryTest do
use ExUnit.Case, async: true
defmodule Client do
use Tesla
plug Tesla.Middleware.Telemetry
adapter fn env ->
case env.url do
"/telemetry" -> {:ok, env}
"/telemetry_error" -> {:error, :econnrefused}
+ "/telemetry_exception" -> raise "some exception"
end
end
end
setup do
Application.ensure_all_started(:telemetry)
+
+ on_exit(fn ->
+ :telemetry.list_handlers([])
+ |> Enum.each(&:telemetry.detach(&1.id))
+ end)
+
:ok
end
- test "Get the info from telemetry" do
- :telemetry.attach(
- "telemetry_test",
- [:tesla, :request],
- fn [:tesla, :request], %{request_time: time}, meta, _config ->
- send(self(), {:ok_called, is_integer(time), meta})
- end,
- nil
- )
-
- Client.get("/telemetry")
-
- assert_receive {:ok_called, true,
- %{result: {:ok, %Tesla.Env{url: "/telemetry", method: :get}}}},
- 1000
+ test "events are all emitted properly" do
+ Enum.each(["/telemetry", "/telemetry_error"], fn path ->
+ :telemetry.attach("start event", [:tesla, :request, :start], &echo_event/4, %{
+ caller: self()
+ })
+
+ :telemetry.attach("stop event", [:tesla, :request, :stop], &echo_event/4, %{
+ caller: self()
+ })
+
+ :telemetry.attach("legacy event", [:tesla, :request], &echo_event/4, %{
+ caller: self()
+ })
+
+ Client.get(path)
+
+ assert_receive {:event, [:tesla, :request, :start], %{system_time: time},
+ %{env: %Tesla.Env{url: path, method: :get}}}
+
+ assert_receive {:event, [:tesla, :request, :stop], %{duration: time},
+ %{env: %Tesla.Env{url: path, method: :get}}}
+
+ assert_receive {:event, [:tesla, :request], %{request_time: time}, %{result: result}}
+ end)
+ end
+
+ test "with an exception raised" do
+ :telemetry.attach("with_exception", [:tesla, :request, :exception], &echo_event/4, %{
+ caller: self()
+ })
+
+ assert_raise RuntimeError, fn ->
+ Client.get("/telemetry_exception")
+ end
+
+ assert_receive {:event, [:tesla, :request, :exception], %{duration: time},
+ %{
+ kind: kind,
+ reason: reason,
+ stacktrace: stacktrace
+ }}
end
- test "Get the error from telemetry" do
- :telemetry.attach(
- "telemetry_test_error",
- [:tesla, :request],
- fn [:tesla, :request], %{request_time: time}, meta, _config ->
- send(self(), {:error_called, is_integer(time), meta})
- end,
- nil
- )
-
- Client.get("/telemetry_error")
- assert_receive {:error_called, true, %{result: {:error, :econnrefused}}}, 1000
+ def echo_event(event, measurements, metadata, config) do
+ send(config.caller, {:event, event, measurements, metadata})
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 7:33 PM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40034
Default Alt Text
(11 KB)

Event Timeline