Current section
Files
Jump to
Current section
Files
lib/sentry/client.ex
defmodule Sentry.Client do
alias Sentry.{Event, Util}
require Logger
@type parsed_dsn :: {String.t, String.t, Integer.t}
@sentry_version 5
quote do
unquote(@sentry_client "sentry-elixir/#{Mix.Project.config[:version]}")
end
@moduledoc """
Provides basic HTTP client request and response handling for the Sentry API.
"""
@spec send_event(%Event{}) :: {:ok, String.t} | :error
def send_event(%Event{} = event) do
{endpoint, public_key, secret_key} = parse_dsn!(Application.fetch_env!(:sentry_elixir, :dsn))
auth_headers = authorization_headers(public_key, secret_key)
request(:post, endpoint, auth_headers, Poison.encode!(event))
end
@spec send_event(%Event{}) :: {:ok, String.t} | :error
def request(method, url, headers, body) do
case :fuse.ask(Sentry.Fuse.api_fuse_name(), :sync) do
:ok ->
do_request(method, url, headers, body)
:blown ->
log_api_error(body)
:error
end
end
defp do_request(method, url, headers, body) do
case :hackney.request(method, url, headers, body, []) do
{:ok, 200, _headers, client} ->
case :hackney.body(client) do
{:ok, body} ->
id = body
|> Poison.decode!()
|> Map.get("id")
{:ok, id}
_ ->
log_api_error(body)
end
_ ->
log_api_error(body)
Sentry.Fuse.api_fuse_name()
|> :fuse.melt()
:error
end
end
@doc """
Generates a Sentry API authorization header.
"""
@spec authorization_header(String.t, String.t) :: String.t
def authorization_header(public_key, secret_key) do
timestamp = Util.unix_timestamp()
"Sentry sentry_version=#{@sentry_version}, sentry_client=#{@sentry_client}, sentry_timestamp=#{timestamp}, sentry_key=#{public_key}, sentry_secret=#{secret_key}"
end
def authorization_headers(public_key, secret_key) do
[
{"User-Agent", @sentry_client},
{"X-Sentry-Auth", authorization_header(public_key, secret_key)}
]
end
@doc """
Parses a Sentry DSN which is simply a URI.
"""
@spec parse_dsn!(String.t) :: parsed_dsn
def parse_dsn!(dsn) do
# {PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}
%URI{userinfo: userinfo, host: host, port: port, path: path, scheme: protocol} = URI.parse(dsn)
[public_key, secret_key] = String.split(userinfo, ":", parts: 2)
[_, binary_project_id] = String.split(path, "/")
project_id = String.to_integer(binary_project_id)
endpoint = "#{protocol}://#{host}:#{port}/api/#{project_id}/store/"
{endpoint, public_key, secret_key}
end
defp log_api_error(body) do
Logger.error(fn ->
["Failed to send sentry event.", ?\n, body]
end, [skip_sentry: true])
end
end