Packages
sentry
10.0.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/transport.ex
defmodule Sentry.Transport do
@moduledoc false
# This module is exclusively responsible for POSTing envelopes to Sentry.
alias Sentry.Config
alias Sentry.Envelope
@default_retries [1000, 2000, 4000, 8000]
@sentry_version 5
@sentry_client "sentry-elixir/#{Mix.Project.config()[:version]}"
@spec default_retries() :: [pos_integer(), ...]
def default_retries do
@default_retries
end
# The "retries" parameter is there for better testing.
@spec post_envelope(Envelope.t(), module(), [non_neg_integer()]) ::
{:ok, envelope_id :: String.t()} | {:error, term()}
def post_envelope(%Envelope{} = envelope, client, retries \\ @default_retries)
when is_atom(client) and is_list(retries) do
with {:json, {:ok, body}} <- {:json, Envelope.to_binary(envelope)},
{:ok, endpoint, headers} <- get_endpoint_and_headers() do
post_envelope_with_retries(client, endpoint, headers, body, retries)
else
{:json, {:error, reason}} -> {:error, {:invalid_json, reason}}
{:error, _reason} = error -> error
end
end
defp post_envelope_with_retries(client, endpoint, headers, payload, retries_left) do
case request(client, endpoint, headers, payload) do
{:ok, id} ->
{:ok, id}
{:error, _reason} when retries_left != [] ->
[sleep_interval | retries_left] = retries_left
Process.sleep(sleep_interval)
post_envelope_with_retries(client, endpoint, headers, payload, retries_left)
{:error, reason} ->
{:error, {:request_failure, reason}}
end
end
defp request(client, endpoint, headers, body) do
with {:ok, 200, _headers, body} <- client.post(endpoint, headers, body),
{:ok, json} <- Config.json_library().decode(body) do
{:ok, Map.get(json, "id")}
else
{:ok, status, headers, _body} ->
error_header =
:proplists.get_value("X-Sentry-Error", headers, nil) ||
:proplists.get_value("x-sentry-error", headers, nil) || ""
{:error, "Received #{status} from Sentry server: #{error_header}"}
{:error, reason} ->
{:error, reason}
end
catch
kind, data -> {:error, {kind, data, __STACKTRACE__}}
end
defp get_endpoint_and_headers do
case get_dsn() do
{endpoint, public_key, secret_key} ->
{:ok, endpoint, authorization_headers(public_key, secret_key)}
{:error, :invalid_dsn} ->
{:error, :invalid_dsn}
end
end
# Made public for testing.
@spec get_dsn() :: {String.t(), String.t(), String.t()} | {:error, :invalid_dsn}
def get_dsn do
with dsn when is_binary(dsn) <- Config.dsn(),
%URI{userinfo: userinfo, host: host, port: port, path: path, scheme: protocol}
when is_binary(path) and is_binary(userinfo) <- URI.parse(dsn),
[public_key, secret_key] <- keys_from_userinfo(userinfo),
uri_path <- String.split(path, "/"),
{binary_project_id, uri_path} <- List.pop_at(uri_path, -1),
base_path <- Enum.join(uri_path, "/"),
{project_id, ""} <- Integer.parse(binary_project_id),
endpoint <- "#{protocol}://#{host}:#{port}#{base_path}/api/#{project_id}/envelope/" do
{endpoint, public_key, secret_key}
else
_ ->
{:error, :invalid_dsn}
end
end
defp keys_from_userinfo(userinfo) do
case String.split(userinfo, ":", parts: 2) do
[public, secret] -> [public, secret]
[public] -> [public, nil]
_ -> :error
end
end
defp authorization_headers(public_key, secret_key) do
auth_query =
[
sentry_version: @sentry_version,
sentry_client: @sentry_client,
sentry_timestamp: System.system_time(:second),
sentry_key: public_key,
sentry_secret: secret_key
]
|> Enum.reject(fn {_, value} -> is_nil(value) end)
|> Enum.map_join(", ", fn {name, value} -> "#{name}=#{value}" end)
[
{"User-Agent", @sentry_client},
{"X-Sentry-Auth", "Sentry " <> auth_query}
]
end
end