Vamos falar sobre log

Esta postagem foi inspirada no tópico do Go Forum lançado por Nate Finch. Este post está focado no Go, mas se você passar por isso, acho que as idéias apresentadas aqui são amplamente aplicáveis.

Por que não há amor?


O pacote de log no Go não possui níveis para logs; você pode adicionar manualmente os prefixos DEBUG, INFO, WARN e ERROR. Além disso, o tipo de criador de logs no Go não tem a capacidade de ativar ou desativar esses níveis separadamente para pacotes selecionados. Para comparação, vejamos algumas substituições de desenvolvedores de terceiros.

imagem

O Google Glog possui níveis:

  • Info
  • Advertência
  • Erro
  • Fatal (finaliza o programa)

Vamos dar uma olhada em outra biblioteca, loggo , desenvolvida para Juju, os níveis estão disponíveis nela:

  • Rastrear
  • Depurar
  • Informações
  • Advertência
  • Erro
  • Crítico

O Loggo também tem a capacidade de definir o nível de detalhe do log para os pacotes desejados individualmente.

Aqui estão dois exemplos, claramente criados sob a influência de outras bibliotecas para efetuar logon em outros idiomas.

De fato, sua origem pode ser rastreada até o syslog (3), possivelmente até mais cedo. E eu acho que eles estão errados.

Eu quero tomar uma posição controversa. Eu acho que todas as bibliotecas de log são ruins porque oferecem muitos recursos; um conjunto impressionante de recursos que impressiona o programador no momento em que ele precisa pensar claramente sobre como se comunicar com o leitor no futuro, com quem visualizará essas revistas.

Argumento que pacotes de log bem-sucedidos exigem muito menos recursos e, é claro, menos níveis.

Vamos falar sobre avisos (AVISO)


Vamos começar com o mais simples. Ninguém precisa do nível de log WARNING (aviso).

Ninguém lê avisos, porque, por definição, nada de ruim aconteceu. Talvez algo possa dar errado no futuro, mas parece outra pessoa, não o meu problema.

Além disso, se você usa algum tipo de log multinível, por que precisa definir o nível WARNING? Você definiria o nível para INFO ou ERRO. Definir o nível WARNING significa que você provavelmente está relatando erros no nível WARNING.

Excluir o nível de aviso - esta é uma mensagem informativa ou um erro.

Vamos falar sobre o nível de erro fatal


O nível FATAL realmente registra a mensagem e chama os.Exit (1). Em princípio, isso significa:

  • expressões adiadas em outras rotinas (goroutines) não são executadas;
  • buffers não são liberados;
  • arquivos e diretórios temporários não são excluídos.

Em essência, log.Fatal é menos detalhado, mas semanticamente equivalente ao pânico.

É geralmente aceito que as bibliotecas não devem usar panic1, mas se a chamada log.Fatal2 tiver o mesmo efeito, ela também deverá ser desativada.

A suposição de que esse problema de limpeza possa ser resolvido registrando manipuladores de desligamento no criador de logs fornece um relacionamento próximo entre o criador de logs usado e cada local onde ocorrem operações de limpeza. Também viola a separação de interesses.

Não grave mensagens com um nível FATAL, prefira retornar um erro ao chamador. Se o erro atingir main.main, este é o lugar certo para executar qualquer limpeza antes de fechar o programa.

Vamos falar sobre o erro (nível de erro)


A manipulação e o log de erros estão intimamente relacionados; portanto, à primeira vista, o log de nível de erro (ERROR) deve ser facilmente justificado. Eu não concordo

No Go, se a chamada de uma função ou método retornar um valor de erro, você realmente terá duas opções:

  • lidar com o erro.
  • retorne um erro ao seu chamador. Você pode maravilhosamente quebrar um erro no embrulho (embrulho), mas isso não é importante para esta discussão.

Se você decidir processar o erro gravando-o no log, por definição, não será mais um erro - você o processou. O ato de registrar o erro em si é o processamento do erro; portanto, não é mais aconselhável gravá-lo no log como um erro.

Deixe-me convencê-lo com este pedaço de código:

err := somethingHard() if err != nil { log.Error("oops, something was too hard", err) return err // what is this, Java ? } 

Você nunca deve registrar nada no nível do erro, pois deve lidar com o erro ou transmiti-lo ao chamador.

Você precisa entender claramente, não estou dizendo que não deve gravar no log que ocorreu uma alteração de estado:

 if err := planA(); err != nil { log.Infof("could't open the foo file, continuing with plan b: %v", err) planB() } 

Mas, na realidade, log.Info e log.Error têm o mesmo objetivo.

Eu não digo "não registre erros"! Em vez disso, faço a pergunta: qual é a menor API possível para o log? E quando se trata de erros, acredito que a grande maioria das coisas registradas no nível de ERRO são feitas simplesmente porque estão relacionadas ao erro. Na verdade, eles são apenas informativos, para que possamos remover o registro em nível de erro (ERROR) da nossa API.

O que resta?


Excluímos WARNING, argumentamos que nada deveria ser registrado no nível de erro (ERRO) e mostramos que apenas o aplicativo de nível superior deveria ter algum tipo de comportamento log.Fatal. O que resta?

Eu acredito que há apenas duas coisas que você deve fazer login:

  • coisas com que os desenvolvedores se preocupam quando desenvolvem ou depuram um programa;
  • coisas que os usuários se preocupam ao usar seu programa.

Obviamente, esses são níveis de depuração (DEBUG) e informativo (INFO), respectivamente.

log.Info deve apenas escrever esta linha na saída do log. Não deve ser possível desativá-lo, pois o usuário deve apenas dizer o que é útil para ele. Se ocorrer um erro que não possa ser processado, ele deverá aparecer em main.main onde o programa é encerrado. O pequeno inconveniente de ter que inserir o prefixo FATAL antes da mensagem de log final ou gravar diretamente no os.Stderr usando fmt.Fprintf não é motivo suficiente para expandir o pacote com o método log.Fatal.

log.Debug, este é um assunto completamente diferente. Um desenvolvedor ou engenheiro de suporte precisa dele para controlar o programa. Durante o desenvolvimento, as expressões de depuração devem ser numerosas sem recorrer ao nível de rastreamento ou debug2 (você sabe quem é). O pacote de log deve suportar controle granular para habilitar ou desabilitar expressões de depuração para os pacotes desejados no pacote, ou possivelmente até em um escopo mais restrito.

Conclusão


Se fosse uma enquete no Twitter, eu pediria para você escolher entre

  • registro é importante
  • registro é difícil

Mas o fato é que o log é ambos. A solução para esse problema DEVE ser destruir e reduzir impiedosamente as distrações desnecessárias.

O que você acha? É extravagante o suficiente para trabalhar, ou é apenas extravagante?

Anotações


Algumas bibliotecas podem usar o pânico / recuperação como um mecanismo de fluxo de controle interno, mas o principal mantra é que elas não devem permitir que essas operações de fluxo de controle vazem para fora do limite do pacote.

Ironicamente, embora não tenha um nível de saída DEBUG, o pacote de registro Go padrão possui recursos Fatal e Panic. Neste pacote, o número de funções que levam ao encerramento repentino do programa excede o número daquelas que não o fazem.

Sobre o autor


O autor deste artigo, Dave Cheney , é o autor de muitos pacotes populares do Go, como github.com/pkg/errors e github.com/davecheney/httpstat . Você pode avaliar a autoridade e a experiência do autor.

Do tradutor


Muitos desenvolvedores estão preocupados com o log, alguns discutiram a adição de uma interface Logger à biblioteca Go padrão para otimizar o log em bibliotecas e estimular seus desenvolvedores. Os caras até elaboraram sua proposta e colocaram o papel em discussão.

Reflexão positiva da apresentação Precisamos de um novo logger e o que deveria ser? de Chris Hines.

Existem várias implementações das idéias de go-log de Dave e um pouco fora de tópico sobre a questão do nível de ERRO e um pacote de logr mais elaborado.

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


All Articles