
Quantas vezes você logger.info('ServiceName.methodName.')
E logger.info('ServiceName.methodName -> done.')
Para cada método do seu serviço que você deseja registrar? Deseja que ele seja automatizado e tenha a mesma assinatura constante em todo o aplicativo? Se é assim, somos muito parecidos, sofremos a mesma dor muitas vezes e agora podemos finalmente tentar resolvê-la. Juntos. Senhoras e senhores, deixe-me apresentar ... registrador de classe !
Engenheiros geralmente são perfeccionistas. Perfeccionistas ao extremo. Nós gostamos de abstrações legais. Nós gostamos de código limpo. Vemos beleza em linguagens artificiais que outras pessoas nem conseguem ler. Gostamos de fabricar pequenos universos digitais, vivendo de acordo com as regras que estabelecemos. Provavelmente gostamos de tudo isso, porque somos muito preguiçosos. Não, não temos medo do trabalho, mas odiamos fazer qualquer trabalho que possa ser automatizado.
Depois de escrever algumas milhares de linhas de código de registro, geralmente criamos certos padrões, padronizando o que queremos registrar. Ainda temos que aplicar esses padrões manualmente. Portanto, a idéia central do criador de classes é fornecer uma maneira padronizada declarativa e altamente configurável de registrar mensagens antes e depois da execução de um método de classe.
Início rápido
Vamos começar a correr e ver como é o código real.
import { LogClass, Log } from 'class-logger' @LogClass() class ServiceCats { @Log() eat(food: string) { return 'purr' } }
Este serviço registrará três vezes:
- Na sua criação, com uma lista de argumentos transmitidos ao construtor.
- Antes de
eat
é executado com uma lista de seus argumentos. - Depois de
eat
é executado com uma lista de seus argumentos e seu resultado.
Em palavras 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.`
Demonstração ao vivo .
O que mais poderíamos registrar? Aqui está a lista completa de eventos:
- Antes da construção da aula.
- Antes de métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
- Após métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
- Erros de métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
Propriedade funcional é uma função de seta atribuída a uma propriedade ( class ServiceCats { private meow = () => null }
).
Ajustando-o às nossas necessidades
Até aí tudo bem, mas nos foi prometido "personalizável", certo? Então, como podemos ajustá-lo?
O class-logger fornece três camadas de configuração hierárquica:
A cada chamada de método, todos os três são avaliados e mesclados de cima para baixo. Há alguma configuração global padrão sã, para que você possa usar a biblioteca sem nenhuma configuração.
Configuração global
É a configuração em todo o aplicativo. Pode ser definido com a chamada setConfig
.
import { setConfig } from 'class-logger' setConfig({ log: console.info, })
Configuração da classe
Isso afeta todos os métodos da sua classe. Pode substituir a configuração global.
import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // It overrides global config for this service log: console.debug, }) class ServiceCats {}
Configuração do método
Afeta apenas o próprio método. Substitui a configuração da classe e, portanto, a configuração 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 } }
Demonstração ao vivo
Opções de configuração
Bem, aprendemos como alterar os padrões, mas não faria mal cobrir o que há para configurar, não é?
Aqui você pode encontrar muitas substituições úteis de configuração .
Aqui está o link para a interface do objeto de configuração, caso você fale TypeScript melhor que o inglês :)
O objeto de configuração possui estas propriedades:
registro
É uma função que efetua o log real da mensagem final formatada. É usado para registrar estes eventos:
- Antes da construção da aula.
- Antes de métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
- Após métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
Padrão: console.log
logError
É uma função que faz o log real da mensagem de erro final formatada. É usado para registrar este e único evento:
- Erros de métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
Padrão: console.error
É um objeto com dois métodos: start
e end
. Ele formata os dados de log na sequência final.
start
formata mensagens para esses eventos:
- Antes da construção da aula.
- Antes de métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
end
formata mensagens para esses eventos:
- Após métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
- Erros de métodos estáticos e não estáticos síncronos e assíncronos e propriedades funcionais.
Padrão: new ClassLoggerFormatterService()
incluir
A configuração do que deve ser incluído na mensagem.
args
Pode ser um objeto booleano ou.
Se for booleano, ele define se deve incluir a lista de argumentos (lembre-se de que Args: [milk]
?) Em ambos, mensagens de início (antes da construção e antes da chamada do método) e final (após a chamada do método, chamada do método de erro).
Se for um objeto, ele deve ter duas propriedades booleanas: start
e end
. start
inclui / exclui a lista de argumentos para mensagens de início; end
faz o mesmo para mensagens de fim.
Padrão: true
construir
Um sinalizador booleano que define se a construção de classe deve ser registrada ou não.
Padrão: true
resultado
Outra configuração de sinalizador booleano deve incluir um valor de retorno de uma chamada de método ou um erro gerado por ela. Lembre-se de Res: purr
? Se você definir esse sinalizador como false
não haverá Res: purr
.
Padrão: true
classInstance
Mais uma vez, um booleano ou um objeto.
Se você habilitá-lo, uma representação estrita da sua instância de classe será adicionada aos logs. Em outras palavras, se sua instância de classe tiver algumas propriedades, elas serão convertidas em uma sequência JSON e adicionadas à mensagem de log.
Nem todas as propriedades serão adicionadas. O registrador de classes segue esta lógica:
- Pegue propriedades próprias (sem protótipo) de uma instância.
- Porque É um caso raro quando o seu protótipo é alterado dinamicamente, portanto, dificilmente faz sentido registrá-lo.
- Solte qualquer um deles que tenha o tipo de
function
.
- Porque Na maioria das vezes
function
propriedades das function
são apenas funções de seta imutáveis usadas em vez de métodos de classe regulares para preservar this
contexto. Não faz muito sentido inchar seus logs com corpos estritos dessas funções.
- Solte qualquer um deles que não seja um objeto simples.
- Quais objetos são simples?
ClassLoggerFormatterService
considera um objeto um objeto simples se seu protótipo for estritamente igual a Object.prototype
. - Porque Frequentemente, incluímos instâncias de outras classes como propriedades (as injetamos como dependências). Nossos logs se tornariam extremamente gordos se incluíssemos versões estritas dessas dependências.
- Stringify 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 } } // 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.'
Padrão: false
E se você gostar da ideia geral, mas quiser que suas mensagens tenham uma aparência diferente? Você pode assumir o controle completo da formatação passando seu próprio formatador personalizado.
Você pode escrever seu próprio formatador a partir do zero. Totalmente. No entanto, não abordaremos esta opção aqui (se você estiver realmente interessado nisso, dirija-se à seção "Formatação" do README).
A coisa mais rápida e, provavelmente, mais fácil de fazer é subclassificar um formatador padrão ClassLoggerFormatterService
- ClassLoggerFormatterService
.
ClassLoggerFormatterService
possui estes métodos protegidos, servindo como blocos de construção da mensagem final:
base
- Retorna o nome da classe com o nome do método. Exemplo:
ServiceCats.eat
.
operation
- Retorna
-> done
ou -> error
base em se foi uma execução bem-sucedida de um método ou um erro.
args
- Retorna uma lista estrita de argumentos. Exemplo :.
. Args: [milk]
. Ele usa o fast-safe-stringify para objetos sob o capô.
classInstance
- Retorna uma instância de classe com string. Exemplo :.
. Class instance: {"prop1":42,"prop2":{"test":42}}
. Se você optar por incluir a instância da classe, mas ela não estiver disponível (é assim para métodos estáticos e construção de classes), ela retornará N/A
result
- Retorna um resultado estrito da execução (mesmo que tenha sido um erro). Usa fast-safe-stringify para serializar objetos. Um erro estrito será composto das seguintes propriedades:
- Nome da classe (função) com a qual o erro foi criado (
error.constructor.name
). - Código do erro (
error.code
). - Mensagem de erro (mensagem de erro).
- Nome do erro (
error.name
). - Rastreio de pilha (
error.stack
).
final
A mensagem start
consiste em:
base
args
classInstance
final
A mensagem end
consiste em:
base
operation
args
classInstance
result
final
Você pode substituir qualquer um desses métodos de blocos de construção. Vamos dar uma olhada em como podemos adicionar um carimbo de data / hora. Não estou dizendo que deveríamos. pino , winston e muitos outros registradores são capazes de adicionar registros de data e hora por conta própria. Portanto, o exemplo é 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(), })
Demonstração ao vivo
Conclusão
Por favor, não esqueça de seguir as etapas de instalação e se familiarizar com os requisitos antes de decidir usar esta biblioteca.
Felizmente, você encontrou algo útil para o seu projeto. Sinta-se livre para me comunicar seus comentários! Certamente aprecio qualquer crítica e pergunta.