El registro de NodeJS es fácil


¿Cuántas veces escribió logger.info('ServiceName.methodName.') Y logger.info('ServiceName.methodName -> done.') Para todos y cada uno de los métodos de su servicio que desea registrar? ¿Desea que sea automatizado y tenga la misma firma constante en toda su aplicación? Si es así, somos muy parecidos, hemos sufrido el mismo dolor muchas veces, y ahora finalmente podríamos tratar de resolverlo. Juntos Damas y caballeros, permítanme presentarles ... ¡ registrador de clase !


"El por qué" de class-logger


Los ingenieros son a menudo perfeccionistas. Perfeccionistas al extremo. Nos gustan las abstracciones ordenadas. Nos gusta el código limpio. Vemos belleza en lenguajes artificiales que otras personas ni siquiera pueden leer. Nos gusta fabricar pequeños universos digitales, seguir las reglas que establecemos. Nos gusta todo eso, probablemente, porque somos muy vagos. No, no tenemos miedo al trabajo, pero odiamos hacer cualquier trabajo que pueda automatizarse.


Habiendo escrito solo un par de miles de líneas de código de registro, generalmente creamos ciertos patrones, estandarizando lo que queremos registrar. Sin embargo, todavía tenemos que aplicar esos patrones manualmente. Entonces, la idea central de class-logger es proporcionar una forma estandarizada declarativa altamente configurable para registrar mensajes antes y después de la ejecución de un método de clase.


Inicio rápido


Comencemos a ejecutar y veamos cómo se ve el código real.


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

Este servicio se registrará tres veces:


  • En su creación con una lista de argumentos pasados ​​al constructor.
  • Antes de eat se ejecuta con una lista de sus argumentos.
  • Después de eat se ejecuta con una lista de sus argumentos y su resultado.

En palabras de código:


 // Logs before the actual call to the constructor // `ServiceCats.construct. Args: [].` const serviceCats = new ServiceCats() // Logs before the actual call to `eat` // `ServiceCats.eat. Args: [milk].` serviceCats.eat('milk') // Logs after the actual call to `eat` // `ServiceCats.eat -> done. Args: [milk]. Res: purr.` 

Demostración en vivo .


¿Qué más podríamos registrar? Aquí está la lista completa de eventos:


  • Antes de la construcción de clase.
  • Antes de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.
  • Después de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.
  • Errores de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.

La propiedad funcional es una función de flecha asignada a una propiedad ( class ServiceCats { private meow = () => null } ).

Ajustándolo a nuestras necesidades.


Hasta ahora todo bien, pero nos han prometido "personalizable", ¿verdad? Entonces, ¿cómo podemos modificarlo?


class-logger proporciona tres capas de configuración jerárquica:


  • Global
  • Clase
  • Método

En cada llamada al método, los tres son evaluados y fusionados de arriba a abajo. Hay una configuración global predeterminada sensata, por lo que puede usar la biblioteca sin ninguna configuración.


Config global


Es la configuración de toda la aplicación. Se puede configurar con la llamada setConfig .


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

Config de clase


Tiene un efecto sobre todos los métodos de su clase. Podría anular la configuración global.


 import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // It overrides global config for this service log: console.debug, }) class ServiceCats {} 

Método config


Afecta solo el método en sí. Invalida la configuración de clase y, por lo tanto, la configuración global.


 import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // It overrides global config for this service log: console.debug, }) class ServiceCats { private energy = 100 @Log({ // It overrides class config for this method only log: console.warn, }) eat(food: string) { return 'purr' } // This method stil uses `console.debug` provided by class config sleep() { this.energy += 100 } } 

Demostración en vivo


Opciones de configuración


Bueno, hemos aprendido a alterar los valores predeterminados, pero no estaría de más cubrir lo que hay que configurar, ¿eh?


Aquí puede encontrar muchas invalidaciones de configuración útiles .

Aquí está el enlace a la interfaz del objeto de configuración en caso de que hable mejor TypeScript que inglés :)

El objeto de configuración tiene estas propiedades:


registro


Es una función que realiza el registro real del mensaje formateado final. Se utiliza para registrar estos eventos:


  • Antes de la construcción de clase.
  • Antes de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.
  • Después de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.

Predeterminado: console.log


logError


Es una función que realiza el registro real del mensaje de error formateado final. Se utiliza para registrar este evento único:


  • Errores de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.

Predeterminado: console.error


formateador


Es un objeto con dos métodos: start y end . Formatea datos de registro en la cadena final.


start formatea mensajes para estos eventos:


  • Antes de la construcción de clase.
  • Antes de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.

end formato de los mensajes para estos eventos:


  • Después de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.
  • Errores de métodos síncronos y asíncronos estáticos y no estáticos y propiedades funcionales.

Valor predeterminado: new ClassLoggerFormatterService()


incluir


La configuración de lo que debe incluirse en el mensaje.


args

Podría ser un booleano o un objeto.


Si es un booleano, establece si se incluye la lista de argumentos (recuerde que Args: [milk] ?) En ambos, inicio (antes de la construcción y antes de la llamada al método) y final (después de la llamada al método, llamada al método de error), mensajes.


Si es un objeto, debe tener dos propiedades booleanas: start y end . start incluye / excluye la lista de argumentos para los mensajes de inicio, end hace lo mismo para los mensajes de finalización.


Predeterminado: true


construir

Una configuración de bandera booleana para registrar la construcción de clases o no.


Predeterminado: true


resultado

Otra configuración de indicador booleano para incluir un valor de retorno de una llamada a método o un error arrojado por él. Recuerde Res: purr ? Si establece este indicador en false no habrá Res: purr .


Predeterminado: true


classInstance

Una vez más, ya sea un booleano o un objeto.
Si lo habilita, se agregará una representación en cadena de su instancia de clase a los registros. En otras palabras, si su instancia de clase tiene algunas propiedades, se convertirán en una cadena JSON y se agregarán al mensaje de registro.


No se agregarán todas las propiedades. class-logger sigue esta lógica:


  • Tomar propiedades propias (no prototipo) de una instancia.
    • Por qué Es un caso raro cuando su prototipo cambia dinámicamente, por lo tanto, no tiene sentido registrarlo.
  • Descarte cualquiera de ellos que tenga tipo de function .
    • Por qué La mayoría de las propiedades de las function tiempo son funciones de flecha inmutables utilizadas en lugar de métodos de clase regulares para preservar this contexto. No tiene mucho sentido hinchar los registros con cuerpos en cadena de esas funciones.
  • Suelta cualquiera de ellos que no sean objetos simples.
    • ¿Qué objetos son simples? ClassLoggerFormatterService considera un objeto un objeto simple si su prototipo es estrictamente igual a Object.prototype .
    • Por qué A menudo incluimos instancias de otras clases como propiedades (inyectarlas como dependencias). Nuestros registros se volverían extremadamente gordos si incluyéramos versiones en cadena de estas dependencias.
  • Stringify lo que queda.

 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 } } // Logs to the console before the class' construction: // 'Test.construct. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' const test = new Test() // Logs to the console before the method call: // 'Test.method2. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' test.method2() // Logs to the console after the method call: // 'Test.method2 -> done. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}. Res: 42.' 

Predeterminado: false


Tomando el control sobre el formateo


Entonces, ¿qué pasa si le gusta la idea general, pero le gustaría que sus mensajes se vean de manera diferente? Puede tomar el control total sobre el formato pasando su propio formateador personalizado.


Podrías escribir tu propio formateador desde cero. Totalmente Sin embargo, no cubriremos esta opción aquí (si está realmente interesado en eso, diríjase a la sección "Formateo" de README).


Lo más rápido y, probablemente, lo más fácil de hacer es subclasificar un formateador predeterminado ClassLoggerFormatterService : ClassLoggerFormatterService .


ClassLoggerFormatterService tiene estos métodos protegidos, que sirven como bloques de construcción del mensaje final:


  • base
    • Devuelve el nombre de la clase con el nombre del método. Ejemplo: ServiceCats.eat .
  • operation
    • Devuelve -> done o -> error basado en si fue una ejecución exitosa de un método o un error.
  • args
    • Devuelve una lista de argumentos en cadena. Ejemplo: . Args: [milk] . Utiliza fast-safe-stringify para objetos debajo del capó.
  • classInstance
    • Devuelve una instancia de clase en cadena. Ejemplo: . Class instance: {"prop1":42,"prop2":{"test":42}} . Si elige incluir una instancia de clase, pero no está disponible (así es para los métodos estáticos y la construcción de clases), devuelve N/A
  • result
    • Devuelve un resultado en cadena de la ejecución (incluso si fue un error). Utiliza fast-safe-stringify para serializar objetos. Un error en cadena estará compuesto por las siguientes propiedades:
    • Nombre de la clase (función) con la que se creó el error ( error.constructor.name ).
    • Código de error ( error.code ).
    • Mensaje de error (mensaje de error).
    • Nombre de error ( error.name ).
    • error.stack pila ( error.stack ).
  • final
    • Devoluciones . Solo .

El mensaje de start consta de:


  • base
  • args
  • classInstance
  • final

El mensaje end consiste en:


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

Puede anular cualquiera de esos métodos de bloques de construcción. Echemos un vistazo a cómo podríamos agregar una marca de tiempo. No estoy diciendo que debamos. pino , winston y muchos otros registradores son capaces de agregar marcas de tiempo por sí mismos. Entonces, el ejemplo es puramente educativo.


 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(), }) 

Demostración en vivo


Conclusión


No olvide seguir los pasos de instalación y familiarizarse con los requisitos antes de decidir utilizar esta biblioteca.


Con suerte, has encontrado algo útil para tu proyecto. ¡No dudes en comunicarme tus comentarios! Aprecio mucho cualquier crítica y pregunta.

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


All Articles