2.1 KiB · text 5af55d9
defmodule GitGud.SentryClient do
@moduledoc """
Sentry HTTP client backed by Req (already a dependency).
Works around our self-hosted GlitchTip (s.gmp.io) returning an **empty
200 body** on successful envelope ingestion. `Sentry.Transport`
requires a 200 response body to be valid JSON — it runs
`Sentry.JSON.decode/2` on it to read the event id (see
`deps/sentry/lib/sentry/transport.ex`) — so an empty body raises a
`JSON.DecodeError` and the SDK reports a false `request_failure` (and
retries), even though the event was accepted. This is not a client bug:
both hackney and Req faithfully return the empty body. We substitute
`"{}"` for an empty successful body so the decode yields `id: nil`,
which Sentry treats as success.
"""
@behaviour Sentry.HTTPClient
@finch GitGud.SentryFinch
@impl true
def child_spec do
{Finch, name: @finch}
end
@impl true
def post(url, headers, body) do
case Req.post(url,
headers: headers,
body: body,
finch: @finch,
# Sentry already gzipped the body + set Content-Encoding, and
# parses the response itself — don't let Req re-compress or
# decode, and let Sentry own retries.
compress_body: false,
decode_body: false,
retry: false
) do
{:ok, %Req.Response{status: status, headers: resp_headers, body: resp_body}} ->
{:ok, status, flatten_headers(resp_headers), normalize_body(status, resp_body)}
{:error, exception} ->
{:error, exception}
end
end
# GlitchTip returns an empty 200 body on a successful send; Sentry needs
# valid JSON to decode. Give it an empty object so the send registers as
# success (id: nil). Non-2xx bodies are left untouched for accurate
# error reporting.
defp normalize_body(status, "") when status in 200..299, do: "{}"
defp normalize_body(_status, body), do: body
# Sentry.HTTPClient wants headers as a list of {key, value}; Req returns
# a map of key => [values].
defp flatten_headers(headers) do
Enum.flat_map(headers, fn {k, vs} -> Enum.map(List.wrap(vs), &{k, &1}) end)
end
end