Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add :telemetry_prefix option to Tesla.Middleware.Telemetry #390

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 44 additions & 32 deletions lib/tesla/middleware/telemetry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,28 @@ if Code.ensure_loaded?(:telemetry) do
end)
```

## Options
- `:prefix` - replaces default `[:tesla]` with desired Telemetry event prefix (see below)

## Custom Prefix

All events will use a `:prefix` which defaults to `[:tesla]`.

You can customize events by providing your own `:prefix` locally:

```
defmodule MyClient do
use Tesla

plug Tesla.Middleware.Telemetry, prefix: [:custom, :prefix]

end

:telemetry.attach("my-tesla-telemetry", [:custom, :prefix, :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.
Expand Down Expand Up @@ -46,11 +68,14 @@ if Code.ensure_loaded?(:telemetry) do

@behaviour Tesla.Middleware

@default_prefix [:tesla]

@impl Tesla.Middleware
def call(env, next, _opts) do
def call(env, next, opts) do
start_time = System.monotonic_time()
prefix = Keyword.get(opts, :prefix, @default_prefix)

emit_start(%{env: env})
emit_start(%{env: env}, prefix)

try do
Tesla.run(env, next)
Expand All @@ -59,62 +84,49 @@ if Code.ensure_loaded?(:telemetry) do
stacktrace = System.stacktrace()
duration = System.monotonic_time() - start_time

emit_exception(duration, %{kind: kind, reason: reason, stacktrace: stacktrace})
emit_exception(duration, %{kind: kind, reason: reason, stacktrace: stacktrace}, prefix)

: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)
emit_stop(duration, %{env: env}, prefix)
emit_legacy_event(duration, result, prefix)

result

{:error, reason} = result ->
duration = System.monotonic_time() - start_time

emit_stop(duration, %{env: env, error: reason})
emit_legacy_event(duration, result)
emit_stop(duration, %{env: env, error: reason}, prefix)
emit_legacy_event(duration, result, prefix)

result
end
end

defp emit_start(metadata) do
:telemetry.execute(
[:tesla, :request, :start],
%{system_time: System.system_time()},
metadata
)
defp emit_start(metadata, prefix) do
event = prefix ++ [:request, :start]
:telemetry.execute(event, %{system_time: System.system_time()}, metadata)
end

defp emit_stop(duration, metadata) do
:telemetry.execute(
[:tesla, :request, :stop],
%{duration: duration},
metadata
)
defp emit_stop(duration, metadata, prefix) do
event = prefix ++ [:request, :stop]
:telemetry.execute(event, %{duration: duration}, metadata)
end

defp emit_legacy_event(duration, result) do
defp emit_legacy_event(duration, result, prefix) do
if !@disable_legacy_event do
event = prefix ++ [:request]
duration_µs = System.convert_time_unit(duration, :native, :microsecond)

:telemetry.execute(
[:tesla, :request],
%{request_time: duration_µs},
%{result: result}
)
:telemetry.execute(event, %{request_time: duration_µs}, %{result: result})
end
end

defp emit_exception(duration, metadata) do
:telemetry.execute(
[:tesla, :request, :exception],
%{duration: duration},
metadata
)
defp emit_exception(duration, metadata, prefix) do
event = prefix ++ [:request, :exception]
:telemetry.execute(event, %{duration: duration}, metadata)
end
end
end
115 changes: 86 additions & 29 deletions test/tesla/middleware/telemetry_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule Tesla.Middleware.TelemetryTest do
use ExUnit.Case, async: true

@moduletag capture_log: true

defmodule Client do
use Tesla

Expand All @@ -26,47 +28,102 @@ defmodule Tesla.Middleware.TelemetryTest do
:ok
end

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()
})
describe "Telemetry" do
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("stop event", [:tesla, :request, :stop], &echo_event/4, %{
caller: self()
})

:telemetry.attach("legacy event", [:tesla, :request], &echo_event/4, %{caller: self()})

Client.get(path)

:telemetry.attach("legacy event", [:tesla, :request], &echo_event/4, %{
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()
})

Client.get(path)
assert_raise RuntimeError, fn ->
Client.get("/telemetry_exception")
end

assert_receive {:event, [:tesla, :request, :exception], %{duration: time},
%{kind: kind, reason: reason, stacktrace: stacktrace}}
end
end

describe "with :prefix" do
defmodule ClientWithPrefix do
use Tesla

assert_receive {:event, [:tesla, :request, :start], %{system_time: time},
%{env: %Tesla.Env{url: path, method: :get}}}
plug Tesla.Middleware.Telemetry, prefix: [:custom, :prefix]

assert_receive {:event, [:tesla, :request, :stop], %{duration: time},
%{env: %Tesla.Env{url: path, method: :get}}}
adapter fn env ->
case env.url do
"/telemetry" -> {:ok, env}
"/telemetry_error" -> {:error, :econnrefused}
"/telemetry_exception" -> raise "some exception"
end
end
end

assert_receive {:event, [:tesla, :request], %{request_time: time}, %{result: result}}
end)
end
test "events are all emitted properly" do
Enum.each(["/telemetry", "/telemetry_error"], fn path ->
:telemetry.attach("start event", [:custom, :prefix, :request, :start], &echo_event/4, %{
caller: self()
})

:telemetry.attach("stop event", [:custom, :prefix, :request, :stop], &echo_event/4, %{
caller: self()
})

test "with an exception raised" do
:telemetry.attach("with_exception", [:tesla, :request, :exception], &echo_event/4, %{
caller: self()
})
:telemetry.attach("legacy event", [:custom, :prefix, :request], &echo_event/4, %{
caller: self()
})

assert_raise RuntimeError, fn ->
Client.get("/telemetry_exception")
ClientWithPrefix.get(path)

assert_receive {:event, [:custom, :prefix, :request, :start], %{system_time: time},
%{env: %Tesla.Env{url: path, method: :get}}}

assert_receive {:event, [:custom, :prefix, :request, :stop], %{duration: time},
%{env: %Tesla.Env{url: path, method: :get}}}

assert_receive {:event, [:custom, :prefix, :request], %{request_time: time},
%{result: result}}
end)
end

assert_receive {:event, [:tesla, :request, :exception], %{duration: time},
%{
kind: kind,
reason: reason,
stacktrace: stacktrace
}}
test "with an exception raised" do
:telemetry.attach(
"with_exception",
[:custom, :prefix, :request, :exception],
&echo_event/4,
%{caller: self()}
)

assert_raise RuntimeError, fn ->
ClientWithPrefix.get("/telemetry_exception")
end

assert_receive {:event, [:custom, :prefix, :request, :exception], %{duration: time},
%{kind: kind, reason: reason, stacktrace: stacktrace}}
end
end

def echo_event(event, measurements, metadata, config) do
Expand Down