Nous créons une nouvelle application et j'aimerais inclure la journalisation structurée. Ma configuration idéale serait quelque chose comme Serilog
pour notre code C # et Bunyan
pour notre JS. Celles-ci alimenteraient fluentd
et pourraient ensuite aller à un certain nombre de choses, je pensais initialement elasticsearch + kibana
. Nous avons déjà une base de données MySQL, donc à court terme, je suis plus intéressé à obtenir la configuration de Serilog + Bunyan et les développeurs à l'utiliser et nous pouvons nous connecter à MySQL pendant que nous prenons un peu plus de temps pour apporter fluentd et le reste.
Cependant, l'un de nos codeurs les plus expérimentés préférerait simplement faire quelque chose comme: log.debug("Disk quota {0} exceeded by user {1}", quota, user);
en utilisant log4net
, Puis exécuter simplement des instructions select contre MySQL comme: SELECT text FROM logs WHERE text LIKE "Disk quota";
Cela étant dit, quelle approche est la meilleure et/ou quelles choses devons-nous considérer lors du choix du type de système de journalisation?
Il y a deux avancées fondamentales avec l'approche structurée qui ne peuvent pas être émulées à l'aide de journaux de texte sans (parfois des niveaux extrêmes) d'efforts supplémentaires.
Lorsque vous écrivez deux événements avec log4net comme:
log.Debug("Disk quota {0} exceeded by user {1}", 100, "DTI-Matt");
log.Debug("Disk quota {0} exceeded by user {1}", 150, "nblumhardt");
Ceux-ci produiront un texte similaire:
Disk quota 100 exceeded by user DTI-Matt
Disk quota 150 exceeded by user nblumhardt
Mais, en ce qui concerne le traitement machine, ce ne sont que deux lignes de texte différent.
Vous souhaiterez peut-être trouver tous les événements "dépassement du quota de disque", mais le cas simpliste de la recherche d'événements like 'Disk quota%'
tombera dès qu'un autre événement se produira ressemblant à:
Disk quota 100 set for user DTI-Matt
La journalisation de texte jette les informations que nous avons initialement sur la source de l'événement, et cela doit être reconstruit lors de la lecture des journaux, généralement avec des expressions de correspondance de plus en plus élaborées.
En revanche, lorsque vous écrivez les deux événements Serilog suivants:
log.Debug("Disk quota {Quota} exceeded by user {Username}", 100, "DTI-Matt");
log.Debug("Disk quota {Quota} exceeded by user {Username}", 150, "nblumhardt");
Ceux-ci produisent une sortie de texte similaire à la version log4net, mais en arrière-plan, le "Disk quota {Quota} exceeded by user {Username}"
modèle de message est porté par les deux événements.
Avec un récepteur approprié, vous pouvez ensuite écrire des requêtes where MessageTemplate = 'Disk quota {Quota} exceeded by user {Username}'
et obtenez exactement les événements où le quota de disque a été dépassé.
Il n'est pas toujours pratique de stocker l'intégralité du modèle de message avec chaque événement de journal, de sorte que certains puits hachent le modèle de message dans une valeur numérique EventType
(par exemple 0x1234abcd
), ou, vous pouvez ajouter un enricher au pipeline de journalisation à faites-le vous-même .
C'est plus subtil que la prochaine différence ci-dessous, mais massivement puissant lorsqu'il s'agit de gros volumes de journaux.
Compte tenu des deux événements concernant l'utilisation de l'espace disque, il peut être assez facile d'utiliser des journaux de texte pour interroger un utilisateur particulier avec like 'Disk quota' and like 'DTI-Matt'
.
Mais les diagnostics de production ne sont pas toujours aussi simples. Imaginez qu'il soit nécessaire de trouver des événements où le quota de disque dépassé était inférieur à 125 Mo?
Avec Serilog, cela est possible dans la plupart des éviers en utilisant une variante de:
Quota < 125
Construire ce type de requête à partir d'une expression régulière c'est possible, mais cela devient vite fatigant et finit généralement par être une mesure de dernier recours.
Ajoutez maintenant à cela un type d'événement:
Quota < 125 and EventType = 0x1234abcd
Vous commencez à voir ici comment ces capacités se combinent de manière simple pour que le débogage de la production avec les journaux soit une activité de développement de premier ordre.
Un autre avantage, peut-être pas aussi facile à éviter à l'avance, mais une fois que le débogage de la production a été retiré du pays du piratage regex, les développeurs commencent à valoriser les journaux beaucoup plus et à faire preuve de plus de soin et de considération lors de leur écriture. De meilleurs journaux -> des applications de meilleure qualité -> plus de bonheur tout autour.
Lorsque vous collectez des journaux pour le traitement, que ce soit pour l'analyse dans une base de données et/ou la recherche dans les journaux traités plus tard, en utilisant la journalisation structurée fait traitement plus facile/plus efficace. L'analyseur peut tirer parti de la structure connue ( par exemple JSON, XML, ASN.1, peu importe) et utiliser des machines à états pour l'analyse, par opposition à régulière les expressions (qui peuvent être relativement coûteuses à compiler et à exécuter). L'analyse du texte de forme libre, tel que celui suggéré par votre collègue, a tendance à s'appuyer sur des expressions régulières, et à s'appuyer sur ce texte ne change pas . Cela peut rendre l'analyse du texte de forme libre assez fragile ( c'est-à-dire l'analyse est étroitement couplée au texte exact dans le code).
Considérez également le cas de recherche/recherche, par exemple :
SELECT text FROM logs WHERE text LIKE "Disk quota";
Les conditions LIKE
nécessitent des comparaisons avec chaque valeur de ligne text
; encore une fois, cela est relativement coûteux en calcul, en particulier lorsque des caractères génériques sont utilisés:
SELECT text FROM logs WHERE text LIKE "Disk %";
Avec la journalisation structurée, votre message de journal lié aux erreurs de disque pourrait ressembler à ceci dans JSON:
{ "level": "DEBUG", "user": "username", "error_type": "disk", "text": "Disk quota ... exceeded by user ..." }
Les champs de ce type de structure peuvent être mappés assez facilement avec par exemple les noms de colonne de table SQL, ce qui signifie que la recherche peut être plus spécifique/granulaire:
SELECT user, text FROM logs WHERE error_type = "disk";
Vous pouvez placer des index sur les colonnes dont vous prévoyez de rechercher/rechercher fréquemment tant que vous n'utilisez pas de clauses LIKE
pour ces valeurs de colonne . Plus vous pouvez diviser votre message de journal en catégories spécifiques, plus vous pouvez cibler votre recherche. Par exemple, en plus du error_type
champ/colonne dans l'exemple ci-dessus, vous pouvez même faire être "error_category": "disk", "error_type": "quota"
ou quelque chose.
Plus vous avez de structure dans vos messages de journal, plus vos systèmes d'analyse/de recherche (tels que fluentd
, elasticsearch
, kibana
) peuvent tirer parti de cette structure et effectuer leurs tâches avec une plus grande vitesse et moins de CPU/mémoire.
J'espère que cela t'aides!
Vous ne bénéficierez pas beaucoup de la journalisation structurée lorsque votre application crée quelques centaines de messages de journal par jour. Vous le ferez certainement lorsque vous aurez quelques centaines de messages de journal par seconde provenant de nombreuses applications déployées différentes.
La configuration dans laquelle les messages de journal se retrouvent dans la pile ELK est également appropriée pour une échelle où la journalisation SQL devient un goulot d'étranglement.
J'ai vu la configuration de la "journalisation et recherche de base" avec SQL select .. like
et regexps poussé à ses limites là où il s'effondre - il y a des faux positifs, des omissions, un code de filtre horrible avec des bogues connus difficiles à maintenir et que personne ne veut toucher, de nouveaux messages de journal qui ne suivent pas les hypothèses du filtre, réticence à toucher les instructions de journalisation dans le code de peur de casser les rapports, etc.
Ainsi, plusieurs progiciels font leur apparition pour mieux résoudre ce problème. Il y a Serilog, j'ai entendu dire que l'équipe NLog le regarde , et nous avons écrit StructuredLogging.Json
pour Nlog , je constate également que les nouveaux abstractions de journalisation de base ASP.Net "permettent aux fournisseurs de journalisation d'implémenter ... une journalisation structurée".
Un exemple avec StructuredLogging. Vous vous connectez à un enregistreur NLog comme ceci:
logger.ExtendedError("Order send failed", new { OrderId = 1234, RestaurantId = 4567 } );
Ces données structurées vont à kibana. La valeur 1234
est stocké dans le champ OrderId
de l'entrée de journal. Vous pouvez ensuite rechercher en utilisant la syntaxe de requête kibana pour, par exemple. toutes les entrées de journal où @LogType:nlog AND Level:Error AND OrderId:1234
.
Message
et OrderId
ne sont plus que des champs qui peuvent être recherchés pour les correspondances exactes ou inexactes selon vos besoins, ou agrégés pour les nombres. C'est puissant et flexible.
À partir des meilleures pratiques StructuredLogging :
Le message enregistré doit être le même à chaque fois. Il doit s'agir d'une chaîne constante et non d'une chaîne formatée pour contenir des valeurs de données telles que des identifiants ou des quantités. Ensuite, il est facile de rechercher.
Le message enregistré doit être distinct, c'est-à-dire différent du message produit par une instruction de journal non liée. Ensuite, la recherche ne correspond pas non plus à des choses indépendantes.