Na maioria das vezes e talvez por habito é comum fazer o logging de uma aplicação através de simples prints para a consola, mas na realidade isto não é aconselhável. Provavelmente pode-se achar mais rápido fazer um System.err.println do que utilizar um mecanismo de logging mas numa aplicação digna desse nome isso já não é bem assim.
Fazer logging de algumas dezenas de classes e métodos através de simples prints pode-se tornar bastante complicado e doloroso não de implementar claro mas de manter e perceber. Para isso o Java dispõe de uma API de logging, existe tem outras externas como a conhecida log4j da Apache mas na minha opinião java.util.logging faz o trabalho.
Vamos começar por um exemplo “ridículo” com logging por prints ao invés de usar o sistema de logging.
import javax.swing.JOptionPane;
/**
* @author magician
*/
public class HelloLogging {
public static void printMsg(String msg){
if(msg == null){
System.err.println("A mensagem não pode ser null!");
return;
}
else if(msg.isEmpty()){
System.err.println("A mensagem não deve ser vazia!");
}
JOptionPane.showMessageDialog(null, msg);
System.err.println("A mensagem foi enviada com sucesso!");
}
public static void main(String args []){
System.err.println("Programa a iniciar...");
printMsg(null);
printMsg("");
printMsg("Programa simples sem sistema de logging");
System.err.println("Programa a encerrar...");
}
}
[HelloLogging.java]
O exemplo esta um pouco exagerado no que toca ao logging mas a ideia é mesmo essa, como podemos ver em determinadas situações é enviado para o System.err mensagens de loggin que permite saber o que aconteceu ou o que correr mau na execução do programa.
Mas trata-se de uma logging muito pobre, não sabemos se é error (linhas 10 e 14)ou apenas informação de execução (restantes System.err.println), nem sabemos quando ocorrer ou em que classe. É verdade que toda essa informação poderia ser colocada sob forma de String nos prints mas isso seria muito trabalhoso e contra produtivo.
Vejamos agora o mesmo exemplo mas utilizando o sistema de logging do Java
import java.util.logging.Logger;
import javax.swing.JOptionPane;
/**
* @author magician
*/
public class HelloLogging {
/**
* Cria um Logger para a classe HelloLogging
*/
private static final Logger LOG = Logger.getLogger(HelloLogging.class.getName());
public static void printMsg(String msg){
if(msg == null){
LOG.severe("A mensagem não pode ser null!");
return;
}
else if(msg.isEmpty()){
LOG.warning("A mensagem não deve ser vazia!");
}
JOptionPane.showMessageDialog(null, msg);
LOG.info("A mensagem foi enviada com sucesso!");
}
public static void main(String args []){
LOG.info("Programa a iniciar...");
printMsg(null);
printMsg("");
printMsg("Programa simples sem sistema de logging");
LOG.info("Programa a encerrar...");
}
}
O uso do logging como se pode ver é bastante simples, basta criar o Logger para a classe e em seguida enviar os registos usando o método com o nível que queremos obtendo assim o seguinte output
28/Jul/2010 11:39:32 HelloLogging main INFO: Programa a iniciar... 28/Jul/2010 11:39:32 HelloLogging printMsg SEVERE: A mensagem não pode ser null! 28/Jul/2010 11:39:32 HelloLogging printMsg WARNING: A mensagem não deve ser vazia! 28/Jul/2010 11:39:35 HelloLogging printMsg INFO: A mensagem foi enviada com sucesso! 28/Jul/2010 11:39:35 HelloLogging printMsg INFO: A mensagem foi enviada com sucesso! 28/Jul/2010 11:39:35 HelloLogging main INFO: Programa a encerrar...
O log dá-nos bastante informação, data e hora, a classe e método de onde foi enviado o registo, o nível de gravidade que é bastante útil por exemplo para filtrar o registo do log e a mensagem enviado para o log.
Pode-se ainda ao invés de criar um Logger por classe, usar apenas um para toda a aplicação, ou seja um global que o sistema de logging cria automaticamente. Para isso basta trocar a linha 12 da classe HelloLoggin para
private static final Logger LOG = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
O resultado será o mesmo que o anterior, a desvantagem deste método é que como temos apenas um logger todas as configurações serão aplicadas a ele não fazendo distinções. Isto é ao definir um Handler ou um Level para o Logger Global ele será genérico.
Mas para alem de enviar o log para a consola é possível enviar para outros destinos como ficheiros, Streams, Sockets ou até mesmo para um destino personalizado como email por exemplo. Para além dos destino podemos ainda definir o formato das mensagens que por defeito é texto mas pode ser XML ou outro formato personalizado tal como HTML ou outros.
Vejamos o exemplo anterior mas agora o log será guardado em um ficheiro e terá o formato XML
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.XMLFormatter;
import javax.swing.JOptionPane;
/**
* @author magician
*/
public class HelloLogging {
private static final Logger LOG = Logger.getLogger(HelloLogging.class.getName());
public static void printMsg(String msg){
if(msg == null){
LOG.severe("A mensagem não pode ser null!");
return;
}
else if(msg.isEmpty()){
LOG.warning("A mensagem não deve ser vazia!");
}
JOptionPane.showMessageDialog(null, msg);
LOG.info("A mensagem foi enviada com sucesso!");
}
public static void main(String args []){
try {
Handler console = new ConsoleHandler();
Handler file = new FileHandler("C:\\hellologgin.xml");
/*Define que na consola apenas aparece log com nível superior ou
a warning e no ficheiro deve aparecer o log de qualquer nível
*/
console.setLevel(Level.WARNING);
file.setLevel(Level.ALL);
//Define o formato de output do ficheiro como XML
file.setFormatter(new XMLFormatter());
//Adiciona os handlers para ficheiro e console
LOG.addHandler(file);
LOG.addHandler(console);
//Ignora os Handlers definidos no Logger Global
LOG.setUseParentHandlers(false);
}
catch(IOException io){
LOG.warning("O ficheiro hellologgin.xml não pode ser criado");
}
LOG.info("Programa a iniciar...");
printMsg(null);
printMsg("");
printMsg("Programa simples sem sistema de logging");
LOG.info("Programa a encerrar...");
}
}
O exemplo acima limita o output da consola a registos de nível igual ou superior a warning ou seja todos os restantes registos são ignorados pelo handler e não aparecem no output, por outro lado o handler para o ficheiro XML guarda os registo de qualquer nível e com o formato XML e não simple text como no caso da consola.
Para além do SimpleFormatter e XMLFormatter podemos ainda criar os nossos próprios formaters para isso basta fazer uma classe que extende a classe java.util.Formatter e nela definirmos os nossos parâmetros de formatação. Por exemplo um Formatter que gera um log em HTML ao invés de XML ou simple text.
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
/**
* @author magician
*/
public class HTMLFormater extends Formatter {
@Override
public String format(LogRecord record) {
StringBuilder sb = new StringBuilder();
sb.append("<tr>\n");
sb.append("<td>\n");
if(record.getLevel().intValue() >= Level.WARNING.intValue()){
sb.append("<strong>color=red>");</strong>
sb.append(record.getLevel());
sb.append("</b></font>");
}
else {
sb.append(record.getLevel());
}
sb.append("</td>\n");
sb.append("<td>\n");
sb.append(milliToDate(record.getMillis()));
sb.append("</td>\n");
sb.append("<td>\n");
sb.append(formatMessage(record));
sb.append("</td>\n");
sb.append("</tr>\n");
return sb.toString();
}
private String milliToDate(long millis){
SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
return format.format(new Date(millis));
}
@Override
public String getHead(Handler h) {
return "\n\n
" charset=utf-8\">\n\n\n
<pre>\n"</pre>
+ "
<table>border>\n "</table>
+ "<tr><th>Nivel</th><th>Data</th><th>Mensagem</th></tr>\n";
}
@Override
public String getTail(Handler h) {
return "\n \n\n";
}
}
Ao aplicar o HTMLFormatter ao nosso FileHandler vamos passar a ter um log com o formato HTML que definimos no Formatter o que vai resultar em algo como
| Nivel | Data | Mensagem |
|---|---|---|
| INFO | 28/07/2010 14:10:13 | Programa a iniciar... |
| SEVERE | 28/07/2010 14:10:13 | A mensagem não pode ser null! |
| WARNING | 28/07/2010 14:10:13 | A mensagem não deve ser vazia! |
| INFO | 28/07/2010 14:10:14 | A mensagem foi enviada com sucesso! |
| INFO | 28/07/2010 14:10:14 | A mensagem foi enviada com sucesso! |
| INFO | 28/07/2010 14:10:14 | Programa a encerrar... |
Para destas configurações em código podemos ainda configurar os loggers através de um ficheiro de propriedades, isto danos a vantagem de poder alterar o comportamento dos loggers sem ter que recompilar todo o programa.
Podemos fazer o exemplo anterior e obter o mesmo resultado utilizando esse ficheiro de configuração da seguinte forma
import java.util.logging.Logger;
import javax.swing.JOptionPane;
/**
* @author magician
*/
public class HelloLogging {
private static final Logger LOG = Logger.getLogger(HelloLogging.class.getName());
public static void printMsg(String msg){
if(msg == null){
LOG.severe("A mensagem não pode ser null!");
return;
}
else if(msg.isEmpty()){
LOG.warning("A mensagem não deve ser vazia!");
}
JOptionPane.showMessageDialog(null, msg);
LOG.info("A mensagem foi enviada com sucesso!");
}
public static void main(String args []){
LOG.info("Programa a iniciar...");
printMsg(null);
printMsg("");
printMsg("Programa simples sem sistema de logging");
LOG.info("Programa a encerrar...");
}
}
Com o seguinte ficheiro logging.properties
java.util.logging.FileHandler.formatter = HTMLFormatter java.util.logging.FileHandler.level = ALL java.util.logging.FileHandler.pattern = C:\\hellologging.html java.util.logging.ConsoleHandler.level = WARNING java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter HelloLogging.level = ALL HelloLogging.handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler
E à semelhança do exemplo anterior na consola irão aparecer os registos com nível igual ou superior a warning e no ficheiro HTML irão aparecer todos os registos.
Para que o programa carregue o ficheiro de configuração é preciso adicionar a seguinte opção é VM
java -Djava.util.logging.config.file=logging.properties <classe>
Podemos ainda dispensar o uso desta opção da VM se no “topo” de execução da nossa aplicação colocarmos o seguinte bloco de código
static {
Properties prop = System.getProperties();
prop.setProperty("java.util.logging.config.file","logging.properties");
}
Neste exemplo coloquei este bloco por cima da criação do Logger LOG, mas o importante será colocar a atribuição de propriedades antes de qualquer chamada à API logging. Existem outras formas como usar o LogManager para carregar as configurações mas acho esta mais simples e rápida.
Referencia:

excelente post, muito bom para sair do 0×0, eu recomendaria um segundo, tendo API logging na prática em uma simples aplicação porem mais funcional, somente para os iniciantes terem ideia de onde trabalhar com API, nem que seja aquela aplicacao que recebe via console a informacao X e depois exibi X + Y.
parabens!! muito bom, foi o melhor que já li sobre o assunto em portugues.
Comentário por camilo — 18 Outubro, 2010 @ 17:04 |
obrigado pelo excelente post, estava na duvida se devia continuar a usar o tradicional System.err.println, e qual a real função da classe logging, mas com este post minhas duvidas foram totalmente sanadas.
Comentário por masami — 16 Novembro, 2010 @ 23:51 |