
¿Todavía estás cansado de escribir logger.info('ServiceName.methodName.')
Y logger.info('ServiceName.methodName -> done.')
Para cada estornudo? ¿Tal vez usted, como yo, ha pensado repetidamente en automatizar este negocio? En este artículo, hablaremos sobre class-logger como una de las soluciones al problema con solo dos decoradores.
¿Por qué es necesario?
No todos los perfeccionistas son ingenieros, pero todos los ingenieros son perfeccionistas (bueno, casi). Nos gustan las hermosas abstracciones lacónicas. Vemos la belleza en el conjunto de cocodrilos, que una persona no está preparada y no puede leer. Nos gusta sentirnos como dioses, creando micro universos que viven según nuestras reglas. Presumiblemente, todas estas cualidades se originan en nuestra inmensa pereza. No, el ingeniero no tiene miedo de trabajar en clase, pero odia ferozmente las acciones repetitivas de rutina que sus brazos intentan automatizar.
Habiendo escrito varios miles de líneas de código destinadas únicamente al registro, generalmente llegamos a la creación de algunos de nuestros propios patrones y estándares para escribir mensajes. Desafortunadamente, todavía tenemos que aplicar estos patrones manualmente. El principal "por qué" de class-logger es proporcionar una forma declarativa y configurable para registrar fácilmente mensajes de plantilla al crear una clase y llamar a sus métodos.
Damas desnudas!
Sin deambular, vayamos directamente al código.
import { LogClass, Log } from 'class-logger' @LogClass() class ServiceCats { @Log() eat(food: string) { return 'purr' } }
Este servicio creará tres entradas de registro:
- Antes de crearlo con una lista de argumentos pasados al constructor.
- Antes de llamar a
eat
con una lista de argumentos. - Después de llamar a
eat
con una lista de argumentos y el resultado de la ejecución.
En palabras de código:
// ServiceCats // `ServiceCats.construct. Args: [].` const serviceCats = new ServiceCats() // `eat` // `ServiceCats.eat. Args: [milk].` serviceCats.eat('milk') // `eat` // `ServiceCats.eat -> done. Args: [milk]. Res: purr.`
Empújalo en vivo .
Lista completa de eventos que se pueden comprometer:
- Antes de crear una clase.
- Antes de llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
- Después de llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
- Errores al llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
Una propiedad funcional es una función de flecha asignada a una propiedad ( class ServiceCats { private meow = () => null }
). La mayoría de las veces se usa para mantener el contexto de ejecución ( this
).
Nos prometieron una forma "configurable" de iniciar sesión
class-logger tres niveles de jerarquía de configuración:
Al llamar a cada método, los tres niveles se fusionan. La biblioteca proporciona una configuración predeterminada global razonable, por lo que puede usarse sin ninguna configuración previa.
Config global
Se crea para toda la aplicación. Se puede anular con setConfig
.
import { setConfig } from 'class-logger' setConfig({ log: console.info, })
Config de clase
Es único para cada clase y se aplica a todos los métodos de esta clase. Puede anular la configuración global.
import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // log: console.debug, }) class ServiceCats {}
Método config
Solo funciona para el método en sí. Tiene prioridad sobre la configuración de clase y la configuración global.
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
¿Qué se puede configurar?
Examinamos cómo cambiar la configuración, pero aún no hemos descubierto qué se puede cambiar exactamente en ella.
Aquí puede encontrar muchos ejemplos de objetos de configuración para diferentes casos .
Enlace a la interfaz si comprende mejor TypeScript. que el ruso :)
El objeto de configuración tiene las siguientes propiedades:
registro
Esta es una función que realmente trata con el registro. Ella recibe un mensaje formateado como una cadena en la entrada. Se utiliza para registrar los siguientes eventos:
- Antes de crear una clase.
- Antes de llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
- Después de llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
Por defecto, console.log
.
logError
Esta es una función que también se ocupa del registro, pero solo de mensajes de error. Ella también recibe un mensaje formateado como una cadena. Se utiliza para registrar los siguientes eventos:
- Errores al llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
El valor predeterminado es console.error
.
Este es un objeto con dos métodos: start
y end
. Estos métodos crean el mismo mensaje formateado.
start
crea mensajes para los siguientes eventos:
- Antes de crear una clase.
- Antes de llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
end
genera mensajes para los siguientes eventos:
- Después de llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
- Errores al llamar a métodos síncronos y asíncronos y propiedades funcionales, tanto estáticas como no estáticas.
Por defecto, new ClassLoggerFormatterService()
.
incluir
La configuración de lo que debe incluirse en el mensaje final.
args
Puede ser un valor booleano o un objeto.
Si es boolean
, establece si se debe incluir una lista de argumentos (¿recuerda Args: [milk]
?) En todos los mensajes ( start
y end
).
Si es un objeto, debe tener dos propiedades booleanas: start
y end
. start
especifica si se debe incluir una lista de argumentos para los mensajes de start
, end
para los mensajes end
.
El valor predeterminado es true
.
construir
Este es un boolean
que controla si registrar la creación de clases o no.
El valor predeterminado es true
.
resultado
Este es un boolean
que controla el registro del valor de retorno del método. Vale la pena señalar que el valor de retorno no es solo lo que el método devuelve en la ejecución exitosa, sino también el error que arroja en caso de ejecución fallida. Recuerde Res: purr
? Si esta bandera es false
, entonces no habrá Res: purr
.
El valor predeterminado es true
.
classInstance
Puede ser un valor booleano o un objeto. El concepto es el mismo que include.args
.
Agrega una vista de instancia serializada de su clase a las publicaciones. En otras palabras, si su clase tiene alguna propiedad, se serializará en JSON y se agregará a los registros.
No todas las propiedades son serializables. class-logger utiliza la siguiente lógica:
- Tomamos todas nuestras propiedades (las que no están en el prototipo) de intansa.
- Por qué Muy raramente, un prototipo cambia dinámicamente, por lo que tiene poco sentido registrar su contenido.
- Echamos fuera de todos ellos la
function
de tipo.
- Por qué La mayoría de las veces, las propiedades de la
function
de tipo son simplemente funciones de flecha que no se realizan mediante métodos convencionales para mantener el contexto ( this
). Raramente tienen una naturaleza dinámica, por lo que no tiene sentido registrarlos.
- Tiramos de ellos todos los objetos que no son simples.
- ¿Qué son estos objetos simples?
ClassLoggerFormatterService
considera un objeto simple si su prototipo es Object.prototype
. - Por qué A menudo, las instancias de otras clases de servicio actúan como propiedades. Nuestros registros se hincharían de la manera más fea si comenzáramos a serializar.
- Serializar en cadena JSON todo 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 } } // `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.'
El valor predeterminado es false
.
¿Qué pasa si te gusta la idea, pero tu idea de lo bello insiste en un formato de línea de mensaje diferente? Puede tomar el control total sobre el formato del mensaje pasando su propio formateador.
Puedes escribir tu propio formateador desde cero. Por supuesto Pero esta opción no se cubrirá dentro de este marco (si está interesado en esta opción en particular, eche un vistazo a la sección "Formateo" en README).
La forma más rápida y fácil de anular el formato es heredar su formateador del formateador predeterminado: ClassLoggerFormatterService
.
ClassLoggerFormatterService
tiene los siguientes métodos protected
que crean pequeños bloques de mensajes finales:
base
- Devuelve el nombre de la clase y el nombre del método. Ejemplo:
ServiceCats.eat
.
operation
- Devuelve
-> done
o -> error
dependiendo de si el método se completó correctamente.
args
- Devuelve una lista de argumentos serializados. Ejemplo:
. Args: [milk]
. Utiliza fast-safe-stringify para serializar objetos debajo del capó.
classInstance
- Devuelve la instancia serializada de la clase. Ejemplo:
. Class instance: {"prop1":42,"prop2":{"test":42}}
. Si incluyó include.classInstance
en la configuración, pero por alguna razón la instancia en sí no está disponible en el momento del registro (por ejemplo, para métodos estáticos o antes de crear una clase), devuelve N/A
result
- Devuelve un resultado de ejecución serializado o un error serializado. Utiliza fast-safe-stringify para objetos debajo del capó. Un error serializado incluye las siguientes propiedades:
- El nombre de la clase (función) que se utilizó para crear el error (
error.constructor.name
). - Código (código de
error.code
). - Mensaje (mensaje de
error.message
). - Nombre (
error.name
). error.stack
pila ( error.stack
).
final
El mensaje de start
consta de:
base
args
classInstance
final
El mensaje end
consiste en:
base
operation
args
classInstance
result
final
Puede anular solo los métodos básicos que necesita.
Veamos cómo puede agregar una marca de tiempo a todas las publicaciones.
No digo que esto deba hacerse en proyectos reales. pino , winston y la mayoría de los otros madereros pueden hacer esto solos. Este ejemplo es solo para fines educativos.
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
Conclusión
Recuerde seguir las instrucciones de instalación y familiarizarse con los requisitos antes de usar esta biblioteca en su proyecto.
Espero que no hayas perdido el tiempo en vano, y el artículo fue al menos un poco útil para ti. Por favor patea y critica. Aprenderemos a codificar mejor juntos.