Skip to content

Commit 84e61f5

Browse files
authored
Add Telemetry integration (#887)
1 parent 97d62ed commit 84e61f5

File tree

6 files changed

+171
-1
lines changed

6 files changed

+171
-1
lines changed

lib/sentry/application.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ defmodule Sentry.Application do
8383
if config[:quantum][:cron][:enabled] do
8484
Sentry.Integrations.Quantum.Cron.attach_telemetry_handler()
8585
end
86+
87+
if config[:telemetry][:report_handler_failures] do
88+
Sentry.Integrations.Telemetry.attach()
89+
end
8690
end
8791

8892
defp resolve_in_app_module_allow_list do

lib/sentry/config.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,24 @@ defmodule Sentry.Config do
9797
]
9898
]
9999
]
100+
],
101+
telemetry: [
102+
type: :keyword_list,
103+
doc: """
104+
Configuration for the [Telemetry](https://hexdocs.pm/telemetry) integration.
105+
*Available since v10.10.0*.
106+
""",
107+
keys: [
108+
report_handler_failures: [
109+
type: :boolean,
110+
default: false,
111+
doc: """
112+
Whether to report failures (to Sentry) that happen in telemetry handlers. These failures
113+
result in the handlers being detached, so capturing them in Sentry can be useful
114+
to detect and fix these issues as soon as possible.
115+
"""
116+
]
117+
]
100118
]
101119
]
102120

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
defmodule Sentry.Integrations.Telemetry do
2+
@moduledoc """
3+
Sentry integration for Telemetry.
4+
5+
*Available since v10.10.0*.
6+
"""
7+
8+
@moduledoc since: "10.10.0"
9+
10+
@failure_event [:telemetry, :handler, :failure]
11+
12+
@doc false
13+
@spec attach() :: :ok
14+
def attach do
15+
_ =
16+
:telemetry.attach(
17+
"#{inspect(__MODULE__)}-telemetry-failures",
18+
@failure_event,
19+
&handle_event/4,
20+
:no_config
21+
)
22+
23+
:ok
24+
end
25+
26+
@doc false
27+
@spec handle_event(
28+
:telemetry.event_name(),
29+
:telemetry.event_measurements(),
30+
:telemetry.event_metadata(),
31+
:telemetry.handler_config()
32+
) :: :ok
33+
def handle_event(@failure_event, _measurements, %{} = metadata, :no_config) do
34+
stacktrace = metadata[:stacktrace] || []
35+
handler_id = stringified_handler_id(metadata[:handler_id])
36+
37+
options = [
38+
stacktrace: stacktrace,
39+
tags: %{
40+
telemetry_handler_id: handler_id,
41+
event_name: inspect(metadata[:event_name])
42+
}
43+
]
44+
45+
_ =
46+
case {metadata[:kind], metadata[:reason]} do
47+
{:error, reason} ->
48+
exception = Exception.normalize(:error, reason, stacktrace)
49+
Sentry.capture_exception(exception, options)
50+
51+
{kind, reason} ->
52+
options =
53+
Keyword.merge(options,
54+
extra: %{kind: inspect(kind), reason: inspect(reason)},
55+
interpolation_parameters: [handler_id]
56+
)
57+
58+
Sentry.capture_message("Telemetry handler %s failed", options)
59+
end
60+
61+
:ok
62+
end
63+
64+
defp stringified_handler_id(handler_id) when is_binary(handler_id), do: handler_id
65+
defp stringified_handler_id(handler_id), do: inspect(handler_id)
66+
end

mix.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ defmodule Sentry.Mixfile do
3535
"pages/setup-with-plug-and-phoenix.md",
3636
"pages/oban-integration.md",
3737
"pages/quantum-integration.md",
38+
"pages/telemetry-integration.md",
3839
"pages/upgrade-8.x.md",
3940
"pages/upgrade-9.x.md",
4041
"pages/upgrade-10.x.md"
@@ -43,7 +44,8 @@ defmodule Sentry.Mixfile do
4344
Integrations: [
4445
"pages/setup-with-plug-and-phoenix.md",
4546
"pages/oban-integration.md",
46-
"pages/quantum-integration.md"
47+
"pages/quantum-integration.md",
48+
"pages/telemetry-integration.md"
4749
],
4850
"Upgrade Guides": [~r{^pages/upgrade}]
4951
],

pages/telemetry-integration.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Telemetry Integration
2+
3+
The Sentry SDK supports integrating with [Telemetry](https://github.com/beam-telemetry/telemetry).
4+
5+
For documentation and setup instructions, see the [Sentry website](https://docs.sentry.io/platforms/elixir/integrations/telemetry/).
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
defmodule Sentry.Integrations.TelemetryTest do
2+
use ExUnit.Case, async: true
3+
4+
alias Sentry.Integrations.Telemetry
5+
6+
describe "handle_event/4" do
7+
test "reports errors" do
8+
Sentry.Test.start_collecting()
9+
10+
handle_failure_event(:error, %RuntimeError{message: "oops"}, [])
11+
12+
assert [event] = Sentry.Test.pop_sentry_reports()
13+
14+
assert event.tags == %{
15+
telemetry_handler_id: "my_handler",
16+
event_name: "[:my_app, :some_event]"
17+
}
18+
19+
assert event.original_exception == %RuntimeError{message: "oops"}
20+
end
21+
22+
test "reports Erlang errors (normalized)" do
23+
Sentry.Test.start_collecting()
24+
25+
handle_failure_event(:error, {:badmap, :foo}, [])
26+
27+
assert [event] = Sentry.Test.pop_sentry_reports()
28+
29+
assert event.tags == %{
30+
telemetry_handler_id: "my_handler",
31+
event_name: "[:my_app, :some_event]"
32+
}
33+
34+
assert event.original_exception == %BadMapError{term: :foo}
35+
end
36+
37+
for kind <- [:throw, :exit] do
38+
test "reports #{kind}s" do
39+
Sentry.Test.start_collecting()
40+
41+
handle_failure_event(unquote(kind), :foo, [])
42+
43+
assert [event] = Sentry.Test.pop_sentry_reports()
44+
45+
assert event.message.message == "Telemetry handler %s failed"
46+
assert event.message.formatted == "Telemetry handler my_handler failed"
47+
48+
assert event.tags == %{
49+
telemetry_handler_id: "my_handler",
50+
event_name: "[:my_app, :some_event]"
51+
}
52+
53+
assert event.extra == %{kind: inspect(unquote(kind)), reason: ":foo"}
54+
55+
assert event.original_exception == nil
56+
end
57+
end
58+
end
59+
60+
defp handle_failure_event(kind, reason, stacktrace) do
61+
Telemetry.handle_event(
62+
[:telemetry, :handler, :failure],
63+
%{system_time: System.system_time(:native), monotonic_time: System.monotonic_time(:native)},
64+
%{
65+
handler_id: "my_handler",
66+
handler_config: %{my_key: "my value"},
67+
event_name: [:my_app, :some_event],
68+
kind: kind,
69+
reason: reason,
70+
stacktrace: stacktrace
71+
},
72+
:no_config
73+
)
74+
end
75+
end

0 commit comments

Comments
 (0)