Logging em Java

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:

Java Logging Overview

java.util.logging

Attaching handlers to loggers using logging config file

Anúncios

3 thoughts on “Logging em Java

  1. excelente post, muito bom para sair do 0x0, 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.

  2. 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.

  3. Daria para criar um logging par mais de uma classe dentro de um programa e gear somente um arquivo por dia, por exemplo implementar o loggin na classe main e como outras casses partem dela pegar esses logs e gerar arquivos separados?

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Google photo

Está a comentar usando a sua conta Google Terminar Sessão /  Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

Connecting to %s