Journalisation Javascript super simple - Deux décorateurs et terminé


Êtes-vous toujours fatigué d'écrire logger.info('ServiceName.methodName.') Et logger.info('ServiceName.methodName -> done.') Pour chaque éternuement? Peut-être avez-vous, comme moi, pensé à plusieurs reprises à automatiser cette entreprise? Dans cet article, nous parlerons de l' enregistreur de classe comme l'une des solutions au problème avec seulement deux décorateurs.


Pourquoi est-ce même nécessaire?


Tous les perfectionnistes ne sont pas des ingénieurs, mais tous les ingénieurs sont des perfectionnistes (enfin, presque). On aime les belles abstractions laconiques. On voit la beauté dans l'ensemble des crocodiles, qu'une personne n'est pas préparée et ne sait pas lire. Nous aimons nous sentir comme des dieux, créant des micro univers qui vivent selon nos règles. Vraisemblablement, toutes ces qualités proviennent de notre immense paresse. Non, l'ingénieur n'a pas peur de travailler en classe, mais il déteste farouchement les actions répétitives de routine que ses bras essaient d'automatiser.


Après avoir écrit plusieurs milliers de lignes de code destinées uniquement à la journalisation, nous arrivons généralement à la création de certains de nos propres modèles et normes pour écrire des messages. Malheureusement, nous devons encore appliquer ces modèles manuellement. Le principal «pourquoi» de l' enregistreur de classe est de fournir un moyen déclaratif et configurable de consigner facilement les messages de modèle lors de la création d'une classe et de l'appel de ses méthodes.


Dames nues!


Sans se promener, passons directement au code.


 import { LogClass, Log } from 'class-logger' @LogClass() class ServiceCats { @Log() eat(food: string) { return 'purr' } } 

Ce service créera trois entrées de journal:


  • Avant de le créer avec une liste d'arguments passés au constructeur.
  • Avant d'appeler eat avec une liste d'arguments.
  • Après avoir appelé eat avec une liste d'arguments et le résultat de l'exécution.

En mots de code:


 //    ServiceCats // `ServiceCats.construct. Args: [].` const serviceCats = new ServiceCats() //    `eat` // `ServiceCats.eat. Args: [milk].` serviceCats.eat('milk') //    `eat` // `ServiceCats.eat -> done. Args: [milk]. Res: purr.` 

Piquez-le en direct .


Liste complète des événements pouvant être annoncés:


  • Avant de créer une classe.
  • Avant d'appeler des méthodes synchrones et asynchrones et des propriétés fonctionnelles, statiques et non statiques.
  • Après avoir appelé les méthodes synchrones et asynchrones et les propriétés fonctionnelles, statiques et non statiques.
  • Erreurs lors de l'appel des méthodes synchrones et asynchrones et des propriétés fonctionnelles, statiques et non statiques.

Une propriété fonctionnelle est une fonction flèche affectée à une propriété ( class ServiceCats { private meow = () => null } ). Le plus souvent utilisé pour maintenir le contexte d'exécution ( this ).

On nous avait promis une méthode de journalisation "configurable"


enregistreur de classe trois niveaux de hiérarchie de configuration:


  • Global
  • Classe
  • La méthode

Lors de l'appel de chaque méthode, les trois niveaux fusionnent. La bibliothèque fournit une configuration globale par défaut raisonnable, elle peut donc être utilisée sans aucune configuration préalable.


Configuration globale


Il est créé pour l'ensemble de l'application. Il peut être remplacé par setConfig .


 import { setConfig } from 'class-logger' setConfig({ log: console.info, }) 

Configuration de classe


Il est unique pour chaque classe et s'applique à toutes les méthodes de cette classe. Peut remplacer la configuration globale.


 import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ //       log: console.debug, }) class ServiceCats {} 

Configuration de la méthode


Fonctionne uniquement pour la méthode elle-même. Il a priorité sur la configuration de classe et la configuration globale.


 import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ //       log: console.debug, }) class ServiceCats { private energy = 100 @Log({ //        log: console.warn, }) eat(food: string) { return 'purr' } //    `console.debug`    sleep() { this.energy += 100 } } 

Poke live


Que peut-on configurer?


Nous avons examiné comment changer la configuration, mais nous n'avons toujours pas compris ce qui peut être changé exactement.


Vous trouverez ici de nombreux exemples d'objets de configuration pour différents cas .

Lien vers l'interface si vous comprenez mieux TypeScript. que le russe :)

L'objet de configuration a les propriétés suivantes:


journal


Il s'agit d'une fonction qui traite réellement de la journalisation. Elle reçoit un message formaté sous forme de chaîne à l'entrée. Utilisé pour enregistrer les événements suivants:


  • Avant de créer une classe.
  • Avant d'appeler des méthodes synchrones et asynchrones et des propriétés fonctionnelles, statiques et non statiques.
  • Après avoir appelé les méthodes synchrones et asynchrones et les propriétés fonctionnelles, statiques et non statiques.

Par défaut, console.log .


logError


Il s'agit d'une fonction qui traite également de la journalisation, mais uniquement des messages d'erreur. Elle reçoit également un message formaté sous forme de chaîne. Utilisé pour enregistrer les événements suivants:


  • Erreurs lors de l'appel des méthodes synchrones et asynchrones et des propriétés fonctionnelles, statiques et non statiques.

La valeur par défaut est console.error .


formateur


Il s'agit d'un objet avec deux méthodes: start et end . Ces méthodes créent le même message formaté.


start crée des messages pour les événements suivants:


  • Avant de créer une classe.
  • Avant d'appeler des méthodes synchrones et asynchrones et des propriétés fonctionnelles, statiques et non statiques.

end génère des messages pour les événements suivants:


  • Après avoir appelé les méthodes synchrones et asynchrones et les propriétés fonctionnelles, statiques et non statiques.
  • Erreurs lors de l'appel des méthodes synchrones et asynchrones et des propriétés fonctionnelles, statiques et non statiques.

Par défaut, new ClassLoggerFormatterService() .


inclure


La configuration de ce qui doit être inclus dans le message final.


args

Il peut s'agir d'une valeur booléenne ou d'un objet.


S'il est boolean , il définit s'il faut inclure une liste d'arguments (rappelez-vous Args: [milk] ?) Dans tous les messages ( start et end ).


S'il s'agit d'un objet, il doit avoir deux propriétés booléennes: start et end . start spécifie s'il faut inclure une liste d'arguments pour les messages de start , end pour les messages de end .


La valeur par défaut est true .


construire

Il s'agit d'un boolean qui contrôle s'il faut enregistrer la création de classe ou non.


La valeur par défaut est true .


résultat

Il s'agit d'un boolean qui contrôle la journalisation de la valeur de retour de la méthode. Il convient de noter que la valeur de retour n'est pas seulement ce que la méthode renvoie en cas d'exécution réussie, mais également l'erreur qu'elle génère en cas d'échec de l'exécution. Rappelez-vous Res: purr ? Si ce drapeau est false , il n'y aura pas de Res: purr .


La valeur par défaut est true .


classInstance

Il peut s'agir d'une valeur booléenne ou d'un objet. Le concept est le même que pour include.args .


Ajoute une vue d'instance sérialisée de votre classe aux publications. En d'autres termes, si votre classe possède des propriétés, elles seront sérialisées en JSON et ajoutées aux journaux.


Toutes les propriétés ne sont pas sérialisables. L'enregistreur de classe utilise la logique suivante:


  • Nous prenons toutes nos propres propriétés (celles qui ne sont pas dans le prototype) d'Intansa.
    • Pourquoi? Très rarement, un prototype change dynamiquement, il est donc peu logique d'enregistrer son contenu.
  • Nous en jetons tous de type function .
    • Pourquoi? Plus souvent qu'autrement, les propriétés de type function sont simplement des fonctions fléchées qui ne sont pas faites par des méthodes conventionnelles afin de maintenir le contexte ( this ). Ils ont rarement un caractère dynamique, il est donc inutile de les enregistrer.
  • On en jette tous les objets qui ne sont pas de simples objets.
    • Quels sont ces objets simples? ClassLoggerFormatterService considère un objet simple si son prototype est Object.prototype .
    • Pourquoi? Souvent, les instances d'autres classes de service agissent comme des propriétés. Nos journaux gonfleraient de la manière la plus laide si nous commencions à sérialiser.
  • Sérialiser dans la chaîne JSON tout ce qui reste.

 class ServiceA {} @LogClass({ include: { classInstance: true, }, }) class Test { private serviceA = new ServiceA() private prop1 = 42 private prop2 = { test: 42 } private method1 = () => null @Log() public method2() { return 42 } } //    `Test` // 'Test.construct. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' const test = new Test() //    `method2` // 'Test.method2. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' test.method2() //    `method2` // 'Test.method2 -> done. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}. Res: 42.' 

La valeur par défaut est false .


Prenez le contrôle total du formatage des messages


Et si vous aimez l'idée, mais votre idée de la belle insiste sur un format de ligne de message différent? Vous pouvez prendre le contrôle total du formatage des messages en passant votre propre formateur.


Vous pouvez écrire votre propre formateur à partir de zéro. Bien sûr. Mais cette option ne sera pas couverte dans ce cadre (si vous êtes intéressé par cette option particulière, jetez un œil à la section "Formatage" dans README).


Le moyen le plus rapide et le plus simple de remplacer la mise en forme consiste à hériter votre formateur du formateur par défaut - ClassLoggerFormatterService .


ClassLoggerFormatterService possède les méthodes protected suivantes qui créent de petits blocs de messages finaux:


  • base
    • Renvoie le nom de classe et le nom de méthode. Exemple: ServiceCats.eat .
  • operation
    • Renvoie -> done ou -> error selon que la méthode s'est terminée avec succès.
  • args
    • Renvoie une liste d'arguments sérialisés. Exemple:. . Args: [milk] . Il utilise fast-safe-stringify pour sérialiser les objets sous le capot.
  • classInstance
    • Renvoie l'instance sérialisée de la classe. Exemple:. . Class instance: {"prop1":42,"prop2":{"test":42}} . Si vous avez inclus include.classInstance dans la configuration, mais pour une raison quelconque, l'instance elle-même n'est pas disponible au moment de la journalisation (par exemple, pour les méthodes statiques ou avant de créer une classe), renvoie N/A
  • result
    • Renvoie un résultat d'exécution sérialisé ou une erreur sérialisée. Utilise une chaîne de sécurité rapide pour les objets sous le capot. Une erreur sérialisée comprend les propriétés suivantes:
    • Nom de la classe (fonction) utilisée pour créer l'erreur ( error.constructor.name ).
    • Code ( error.code ).
    • Message ( error.message ).
    • Nom ( error.name ).
    • Trace de pile ( error.stack ).
  • final
    • Retours . . C'est simple . .

Le message de start compose de:


  • base
  • args
  • classInstance
  • final

Le message end compose de:


  • base
  • operation
  • args
  • classInstance
  • result
  • final

Vous pouvez remplacer uniquement les méthodes de base dont vous avez besoin.


Voyons comment vous pouvez ajouter un horodatage à tous les messages.


Je ne dis pas que cela devrait se faire dans de vrais projets. pino , winston et la plupart des autres enregistreurs peuvent le faire par eux-mêmes. Cet exemple est uniquement à des fins éducatives.

 import { ClassLoggerFormatterService, IClassLoggerFormatterStartData, setConfig, } from 'class-logger' class ClassLoggerTimestampFormatterService extends ClassLoggerFormatterService { protected base(data: IClassLoggerFormatterStartData) { const baseSuper = super.base(data) const timestamp = Date.now() const baseWithTimestamp = `${timestamp}:${baseSuper}` return baseWithTimestamp } } setConfig({ formatter: new ClassLoggerTimestampFormatterService(), }) 

Poke live


Conclusion


N'oubliez pas de suivre les instructions d'installation et de vous familiariser avec les exigences avant d'utiliser cette bibliothèque sur votre projet.


J'espère que vous n'avez pas perdu de temps en vain, et l'article vous a au moins été un peu utile. Veuillez donner un coup de pied et critiquer. Nous apprendrons à mieux coder ensemble.

Source: https://habr.com/ru/post/fr447970/


All Articles