diff --git a/README.md b/README.md index 7ac097cd..18bd446d 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ config :sentry, | `hackney_pool_timeout` | False | 5000 | | | `before_send_event` | False | | | | `after_send_event` | False | | | +| `sample_rate` | False | 1.0 | | | `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 | | diff --git a/docs/config.rst b/docs/config.rst index 2c21e290..e844bf80 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -89,6 +89,10 @@ Optional settings 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. +.. describe:: sample_rate + + The sampling factor to apply to events. A value of 0.0 will deny sending any events, and a value of 1.0 will send 100% of events. + .. 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``. diff --git a/lib/sentry/client.ex b/lib/sentry/client.ex index 6cdcd53c..110957c2 100644 --- a/lib/sentry/client.ex +++ b/lib/sentry/client.ex @@ -41,6 +41,7 @@ defmodule Sentry.Client do @type get_dsn :: {String.t, String.t, Integer.t} @sentry_version 5 @max_attempts 4 + @default_sample_rate 1.0 @hackney_pool_name :sentry_pool quote do @@ -54,12 +55,23 @@ defmodule Sentry.Client do ### Options * `:result` - Allows specifying how the result should be returned. Options include `:sync`, `:none`, and `:async`. `:sync` will make the API call synchronously, and return `{:ok, event_id}` if successful. `:none` sends the event from an unlinked child process under `Sentry.TaskSupervisor` and will return `{:ok, ""}` regardless of the result. `:async` will start an unlinked task and return a tuple of `{:ok, Task.t}` on success where the Task can be awaited upon to receive the result asynchronously. When used in an OTP behaviour like GenServer, the task will send a message that needs to be matched with `GenServer.handle_info/2`. See `Task.Supervisor.async_nolink/2` for more information. `:async` is the default. + * `:sample_rate` - The sampling factor to apply to events. A value of 0.0 will deny sending any events, and a value of 1.0 will send 100% of events. """ - @spec send_event(Event.t) :: {:ok, Task.t | String.t} | :error + @spec send_event(Event.t) :: {:ok, Task.t | String.t} | :error | :unsampled def send_event(%Event{} = event, opts \\ []) do result = Keyword.get(opts, :result, :async) + sample_rate = Keyword.get(opts, :sample_rate) || Application.get_env(:sentry, :sample_rate, @default_sample_rate) event = maybe_call_before_send_event(event) + + if sample_event?(sample_rate) do + encode_and_send(event, result) + else + :unsampled + end + end + + defp encode_and_send(event, result) do case Poison.encode(event) do {:ok, body} -> do_send_event(event, body, result) @@ -225,4 +237,12 @@ defmodule Sentry.Client do |> Kernel.round() |> :timer.sleep() end + + defp sample_event?(1), do: true + defp sample_event?(1.0), do: true + defp sample_event?(0), do: false + defp sample_event?(0.0), do: false + defp sample_event?(sample_rate) do + :rand.uniform < sample_rate + end end diff --git a/test/client_test.exs b/test/client_test.exs index 474f9dcb..c8e8930d 100644 --- a/test/client_test.exs +++ b/test/client_test.exs @@ -156,4 +156,46 @@ defmodule Sentry.ClientTest do end) =~ "AFTER_SEND_EVENT" end end + + test "sends event with sample_rate of 1" 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", + client: Sentry.Client + ] + ) + + try do + Event.not_a_function + rescue + e -> + {:ok, _} = Sentry.capture_exception(e, result: :sync, sample_rate: 1) + end + end + + test "does not send event with sample_rate of 0" 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", + client: Sentry.Client + ] + ) + + try do + Event.not_a_function + rescue + e -> + {:ok, _} = Sentry.capture_exception(e, result: :sync, sample_rate: 1) + Bypass.down(bypass) + :unsampled = Sentry.capture_exception(e, result: :sync, sample_rate: 0.0) + end + end end