Voici un retour d'expérience sur la mise en place de Fluentd avec une application Play2/Scala. Ce qui va suivre devrait intéresser n'importe quel développeur, Java ou non. Asseyez-vous et on y va.
La gestion des logs applicatifs a bien évolué, avec l’arrivée de solutions open-source intéressantes. Sur une application en prod, le volume des logs est un problème à traiter correctement. En ayant une application plutôt bien écrite, et en ne s’intéressant qu’aux erreurs et et aux warnings, cela peut cependant vite représenter un volume important d’informations à traiter.
Volume important, traitement important, tout ceci ressemble à un cas d’usage Big Data. Mais nous n’en sommes pas encore là. Voyons comment configurer une application Java (ou Scala) avec Logback, comment installer Fluentd et enfin, comment envoyer vos logs vers Amazon RDS. J’ai fait le choix de vous parler d’Amazon RDS (MySQL dans le Cloud), ce qui peut surprendre. Mais vous verrez qu’il est vraiment simple de passer à Amazon DynamoDB, ou d’utiliser une solution type SaaS comme celle proposée par Treasure Data.
Introduction
Lorsque l’on parle gestion des logs, il y a différentes étapes :
- la collecte
- le stockage
- le traitement
- la visualisation
Visualiser ses logs, c’est facile avec des solutions comme Kibana. Traiter les logs, on peut utiliser ElasticSearch ou Apache Hive lorsque vous travaillez avec Apache Hadoop ou Amazon DynamoDB. Pour le stockage donc, s’agissant d’un volume important mais qu’il faut pouvoir requêter, vous pouvez utiliser une solution type NoSQL comme Hadoop (le choix de Treasure Data) ou autre. Vous pouvez aussi utiliser MongoDB, Redis ou même une base MySQL. C’est ce choix simple que j’ai fait… pour l’instant. On peut se la raconter avec une base NoSQL, mais lorsqu’il s’agit de trier et de lancer des requêtes avancées, moi le SQL ça me plaît toujours. Je basculerai à une solution nosql le moment venu, sans que cela n’impacte mon application… Bref, si JavaEE 7 est à poil du côté logging, ici on est en plein dans les vrais problèmes de gestion des logs.
Du côté collecteur, il existe différents projets. J’ai regardé Scribe (qui vient de Facebook) puis Apache Flume. Au final, par la qualité des plugins et de la documentation, j’ai sélectionné Fluentd.
Fluentd est un projet open-source, codé en Ruby, qui permet de collecter des logs, de les structurer et ensuite de les faire suivre vers différents collecteurs. Après l’avoir installé, vous pouvez configurer un daemon TCP, UDP ou HTTP, afin de recevoir les logs venant de différentes applications. Bien entendu, Fluentd peut aussi remonter et streamer des fichiers venant de l’OS. Par exemple vos logs systèmes, des logs apache ou autre. Bref c’est un espèce de canon à électron, qui permet de collecter les logs, puis de les streamer vers différents systèmes de stockage.
Fluentd est très similaire à syslogd, sauf qu’il utilise le format JSON. Lorsque nous allons brancher notre application Java/Scala, il faudra donc émettre des trames au format JSON, pour bénéficier de toutes les fonctions de Fluentd.
Fluentd permet de brancher différents plugins, tous écrit en Ruby. L’installation d’un plugin est vraiment simple, que ce soit sur MacOS X ou sur Linux. Vous pouvez donc ajouter des plugins pour traiter les entrées (input), la gestion des buffers et enfin les flux de sortie.
Côté client, il est possible de brancher n’importe quelle technologie, que ce soit du Java, du Scala, du Ruby, du Python, du Shell… etc.
Solution pour une application Java/Scala avec logback
Ce qui suit intéressera plus les développeurs Java/Scala. Pour Zaptravel, j’ai adapté et recodé un adapteur Logback, afin de pouvoir configurer mon application avec Fluentd. Le code de l’adapteur est sur Gitub pour ceux qui sont intéressés. J’ai aussi placé mon exemple de fichier de configuration logger.xml afin de vous montrer comment cela s’utilise.
Log4J ou Logback peuvent donc envoyer les trames de log vers un serveur Fluentd. Il existe différentes solutions, rien de très compliqué à mettre en oeuvre.
Installer et configurer Fluentd
Dès la page d’accueil du projet, avec brew, yum, gem ou apt-get, vous serez pris en main pour l’installation. Sur Mac, pensez à mettre à jour votre installation de brew et XCode avant. FluentD avec un D comme Daemon, est donc un service qui écoute et qui attend de recevoir des logs. Il peut aussi surveiller des fichiers systèmes et vous faire suivre ce qui s’y passe. Ce qui serait dommage (j’ai pas dit stupide…) c’est d’écrire vos logs applicatifs dans un fichier plat… pour le relire avec FluentD et ensuite envoyer cela vers une base. Ce qui est mieux, c’est d’utiliser un appender Logback (cf paragraphe suivant) et de complètement désactiver la sortie dans votre application Java/Scala.
Fluentd s’installe sur Amazon EC2 avec apt-get :
curl -L http://toolbelt.treasure-data.com/sh/install-ubuntu-precise.sh | sh
Une fois l’installation terminée (voir la doc ici), vous pouvez vérifier que le daemon fonctionne correctement
$ /etc/init.d/td-agent restart
$ /etc/init.d/td-agent status
td-agent (pid 21678) is running...
Il est ensuite temps d’installer un plugin afin de pouvoir envoyer vers MySQL vos logs.
sudo /usr/lib/fluent/ruby/bin/fluent-gem install fluent-plugin-mysql
La configuration de Fluentd se fait via un fichier de configuration :
sudo vi /etc/td-agent/td-agent.conf
Dans ce fichier, nous allons déclarer une source, un adapter et un flux de sortie, ici ma base de données MySQL qui tourne quelque part dans le Cloud de la NSA :
<match amazon.rds.*>
type mysql
host prodlogs3.somehost.onezone.rds.amazonaws.com
# port 3306 # default
database application_logs
username zaptravel
password mon_super_password_nan_mais_oh_eh_un_
include_time_key yes
### default `time_format` is ISO-8601
# time_format %Y%m%d-%H%M%S
### default `time_key` is 'time'
# time_key timekey
include_tag_key yes
key_names time,tag,level,thread,logger,uri,msg
sql INSERT INTO prod (coltime,coltag,level,thread,logger,uri,msg) VALUES (?,?,?,?,?,?,?)
flush_interval 5s
</match>
Tout message destiné à amazon.rds sera donc capté par ce matcher, qui ensuite se débrouillera pour envoyer vers Amazon RDS (MySQL in da cloud) mes messages. Voili voilou, ça c’est fait.
Après avoir relancé le daemon td-agent, puis ensuite mon application Play2/Scala, les logs applicatifs commencent à arriver.
Pour que cela fonctionne, il faut que votre fichier logger.xml soit aussi correctement configuré :
<configuration info="true" scan="false" scanPeriod="30 seconds">
<conversionRule conversionWord="coloredLevel" converterClass="play.api.Logger$ColoredLevel"/>
<appender name="FLUENTD" class="org.zaptravel.logback.fluentd.FluentdLogbackAppenderBase">
<tagprefix>amazon.rds</tagprefix>
<clientid>Hawai05</clientid>
<port>24224</port>
<fluentdHost>127.0.0.1</fluentdHost>
<maxQueueSize>1024</maxQueueSize>
</appender>
<logger name="play" level="WARN"/>
<logger name="akka" level="WARN"/>
<logger name="akka.event.slf4j.Slf4jEventHandler" level="WARN"/>
<logger name="com.typesafe.config" level="WARN"/>
<logger name="application-akka" level="WARN"/>
<root level="INFO">
<appender-ref ref="FLUENTD"/>
</root>
</configuration>
Et c’est un peu près tout. Bon, il faut quelques connaissances unix, un peu de recherche sur le site de Fluentd, mais rien de trop compliqué.
Pourquoi c’est mieux qu’une solution de log sur le système ?
Nous avons en général 2 machines pour la production, pour le site web. Aller lire les fichiers de logs sur le système ? Non merci, j’ai arrêté. Ensuite je vous avoue que lorsque je code, j’essaye de ne pas tartiner des logs à tout va. Je suis très pragmatique. Surtout en devenant un vieux développeur. J’essaye de blinder ce qui est fragile. Dans mon système par exemple, c’est le parsing JSON sur Redis, qui demande un peu plus d’attention. Lorsqu’un des workers écrit sur Redis, et qu’il y a un problème de format, ce qui arrive de temps en temps, ce système permet de gérer des erreurs métiers. Je fais un peu plus d’efforts dans ce cas pour donner la clé appelée, ce que le parser a lu et ce qu’il attendait.
Autre astuce de cow-boy, surtout lorsque l’on fait un site Internet, c’est le MDC.
Qu’est-ce qu’un MDC ?
Mapped Diagnostic Context, tiens tu peux lire la doc ici.
C’est un système qui permet de placer une variable de contexte, de sorte que lorsque votre système crashe, vous puissiez loguer cette valeur. Je vous sens perplexe.
Lorsque vous visitez Zaptravel, je place l’URI courante dans le MDC avec ce petit bout de code (scala ou java) :
def home(origin: String , segment: String) = ActionWithWebuser {
implicit context =>
MDC.put("URL", context.request.uri)
// fait pleins de trucs sympa.
...
}
Pour chaque visiteur de mon site, dans mes différentes actions, je stocke l’URI courante (et parfois les paramètres) comme diagnostic de contexte. Ceci va me permettre de loguer l’URI du visiteur lorsqu’une erreur survient. C’est vraiment très pratique.
Ensuite, pour afficher l’URI, il suffit de modifier votre fichier logger.xml et d’ajouter %X{URL} :
%coloredLevel %d{HH:mm:ss,SSS} %X{URL} %logger: %message%n%ex{10} ...
Ainsi, dès lors qu’une belle et jeune exception montrera le bout de son nez, paf, vous aurez aussi l’URI utilisée par votre client. Très pratique lorsqu’il s’agit de détecter du contenu invalide sur un site web.
Une fois que l’on y a goûté, on ne peut plus s’en passer.
Côté code, je pourrais faire un article là-dessus, je crois que nous n’avons pas une seule exception Java (checked ou non) dans notre projet. Mort aux Unchecked Exceptions, surtout lorsque c’est pour faire du métier. Vous pouvez écrire « InvalidFlightException » ou « TooMuchDrinkLastNightException » ou encore « NotMyFaultException »… Mais bon… dans le monde Scala nous utilisons Options, Either ou encore Try pour gérer correctement les données incomplètes ou manquantes.
Il faudra que je vous parle de Scala un jour, c’est assez sympa.
Sur ce, je retourne coder.
0 no like
Ca risque pas de poser de problèmes de faire du Play/Scala/Akka/ReactiveProgramming et du MDC en même temps?
Dans un contexte ou on a pas un utilisateur = un thread j’entend.