diff --git a/lib/posthog.ex b/lib/posthog.ex index a6cc9c9..09c8dff 100644 --- a/lib/posthog.ex +++ b/lib/posthog.ex @@ -3,6 +3,8 @@ defmodule PostHog do Main API for working with PostHog """ + require Logger + @typedoc "Name under which an instance of PostHog supervision tree is registered." @type supervisor_name() :: atom() @@ -78,7 +80,36 @@ defmodule PostHog do properties: properties } - PostHog.Sender.send(event, name) + case run_before_send(Map.get(config, :before_send), event) do + nil -> :ok + event -> PostHog.Sender.send(event, name) + end + end + + defp run_before_send(nil, event), do: event + + defp run_before_send(before_send, event) when is_function(before_send, 1) do + case before_send.(event) do + nil -> + nil + + %{} = event -> + event + + other -> + Logger.error( + "PostHog before_send callback returned #{inspect(other)} instead of an event map or nil; dropping event" + ) + + nil + end + rescue + exception -> + Logger.error( + "PostHog before_send callback raised; dropping event: #{Exception.message(exception)}" + ) + + nil end @doc false diff --git a/lib/posthog/config.ex b/lib/posthog/config.ex index fb82a6d..828ea53 100644 --- a/lib/posthog/config.ex +++ b/lib/posthog/config.ex @@ -60,6 +60,12 @@ defmodule PostHog.Config do default: %{}, doc: "Map of properties that should be added to all events" ], + before_send: [ + type: {:or, [{:fun, 1}, nil]}, + default: nil, + doc: + "Callback invoked with the fully enriched event before it is queued. Return the event to send a modified version, or nil to drop it." + ], is_server: [ type: :boolean, default: true, diff --git a/lib/posthog/registry.ex b/lib/posthog/registry.ex index 0a787ba..90e22cd 100644 --- a/lib/posthog/registry.ex +++ b/lib/posthog/registry.ex @@ -21,6 +21,7 @@ defmodule PostHog.Registry do enabled: false, api_client: nil, global_properties: %{}, + before_send: nil, test_mode: false } end diff --git a/test/posthog_test.exs b/test/posthog_test.exs index 324d864..ce19d5f 100644 --- a/test/posthog_test.exs +++ b/test/posthog_test.exs @@ -86,6 +86,27 @@ defmodule PostHogTest do refute Map.has_key?(properties, :"$is_server") end + @tag config: [ + global_properties: %{source: "global"}, + before_send: &__MODULE__.modify_before_send/1, + supervisor_name: PostHog + ] + test "before_send can modify fully enriched events" do + PostHog.bare_capture("case tested", "distinct_id", %{secret: "remove"}) + + assert [%{properties: properties}] = all_captured() + assert properties[:before_send] == true + assert properties[:saw_fully_enriched_event] == true + refute Map.has_key?(properties, :secret) + end + + @tag config: [before_send: &__MODULE__.drop_before_send/1, supervisor_name: PostHog] + test "before_send can drop events" do + assert :ok = PostHog.bare_capture("case tested", "distinct_id") + + assert [] = all_captured() + end + @tag config: [supervisor_name: CustomPostHog] test "simple call for custom supervisor" do PostHog.bare_capture(CustomPostHog, "case tested", "distinct_id") @@ -260,6 +281,21 @@ defmodule PostHogTest do end end + def modify_before_send(event) do + saw_fully_enriched_event = + event.properties[:"$lib"] == "posthog-elixir" and + is_binary(event.properties[:"$lib_version"]) and + event.properties[:"$is_server"] == true and + event.properties[:source] == "global" + + event + |> put_in([:properties, :before_send], true) + |> put_in([:properties, :saw_fully_enriched_event], saw_fully_enriched_event) + |> update_in([:properties], &Map.delete(&1, :secret)) + end + + def drop_before_send(_event), do: nil + describe "set_event_context/2 + get_event_context/2" do test "default scope" do PostHog.set_event_context("$exception", %{foo: "bar"})