diff --git a/lib/sentry/application.ex b/lib/sentry/application.ex index e5c574df..4b761efb 100644 --- a/lib/sentry/application.ex +++ b/lib/sentry/application.ex @@ -5,6 +5,7 @@ defmodule Sentry.Application do alias Sentry.Config alias Sentry.LoggerUtils + alias Sentry.Metric @compile {:no_warn_undefined, [NimbleOwnership]} @@ -82,6 +83,7 @@ defmodule Sentry.Application do [Sentry.Transport.SenderPool] cache_loaded_applications() + Metric.init_sequence() with {:ok, pid} <- Supervisor.start_link(children, strategy: :one_for_one, name: Sentry.Supervisor) do diff --git a/lib/sentry/metric.ex b/lib/sentry/metric.ex index a2e63d5a..fbead07a 100644 --- a/lib/sentry/metric.ex +++ b/lib/sentry/metric.ex @@ -42,6 +42,8 @@ defmodule Sentry.Metric do @sdk_version Mix.Project.config()[:version] + @sequence_key {:sentry, :metric_sequence} + @doc """ Attaches default attributes to a metric. @@ -64,7 +66,22 @@ defmodule Sentry.Metric do |> maybe_put_attr("server.address", Config.server_name()) # Merge with user attributes (user attributes take precedence) - %{metric | attributes: Map.merge(default_attrs, metric.attributes)} + attributes = + default_attrs + |> Map.merge(metric.attributes) + |> Map.put("sentry.timestamp.sequence", next_sequence()) + + %{metric | attributes: attributes} + end + + @doc false + @spec init_sequence() :: :ok + def init_sequence do + :persistent_term.put(@sequence_key, :atomics.new(1, signed: true)) + end + + defp next_sequence do + :atomics.add_get(:persistent_term.get(@sequence_key), 1, 1) - 1 end defp maybe_put_attr(attrs, _key, nil), do: attrs diff --git a/test/sentry/metric_sequence_test.exs b/test/sentry/metric_sequence_test.exs new file mode 100644 index 00000000..514f0c3a --- /dev/null +++ b/test/sentry/metric_sequence_test.exs @@ -0,0 +1,37 @@ +defmodule Sentry.MetricSequenceTest do + use Sentry.Case, async: false + + alias Sentry.Metric + + setup do + Metric.init_sequence() + :ok + end + + test "starts at 0 and increments by 1 for every metric captured" do + assert sequence_of(build(:counter)) == 0 + assert sequence_of(build(:gauge)) == 1 + assert sequence_of(build(:distribution)) == 2 + end + + test "cannot be overridden by a user-supplied attribute" do + assert sequence_of(build(:counter, %{"sentry.timestamp.sequence" => 999})) == 0 + end + + defp build(type, attributes \\ %{}) do + %Metric{ + type: type, + name: "test.#{type}", + value: 1, + timestamp: 1_234_567_890.0, + attributes: attributes + } + end + + defp sequence_of(metric) do + metric + |> Metric.attach_default_attributes() + |> Map.fetch!(:attributes) + |> Map.fetch!("sentry.timestamp.sequence") + end +end