O documento discute logs e exceções em sistemas de software. Ele explica o que são logs e níveis de logs, como debug, info e error. Também discute onde gravar logs, ferramentas como Log4j e Logback, e boas práticas como usar logs enxutos e tratar exceções corretamente.
Como os logs de Bart revelaram que ele não estava indo à escola
1. E exceções !
Autor: Pedro H. F. S. Diniz
Grupo de GIS, Tecgraf
2. Sobre esta apresentação
Logs
● O que é e porque é importante
● Níveis de log
● Appenders
● Onde e quando devemos logar
● Estudo de caso
● Ferramentas que usamos hoje?
● Tem coisa melhor?
● Log assíncrono?
● Boas práticas sobre o nosso código
3. Sobre esta apresentação
Exceção
● O que é?
● Tipos de exceção
● Classificação
● Quando lançar?
● Quando tratar?
● Boas práticas
● Fique Atento!
4. O que é
● É a história de vida do sistema
● É uma instrução do código que grava informações de execução em algum
lugar (email, arquivo, jms, banco de dados, etc..)
● É a melhor maneira de se identificar um problema em produção
● É uma ferramenta para realizar auditoria no sistema
● É a ferramenta mais eficaz para que o cliente consiga rodar a manter a
aplicação sem a intervenção dos desenvolvedores
5. Porque é importante
Pense que sua aplicação agora está fora do ambiente de desenvolvimento -
não está mais rodando na sua ide favorita e muito menos disponível para
debug.
É nessa hora que você irá percebeber a importância dos logs. Depois de ler
inúmeras linhas de log tentando identificar o que há de errado na aplicação
você vai entender que não é uma tarefa trivial.
Daí você se pergunta: com toda a minha experiência e conhecimento, é isso o
que eu estou destinado a fazer? Encontrar uma agulha em um palheiro de
logs?
Ou no nosso caso, delegando para o cliente (como na petrobras por exemplo),
que possui uma equipe de suporte e manutenção que igualmente nos
amaldiçoa pela gratificante e entediante tarefa!
Ou pior ainda, ter que levar até o cliente os arquitetos do software e fazer eles
se involverem, enquanto eles pensam a cada minuto como é um erro gastar o
seu tempo e habilidade com isso.
6. Níveis de log
Em geral são:
- Trace
- Debug
- Info
- Warning
- Error
-Fatal
7. Níveis de log
Trace
Traça toda entrada e saída de método, indicando os parâmetros. Geralmente só é ativada
temporariamente. O que diferencia esse nível do nível debug é que em produção o trace quase nunca
precisa ser utilizado.
public int sumNumbers(int first, int second){
log.trace("sumNumbers({},{})", first, second);
return first+second;
}
Debug
Grava todo e qualquer event ocom informações importantes sobre o funcionamento da aplicação.
Geralmente é ativada em produção para ajudar os desenvolvedores com informações extras.
public int sumNumbers(int first, int second){
log.debug("sumNumbers() returned {}", first+second);
return first+second;
}
8. Níveis de log
Info
Informa que algum evento importante da aplicação foi concluído. Em um mundo ideal, tanto
administradores como usuários avançados deveriam ser capazes de entender o que a aplicação está
fazendo através desses logs
public String importXml(String url){
String xml = null;
Connection connection = null;
log.info("connecting to {}", url);
connection = connectToUrl(url);
log.info("importing xml");
xml = importXml(connection);
log.info("xml imported successfully");
return xml;
}
Warning
Mensagem indicando que embora o processo possa continuar, algo está fora do esperado a atenção
deverá ser redobrada. Ex.
while ((nextLine = reader.readNext()) != null) {
if (!StringUtil.toString(nextLine).trim().isEmpty()) {
readLine(nextLine);
} else {
logger.warn("Foi lida uma linha em branco do csv, remova linhas sem conteúdo.");
}
}
9. Níveis de log
Error
Algum problema intolerável aconteceu, que precisa ser investigado imediatamente. Nenhum sistema
deve tolerar eventos desse nível. Ex. NullPointerException, Connection
if (!FTPReply.isPositiveCompletion(reply)) {
client.disconnect();
logger.error("FTP server refused connection. Reply was {}", reply);
return false;
}
Fatal
Eventos severos que impedem o funcionamento do aplicação. Após esse erro a aplicação
possivelmente irá parar de responder.
try {
new GlobalsatDriverFtp().run();
} catch (Exception e) {
logger.fatal("Error running GlobalsatDriver: ", e);
e.printStackTrace();
}
10. Níveis de log
Trace x Debug
O trace deve ser usado apenas para indicar em qual método o sistema está passando, enquanto o
debug indica estados do sistema. O trace não deve indicar valor de variáveis.
Se existe a possibilidade de usar esse log em produção então não é trace. O trace é utilizado quase
que exclusivamente em ambiente de desenvolvimento somente.
11. Níveis de log
Debug x Info
O debug contém informações pertinentes ao desenvolvedor do sistema.
O Info contém informações pertinentes aos usuários do sistema, auditores, equipe de suporte,
desenvolvedores e outros...
12. Níveis de log
Info x Warning
O Info indica etapas do sistema que não requerem intervenção do usuário nem do desenvolvedor. Em
geral são apenas informações sobre o que está acontecendo por trás dos panos.
O Warning indica que ocorreu um comportamento inesperado, e embora o sistema possa continuar
geralmente espera-se investigação do ocorrido.
13. Níveis de log
Warning x Error
O Warning indica que ocorreu um comportamento inesperado mas mesmo assim o sistema
conseguiu continuar. Seria interessante por parte do desenvolvedor corrigir o problema.
O Error indica que ocorreu um comportamento inesperado no código em execução e o sistema não
conseguiu continuar. O desenvolvedor precisa corrigir o problema.
14. Níveis de log
Error x Fatal
O Error indica que um bloco de código foi abortado e não pôde continuar.
O Fatal indica que o sistema foi abortado e não pôde continuar.
15. Appenders
Common Appenders
FileAppender - Escreve os logs em um arquivo
RollingFileAppender - Estende o FileAppender permitindo backups a medida que o arquivo
atinge um tamanho limite
DailyRollingFileAppender - Estende o FileAppender para que o roll em arquivo possa ser
definido por alguma frequência dada pelo usuário.
ConsoleAppender - Escreve os logs no System.out ou System.error
Network Appenders
SocketAppender - Envia os logs para um log server remoto
SocketHubAppender - Envia os logs para um conjunto de log servers remotos
JMSAppender - Publica logs em um tópico JMS.
NTEventLogAppender - Registra log no sistema de eventos do sistema operacional
16. Appenders
Special Appenders
AsyncAppender - Faz com que um appender seja asincrono, permitindo que a aplicação
continue a rodar independentemente.
ExternallyRolledFileAppender - Permite que o sistema escuta uma porta do computador e ao
receber mensagem execute um rollover no log.
JDBCAppender - Salva os logs em um banco de dados
LF5Appender - Envia log para o console de uma aplicação java em swing.
NullAppender - Útil para testes internos ou benchmarking
SMTPAppender - Envia e-mail quando o log especificado for lançado pelo sistema.
SyslogAppender - Envia os logs para um syslog daemon remoto.
TelnetAppender - Envia log pelo telnet
WriterAppender - WriterAppender anexa eventos de log a um Write ou OutputStream
dependendo da escolha do usuário.
17. Onde e quando devemos logar
Primeiro: identifique o público alvo
● Auditores
● Transações que envolvem dinheiro
● Equipe de suporte ao sistema dentro do cliente (TI Petrobras)
● Segurança, detectar ataques de DoS, etc..
● Facilitar
Segundo: identifique para onde irá o log
● Arquivo (desenvolvedores)
● Jms
● Email (Segurança)
● Banco de dados (auditoria)
● ETC...
Por último:
● Se interessa ao público alvo, identifique o nível em que o log se adequa e logue!
18. Estudo de Caso
Como todo log é uma história, no nosso caso iremos contar uma história dos Simpsons
19. Estudo de Caso
O diretor da escola ligou para Marge e avisou que Bart anda matando aula. Ele disse que na semana
passada Bart faltou 2 vezes e que isso é preocupante pois suas notas não estão boas.
Marge preocupada, teve uma idéia de como descobrir o que estava acontecendo para mandar Bart
de volta à escola.
20. Estudo de Caso
Marge deu de presente um novo relógio para o Bart, e sem que ele saiba, o relógio irá enviar para o
email da Marge um log com uma foto do local e posição gps de onde ele está a cada minuto.
21. Estudo de Caso
Bart, feliz com seu novo relógio do Krusty, cola o relogio antes de dormir e o relógio começa a
funcionar.
Assim que Bart acorda de manhã para ir à escola o relógio faz seu primeiro log ((info))
Esse log é importante pois indica se Bart está com problema para acordar no horário.
22. Estudo de Caso
Após tomar seu café da manhã, Bart sai de casa para ir para a escola.
Nesse momento o relógio faz mais um log ((info))
Esse log será importante pois indica se ele se atrasa para pegar o ônibus ou sai no horário certo.
23. Estudo de Caso
Ao invés de pegar o ônibus, bart vai para a escola de skate.
O relógio mais uma vez faz um log com foto ((warn))
Esse log será importante pois indica que algo não está indo conforme o esperado, embora não seja
necessariamente um problema.
24. Estudo de Caso
A cada rua que Bart passa, o relógio realiza mais logs ((trace)).
O cada casa que o Bart passa o relógio faz log ((debug)) novamente , i
ndicando quem mora na casa.
Esse log será importante pois indica que ele passou na casa do Milhouse. Agora a mudança de rotina
faz começa a fazer sentido.
25. Estudo de Caso
Como Bart passa pela escola e segue em frente o sistema realiza mais um log ((error)).
Esse log é fundamental, o objetivo é a escola se ela não parou então algo está errado. Isso não pode
acontecer.
26. Estudo de Caso
Após passar pela escola Bart para em frente a um show de rock. Nesse momento o relógio faz mais
um log ((fatal)).
Esse log é o log final, de fato Bart não foi para escola. Agora não faz mais sentido fazer log, Marge
precisará intervir.
27. Estudo de Caso
Conclusão:
O log precisa contar uma história. E essa história precisa estar clara para que quem lê consiga
entender não só os acontecimentos, mas a sua importância, como corrigi-los e como evitá-los.
28. Ferramentas que usamos hoje?
Aqui no Tecgraf usamos apenas o Log4j (1.2.x)
● Ferramenta para java criada pela apache.
● Foi quem primeiro definiu padrões de log em java
● A versão 1.x apesar de estável não é mais ativamente desenvolvida
● Em geral todo log é feito concatenando strings. Ex. log.error("Fulano "+x+" disse "+y)
● Para debug usa-se if(logger.isDebugEnabled()), toda vez.
● Tem bons appenders
.
Commons login (JCL)
● Jakarta Commons logging.
● Não faz log, apenas define um façade para apis de log.
● Apesar de estável não é mais ativamente desenvolvida
● É muito comum encontrarmos essa biblioteca causando problemas de classloader em
servidores.
29. Tem coisa melhor?
LOGBack
● Ferramenta java criada pelo desenvolvedor do Log4j 1.x
● Permitir mudar o nível de log sem reiniciar a aplicação
● É o novo padrão em log
● Todos recomendam logback ao invés de log4j 1.x
● Mais rápida que o log4j 1.x
● Já foi criado pensando no sl4j
● Melhor recuperação em falhas de I/O
● Mais e melhores opções de filtro
● Indica no log em que jar está a classe que gerou a exceção
● Não usa concatenação de string para log. Ex. log.error("Fulano {} disse {}", x, y);
● Não precisa de if(logger.isDebugEnabled());
● Tem bons appenders
● Permite agrupar logs para que sejam exibidos em ordem
● Permite separar log por usuário do sistema ou outra variável qualquer
slf4j
● Ferramenta java criada pelo desenvolvedor do Log4j 1.x
● Não faz log, apenas define um façade para apis de log.
● Todos recomendam slf4j ao invés de commons-logging
● Até a apache utiliza o slf4j
● Possui um jcl-over-slf4j para não criar conflito com o jcl e redirecionar para o slf4j
● Possui um log4j-over-slf4j para não criar conflito com o log4j e redirecionar para o slf4j
● Possui um sl4j migrator tool que migra automaticamente o seu código de log4j para sl4j
30. Tem coisa melhor?
Log4j (2.x.beta)
● Ferramenta para java criada pela apache.
● Substitui e o log4j 1.x
● Foi criada para ter tudo o que o logback tem.
● Resolve alguns problemas de arquitetura do logback
● Oferece mais funcionalidades que o logback
● As chances são de que ela seja o novo padrão, ao invés do logback
31. Log asíncrono?
● Acaba sendo mais rápido para a aplicação
● Faz log sem bloquear o código principal
● Especialmente eficaz em aplicações multithread uma vez que várias
threads precisarão escrever no mesmo arquivo
● Extremamente recomendável quando a aplicação tem um número elevado
de logs.
● Não é suportado em containers J2EE, para esses caso usa-se o
JMSAppender
32. Boas práticas sobre o nosso código
O catch foi criado para tratar exceções e não para fazer log. Neste caso o ideal
seria propagar a exceção.
catch (Exception e) {
logger.error("", e);
return false;
}
33. Boas práticas sobre o nosso código
Por utilizarmos o log4j 1.x nosso logs são cheios de contaneção. Devemos
atualizar a nossa biblioteca para usar novas versão de log e evitar esse
overhead desnecessário.
if (v == null || DatabaseConstants.VEHICLE_STATUS_INACTIVE.equals(v.getStatus())) {
logger.info("Discarding planning " + plannedTrip.getPlanningNumber()
+ ". Vehicle " + plate + " is not registered or inactive.");
return true;
}
34. Boas práticas sobre o nosso código
Use logs enxutos. O ideal é que cada mensagem de log não passe de uma
linha de texto.
PS: Repare que novamente a exceção não é tratada.
catch (InvalidParameterException e) {
logger.warn(buildVDBDocWarnMessage(meta.getCanonicalName()), e);
}
35. Boas práticas sobre o nosso código
Repare que a concatenação de strings também faz com que a leitura do log
ganhe complexidade.
catch (IOException e) {
logger.error("Error while creating the bean" + clazz + " / " + metadataName + " in
" + getClass().getSimpleName(), e);
}
catch(Exception e){
logger.error("Login denied for user "+user.getLogin());
}
36. Boas práticas sobre o nosso código
Catch de Exception não pode ser feito:
Primeiro porque você não sabe que erro é esse portanto não tem como tratar
Segundo porque você impede o recibemto da exceção para quem sabe tratá-
la.
PS: Mais uma vez a exceção não é tratada e se perde no código
try{
client.disconnect();
}catch(Exception e){
logger.error(e);
}
37. Boas práticas sobre o nosso código
Não acesse variáveis dentro das mensagens de log, elas podem estar nulas.
A última coisa que faltava é o sistema parar de responder pq houve
NullPointerException ao criar uma mensagem de log.
_logger.trace("Ended task '" + task.getName() + "' (progress rate = " + _currentProgressRate +
"%)");
try {
processingService.saveResults(getUserContext(), env);
} catch (IOException e) {
LOGGER.error("Error saving processing results for moving object: " + moCache.getMovingObject().
getIdentifier(), e);
throw e;
}
_logger.debug("created start node id " + startNode.getId());
38. Boas práticas sobre o nosso código
Parábens para o nosso código que usou corretamente o if(debug).
if (debug) {
logger.debug("andAnyAttributeLocation()-" + clause);
}
39. Boas práticas sobre o nosso código
Pena que não é sempre assim, a maior parte do nosso código não usa o if
(debug).
Esse tipo de overhead pode ser evitado se usarmos o LogBack ou slf4j.
PS: Utilize logs de no máximo uma linha.
PS: Não acesse variáveis dentro das mensagens
String debugLine = "Received httpData Version: " + getVersionNumber() + "- deviceId: "
+ id[0] + ", cont: " + cont + ", time: "+ sessionTime.getTime() + ", httpData: " +
msgsDebug;
if (!hasInvalidCharacters(debugLine)){
logger.debug(debugLine);
}else{
logger.debug("Recebida requisição que contém caracteres inválidos.");
}
40. Boas práticas sobre o nosso código
Embora muito comumn, fazer catch de RuntimeException não é uma boa
prática.
Se o seu código tem muito tratamento de RuntimeException isso é um sinal de
que ele não está estável.
try{
connect();
}catch(NullPointerException npe){
logger.error("Erro ao conectar", npe);
}
43. O que é?
● É um aviso indicando que o código em execução
encontrou um comportamento impeditivo com o qual ele
não sabe lidar.
● É uma subclasse de "java.lang.Throwable".
● Contém informações sobre o comportamento
encontrado para que o código no topo da hierarquia
possa tratá-lo ou seguir por outro caminho.
45. Classificação
Checked Exceptions
São subclasses de Exception. Representam exceções onde o desenvolvedor consegue se recuperar.
Embora indiquem um comportamento inesperado, não são necessariamente um erro e não impedem
o sistema de continuar.
Ex. FileNotFoundException, ParseException , ...
Unchecked Exceptions
São subclasses de Error ou RuntimeException. Representam o inverso das Checked Exceptions.
Quando essas exceções ocorrem elas indicam que existe um erro no código e o sistema ou bloco de
código não pode continuar.
Geralmente essas exceções fazem com que o sistema seja desligado.
Ex. NullPointerException, StackOverflowError, OutOfMemoryError, NoClassDefFoundError,
IllegalArgumentException
46. Classificação
Porque vemos mais classes que herdam de Exception do que Error ou RuntimeException?
Não vemos muitas classes que herdam de Error porque por convenção somente a JVM pode criar e
lançar classes que herdam de Error.
Não vemos muitas classes que herdam de RuntimeException porque, como elas não precisam ser
declaradas, o sistema não fica preparado para que ela aconteça.
Relatos de desenvolvedores que usaram bastante RuntimeExceptions, explicam que todas as vezes
que a aplicação entrava em produção apareciam erros graves que bloqueavam o sistema.
O que provavelmente já teria sido tratado e não bloquearia o sistema se tivesse sido declarado
corretamente no throws.
Na comunidade Java há quem diga que os sistemas deveriam apenas usar apenas Exception, e que
RuntimeException e Error deveriam ser exclusivos da JVM.
47. Quando lançar?
THROW EARLY catch late
Quanto mais informação houver no stack trace, melhor. Portanto ao invés de deixar exceções como
NullpointerException acontecerem em locais pouco informativos, detecte o problema e lance a
exceção onde se encontra o verdadeiro problema.
public void readPreferences(String filename) throws IllegalArgumentException{
if (filename == null) {
throw new IllegalArgumentException("filename is null");
} //if
//...perform other operations...
InputStream in = new FileInputStream(filename);
//...read the preferences file...
}
48. Quando tratar?
throw early CATCH LATE
A exceção deve ser lançada o suficiente para que quem tem mais informação possa tratá-la. Um erro
muito comum é tratar a exceção assim que ela acontece, impedindo que o sistema trate-a da maneira
apropriada. Propague a exceção para quem pode tratá-la.
public void readPreferences(String filename){
//...
InputStream in = null;
// NÂO FAÇA ISSO!!!
try{
in = new FileInputStream(filename);
}catch (FileNotFoundException e) {
logger.log(e);
return;
}
in.read(...);
//...
}
49. Boas práticas
Valide argumentos quando necessário
Mas também não precisa fazer isso em todos os métodos!
Valide os inputs nos métodos que já originaram bugs, assim você irá evitar que esses bugs ocorram
novamente.
if ( null == variable || variable.isEmpty()){
throw new NullPointerException("the variable cannot be null at this point");
}
50. Boas práticas
Evite grandes blocos de try catch
Separe os try catch por escopo, assim o código fica mais fácil de entender e cada bloco irá pegas
apenas as exceções pertinentes a tarefas pontuais. Considere dividi-los em métodos diferentes.
try{
s = pegarNomeDoArquivo();
f = lerArquivo(s);
v = validarArquivo(f);
}catch(FileNotFoundException fne){
}
try{
x = fazerParseDoArquivo(f);
z = modificarXml()
}catch(ParseException pe){
}
51. Boas práticas
Se não vai tratar, propague!
Não faça catch em exceções apenas para encher log. O catch foi feito para tratar exceções e não para
fazer log. Dessa forma você inpedirá que a exceção seja tratada por quem sabe como tratá-la.
try{
s = pegarNomeDoArquivo();
f = lerArquivo(s);
v = validarArquivo(f);
}catch(Exception e){
log.error("Error ao ler arquivo", e);
}
52. Boas práticas
Cuidado para não engulir exceções
No bloco abaixo caso aconteça um FileNotFoundException veremos apenas um NullPointerException
gerado pelo descuidado finally. O correto seria verificar se "input" é nulo no finally.
InputStream input = null;
try{
input = new FileInputStream("myFile.txt");
//do something with the stream
} catch(IOException e){
throw new WrapperException(e);
} finally {
try{
input.close();
} catch(IOException e){
throw new WrapperException(e);
}
}
53. Boas práticas
Enriqueça as exceções lançadas
Aproveite a propagação das exceções para enriquecê-las
public void parseXml(String filePath) throws ParseException, IOException{
try{
File f = leArquivo(filePath);
fazParse(f);
}catch(IOException ioe){
throw new IOException("Parse falhou pois arquivo não existe {}", filePath, ioe);
}
}
public File leArquivo(String filePath) throws IOException{
//Irá gerar uma exceção
return new File(filePath);
}
54. Fique atento!
No JAVA 7 haverá uma nova forma de tratar exceções
Para evitar o uso do try catch dentro do finally o java 7 criou o try-with-resources. Assim qualquer classe que
herde de java.lang.AutoCloseable é fechada pela java automaticamente no try catch
Try-with-resources
ANTIGO NOVO
OldResource res = null;
try { try(NewResource res = new NewResource("Res1 closing")){
res = new OldResource(); res.doSomeWork("Listening to podcast");
res.doSomeWork("Writing"); } catch(Exception e){
} catch (Exception e) { System.out.println("Exception");
System.out.println("Exception"); }
} finally{
try {
res.close();
} catch (Exception e) {
System.out.println("Exception");
}
}