Skip to content

Instantly share code, notes, and snippets.

@mitchellhenke
Last active July 13, 2020 15:05
Show Gist options
  • Save mitchellhenke/dce120a5515565076b13962ee0be749b to your computer and use it in GitHub Desktop.
Save mitchellhenke/dce120a5515565076b13962ee0be749b to your computer and use it in GitHub Desktop.
Sentry 7.x -> 8.x Upgrade Guide

Sentry 8.0

Sentry Elixir 8.0 is a major release containing significant changes, particularly around Elixir's major improvements to the core Logger module. Because of that, the required Elixir version will be Elixir 1.10. There are also a handful of steps to complete the upgrade below. A special thank you goes to José Valim for his help and contributions to this release.

A more detailed explanation of the changes is below, with instructions to upgrade, but the summary of changes is:

  • Enhancements

    • Cache environment config in application config (#393)
    • Allow configuring LoggerBackend to send all messages, not just exceptions (e.g. Logger.error("I am an error message"))
  • Bug Fixes

    • Fix request URL port in payloads for HTTPS requests (#391)
  • Breaking Changes

    • Change default included_environments to only include :prod by default (#370)
    • Change default event send type to :none instead of :async (#341)
    • Make hackney an optional dependency, and simplify Sentry.HTTPClient behaviour (#400)
    • Use Logger.metadata for Sentry.Context, no longer return metadata values on set_* functions, and rename set_http_context to set_request_context (#401)
    • Move excluded exceptions from Sentry.Plug to Sentry.DefaultEventFilter (#402)
    • Remove Sentry.Plug and Sentry.Phoenix.Endpoint in favor of Sentry.PlugContext and Sentry.PlugCapture (#402)
    • Remove feedback form rendering and configuration (#402)
    • Logger metadata is now specified by key in LoggerBackend instead of enabled/disabled (#403)
    • Require Elixir 1.10 and optionally plug_cowboy 2.3 (#403)
    • Sentry.capture_exception/1 now only accepts exceptions (#403)

Upgrading

Dependencies

Update your :sentry dependency, and add :hackney. If you are using :plug_cowboy, update it to be 2.3 or greater.

  defp deps do
    [
      {:sentry, "~> 8.0"},
      {:hackney, "~> 1.8"},
      {:plug_cowboy, "~> 2.3"},
      {:jason, "~> 1.1"},
      # ...
    ]
  end

Replace Sentry.Plug and Sentry.Phoenix.Endpoint

Sentry.Plug and Sentry.Phoenix.Endpoint have been combined (and split) into Sentry.PlugCapture and Sentry.PlugContext. PlugContext retrieves information from the request, and sets request context using Sentry.Context. PlugCapture is responsible for catching exceptions in the Plug stack and sending it to Sentry with the context that was previously set by PlugContext.

To upgrade, remove Sentry.Plug and Sentry.Phoenix.Endpoint. Plug.ErrorHandler should also be removed if Sentry was the only user of it. Add plug Sentry.PlugContext and use Sentry.PlugCapture. It is important to place Sentry.PlugContext after Plug.Parsers and put Sentry.PlugCapturebefore use Phoenix.Endpoint in a Phoenix application.

If you are using any of the :body_scrubber, :header_scrubber, :cookie_scrubber, :request_id_header options from Sentry.Plug, they should be passed as options to Sentry.PlugContext.

Phoenix:

 defmodule MyAppWeb.Endpoint do
+  use Sentry.PlugCapture
   use Phoenix.Endpoint, otp_app: :my_app
-  use Sentry.Phoenix.Endpoint
   # ...
   plug Plug.Parsers,
     parsers: [:urlencoded, :multipart, :json],
     pass: ["*/*"],
     json_decoder: Phoenix.json_library()

+  plug Sentry.PlugContext
defmodule MyAppWeb.Router do
   use SmartfinWeb, :router
-  use Plug.ErrorHandler
-  use Sentry.Plug
# ...

Plug:

 defmodule MyApp.Router do
   use Plug.Router
-  use Plug.ErrorHandler
-  use Sentry.Plug
+  use Sentry.PlugCapture
   # ...
   plug Plug.Parsers,
     parsers: [:urlencoded, :multipart]
+  plug Sentry.PlugContext

User Feedback

Support for Sentry user feedback was recently added to Sentry.Plug in version 7.2.3, but the rendering could be a bit difficult and brittle, and so was removed. To use it in 8.0.0, your error view renderer can check whether Sentry.PlugCapture has sent an error, and conditionally render the feedback form. In a Phoenix application, this would be in the ErrorView:

defmodule MyAppWeb.ErrorView do
  # ...
  def render("500.html", _assigns) do
    case Sentry.get_last_event_id_and_source() do
      # Check if an event was sent from Sentry.PlugCapture
      {event_id, :plug} when is_binary(event_id) ->
        opts =
          # can do %{eventId: event_id, title: "My custom title"}
          %{eventId: event_id}
          |> Jason.encode!()

        ~E"""
          <script src="https://browser.sentry-cdn.com/5.9.1/bundle.min.js" integrity_no="sha384-/x1aHz0nKRd6zVUazsV6CbQvjJvr6zQL2CHbQZf3yoLkezyEtZUpqUNnOLW9Nt3v" crossorigin="anonymous"></script>
          <script>
            Sentry.init({ dsn: '<%= Sentry.Config.dsn() %>' });
            Sentry.showReportDialog(<%= raw opts %>)
          </script>
        """

      _ ->
        "Error"
    end
  # ...
  end

Event Filtering

Exceptions from Plug and Phoenix caused by unmatched routes were previously filtered within Sentry.Plug, and this behavior could not be configured. To enable configuration this functionality was moved to the default event filter Sentry.DefaultEventFilter. If you are using a custom event filter module and would like to continue ignoring these exceptions, adding the following lines to your module will maintain the current behavior:

defmodule MyApp.SentryEventFilter do
  @behaviour Sentry.EventFilter
  # ...
  # Ignore Phoenix route not found exception
  def exclude_exception?(%x{}, :plug) when x in [Phoenix.Router.NoRouteError] do
    true
  end

  # Ignore Plug route not found exception
  def exclude_exception?(%FunctionClauseError{function: :do_match, arity: 4}, :plug), do: true
  # ...
end

Sentry.LoggerBackend

In version 7.x, Sentry.LoggerBackend supported including any Logger metadata that held a value of number, string or atom by setting :include_logger_metadata to true. In 8.0, that option was removed in favor of the :metadata key. Keys listed will be included as context within any Sentry events reported. This should be more flexible by removing the limitation on types by allowing users to specify protocols for JSON encoding, and allowing more granular specification of the keys/values added.

The :level option was previously accepted, but had no effect on the behavior of the module. Now, that option is used as the minimum level to potentially send an event.

Two other options were added in 8.0. :capture_log_messages offers the ability to send non-exception messages to Sentry. If you would like to receive events in Sentry when Logger.error("oops an error") is called, this option can be set to true. Metadata set with Sentry.Context will be included in the events reported. The :level option can be used in combination with this one to receive warning logs as well.

Using Logger metadata also means context can be set directly in a Logger call, though care should be taken as Logger metadata does not get merged and will overwrite current data.

:excluded_domains is an option used to ignore Logger messages from certain domains, a metadata feature added to Logger in Elixir 1.10 (docs). plug_cowboy 2.3.0 added the :cowboy domain to Logger messages coming from Cowboy, which allows the Logger backend to more reliably avoid double reporting exceptions that are also being sent by PlugCapture.

An example configuration using all of the options could look like:

config :logger, Sentry.LoggerBackend,
  # Send messages like `Logger.error("error")` to Sentry
  capture_log_messages: true,
  # Also send warn messages like `Logger.warn("warning")` to Sentry
  level: :warn,
  # Do not exclude exceptions from Plug/Cowboy
  excluded_domains: [],
  # Include metadata added with `Logger.metadata([foo_bar: "value"])`
  metadata: [:foo_bar]

The LoggerBackend will now wrap non-exception errors like throws and bad exits into a Sentry.CrashError to preserve the difference between exceptions and other types of errors.

Sentry.Context

This module now uses Logger metadata instead of using the process dictionary directly. The set_ functions no longer return their prior values, and the set_http_context function was renamed to set_request_context to match the data structure and verbiage used by Sentry.

Sentry.Client

To allow for more flexibility around HTTP libraries, the Sentry.HTTPClient behaviour was refactored to be responsible only for transmitting the request and returning the response in #400. Sentry will still use hackney by default, but the dependency must now be explicitly listed. This change is intended to make it easier to use alternative HTTP libraries.

If you are currently using a custom Sentry.HTTPClient, you will have to alter it to conform to the new callbacks.

Sentry

The Sentry.capture_exception function will now only accept an Elixir Exception as the first argument. Non-exception values can be wrapped in other exceptions, or Sentry.capture_message can be used to send a string message.

Issues

As always, if you run into any issues, don't hesitate to open an issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment