Packages
sentry
13.3.0
13.3.0
13.2.0
13.1.0
13.0.1
13.0.0
12.0.3
12.0.2
12.0.1
12.0.0
11.0.4
11.0.3
11.0.2
11.0.1
11.0.0
10.10.0
10.9.0
10.8.1
10.8.0
10.7.1
10.7.0
10.6.2
10.6.1
10.6.0
10.5.0
10.4.0
10.3.0
10.2.1
10.2.0
10.2.0-rc.2
10.2.0-rc.1
10.1.0
10.0.3
10.0.2
10.0.1
10.0.0
9.1.0
9.0.0
8.1.0
8.0.6
8.0.5
8.0.4
8.0.3
8.0.2
8.0.1
8.0.0
8.0.0-rc.2
8.0.0-rc.1
8.0.0-rc.0
retired
7.2.5
7.2.4
7.2.3
7.2.2
7.2.1
7.2.0
7.1.0
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.4.2
6.4.1
6.4.0
6.3.0
6.2.1
6.2.0
6.1.0
6.0.5
6.0.4
6.0.3
6.0.2
6.0.1
6.0.0
5.0.1
5.0.0
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
The Official Elixir client for Sentry
Current section
Files
Jump to
Current section
Files
lib/sentry/metric.ex
defmodule Sentry.Metric do
@moduledoc """
Represents a metric that can be sent to Sentry.
Metrics follow the Sentry Metrics Protocol as defined in:
<https://develop.sentry.dev/sdk/telemetry/metrics/>
This module is used by `Sentry.Metrics` to create and send metric data to Sentry.
"""
@moduledoc since: "13.0.0"
alias Sentry.Config
@type metric_type :: :counter | :gauge | :distribution
@typedoc """
A metric struct.
"""
@type t :: %__MODULE__{
type: metric_type(),
name: String.t(),
value: number(),
timestamp: float(),
trace_id: String.t() | nil,
span_id: String.t() | nil,
unit: String.t() | nil,
attributes: map()
}
@enforce_keys [:type, :name, :value, :timestamp]
defstruct [
:type,
:name,
:value,
:timestamp,
:trace_id,
:span_id,
:unit,
attributes: %{}
]
@sdk_version Mix.Project.config()[:version]
@doc """
Attaches default attributes to a metric.
This adds Sentry-specific attributes like environment, release, SDK info, and server name.
Per the Sentry Metrics spec, default attributes should be attached before the
`before_send_metric` callback is applied (step 5 before step 6).
"""
@spec attach_default_attributes(t()) :: t()
def attach_default_attributes(%__MODULE__{} = metric) do
default_attrs = %{
"sentry.sdk.name" => "sentry.elixir",
"sentry.sdk.version" => @sdk_version
}
# Add optional attributes if configured
default_attrs =
default_attrs
|> maybe_put_attr("sentry.environment", Config.environment_name())
|> maybe_put_attr("sentry.release", Config.release())
|> maybe_put_attr("server.address", Config.server_name())
# Merge with user attributes (user attributes take precedence)
%{metric | attributes: Map.merge(default_attrs, metric.attributes)}
end
defp maybe_put_attr(attrs, _key, nil), do: attrs
defp maybe_put_attr(attrs, key, value), do: Map.put(attrs, key, value)
@doc """
Converts a metric to a map suitable for JSON encoding.
The output matches the Sentry metrics schema with top-level fields: timestamp, type, name,
value, unit, trace_id, span_id, and attributes. The attributes are formatted with type
information as required by the protocol.
"""
@spec to_map(t()) :: %{optional(atom()) => term()}
def to_map(%__MODULE__{} = metric) do
%{
timestamp: metric.timestamp,
type: to_string(metric.type),
name: metric.name,
value: metric.value,
attributes: format_attributes(metric.attributes)
}
|> maybe_put(:unit, metric.unit)
|> maybe_put(:trace_id, metric.trace_id)
|> maybe_put(:span_id, metric.span_id)
end
@doc false
@spec call_before_send_callback(t(), function() | {module(), atom()}) :: t() | nil
def call_before_send_callback(metric, function) when is_function(function, 1) do
function.(metric)
rescue
error ->
require Logger
Logger.warning("before_send_metric callback failed: #{inspect(error)}")
metric
end
def call_before_send_callback(metric, {mod, fun}) do
apply(mod, fun, [metric])
rescue
error ->
require Logger
Logger.warning("before_send_metric callback failed: #{inspect(error)}")
metric
end
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
## Helpers
# Format attributes to the protocol format with type information
defp format_attributes(attributes) when is_map(attributes) do
Enum.into(attributes, %{}, fn {key, value} ->
safe_value = sanitize_attribute_value(value)
{to_string(key), %{value: safe_value, type: attribute_type(safe_value)}}
end)
end
# Converts values to JSON-safe attribute types.
# Primitives (string, boolean, integer, float) pass through unchanged.
# Atoms are converted to strings. All other types (structs, maps, lists,
# tuples, PIDs, etc.) are converted to their inspect() representation.
# Note: is_boolean must come before is_atom since true/false are atoms
defp sanitize_attribute_value(value) when is_binary(value), do: value
defp sanitize_attribute_value(value) when is_boolean(value), do: value
defp sanitize_attribute_value(value) when is_atom(value), do: Atom.to_string(value)
defp sanitize_attribute_value(value) when is_integer(value), do: value
defp sanitize_attribute_value(value) when is_float(value), do: value
defp sanitize_attribute_value(value), do: inspect(value)
defp attribute_type(value) when is_boolean(value), do: "boolean"
defp attribute_type(value) when is_integer(value), do: "integer"
defp attribute_type(value) when is_float(value), do: "double"
defp attribute_type(_value), do: "string"
end