11 Mar, 2014 17:18
Logging no Android com logback
A instalação é bem simples:
- Baixe a última versão da logback-android e da slf4j-api;
- Configure o comportamento do log através do arquivo ${project-root}/assets/logback.xml ou diretamente via código;
- Use a slf4j-api para escrever no log da aplicação.
Exemplo:
package com.example;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// SLF4J
Logger log = LoggerFactory.getLogger(MainActivity.class);
log.info("hello world");
}
}
No exemplo acima, obtermos uma instância de org.slf4j.Logger através de LoggerFactory.getLogger(this.getClass());
. Com essa instância podemos usar os métodos info(...)
, debug(...)
, error(...)
e warn(...)
- semelhante ao que havia no android.util.Log.
Configurando o logback via código
Eu prefiro configurar usando código Java, pois é mais fácil de encontrar erros e descobrir funcionalidades com o code complete da IDE. Se você preferir configurar via XML, pode olhar o site do logback-android.
No exemplo abaixo, o método configureLogger()
prepara o logger da seguinte maneira:
- Arquivos de log genéricos para todos os logs com nível maiores que DEBUG (
LevelFilter filter1
), nomeados de acordo com o dia (RollingFileAppender
,TimeBasedRollingPolicy
); - Saída para o LogCat (
LogcatAppender
) sem qualquer filtro; - Arquivo de log exclusivo para erros de conexão(
FileAppender<ILoggingEvent> conexaoFileAppender
) com nível somente DEBUG (LevelFilter filter2
).
Com essas configurações, qualquer chamada a um dos métodos de log (ex. logger.info(...)
) faz o conteúdo ser escrito automatica e simultaneamente em todos os arquivos de log que atendam os filtros especificados.
Eis o código:
private void configureLogger() {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.reset();
PatternLayout.defaultConverterMap.put("user",
LoggerUserConverter.class.getName());
/*
1: Configuração do log genérico rotacionado por dia
*/
// setup FileAppender
PatternLayoutEncoder encoder1 = new PatternLayoutEncoder();
encoder1.setContext(lc);
encoder1.setPattern("%date %-5level %logger{36}:%line - [%user] %msg%n");
encoder1.start();
LevelFilter filter1 = new LevelFilter();
filter1.setContext(lc);
filter1.setLevel(Level.DEBUG);
filter1.setOnMatch(FilterReply.DENY);
filter1.setOnMismatch(FilterReply.ACCEPT);
filter1.start();
String logDirectory = Environment
.getExternalStoragePublicDirectory("MinhaApp/logs")
.getAbsolutePath();
String logFileName = logDirectory + "/minha-app-%d.log.gz";
RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<ILoggingEvent>();
fileAppender.setContext(lc);
fileAppender.setAppend(true);
fileAppender.addFilter(filter1);
// setup time based rolling
TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<ILoggingEvent>();
rollingPolicy.setContext(lc);
rollingPolicy.setFileNamePattern(logFileName);
rollingPolicy.setParent(fileAppender);
rollingPolicy.setCleanHistoryOnStart(true);
rollingPolicy.start();
fileAppender.setRollingPolicy(rollingPolicy);
fileAppender.setEncoder(encoder1);
fileAppender.start();
/*
2: Configuração de saída para o Logcat
*/
// setup LogcatAppender
PatternLayoutEncoder encoder2 = new PatternLayoutEncoder();
encoder2.setContext(lc);
encoder2.setPattern("%msg%n");
encoder2.start();
LogcatAppender logcatAppender = new LogcatAppender();
logcatAppender.setContext(lc);
logcatAppender.setEncoder(encoder2);
logcatAppender.start();
/*
3: Configuração do log de DEBUG para erros de conexão
*/
// setup FileAppender para erros de conexao
PatternLayoutEncoder encoder3 = new PatternLayoutEncoder();
encoder3.setContext(lc);
encoder3.setPattern("%date %-5level %logger{36}:%line - [%user] %msg%n");
encoder3.start();
String logConexaoFileName = logDirectory + "/minha-app-conexoes-errors.log";
LevelFilter filter2 = new LevelFilter();
filter2.setContext(lc);
filter2.setLevel(Level.ERROR);
filter2.setOnMatch(FilterReply.ACCEPT);
filter2.setOnMismatch(FilterReply.DENY);
filter2.start();
FileAppender<ILoggingEvent> conexaoFileAppender = new FileAppender<ILoggingEvent>();
conexaoFileAppender.setContext(lc);
conexaoFileAppender.setAppend(true);
conexaoFileAppender.setEncoder(encoder3);
conexaoFileAppender.setFile(logConexaoFileName);
conexaoFileAppender.addFilter(filter2);
conexaoFileAppender.start();
// add the newly created appenders to the root logger;
// qualify Logger to disambiguate from org.slf4j.Logger
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory
.getLogger(Logger.ROOT_LOGGER_NAME);
root.addAppender(conexaoFileAppender);
root.addAppender(fileAppender);
root.addAppender(logcatAppender);
}
Como pode ver ao final do método, as chamadas consecutivas a root.addAppender(...)
registram cada uma das regras de log definidas logo acima.
Além disso, nas primeiras linhas, adicionei um "pattern converter":
PatternLayout.defaultConverterMap.put("user",
LoggerUserConverter.class.getName());
Esse LoggerUserConverter
(abaixo) permite que eu coloque "%user" no pattern dos logs e seja substituído automaticamente pelo nome do usuário que está logado no aplicativo no momento. Eis o código do LoggerUserConverter
:
public class LoggerUserConverter extends ClassicConverter {
@Override
public String convert(ILoggingEvent arg0) {
Usuario usuario = MinhaApplication.getUsuario();
if (usuario != null && usuario.getLogin() != null) {
return usuario.getLogin();
}
return "anonimo";
}
}
Tentei montar um exemplo mais complexo, mas com recursos usados normalmente quando precisamos de um log mais robusto do que o android.util.Log. Você pode ver mais exemplos a partir do manual do logback.