
¿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 !
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:
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
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
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
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.