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.