Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ config :sentry,
| `hackney_pool_max_connections` | False | 50 | |
| `hackney_pool_timeout` | False | 5000 | |
| `before_send_event` | False | | |
| `after_send_event` | False | | |
| `enable_source_code_context` | True | | |
| `root_source_code_path` | Required if `enable_source_code_context` is enabled | | Should generally be set to `File.cwd!`|
| `context_lines` | False | 3 | |
Expand Down
4 changes: 4 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ Optional settings

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.

.. describe:: after_send_event

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

.. describe:: context_lines

The number of lines of source code before and after the line that caused the exception to be included. Defaults to ``3``.
Expand Down
30 changes: 26 additions & 4 deletions lib/sentry/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ defmodule Sentry.Client do
it is sent. Accepts an anonymous function or a {module, function} tuple, and
the event will be passed as the only argument.

* `:after_send_event` - callback that is called after an event is successfully sent.
Accepts an anonymous function or a {module, function} tuple, and the event will be passed as the only argument.

Example configuration of putting Logger metadata in the extra context:

config :sentry,
Expand Down Expand Up @@ -49,32 +52,35 @@ defmodule Sentry.Client do
event = maybe_call_before_send_event(event)
case Poison.encode(event) do
{:ok, body} ->
do_send_event(body, result)
do_send_event(event, body, result)
{:error, error} ->
log_api_error("Unable to encode Sentry error - #{inspect(error)}")
:error
end
end

defp do_send_event(body, :async) do
defp do_send_event(event, body, :async) do
{endpoint, public_key, secret_key} = get_dsn!()
auth_headers = authorization_headers(public_key, secret_key)
{:ok, Task.Supervisor.async_nolink(Sentry.TaskSupervisor, fn ->
try_request(:post, endpoint, auth_headers, body)
|> maybe_call_after_send_event(event)
end)}
end

defp do_send_event(body, :sync) do
defp do_send_event(event, body, :sync) do
{endpoint, public_key, secret_key} = get_dsn!()
auth_headers = authorization_headers(public_key, secret_key)
try_request(:post, endpoint, auth_headers, body)
|> maybe_call_after_send_event(event)
end

defp do_send_event(body, :none) do
defp do_send_event(event, body, :none) do
{endpoint, public_key, secret_key} = get_dsn!()
auth_headers = authorization_headers(public_key, secret_key)
Task.Supervisor.start_child(Sentry.TaskSupervisor, fn ->
try_request(:post, endpoint, auth_headers, body)
|> maybe_call_after_send_event(event)
end)

{:ok, ""}
Expand Down Expand Up @@ -157,6 +163,22 @@ defmodule Sentry.Client do
{endpoint, public_key, secret_key}
end

def maybe_call_after_send_event({:ok, _} = result, event) do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeregrine Right now we only try to call the hook if the sending event was successful, but I was thinking since we're passing the result, we could call function.(result, event) and let the user decide so we can skip the logic on our side?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me

case Application.get_env(:sentry, :after_send_event) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably break this case into a function and use it for both scenarios instead of copying it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they each call the configured function differently because of different arities, so this makes less sense now right?

function when is_function(function, 1) -> function.(event)
{module, function} -> apply(module, function, [event])
nil -> nil
_ ->
raise ArgumentError, message: ":after_send_event must be an anonymous function or a {Module, Function} tuple"
end

result
end

def maybe_call_after_send_event(result, _event) do
result
end

def maybe_call_before_send_event(event) do
case Application.get_env(:sentry, :before_send_event) do
function when is_function(function, 1) ->
Expand Down
52 changes: 52 additions & 0 deletions test/client_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule Sentry.ClientTest do
use ExUnit.Case
import ExUnit.CaptureLog
import Sentry.TestEnvironmentHelper
require Logger

alias Sentry.Client

Expand Down Expand Up @@ -104,4 +105,55 @@ defmodule Sentry.ClientTest do
end)
end
end

test "calls anonymous after_send_event synchronously" do
bypass = Bypass.open
Bypass.expect bypass, fn conn ->
{:ok, _body, conn} = Plug.Conn.read_body(conn)
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end

modify_env(:sentry, [dsn: "http://public:secret@localhost:#{bypass.port}/1",
after_send_event: fn(_e) ->
Logger.error("AFTER_SEND_EVENT")
end,
client: Sentry.Client
]
)

try do
Event.not_a_function
rescue
e ->
assert capture_log(fn ->
Sentry.capture_exception(e, result: :sync)
end) =~ "AFTER_SEND_EVENT"
end
end

test "calls anonymous after_send_event asynchronously" do
bypass = Bypass.open
Bypass.expect bypass, fn conn ->
{:ok, _body, conn} = Plug.Conn.read_body(conn)
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
end

modify_env(:sentry, [dsn: "http://public:secret@localhost:#{bypass.port}/1",
after_send_event: fn(_e) ->
Logger.error("AFTER_SEND_EVENT")
end,
client: Sentry.Client
]
)

try do
Event.not_a_function
rescue
e ->
assert capture_log(fn ->
{:ok, task} = Sentry.capture_exception(e, result: :async)
Task.await(task)
end) =~ "AFTER_SEND_EVENT"
end
end
end