Packages
honeybadger
0.23.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.1
0.18.0
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.0
0.7.0
0.7.0-beta
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.0
0.3.1
0.3.0
0.2.0
0.1.2
0.1.1
0.1.0
Elixir client, Plug and error_logger for integrating with the Honeybadger.io exception tracker"
Current section
Files
Jump to
Current section
Files
lib/honeybadger/http_adapter.ex
defmodule Honeybadger.HTTPAdapter do
@moduledoc """
HTTP adapter helper module.
You can configure the HTTP adapter that Honeybadger uses by setting the
following option:
http_adapter: Honeybadger.HTTPAdapter.Req
Default options can be set by passing a list of options:
http_adapter: {Honeybadger.HTTPAdapter.Req, [...]}
You can also set the client for the application:
config :honeybadger, :http_adapter, Honeybadger.HTTPAdapter.Req
## Usage
defmodule MyApp.MyHTTPAdapter do
@behaviour Honeybadger.HTTPAdapter
@impl true
def request(method, url, body, headers, opts) do
# ...
end
@impl true
def decode_response_body(response, opts) do
# ...
end
end
"""
alias Honeybadger.{InvalidResponseError, ServerUnreachableError}
defmodule HTTPResponse do
@moduledoc """
Struct used by HTTP adapters to normalize HTTP responses.
"""
@type header :: {binary(), binary()}
@type t :: %__MODULE__{
http_adapter: atom(),
request_url: binary(),
status: integer(),
headers: [header()],
body: binary() | term()
}
defstruct http_adapter: nil, request_url: nil, status: 200, headers: [], body: ""
def format(response) do
[request_url | _rest] = String.split(response.request_url, "?", parts: 2)
"""
HTTP Adapter: #{inspect(response.http_adapter)}
Request URL: #{request_url}
Response status: #{response.status}
Response headers:
#{Enum.map_join(response.headers, "\n", fn {key, val} -> "#{key}: #{val}" end)}
Response body:
#{inspect(response.body)}
"""
end
end
@type method :: :get | :post
@type body :: binary() | nil
@type headers :: [{binary(), binary()}]
@callback request(method(), binary(), body(), headers(), Keyword.t()) ::
{:ok, map()} | {:error, any()}
@callback decode_response_body(HTTPResponse.t(), Keyword.t()) ::
{:ok, HTTPResponse.t()} | {:error, InvalidResponseError.t()}
@doc """
Makes an HTTP request.
## Options
- `:http_adapter` - The HTTP adapter to use, defaults to to one of the available adapters
(Req is preferred, falling back to Hackney if available)
"""
@spec request(atom(), binary(), binary() | nil, list(), Keyword.t()) ::
{:ok, HTTPResponse.t()} | {:error, HTTPResponse.t()} | {:error, term()}
def request(method, url, body, headers, opts) do
{http_adapter, http_adapter_opts} = get_adapter(opts)
with {:ok, response} <- http_adapter.request(method, url, body, headers, http_adapter_opts),
{:ok, decoded_response} <- http_adapter.decode_response_body(response, opts) do
{request_result(decoded_response),
%{decoded_response | http_adapter: http_adapter, request_url: url}}
else
{:error, %Honeybadger.InvalidResponseError{} = error} ->
{:error, error}
{:error, error} ->
{:error,
ServerUnreachableError.exception(
reason: error,
http_adapter: http_adapter,
request_url: url
)}
end
end
defp request_result(%{status: status}) when status in 200..399, do: :ok
defp request_result(%{status: status}) when status in 400..599, do: :error
defp get_adapter(opts) do
default_http_adapter = Application.get_env(:honeybadger, :http_adapter, installed_adapter())
case Keyword.get(opts, :http_adapter, default_http_adapter) do
{http_adapter, opts} -> {http_adapter, opts}
http_adapter when is_atom(http_adapter) -> {http_adapter, nil}
end
end
defp installed_adapter do
key = {__MODULE__, :installed_adapter}
case :persistent_term.get(key, :undefined) do
:undefined ->
adapter = find_installed_adapter()
:persistent_term.put(key, adapter)
adapter
adapter ->
adapter
end
end
defp find_installed_adapter do
Enum.find_value(
[
{Req, Honeybadger.HTTPAdapter.Req},
{:hackney, Honeybadger.HTTPAdapter.Hackney}
],
fn {dep, module} ->
Code.ensure_loaded?(dep) && {module, []}
end
)
end
@doc """
Validates that the configured HTTP adapter's dependencies are available.
This should be called during application startup to ensure that the
configured adapter can be used.
"""
def validate_adapter_availability! do
{adapter, _opts} = get_adapter([])
case adapter do
Honeybadger.HTTPAdapter.Hackney ->
ensure_dependency_available!(:hackney, "~> 1.8", adapter)
Honeybadger.HTTPAdapter.Req ->
ensure_dependency_available!(Req, "~> 0.3", adapter)
nil ->
raise """
Honeybadger requires an HTTP client but neither Req nor Hackney is available.
Please add one of the following to your dependencies:
{:req, "~> 0.3"} # Recommended
{:hackney, "~> 1.8"}
"""
_ ->
# Custom adapter - assume user knows what they're doing
:ok
end
end
defp ensure_dependency_available!(module, version, adapter) do
unless Code.ensure_loaded?(module) do
raise """
Honeybadger is configured to use #{inspect(adapter)}, but #{inspect(module)} is not available.
Please add it to your dependencies:
{#{inspect(module)}, "#{version}"}
Or configure a different HTTP adapter in your config.
"""
end
end
end