Log Javascript super simples - dois decoradores e pronto


Você ainda está cansado de escrever logger.info('ServiceName.methodName.') E logger.info('ServiceName.methodName -> done.') Para cada espirro? Talvez você, como eu, tenha pensado repetidamente em automatizar esse negócio? Neste artigo, falaremos sobre o class-logger como uma das soluções para o problema com apenas dois decoradores.


Por que isso é necessário?


Nem todos os perfeccionistas são engenheiros, mas todos os engenheiros são perfeccionistas (bem, quase). Gostamos de belas abstrações lacônicas. Vemos a beleza no conjunto de crocodilos, que uma pessoa está despreparada e não consegue ler. Gostamos de nos sentir como deuses, criando micro universos que seguem nossas regras. Presumivelmente, todas essas qualidades vêm de nossa imensa preguiça. Não, o engenheiro não tem medo de trabalhar em classe, mas odeia ferozmente as ações repetitivas de rotina que seus braços alcançam para automação.


Depois de escrever vários milhares de linhas de código destinadas apenas ao log, geralmente chegamos à criação de alguns de nossos próprios padrões e padrões para escrever mensagens. Infelizmente, ainda precisamos aplicar esses padrões manualmente. O principal "porquê" do registrador de classes é fornecer uma maneira declarativa e configurável de registrar facilmente as mensagens de modelo ao criar uma classe e chamar seus métodos.


Damas nua!


Sem vagar, vamos direto ao código.


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

Este serviço criará três entradas de log:


  • Antes de criá-lo com uma lista de argumentos transmitidos ao construtor.
  • Antes de ligar, eat com uma lista de argumentos.
  • Depois de ligar, eat com uma lista de argumentos e o resultado da execução.

Em palavras 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.` 

Pique ao vivo .


Lista completa de eventos que podem ser prometidos:


  • Antes de criar uma classe.
  • Antes de chamar métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.
  • Depois de chamar métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.
  • Erros na chamada de métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.

Uma propriedade funcional é uma função de seta atribuída a uma propriedade ( class ServiceCats { private meow = () => null } ). Geralmente usado para manter o contexto de execução ( this ).

Nos foi prometida uma maneira "configurável" de registrar


registrador de classe três níveis de hierarquia de configuração:


  • Global
  • Class
  • Método

Ao chamar cada método, todos os três níveis se fundem. A biblioteca fornece uma configuração padrão global razoável, para que possa ser usada sem nenhuma configuração anterior.


Configuração global


É criado para todo o aplicativo. Pode ser substituído com setConfig .


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

Configuração da classe


É exclusivo para cada classe e é aplicado a todos os métodos dessa classe. Pode substituir a configuração global.


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

Configuração do método


Funciona apenas para o próprio método. Ela tem precedência sobre a configuração de classe e a configuração 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 } } 

Picar ao vivo


O que pode ser configurado?


Examinamos como alterar a configuração, mas ainda não descobrimos o que exatamente pode ser alterado nela.


Aqui você pode encontrar muitos exemplos de objetos de configuração para diferentes casos .

Link para a interface se você entender melhor o TypeScript. do que russo :)

O objeto de configuração possui as seguintes propriedades:


registro


Esta é uma função que realmente lida com o log. Ela recebe uma mensagem formatada como uma sequência na entrada. Usado para registrar os seguintes eventos:


  • Antes de criar uma classe.
  • Antes de chamar métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.
  • Depois de chamar métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.

Por padrão, console.log .


logError


Essa é uma função que também lida com o log, mas apenas com mensagens de erro. Ela também recebe uma mensagem formatada como uma sequência. Usado para registrar os seguintes eventos:


  • Erros na chamada de métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.

O padrão é console.error .


formatador


Este é um objeto com dois métodos: start e end . Esses métodos criam a mesma mensagem formatada.


start cria mensagens para os seguintes eventos:


  • Antes de criar uma classe.
  • Antes de chamar métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.

end gera mensagens para os seguintes eventos:


  • Depois de chamar métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.
  • Erros na chamada de métodos síncronos e assíncronos e propriedades funcionais, estáticas e não estáticas.

Por padrão, o new ClassLoggerFormatterService() .


incluir


A configuração do que deve ser incluído na mensagem final.


args

Pode ser um valor booleano ou um objeto.


Se for boolean , ele define se deve incluir uma lista de argumentos (lembre-se de Args: [milk] ?) Em todas as mensagens ( start e end ).


Se for um objeto, ele deve ter duas propriedades booleanas: start e end . start especifica se deseja incluir uma lista de argumentos para mensagens de start e end para mensagens de end .


O padrão é true .


construir

Este é um boolean que controla se a criação de classe deve ser registrada ou não.


O padrão é true .


resultado

Este é um boolean que controla o log do valor de retorno do método. Vale ressaltar que o valor de retorno não é apenas o que o método retorna na execução bem-sucedida, mas também o erro que ele lança em caso de execução malsucedida. Lembre-se de Res: purr ? Se esse sinalizador for false , não haverá Res: purr .


O padrão é true .


classInstance

Pode ser um valor booleano ou um objeto. O conceito é o mesmo que include.args .


Adiciona uma exibição de instância serializada da sua classe às postagens. Em outras palavras, se sua classe tiver alguma propriedade, elas serão serializadas em JSON e adicionadas aos logs.


Nem todas as propriedades são serializáveis. O class-logger usa a seguinte lógica:


  • Tomamos todas as nossas próprias propriedades (aquelas que não estão no protótipo) da intansa.
    • Porque Muito raramente, um protótipo muda dinamicamente, por isso faz pouco sentido registrar seu conteúdo.
  • Nós jogamos fora todos eles da function type.
    • Porque Mais frequentemente, as propriedades da function type são simplesmente funções de seta que não são criadas por métodos convencionais para manter o contexto ( this ). Eles raramente têm uma natureza dinâmica, portanto não faz sentido registrá-los.
  • Jogamos fora deles todos os objetos que não são objetos simples.
    • Quais são esses objetos simples? ClassLoggerFormatterService considera um objeto simples se seu protótipo for Object.prototype .
    • Porque Muitas vezes, instâncias de outras classes de serviço agem como propriedades. Nossos registros inchariam da maneira mais feia se começássemos a serializar.
  • Serialize na string JSON tudo o que resta.

 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.' 

O padrão é false .


Assuma o controle total sobre a formatação da mensagem


E se você gosta da ideia, mas a sua idéia do belo insiste em um formato de linha de mensagem diferente? Você pode assumir o controle completo da formatação de mensagens passando seu próprio formatador.


Você pode escrever seu próprio formatador a partir do zero. Claro. Mas essa opção não será abordada nesta estrutura (se você estiver interessado nessa opção específica, consulte a seção "Formatação" no README).


A maneira mais rápida e fácil de substituir a formatação é herdar o formatador do formatador padrão - ClassLoggerFormatterService .


ClassLoggerFormatterService possui os seguintes métodos protected que criam pequenos blocos de mensagens finais:


  • base
    • Retorna o nome da classe e o nome do método. Exemplo: ServiceCats.eat .
  • operation
    • Retorna -> done ou -> error dependendo se o método foi concluído com êxito.
  • args
    • Retorna uma lista de argumentos serializada. Exemplo :. . Args: [milk] . Ele usa o fast-safe-stringify para serializar objetos sob o capô.
  • classInstance
    • Retorna a instância serializada da classe. Exemplo :. . Class instance: {"prop1":42,"prop2":{"test":42}} . Se você incluiu include.classInstance na configuração, mas por algum motivo a própria instância não está disponível no momento do log (por exemplo, para métodos estáticos ou antes de criar uma classe), retorna N/A
  • result
    • Retorna um resultado de execução serializada ou erro serializado. Utiliza strings de segurança rápida para objetos sob o capô. Um erro serializado inclui as seguintes propriedades:
    • O nome da classe (função) usada para criar o erro ( error.constructor.name ).
    • Código ( error.code ).
    • Mensagem (mensagem de error.message ).
    • Nome (nome do error.name ).
    • Rastreio de pilha ( error.stack ).
  • final
    • Retorna . . Simples . .

A mensagem start consiste em:


  • base
  • args
  • classInstance
  • final

A mensagem end consiste em:


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

Você pode substituir apenas os métodos básicos necessários.


Vejamos como você pode adicionar registro de data e hora a todas as postagens.


Não estou dizendo que isso deva ser feito em projetos reais. pino , winston e a maioria dos outros registradores podem fazer isso sozinhos. Este exemplo é apenas para fins educacionais.

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

Picar ao vivo


Conclusão


Lembre-se de seguir as instruções de instalação e familiarize-se com os requisitos antes de usar esta biblioteca em seu projeto.


Espero que você não tenha perdido tempo em vão, e o artigo tenha sido pelo menos um pouco útil para você. Por favor, chute e critique. Vamos aprender a codificar melhor juntos.

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


All Articles