Motivation
Ce document dĂ©finit la structure en sortie des logs, lâAPI pour les dĂ©veloppeurs et la façon de rassembler les diffĂ©rents Ă©lĂ©ments des logs (nom des paramĂštres, sĂ©vĂ©ritĂ©, texte du syslog et sa description pour le manuel).
Ce document ne traite pas de lâamĂ©lioration du contenu de chacun des logs (paramĂštres, texte, description, niveau de log).
- Facile pour les dĂ©veloppeurs : Ăcriture des logs, Maintenance du framework.
- Facile pour le manuel de lâutilisateur : Maintenance de la liste des logs et de leur description (avec internationalisation).
- Facile pour lâIT du client => Les logs sont structurĂ©s et nativement comprĂ©hensibles par les outils dâanalyse de logs.
Analyse automatique des logs
Les Ă©quipes IT de supervision des applications utilisent des outils dâaide au traitement des logs issus de leur parc applicatif. Afin de rĂ©duire la charge de travail, les logs doivent ĂȘtre le plus compatible possible avec ces outils.
Ces outils sont compatibles avec le format Syslog dont la derniÚre norme est la RFC 5424 (la RFC 3164 était une recommandation préliminaire).
Ces outils sont aussi compatibles avec le format JSON. Mais ce document ne traite pas de la possibilité JSON.
Quelques exemples dâoutils dâanalyse de logs :
Splunk Enterprise
https://www.splunk.com
Logstash
https://www.elastic.co/fr/products
TrÚs utilisée avec avec ElasticSearch et Kibana.
GrayLog
https://www.graylog.org
DataLog
https://www.datadoghq.com
Finalité des logs
Les logs sont utilisĂ©s pour des besoins trĂšs variĂ©s. Voici quelques exemples dâactivitĂ©s :
- Alarme
- MĂ©triques
- Informations diverses
- Surveillance au fil de lâeau (par lâIT du client)
- Investigation dâune anomalie (par lâIT du client)
- Capacity planning (prĂ©voir Ă lâavance quand augmenter ou rĂ©duire les ressources matĂ©rielles)
- Audit (ANSSI, procĂ©dure judiciaireâŠ)
- Debug (pour les développeurs ou le support)
La liste finale des activitĂ©s sera Ă Ă©tablir en faisant une passe sur les logs existants. (la liste sera certainement rĂ©duite Ă un plus petit nombre dâactivitĂ©s) Afin dâobtenir une liste dâactivitĂ©s pertinentes, les pratiques les plus courantes pourraient ĂȘtre analysĂ©es.
Format syslog
<123>1 DateTimeTZ Hostname AppName ProcID MsgID [idA@1234 name="value"] [idB@1234 a="1" b="2"] BOMmessageUTF8
--- \ \ -----------------------
| \ 2017-08-24T17:14:15.000003-07:00 |
| \ |
Priority SyslogVersion Structure Data
Priority
<123>
- Priority = Facility * 8 + Severity
- Facility
- 1 âuser-level messagesâ
- ou la plage [16 .. 23] rĂ©servĂ©e âfor local useâ
- Severity = [0 .. 7] (niveau de sévérité du log)
Version
<123>1
^
Toujours 1
DateTimeTZ
Horodatage
-
Précision = la milliseconde
2017-08-24T17:14:15.000003-07:00
-
Notation courte en UTC
2017-08-24T10:14:15Z
Hostname
FQDN, Hostname, IPâŠ
TODO: VĂ©rifier les pratiques courantes utilisĂ©es par dâautres applications similaires Pour des logs transmis par des clients Ă un serveur, ce dernier pout indiquer lâidentifiant unique du client.
AppName
TODO: VĂ©rifier les recommandation de la RFC 5424 et les conventions les plus courantes afin de ne pas suprendre les Ă©quipes IT de surveillance des logs.
ProcID
Process ID
MsgID
Identifiant unique pour un type de message de log
TODO: DĂ©finir comment obtenir cet identifiant unique
Structure Data
[sdId@1234 name1="value1" name2="value2" name3="value3"]
sdId
= Nom de la structure de donnée (SD)1234
= Le Private Enterprise Number (PEN) spécifique à une entreprisename="value"
= Un paramÚtre représenté par une paire de clé-valeur
Si plusieurs SD utilisent un paramĂštre ayant la mĂȘme signification, le name
doit ĂȘtre le mĂȘme.
Un mĂȘme SD peut contenir plusieurs paramĂštres ayant le mĂȘme name
.
Exemple pour indiquer deux adresses IP :
[sdId@1234 ip="10.22.22.22" ip="10.33.33.33"]
En absence de SD, mettre le signe tiret â-
â.
Si plusieurs SD, un mĂȘme sdId@1234
ne doit pas ĂȘtre prĂ©sent plus de une fois.
Ces structures de donnĂ©es sont Ă destination des outils dâanalyse automatique de log. Donc, leur changement doit ĂȘtre bien pensĂ© pour Ă©viter toute rĂ©gression. Par consĂ©quent, chaque nouvelle structure doit bien ĂȘtre rĂ©flĂ©chie pour assurer sa pĂ©rennitĂ©.
BOMmessageUTF8
Optionnel, câest une chaĂźne de caractĂšres en UTF-8 commençant par un BOM. Cette chaĂźne de caractĂšres est Ă destination des humains, et peut changer entre deux versions. Les paramĂštres variables doivent se trouver dans la partie SD (structure de donnĂ©es). NĂ©anmoins cette chaĂźne peut Ă©galement rĂ©pĂ©ter quelques paramĂštres si cela amĂ©liore la comprĂ©hension du message Ă destination des humains.
Exemples valides
<134>1 2017-10-11T22:14:15.003Z myapp.company.com MyModule1 1234 M42 - BOMCeci est un message de niveau information et sans structure de données
<134>1 2017-10-11T22:14:15.003Z myapp.company.com MyModule1 1235 M43 [metric@1234 sd="2"] BOMCeci est un métrique
<134>1 2017-10-11T22:14:15.003Z myapp.company.com MyModule2 1235 M43 [metric@1234 sd="2"] [debug@1234 file="a.c" line="111"]
Structuration des informations des logs
Les informations nécessaires à produire une ligne de log proviennent de différents objets.
Cette section propose dâĂ©viter la redondance de ces informations et structure ces informations en les regroupant par affinitĂ© :
- Les coordonnĂ©es locales (Timestamp, ThreadID, File/Function/Line) sont obtenues directement Ă lâappel de la fonction de log ;
- Les informations gĂ©nĂ©riques du log (MsgID, Severity, textesâŠ) sont stockĂ©es dans la liste gĂ©nĂ©rale des logs ;
- Short message = le texte qui sâaffiche dans la ligne Syslog juste aprĂšs le BOM (et qui est Ă©galement rĂ©utilisĂ© dans le manuel de lâutilisateur) ;
- Long description = le texte prĂ©vue pour le manuel de lâutilisateur.
- Chaque âStructured Dataâ peut ĂȘtre rĂ©utilisĂ©e par plusieurs logs. La liste gĂ©nĂ©rale des logs doit alors indiquer les âStructured Dataâ utilisĂ©es par chaque log ;
- Les valeur des paramĂštres de chaque âStructured Dataâ sont Ă passer Ă la fonction de log ;
- Les informations du module produisant ce log (ModuleName, Facility) sont centralisées par le Module ;
- Les informations gĂ©nĂ©rales de lâapplication (AppName, ProcessID, Hostname) sont centralisĂ©e par lâApplication.
Quelques exemples de Structured Data
id@1234
Structure générique pour tous les logs
[id@1234 activity="event" threadId="12345" threadName="main" moduleName="MyModule" transactionId="123"]
- activity (event, alarm, metrique, trace, auditâŠ)
- threadId
- threadName
- moduleName (mandatory)
- transactionId (multiple events are about the same chaining transaction)
Le threadName est le nom du thread (fil dâexĂ©cution). Le nom du thread est bien souvent complĂ©mentaire (et plus explicite) que son threadId.
Les systĂšmes Windows et GNU permettent de nommer les threads.
Pour la glibc (pthread), cette fonctionnalité est arrivée avec la version 2.12 sortie en 2010 :
#include <pthread.h>
int pthread_setname_np (pthread_t thread, const char *name);
int pthread_getname_np (pthread_t thread, char *name, size_t len);
debug@1234
Uniquement en mode debug
[debug@1234 file="a.c" line="111" function="foo()"]
- file : source code filename (le compilateur fournit cette information selon le filename est passé en argument de la commande du compilateur)
- line : line number in filename
- function : function name (if relevant)
Consignes dâimplĂ©mentation
Ne pas compliquer inutilement le code
-
Ăviter les switch de compilation (
#ifdef
), les abstractions, lâabus de design patterns ou de templates si cela ne se justifie pas. -
Si câest pour des questions de performance, des benchmark reproductibles sont nĂ©cessaires.
-
Il est tentant de cacher la complexitĂ© sous-jacente, mais cela peut devenir couteux Ă long terme car ce code sous-jacent aura du mal Ă ĂȘtre maintenu. LâexpĂ©rience sur de longs projets dĂ©montre quâun code complexe et peu comprĂ©hensif bloquera certaines Ă©volutions du produit ou sera tout jetĂ© pour ĂȘtre rĂ©Ă©crit plus simplement. Donc, Ă©crire du code simple avec moins de fonctionnalitĂ© dĂšs le dĂ©but.
Compatibilités
- La solution doit aussi ĂȘtre facilement intĂ©grable dans les diffĂ©rents langages utilisĂ©es (actuels et futurs) ;
- La solution doit fonctionner sur toutes les plateformes prises en charge par lâĂ©quipe.
Interface intuitive
- LâAPI doit ĂȘtre claire est facile Ă comprendre ;
- Pour des cas simple, lâAPI ne doit pas obliger la crĂ©ation de class ou fonction.
Manuel de lâutilisateur
- Afin de faciliter lâĂ©dition du manuel de lâutilisateur, identifier les fichiers contenant les messages des logs, quitte Ă les centraliser.
- Trouver un moyen facilement maintenable pour lier les logs et leur description dĂ©taillĂ©e (par exemple en les mettant dans les mĂȘmes fichiers).
- Ces deux premier point vont faciliter dâautant plus lâinternationalisation. Le top est de les formaliser en dehors du code source pour faciliter les traductions et relectures.
- Uniformiser la représentation des différents logs quel que soit le langage de programmation.
- Penser Ă scripter la gĂ©nĂ©ration la partie du manuel de lâutilisateur qui correspond Ă la documentation des logs (ce qui est fastidieux et rĂ©pĂ©titif doit ĂȘtre automatisĂ©).
- La formalisation des messages et descriptions doit Ă©galement faciliter lâinternationalisation (leur traduction et sa maintenance sur le long terme).
Compatibilité des bibliothÚques de Log avec Syslog
Syslog(3) POSIX
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
int setlogmask(int maskpri); // active/désactive les différents niveaux de log
void syslog (int priority, const char *format, ...);
void vsyslog (int priority, const char *format, va_list ap); // identical but using
void closelog(void);
const char* ident = "the AppName"; //
int option = LOG_PID; // include PID with each message
int facility = LOG_USER;
int level = LOG_DEBUG; // LOG_INFO LOG_NOTICE LOG_WARNING LOG_ERR LOG_CRIT LOG_ALERT LOG_EMERG
int priority = LOG_MAKEPRI(facility, level);
int maskpri = LOG_MASK(LOG_ERR) | LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ALERT) | LOG_MASK(LOG_EMERG);
maskpri = LOG_UPTO(LOG_ERR); // revient au mĂȘme
Inconvénients
- Pas de prise en charge de la structure de donnée (SD) avant le BOM.
- Semble utiliser UDP uniquement.
Implémentations pour Windows
http://syslog-win32.sourceforge.net
Dernier changement en 2005 pour le code du client, et en 2014 pour le code du du démon qui dépend des bibliothÚques Glib, libiconv and libintl.
Forks avec des améliorations :
- http://github.com/fbzhong/syslog-win32
- http://github.com/asankah/syslog-win32
Spdlog
Very fast, header only, C++ logging library.
http://github.com/gabime/spdlog
spdlog permet de wrapper les appels vers lâAPI Syslog POSIX.
#define SPDLOG_ENABLE_SYSLOG
#include "spdlog/spdlog.h"
int main(int, char*[])
{
std::string name = "le nom du logger";
std::string ident = "l'AppName";
int option = LOG_PID;
auto logger = spdlog::syslog_logger(name, ident, option);
logger->warn("This is warning that will end up in syslog.");
}
Google glog
http://github.com/google/glog
Les macros SYSLOG
, SYSLOG_IF
et SYSLOG_EVERY_N
utilisent lâAPI Syslog POSIX doit voici le wrapping :
void LogMessage::SendToSyslogAndLog() {
#ifdef HAVE_SYSLOG_H
// Before any calls to syslog(), make a single call to openlog()
static bool openlog_already_called = false;
if (!openlog_already_called) {
openlog(glog_internal_namespace_::ProgramInvocationShortName(),
LOG_CONS | LOG_NDELAY | LOG_PID,
LOG_USER);
openlog_already_called = true;
}
// This array maps Google severity levels to syslog levels
const int SEVERITY_TO_LEVEL[] = { LOG_INFO, LOG_WARNING, LOG_ERR, LOG_EMERG };
syslog(LOG_USER | SEVERITY_TO_LEVEL[static_cast<int>(data_->severity_)], "%.*s",
int(data_->num_chars_to_syslog_),
data_->message_text_ + data_->num_prefix_chars_);
SendToLog();
#else
LOG(ERROR) << "No syslog support: message=" << data_->message_text_;
#endif
}
La remarque dans la documentation de glog sâapplique Ă©galement Ă toutes les autres utilisation des implĂ©mentations standards de Syslog POSIX :
These log to syslog as well as to the normal logs. If you use these at all, you need to be aware that syslog can drastically reduce performance, especially if it is configured for remote logging! Donât use these unless you fully understand this and have a concrete need to use them. Even then, try to minimize your use of them.
Boost::log
http://www.boost.org/libs/log/doc/html/boost/log/sinks/syslog_backend.html
ImplĂ©mente une alternative Ă lâimplĂ©mentation Syslog POSIX standard.
Mais nâest pas conforme Ă la RFC 5424 et ne semble pas utiliser TCP (seulement UDP).
Par contre, la documentation conseille dâutiliser plutĂŽt lâimplĂ©mentation Syslog POSIX standard afin de ne pas contourner les Ă©ventuels dispositifs de sĂ©curitĂ© :
On systems with native syslog implementation it may be preferable to utilize the POSIX syslog API instead of direct socket management in order to bypass possible security limitations that may be in action. To do so one has to pass the use_impl = native to the backend constructor. [âŠ] Using use_impl = native on platforms with no native support for POSIX syslog API will have no effect.
G3log
Projet trĂšs actif, haute performance comme Spdlog.
TODO
Log4cpp
Log for C++
http://log4cpp.sourceforge.net/
Son interface (API) est trĂšs similaire Ă celle de Log4j. Log4cpp est un vieux projet, en phase de maintenance corrective, avec du vieux C++98, compatible avec de trĂšs nombreuse versions de compilateurs, mais pas trĂšs sexy, ni performant.
Log4cpp propose un backend Syslog qui utilise lâimplĂ©mentation POSIX standard :
SyslogAppender sends LoggingEvents to the local syslog system.
AutresâŠ
Autres bibliothĂšques C++ de logging : https://cpp.libhunt.com/categories/779-logging
Aspects qui restent à définir
- Permettre de mieux distinguer les AppName des Modules â Revoir RFC pour recommandations
- Trancher entre
Facility=1
(user-level messages) etFacility
dans la plage [16 .. 23] (local use) â Quellle est la best practice, la convention ? - Quel
Hostname
pour les clients qui envoient leurs logs au serveur ? â Quelle est la pratique ?