Skip to content

Commit f1400d6

Browse files
Merge pull request #175 from getsentry/post-event-hook
post event hook
2 parents 5e4605e + 4c663a1 commit f1400d6

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ config :sentry,
7272
| `hackney_pool_max_connections` | False | 50 | |
7373
| `hackney_pool_timeout` | False | 5000 | |
7474
| `before_send_event` | False | | |
75+
| `after_send_event` | False | | |
7576
| `enable_source_code_context` | True | | |
7677
| `root_source_code_path` | Required if `enable_source_code_context` is enabled | | Should generally be set to `File.cwd!`|
7778
| `context_lines` | False | 3 | |

docs/config.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ Optional settings
8585

8686
This option allows performing operations on the event before it is sent by ``Sentry.Client``. Accepts an anonymous function or a {module, function} tuple, and the event will be passed as the only argument.
8787

88+
.. describe:: after_send_event
89+
90+
This option allows performing arbitrary operations after attempting to send an event. Accepts an anonymous function or a {module, function} tuple, and the event will be passed as the first argument, and the result of sending the event will be passed as the second argument.
91+
8892
.. describe:: context_lines
8993

9094
The number of lines of source code before and after the line that caused the exception to be included. Defaults to ``3``.

lib/sentry/client.ex

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Sentry.Client do
22
@behaviour Sentry.HTTPClient
33

4-
@moduledoc """
4+
@moduledoc ~S"""
55
This module is the default client for sending an event to Sentry via HTTP.
66
77
It makes use of `Task.Supervisor` to allow sending tasks synchronously or asynchronously, and defaulting to asynchronous. See `Sentry.Client.send_event/2` for more information.
@@ -12,12 +12,25 @@ defmodule Sentry.Client do
1212
it is sent. Accepts an anonymous function or a {module, function} tuple, and
1313
the event will be passed as the only argument.
1414
15+
* `:after_send_event` - callback that is called after attempting to send an event.
16+
Accepts an anonymous function or a {module, function} tuple. The result of the HTTP call as well as the event will be passed as arguments.
17+
The return value of the callback is not returned.
18+
1519
Example configuration of putting Logger metadata in the extra context:
1620
1721
config :sentry,
1822
before_send_event: fn(event) ->
1923
metadata = Map.new(Logger.metadata)
2024
%{event | extra: Map.merge(event.extra, metadata)}
25+
end,
26+
27+
after_send_event: fn(event, result) ->
28+
case result do
29+
{:ok, id} ->
30+
Logger.info("Successfully sent event!")
31+
_ ->
32+
Logger.info(fn -> "Did not successfully send event! #{inspect(event)}" end)
33+
end
2134
end
2235
"""
2336

@@ -49,32 +62,35 @@ defmodule Sentry.Client do
4962
event = maybe_call_before_send_event(event)
5063
case Poison.encode(event) do
5164
{:ok, body} ->
52-
do_send_event(body, result)
65+
do_send_event(event, body, result)
5366
{:error, error} ->
5467
log_api_error("Unable to encode Sentry error - #{inspect(error)}")
5568
:error
5669
end
5770
end
5871

59-
defp do_send_event(body, :async) do
72+
defp do_send_event(event, body, :async) do
6073
{endpoint, public_key, secret_key} = get_dsn!()
6174
auth_headers = authorization_headers(public_key, secret_key)
6275
{:ok, Task.Supervisor.async_nolink(Sentry.TaskSupervisor, fn ->
6376
try_request(:post, endpoint, auth_headers, body)
77+
|> maybe_call_after_send_event(event)
6478
end)}
6579
end
6680

67-
defp do_send_event(body, :sync) do
81+
defp do_send_event(event, body, :sync) do
6882
{endpoint, public_key, secret_key} = get_dsn!()
6983
auth_headers = authorization_headers(public_key, secret_key)
7084
try_request(:post, endpoint, auth_headers, body)
85+
|> maybe_call_after_send_event(event)
7186
end
7287

73-
defp do_send_event(body, :none) do
88+
defp do_send_event(event, body, :none) do
7489
{endpoint, public_key, secret_key} = get_dsn!()
7590
auth_headers = authorization_headers(public_key, secret_key)
7691
Task.Supervisor.start_child(Sentry.TaskSupervisor, fn ->
7792
try_request(:post, endpoint, auth_headers, body)
93+
|> maybe_call_after_send_event(event)
7894
end)
7995

8096
{:ok, ""}
@@ -157,6 +173,21 @@ defmodule Sentry.Client do
157173
{endpoint, public_key, secret_key}
158174
end
159175

176+
def maybe_call_after_send_event(result, event) do
177+
case Application.get_env(:sentry, :after_send_event) do
178+
function when is_function(function, 2) ->
179+
function.(event, result)
180+
{module, function} ->
181+
apply(module, function, [event, result])
182+
nil ->
183+
nil
184+
_ ->
185+
raise ArgumentError, message: ":after_send_event must be an anonymous function or a {Module, Function} tuple"
186+
end
187+
188+
result
189+
end
190+
160191
def maybe_call_before_send_event(event) do
161192
case Application.get_env(:sentry, :before_send_event) do
162193
function when is_function(function, 1) ->

test/client_test.exs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule Sentry.ClientTest do
22
use ExUnit.Case
33
import ExUnit.CaptureLog
44
import Sentry.TestEnvironmentHelper
5+
require Logger
56

67
alias Sentry.Client
78

@@ -104,4 +105,55 @@ defmodule Sentry.ClientTest do
104105
end)
105106
end
106107
end
108+
109+
test "calls anonymous after_send_event synchronously" do
110+
bypass = Bypass.open
111+
Bypass.expect bypass, fn conn ->
112+
{:ok, _body, conn} = Plug.Conn.read_body(conn)
113+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
114+
end
115+
116+
modify_env(:sentry, [dsn: "http://public:secret@localhost:#{bypass.port}/1",
117+
after_send_event: fn(_e, _r) ->
118+
Logger.error("AFTER_SEND_EVENT")
119+
end,
120+
client: Sentry.Client
121+
]
122+
)
123+
124+
try do
125+
Event.not_a_function
126+
rescue
127+
e ->
128+
assert capture_log(fn ->
129+
Sentry.capture_exception(e, result: :sync)
130+
end) =~ "AFTER_SEND_EVENT"
131+
end
132+
end
133+
134+
test "calls anonymous after_send_event asynchronously" do
135+
bypass = Bypass.open
136+
Bypass.expect bypass, fn conn ->
137+
{:ok, _body, conn} = Plug.Conn.read_body(conn)
138+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
139+
end
140+
141+
modify_env(:sentry, [dsn: "http://public:secret@localhost:#{bypass.port}/1",
142+
after_send_event: fn(_e, _r) ->
143+
Logger.error("AFTER_SEND_EVENT")
144+
end,
145+
client: Sentry.Client
146+
]
147+
)
148+
149+
try do
150+
Event.not_a_function
151+
rescue
152+
e ->
153+
assert capture_log(fn ->
154+
{:ok, task} = Sentry.capture_exception(e, result: :async)
155+
Task.await(task)
156+
end) =~ "AFTER_SEND_EVENT"
157+
end
158+
end
107159
end

0 commit comments

Comments
 (0)