Gestion des erreurs 404 et 500 en Ruby on Rails

Concept

La gestion native des erreurs en ROR souffre de deux manques : d’abord, les erreurs ne sont pas chartées selon le style de votre application, ensuite elles n’alertent pas les développeurs de ce qui s’est produit. Le code ci-dessous peut être placé directement dans l’application ou au sein d’une gem.

Étape 1 : Intercepter les erreurs 404, 422 et 500

Dans le routeur d’URL, ajouter les lignes suivantes :

  match "/404", to: "erreurs#page_introuvable", via: :all
  match "/422", to: "erreurs#erreur_interne", via: :all
  match "/500", to: "erreurs#erreur_interne", via: :all

Lorsqu’une erreur se produit, le moteur de Rails appelle automatiquement ces routes. En ajoutant les routes ci-dessus, on va pouvoir modifier le comportement natif de votre appli en les interceptant dans nos méthodes.

Étape 2 : Méthodes dans le contrôleur

Tout commence par la création d’un contrôleur dédié :

class ErreursController < ::ApplicationController
  skip_before_action :verify_authenticity_token, only: [:erreur_interne]
 
  def page_introuvable
    # On envoie le mail
    Erreurs404500mailer.envoi_erreur_404(request, session).deliver
 
    # Et on affiche de jolies erreurs
    respond_to do |format|
      if defined?(UTILISER_CHARTE) && UTILISER_CHARTE
        # Ici on utilise Rails.root pour remonter dans l'application qui utilise la gem, et on va chercher la page statique des 404
        format.html { render "#{Rails.root}/public/erreur_404.html", layout: false, status: 404 }
      else
        # Sinon on utilise le template de ce controleur de manière générique (le fichier 404.html présent dans public/ quand on créé un projet Rails, par exemple)
        format.html { render template: "erreurs/page_introuvable", layout: false, status: 404 }
      end
      format.all  { render nothing: true, status: 404 }
    end
  end
 
  # La même pour les 500
  def erreur_interne
    exception = env['action_dispatch.exception']
    Erreurs404500mailer.envoi_erreur_500(request, session, exception).deliver
 
    respond_to do |format|
      if defined?(UTILISER_CHARTE) && UTILISER_CHARTE
        format.html { render "#{Rails.root}/public/erreur_500.html", layout: false, status: 500 }
      else
        format.html { render  template: "erreurs/erreur_interne", layout: false, status: 500 }
      end
      format.all  { render nothing: true, status: 500 }
    end
  end
end

Ce code est simplifiable si vous ne le placez pas au sein d’une gem, mais bon, ça peut rapidement devenir un couteau suisse dans tous vos projets.

Étape 3 : le mailer

Bon là rien d’exceptionnel :

class Erreurs404500mailer < ActionMailer::Base
  def envoi_erreur_404(requete, session)
    @nom_application = NOM_APPLICATION
    @requete = requete
    @session = session
 
    # Une petite astuce si vous avez une delivery_method mailjet par exemple
    #ActionMailer::Base.delivery_method = :smtp if ...
 
    mail(to: "web@groupebatteur.fr", subject: "[#{@nom_application}] Erreur 404", from: "")
 
    # Bien remettre mailjet sinon les prochains mails partiront avec le smtp du serveur
    ActionMailer::Base.delivery_method = :mailjet if ...
  end
 
  # Pareil pour les 500
  def envoi_erreur_500(requete, session, exception)
    @nom_application = NOM_APPLICATION
    @requete = requete
    @session = session
 
    @exception = exception
 
    #ActionMailer::Base.delivery_method = :smtp if ...
 
    mail(to: "web@groupebatteur.fr", subject: "[#{@nom_application}] Erreur 500", from: "")
 
    #ActionMailer::Base.delivery_method = :mailjet if ...
  end
end

Étape 4 : Les vues des mailers fraichement créés

Pour les 404 je vous suggère d’extraire quelques infos utiles telles que @requete.remote_ip, @requete.user_agent, @requete.request_method, @requete.original_url ou encore @requete.filtered_parameters.to_s (les paramètres de la requête).
Pour les 500, vous pouvez ajouter :

<% if defined?(@exception.cause) && @exception.cause.present? %>
  <p>Message parent : <%= @exception.cause.inspect %></p>
  <p>Backtrace parente :<br /><%= @exception.cause.backtrace.join("<br />").html_safe %></p>
<% end %>
<p>Message : <%= @exception.inspect %></p>
<p>Backtrace :<br /><%= @exception.backtrace.join("<br />").html_safe %></p>

Étape 5 : les vues des méthodes en question

N’oubliez donc pas de créer les vues correspondant aux méthodes selon les choix que vous aurez réalisé à la lecture de l’article.

Nous sommes à l’écoute de toute suggestion car la gestion des erreurs ne semble pas très élaboré d’origine.

2 réflexions au sujet de « Gestion des erreurs 404 et 500 en Ruby on Rails »

  1. Salut ! Une suggestion : pour les notifications d’erreurs ne pas faire ça soi même. Bugsnag, Rollbar, Honeybadger ou n’importe quel autre service. La stack trace est bien plus complète, on peut brancher Slack, Redmine, Trello ou autre, on peut l’utiliser aussi en JS, etc. Pour de l’auto hébergé il y a errbit : https://github.com/errbit/errbit

  2. Pour les erreurs il suffit de modifier les fichiers public/404.html et public/500.html. Pour avoir le style de l’application, il faut copier les assets sans le fingerprint au deploy et les référencer depuis ces pages.

    Un article qui explique les options pour copier les assets sans fingerprint : https://bibwild.wordpress.com/2014/10/02/non-digested-asset-names-in-rails-4-your-options/

    La rake task (à adapter, notamment le nom du manifest pour une version récente de Sprockets) : https://github.com/team-umlaut/umlaut/blob/5edcc609389edf833a79caa6f3ef92982312f0c5/lib/tasks/umlaut_asset_compile.rake

    Une autre option plus simple consiste à rsync les assets vers public, mais cela ne copie pas les assets des gems comme TinyMCE.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.