Arquitetura em várias camadas: onde devo implementar o log de erros \ manipulação?

17

No momento, estou refatorando um grande subsistema com uma arquitetura de várias camadas e estou lutando para criar uma estratégia eficaz de registro de erros \ manipulação.

Digamos que minha arquitetura consiste nas três camadas a seguir:

  • Interface pública (IE um controlador MVC)
  • Camada de domínio
  • Camada de acesso a dados

Minha fonte de confusão é onde eu devo implementar o log de erros \ manipulação:

  1. A solução mais fácil seria implementar o log no nível superior (ou seja, Public Interface \ MVC Controller). No entanto, isso parece errado, porque significa modificar a exceção através das diferentes camadas e depois registrá-la; em vez de registrar a exceção na origem.

  2. Registrar a exceção na fonte é obviamente a melhor solução, porque eu tenho mais informações. Meu problema é que não consigo capturar todas as exceções na origem sem capturar TODAS as exceções, e na camada de domínio / interface pública, isso levará à captura de exceções que já foram capturadas, registradas e reproduzidas pela camada abaixo .

  3. Outra estratégia possível é uma mistura de # 1 e # 2; onde pego exceções específicas na camada com maior probabilidade de serem lançadas (captura do IE, registro e re-lançamento SqlExceptionsna camada de acesso a dados) e, em seguida, registro quaisquer outras exceções não capturadas no nível superior. No entanto, isso também exigiria que eu capturasse e registrasse novamente todas as exceções no nível superior, porque não consigo distinguir entre os erros que já foram registrados \ manipulados e os que não o fizeram.

Agora, obviamente, esse é um problema na maioria dos aplicativos de software; portanto, deve haver uma solução padrão para esse problema que resulte em exceções capturadas na origem e registradas uma vez; no entanto, eu simplesmente não consigo ver como fazer isso sozinho.

Observe que o título desta pergunta é muito semelhante a ' Exceções de log em um aplicativo de várias camadas " ', no entanto, as respostas nessa postagem estão sem detalhes e não são suficientes para responder à minha pergunta.

KidCode
fonte
1
Uma abordagem que às vezes uso é criar minha própria subclasse Exception (ou RuntimeException) e lançá-la, incluindo a exceção original como aninhada. Obviamente, isso significa que, ao capturar exceções em níveis superiores, eu preciso verificar o tipo da exceção (minhas próprias exceções são relançadas, outras exceções são registradas e incluídas em novas instâncias). Mas eu trabalho solo há muito tempo, então não posso dar conselhos "oficiais".
SJuan76
4
The easiest solution would be to implement the logging at the top level- faça isso. Registrar exceções na origem não é uma boa ideia e todos os aplicativos que encontrei que fazem isso eram um PITA para depuração. Deve ser responsabilidade do chamador lidar com exceções.
Justin
Parece que você tem em mente alguma técnica / linguagem de implementação específica; caso contrário, sua declaração "exceções registradas são indistinguíveis das que não eram" é difícil de entender. Você pode dar mais contexto?
Vroomfondel # 23/17
@Vroomfondel - Você está certo. Eu estou usando c # e agrupar o código em cada camada com try{ ... } catch(Exception ex) { Log(ex); }resultaria na mesma exceção sendo registrada em cada camada. (Ele também parece ser bastante ruim prática para ser captura cada exceção em todas as camadas na base de código.)
KidCode

Respostas:

18

Para suas perguntas:

A solução mais fácil seria implementar o log no nível superior

Ter a bolha de exceção até o nível superior é uma abordagem absolutamente correta e plausível. Nenhum dos métodos da camada superior tenta continuar com algum processo após a falha, que normalmente não pode ser bem-sucedido. E uma exceção bem equipada contém todas as informações necessárias para o log. E não fazer nada sobre exceções ajuda a manter seu código limpo e focado na tarefa principal, e não nas falhas.

Registrar a exceção na fonte é obviamente a melhor solução, porque eu tenho mais informações.

Isso é meio correto. Sim, as informações mais úteis estão disponíveis lá. Mas eu recomendo colocar tudo isso no objeto de exceção (se ainda não estiver lá) em vez de registrá-lo imediatamente. Se você fizer logon em um nível baixo, ainda precisará abrir uma exceção para informar aos chamadores que você não concluiu seu trabalho. Isso acaba em vários logs do mesmo evento.

Exceções

Minha principal orientação é capturar e registrar exceções apenas no nível superior. E todas as camadas abaixo devem garantir que todas as informações de falha necessárias sejam transportadas até o nível superior. Dentro de um aplicativo de processo único, por exemplo, em Java, isso significa principalmente não tentar / capturar ou registrar tudo fora do nível superior.

Às vezes, você deseja incluir algumas informações de contexto no log de exceções que não estão disponíveis na exceção original, por exemplo, a instrução SQL e os parâmetros que foram executados quando a exceção foi lançada. Em seguida, você pode capturar a exceção original e lançar novamente uma nova, contendo a original mais o contexto.

Obviamente, a vida real às vezes interfere:

  • Em Java, às vezes você precisa capturar uma exceção e agrupá-la em um tipo de exceção diferente, apenas para obedecer a algumas assinaturas de método fixo. Mas se você repetir uma exceção, verifique se a repetida contém todas as informações necessárias para o log posterior.

  • Se você está cruzando uma borda entre processos, geralmente não é possível transferir tecnicamente o objeto de exceção completa, incluindo o rastreamento de pilha. E é claro que a conexão pode se perder. Então, aqui está um ponto em que um serviço deve registrar exceções e, em seguida, tentar o melhor para transmitir o máximo possível de informações de falha através da linha para seu cliente. O serviço deve garantir que o cliente receba um aviso de falha, recebendo uma resposta de falha ou executando um tempo limite em caso de uma conexão interrompida. Isso normalmente resultará na mesma falha no logon duas vezes, uma vez dentro do serviço (com mais detalhes) e uma vez no nível superior do cliente.

Exploração madeireira

Estou adicionando algumas frases sobre o log em geral, não apenas sobre o log de exceções.

Além de situações excepcionais, você deseja que atividades importantes do seu aplicativo também sejam registradas no log. Portanto, use uma estrutura de log.

Tenha cuidado com os níveis de log (ler logs em que informações de depuração e erros sérios não são sinalizados com diferentes de acordo com isso é uma dor!). Os níveis típicos de log são:

  • ERRO: Algumas funções falharam irrecuperavelmente. Isso não significa necessariamente que todo o seu programa travou, mas algumas tarefas não puderam ser concluídas. Normalmente, você tem um objeto de exceção que descreve a falha.
  • AVISO: Algo estranho aconteceu, mas não causou falha em nenhuma tarefa (configuração estranha detectada, interrupção temporária da conexão, causando algumas tentativas, etc.)
  • INFORMAÇÃO: Você deseja comunicar alguma ação significativa do programa ao administrador do sistema local (iniciando algum serviço com sua versão de configuração e software, importando arquivos de dados no banco de dados, usuários efetuando login no sistema, você entendeu ...).
  • DEBUG: Coisas que você como desenvolvedor deseja ver quando estiver depurando algum problema (mas você nunca saberá com antecedência o que realmente precisa no caso desse ou daquele bug específico - se você puder prever, corrigirá o erro ) Uma coisa que sempre é útil é registrar atividades em interfaces externas.

Na produção, defina o nível do log como INFO. Os resultados devem ser úteis para um administrador do sistema, para que ele saiba o que está acontecendo. Espere que ele ligue para você para obter assistência ou correção de erros para cada ERRO no log e metade dos AVISOS.

Habilite o nível DEBUG apenas durante sessões de depuração reais.

Agrupe as entradas de log em categorias apropriadas (por exemplo, pelo nome completo da classe do código que gera a entrada), permitindo ativar os logs de depuração para partes específicas do seu programa.

Ralf Kleberhoff
fonte
Obrigado por uma resposta tão detalhada, eu realmente aprecio a parte de log também.
KidCode 23/10
-1

Estou me preparando para os votos negativos, mas vou me expor e dizer que não tenho certeza se posso concordar com isso.

Ocultar exceções, muito menos registrá-las novamente, é um esforço extra com pouco benefício. Capture a exceção na fonte (sim, mais fácil), registre-a, mas depois não volte a lançar a exceção, apenas relate "erro" ao chamador. "-1", nulo, sequência vazia, alguma enumeração, qualquer que seja. O chamador só precisa saber que a chamada falhou, quase nunca os detalhes horríveis. E esses estarão no seu registro, certo? Nos casos raros em que o chamador precisa dos detalhes, vá em frente e borbulhe, mas não como um padrão automático e impensado.

mickeyf_supports_Monica
fonte
3
O problema em relatar o erro pelos valores retornados é: 1. Se o chamador se importa com falhas, ele deve verificar o valor especial. Mas espere, é nullou a string vazia? É -1 ou qualquer número negativo? 2. Se o chamador não se importa (ou seja, não verifica), isso leva a erros de acompanhamento não relacionados à causa original, por exemplo, a NullPointerException. Ou pior: o cálculo continua com valores incorretos. 3. Se o chamador se importaria, mas o programador não acha que esse método falhou, o compilador não o lembra. As exceções não têm esses problemas, você captura ou reproduz novamente.
Siegi
1
Desculpe, eu tive que diminuir o voto. A abordagem que você descreve (substitui as exceções por valores de retorno especiais) era moderna na década de 1970, quando a linguagem C se originou, e há muitas boas razões pelas quais as linguagens modernas têm exceções, e para mim a principal é que o uso adequado de exceções facilita muito a criação de código robusto. E suas "Exceções em bolhas [...] são um esforço extra" [...] "estão completamente erradas: simplesmente não faça nada em relação a exceções, e elas explodirão sozinhas.
Ralf Kleberhoff