Sentry error tracking for Kemal

programming, crystal, sentry, kemal

Kemal ⚡️

Kemal is sinatra-like, lightning fast, super simple web framework written in Crystal. It's perfect for prototyping or simple backend services.

Setup

Create a demo app:

crystal init app your-kemal-app
cd your-kemal-app

Add raven and kemal under dependencies key inside shard.yml:

dependencies:
  raven:
    github: Sija/raven
  kemal:
    github: kemalcr/kemal

Next, install newly added dependencies:

shards install

Integration

Add the code below to your main entrypoint, src/your-kemal-app.cr:

require "raven"
require "raven/integrations/kemal"

# Perform basic raven configuration, none of it is required though
Raven.configure do |config|
  # Keep main fiber responsive by sending the events in the background
  config.async = true
  # Set the environment name using `Kemal.config.env`, which uses `KEMAL_ENV` variable under-the-hood
  config.current_environment = Kemal.config.env
end

# Replace the built-in `Kemal::LogHandler` with a
# dedicated `Raven::Kemal::LogHandler`, capturing all
# sent messages and requests as Sentry breadcrumbs

# If you'd like to preserve default logging provided by
# Kemal, pass `Kemal::LogHandler.new` to the constructor
if Kemal.config.logging
  Kemal.config.logger = Raven::Kemal::LogHandler.new(Kemal::LogHandler.new)
else
  Kemal.config.logger = Raven::Kemal::LogHandler.new
end

# Add raven's exception handler in order to capture
# all unhandled exceptions thrown inside your routes.
# Captured exceptions are re-raised afterwards
Kemal.config.add_handler Raven::Kemal::ExceptionHandler.new

# Register your explosive route
get "/will/:name/blend" do |env|
  if env.params.url["name"].downcase == "earth"
    # Earth doesn't blend, yet
    raise "Cling, Scratch, Boom!"
  else
    # All the rest will, obviously
    "Sure it will!"
  end
end

# Run, Kemal, run!
Kemal.run

Showtime

Run this in the terminal (replacing ... with your actual DSN):

SENTRY_DSN=... crystal run src/your-kemal-app.cr

Now visit:

You should be greeted by Kemal's default (only in development env) exception page:

In the meantime Sentry should be already notified of the event:

Fine-tuning

Raven.configure do |config|
  # If your requests are failing because of connection
  # timeout error, try setting bigger value
  # (defaults to `1.second`).
  #
  # NOTE: Avoid using bigger values without `#async` option enabled
  config.connect_timeout = 3.seconds

  # In case of hitting rate limit you might want to try
  # lower sample rate threshold, in this case to 75%
  config.sample_rate = 0.75

  # Remove default processors you don't need
  config.processors -= [Raven::Processor::Cookies, Raven::Processor::RequestMethodData]

  # Ignore certain exception classes
  # `Kemal::Exceptions::RouteNotFound` is added automatically
  config.excluded_exceptions << NotImplementedError

  # Sanitize additional fields
  config.sanitize_fields << /\Aaddress_(.*?)\Z/i

  # Setup `#before_send` hook, which allows modifying
  # the event before sending, or dropping it entirely
  config.before_send do |event, hint|
    # Group events by topic based on exception message
    if hint.try(&.exception).try(&.message) =~ /database unavailable/i
      event.fingerprint << "database-unavailable"
    end
    # Conditionally skip sending the event
    event unless ENV["CI"]? == "1"
  end
end

What's in the package?

All the goodies offered by raven shard +:

  • Log handler, turning server logs and requests into breadcrumbs
  • Exception handler, reporting unhandled exceptions to Sentry
  • Integration with kemal-basic-auth shard
  • Events enhanced with request metadata, i.e.:
    • Status code
    • Method
    • URL (scheme included)
    • Query string
    • Headers
    • Cookies
    • JSON / FormData payload