Contextualiser ses événements Sentry

Sentry est un service qui permet la sauvegarde, analyse des erreurs qui surviennent dans vos applications.

Quand vous ne pouvez pas analyser vos logs applicatifs facilement (application mobile ou installée directement sur les postes de vos utilisateurs) il est important de récupérer les erreurs qui se produisent pour les corriger. Pour cela on peut utiliser un service en ligne comme Sentry.

Le service propose des intégrations pour toute sorte de langage, plate-forme. On suivra ici l’intégration au sein d’une JVM avec Logback.

Installation

Tout repose sur un client Java nommé raven pour envoyer des erreurs vers Sentry. Il suffit donc d’ajouter une dépendance à votre projet (par exemple avec Maven) :

<dependency>
  <groupId>com.getsentry.raven</groupId>
  <artifactId>raven-logback</artifactId>
  <version>7.3.0</version>
</dependency>

Configuration

Il faut ajouter un nouvel appender à sa configuration Logback :

  <appender name="Sentry" class="com.getsentry.raven.logback.SentryAppender">
    <dsn>https://<key>:<secret>@app.getsentry.com/<project>?options</dsn>
  </appender>
  <root level="warn">
    <appender-ref ref="Sentry"/>
  </root>

Avec cet exemple, tous les messages loggés via Logback de niveau WARN ou ERROR seront envoyés à Sentry.

C’est très bien on a maintenant nos erreurs qui sont enregistrées mais les informations remontées se limitent en gros aux messages loggés et stracktraces (si présente) associées.
On peut faire mieux en identifiant les utilisateurs victimes d’erreurs.

Identification

Les messages Sentry peuvent être enrichis à l’aide de SentryInterface. Une des interface fournie par défaut permet d’associer un utilisateur à chaque message : UserInterface.
Pour définir ces interfaces il faut pimper l’initialisation du client Raven à l’aide d’une factory personnalisée.

package fr.jcgay.github.sentry

import java.net.InetAddress.getLocalHost
import java.util.UUID

import com.getsentry.raven.dsn.Dsn
import com.getsentry.raven.event.EventBuilder
import com.getsentry.raven.event.helper.EventBuilderHelper
import com.getsentry.raven.event.interfaces.UserInterface
import com.getsentry.raven.{DefaultRavenFactory, Raven}

case class MyAppRavenFactory() extends DefaultRavenFactory {

  lazy val uuid: String = UUID.randomUUID.toString

  override def createRavenInstance(dsn: Dsn): Raven = {
    val raven = super.createRavenInstance(dsn)
    raven.addBuilderHelper(AddUser("jcgay", "contact@jeanchristophegay.com", uuid))
    raven
  }
}

case class AddUser(id: String, email: String, uuid: String) extends EventBuilderHelper {
  override def helpBuildingEvent(eventBuilder: EventBuilder): Unit =
    eventBuilder
      .withSentryInterface(new UserInterface(id, getLocalHost.getHostName, null, email))
      .withTag("session-uuid", uuid)
}

On crée donc un utilisateur avec :

new UserInterface(id, getLocalHost.getHostName, null, email)

Le 3ième argument est l’adresse IP de l’utilisateur. Utiliser null pour l’absence de valeur.

On peut aussi ajouter des informations taggées au sein d’un message. Ces données seront indexées par Sentry. On pourra donc les utiliser dans des recherches, etc.
Ici par exemple on génère un identifiant de session qui sera associé aux actions de l’utilisateur pour toute la durée de sa session.

L’EventBuilder que l’on enrichi sera exécuté à chaque construction de message, attention donc à ce que l’on y met 😛.

En terme de configuration il faut enregistrer notre factory et la référencer dans le SentryAppender.

Exécuter quelque-part pendant l’initialisation de l’application :

RavenFactory.registerFactory(MyAppRavenFactory())

Et dans le logback.xml :

    <appender name="Sentry" class="com.getsentry.raven.logback.SentryAppender">
      <dsn>https://<key>:<secret>@app.getsentry.com/<project>?options</dsn>
      <ravenFactory>fr.jcgay.github.sentry.MyAppRavenFactory</ravenFactory>
    </appender>
    <root level="warn">
      <appender-ref ref="Sentry"/>
    </root>

Version de l’application

Une information importante comprise et utilisée par Sentry est la version de l’application. Elle est renseignable avec la balise <release> au niveau de la configuration du SentryAppender :

    <appender name="Sentry" class="com.getsentry.raven.logback.SentryAppender">
      <dsn>https://<key>:<secret>@app.getsentry.com/<project>?options</dsn>
      <ravenFactory>fr.jcgay.github.sentry.MyAppRavenFactory</ravenFactory>
      <release>${project.version}</release>
    </appender>
    <root level="warn">
      <appender-ref ref="Sentry"/>
    </root>

On peut par exemple utiliser la fonctionnalité de Filtering avec Maven pour injecter la version de l’application au moment du build.

Le MDC

Le dernier moyen pour contextualiser ses messages est d’utiliser le MDC.

Par défaut toutes les valeurs comprises dans le MDC sont envoyées à Sentry. Par contre elles ne sont pas indexées par défaut. Pour activer l’indexation sur une clef particulière il faut la déclarer en tant que tag :

MDC.put("Environment", "Development");
    <appender name="Sentry" class="com.getsentry.raven.logback.SentryAppender">
      <dsn>https://<key>:<secret>@app.getsentry.com/<project>?options</dsn>
      <ravenFactory>fr.jcgay.github.sentry.MyAppRavenFactory</ravenFactory>
      <release>${project.version}</release>
      <extraTag>Environment</extraTag>
    </appender>
    <root level="warn">
      <appender-ref ref="Sentry"/>
    </root>

Conclusion

Une fois correctement configuré les messages récupérés dans Sentry permettent de surveiller les erreurs qui interviennent dans vos applications déployées un peu partout dans la nature.
On atteint assez vite les quotas (à la minute) dans le premier plan payant, il existe pas mal d’alternatives mais je n’en ai pas essayé 😱.

Le code utilisé dans les exemples est disponible ici.

comments powered by Disqus