
Ê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:
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
.
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
.
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.