Como ativar / desativar os níveis de log no Android?

149

Estou tendo muitas instruções de log para depuração, por exemplo.

Log.v(TAG, "Message here");
Log.w(TAG, " WARNING HERE");

ao implantar esse aplicativo no telefone do dispositivo, desejo desativar o log detalhado de onde posso ativar / desativar o log.

d-man
fonte
Possível duplicado de níveis de log Android

Respostas:

80

Uma maneira comum é criar um int chamado loglevel e definir seu nível de depuração com base no loglevel.

public static int LOGLEVEL = 2;
public static boolean ERROR = LOGLEVEL > 0;
public static boolean WARN = LOGLEVEL > 1;
...
public static boolean VERBOSE = LOGLEVEL > 4;

    if (VERBOSE) Log.v(TAG, "Message here"); // Won't be shown
    if (WARN) Log.w(TAG, "WARNING HERE");    // Still goes through

Mais tarde, você pode simplesmente alterar o LOGLEVEL para todo o nível de saída de depuração.

Cytown
fonte
1
agradáveis, mas como é que você desativar depuração no seu exemplo, mas ainda mostrando avisos ....
Andre Bossard
1
As instruções if não acabariam no código de byte .apk? Eu pensei que queríamos (geralmente) desativar o log quando o aplicativo foi implantado, mas a instrução if não seria removida.
Chessofnerd
2
no seu exemplo, as mensagens DEBUG serão mostradas, enquanto os WARNs não serão? você normalmente não gostaria de oposto?
22412 Sam
15
Use BuildConfig.DEBUG em vez de variáveis personalizadas
hB0
1
@chessofnerd "em Java, o código dentro do if nem fará parte do código compilado. Ele deve ser compilado, mas não será gravado no código de código compilado." stackoverflow.com/questions/7122723/…
stoooops
197

A documentação do Android diz o seguinte sobre os níveis de log :

O verboso nunca deve ser compilado em um aplicativo, exceto durante o desenvolvimento. Os logs de depuração são compilados, mas removidos em tempo de execução. Os registros de erro, aviso e informações são sempre mantidos.

Portanto, você pode considerar retirar as instruções de log de log detalhado, possivelmente usando o ProGuard, conforme sugerido em outra resposta .

De acordo com a documentação, você pode configurar o log em um dispositivo de desenvolvimento usando as Propriedades do sistema. A propriedade de conjunto é log.tag.<YourTag>e deve ser definido para um dos seguintes valores: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, ou SUPPRESS. Mais informações sobre isso estão disponíveis na documentação do isLoggable()método.

Você pode definir propriedades temporariamente usando o setpropcomando Por exemplo:

C:\android>adb shell setprop log.tag.MyAppTag WARN
C:\android>adb shell getprop log.tag.MyAppTag
WARN

Como alternativa, você pode especificá-los no arquivo '/data/local.prop' da seguinte maneira:

log.tag.MyAppTag=WARN

Versões posteriores do Android parecem exigir que /data/local.prop seja somente leitura . Esse arquivo é lido no momento da inicialização, portanto, você precisará reiniciar após a atualização. Se /data/local.propfor gravável no mundo, provavelmente será ignorado.

Finalmente, você pode configurá-los por meio de programação usando o System.setProperty()método .

Dave Webb
fonte
4
Eu tive a mesma experiência; os documentos da API não são muito claros sobre exatamente como isso deve funcionar e até parecem mencionar a maioria das android.util.Configconstantes obsoletas. Os valores codificados especificados nos documentos da API são inúteis, pois eles (supostamente) variam de acordo com a compilação. Portanto, a rota ProGuard parecia a melhor solução para nós.
Christopher Orr
3
Você teve alguma sorte em configurar o log do Android usando o arquivo /data/local.prop, o método setprop ou com um System.setProperty? Estou com problemas para obter um Log.isLoggable (TAG, VERBOSE) para retornar verdadeiro para mim.
Seanoshea
2
Eu consegui a depuração do Android funcionando. O truque é que, quando você chama algo como Log.d ("xyz"), a mensagem é gravada no logcat mesmo se a depuração estiver desativada para o criador de logs. Isso significa que a filtragem geralmente acontece após a gravação. Para filtrar antes algo como Log.isLoggable (TAG, Log.VERBOSE)) {Log.v (TAG, "minha mensagem de log"); } é preciso. Isso geralmente é bastante cansativo. Eu uso uma versão modificada do slf4j-android para obter o que quero.
Phreed
2
@ Dave você já conseguiu o método local.prop funcionando corretamente. Também não consigo fazer isso funcionar, criei uma entrada log.tag.test = INFO e tentei alterá-la executando setprop log.tag.test SUPPRESS no shell adb e isso não muda nada. Também usar System.getProperty e System.setProperty não faz nada. Queria receber uma atualização sua. Obrigado.
JjNford
2
+1 no comentário "os documentos da API não são muito claros sobre exatamente como isso deve funcionar".
Alan
90

A maneira mais fácil é provavelmente executar o JAR compilado através do ProGuard antes da implantação, com uma configuração como:

-assumenosideeffects class android.util.Log {
    public static int v(...);
}

Isso - além de todas as outras otimizações do ProGuard - removerá quaisquer instruções de log detalhadas diretamente do bytecode.

Christopher Orr
fonte
isso contém qualquer arquivo log.property onde podemos definir configurações.
que você
1
eliminar linhas com proguard significa que os rastreamentos de pilha da produção podem não estar alinhados com o seu código.
larham1
3
@ larham1: o ProGuard atua no bytecode, então eu imagino que remover as chamadas de log não alteraria os metadados do número da linha incorporada.
Christopher Orr
19
Lembre-se disso - mesmo que a chamada real para Log.v () esteja sendo removida, seus argumentos ainda serão avaliados. Portanto, se você tiver alguma chamada de método dispendiosa, por exemplo, Log.v (TAG, generateLog ()), poderá prejudicar seu desempenho se estiver em algum caminho de código ativo. Até coisas como toString () ou String.format () podem ser significativas.
Błażej Czapp
4
@GaneshKrishnan Não, isso não é verdade. A chamada para Log.v () é removida, mas, por padrão, as chamadas de método para criar a sequência não serão removidas. Veja esta resposta do autor do ProGuard: stackoverflow.com/a/6023505/234938
Christopher Orr
18

Peguei uma rota simples - criando uma classe de wrapper que também utiliza listas de parâmetros variáveis.

 public class Log{
        public static int LEVEL = android.util.Log.WARN;


    static public void d(String tag, String msgFormat, Object...args)
    {
        if (LEVEL<=android.util.Log.DEBUG)
        {
            android.util.Log.d(tag, String.format(msgFormat, args));
        }
    }

    static public void d(String tag, Throwable t, String msgFormat, Object...args)
    {
        if (LEVEL<=android.util.Log.DEBUG)
        {
            android.util.Log.d(tag, String.format(msgFormat, args), t);
        }
    }

    //...other level logging functions snipped
kdahlhaus
fonte
1
Como eu mencionei acima. Eu usei uma versão modificada do slf4j-android para implementar esta técnica.
Phreed
3
Há uma grande preocupação com isso, consulte stackoverflow.com/questions/2446248/…
OneWorld 20/13
10

A melhor maneira é usar a API SLF4J + parte de sua implementação.

Para aplicativos Android, você pode usar o seguinte:

  1. O Android Logger é a implementação SLF4J leve, mas fácil de configurar (<50 Kb).
  2. O LOGBack é a implementação mais poderosa e otimizada, mas seu tamanho é de cerca de 1 Mb.
  3. Qualquer outro pelo seu gosto: slf4j-android, slf4android.
Fortess Nsk
fonte
2
No Android, você precisaria usar logback-android(já que o logbackadequado é incompatível). logback-android-1.0.10-1.jartem 429 KB, o que não é muito ruim, considerando os recursos fornecidos, mas a maioria dos desenvolvedores usaria o Proguard para otimizar seu aplicativo de qualquer maneira.
precisa saber é
Isso não evita que você use as instruções if para verificar o nível do log antes do log. Veja stackoverflow.com/questions/4958860/…
OneWorld
8

Você deveria usar

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "my log message");
    }
Diego Torres Milano
fonte
2
Como configurar a saída de isLoggable? A depuração e o verbose não são registráveis ​​quando isDebugable é definido como falso no manifesto?
OneWorld 20/06
5

Retirar o log com proguard (ver resposta de @Christopher) foi fácil e rápido, mas fez com que os rastreamentos de pilha da produção não correspondessem à origem se houvesse algum log de depuração no arquivo.

Em vez disso, aqui está uma técnica que usa diferentes níveis de registro no desenvolvimento versus produção, supondo que o proguard seja usado apenas na produção. Ele reconhece a produção vendo se proguard renomeou o nome de uma determinada classe (no exemplo, eu uso "com.foo.Bar" - você a substitui por um nome de classe totalmente qualificado que você sabe que será renomeado por proguard).

Essa técnica utiliza o log comum.

private void initLogging() {
    Level level = Level.WARNING;
    try {
        // in production, the shrinker/obfuscator proguard will change the
        // name of this class (and many others) so in development, this
        // class WILL exist as named, and we will have debug level
        Class.forName("com.foo.Bar");
        level = Level.FINE;
    } catch (Throwable t) {
        // no problem, we are in production mode
    }
    Handler[] handlers = Logger.getLogger("").getHandlers();
    for (Handler handler : handlers) {
        Log.d("log init", "handler: " + handler.getClass().getName());
        handler.setLevel(level);
    }
}
larham1
fonte
3

Há um pequeno substituto para a classe Log padrão do Android - https://github.com/zserge/log

Basicamente, tudo o que você precisa fazer é substituir as importações de android.util.Logpara trikita.log.Log. Em seguida, no seu Application.onCreate()ou em algum inicializador estático, verifique o BuilConfig.DEBUGsinalizador ou qualquer outro sinalizador e use Log.level(Log.D)ou Log.level(Log.E)para alterar o nível mínimo de log. Você pode usar Log.useLog(false)para desativar o log.

Eric Weyant
fonte
2

Talvez você possa ver esta classe de extensão de log: https://github.com/dbauduin/Android-Tools/tree/master/logs .

Ele permite que você tenha um controle preciso dos logs. Você pode, por exemplo, desativar todos os logs ou apenas os logs de alguns pacotes ou classes.

Além disso, ele adiciona algumas funcionalidades úteis (por exemplo, você não precisa passar uma tag para cada log).

Asno
fonte
2

Eu criei um utilitário / wrapper que resolve esse problema + outros problemas comuns em relação ao log.

Um utilitário de depuração com os seguintes recursos:

  • Os recursos usuais fornecidos pela classe Log agrupados pelos LogMode s.
  • Logs de entrada e saída do método: pode ser desativado por um switch
  • Depuração seletiva: depura classes específicas.
  • Medição do tempo de execução do método: meça o tempo de execução de métodos individuais e o tempo coletivo gasto em todos os métodos de uma classe.

Como usar?

  • Inclua a classe no seu projeto.
  • Use-o como você usa os métodos android.util.Log, para começar.
  • Use o recurso de logs de entrada e saída fazendo chamadas para os métodos entry_log () - exit_log () no início e no final dos métodos em seu aplicativo.

Eu tentei tornar a documentação auto-suficiente.

Sugestões para melhorar este utilitário são bem-vindas.

Livre para usar / compartilhar.

Faça o download no GitHub .

Vinay W
fonte
2

Aqui está uma solução mais complexa. Você obterá o rastreamento completo da pilha e o método toString () será chamado apenas se necessário (Desempenho). O atributo BuildConfig.DEBUG será falso no modo de produção, para que todos os logs de rastreamento e depuração sejam removidos. O compilador de hot spot tem a chance de remover as chamadas devido às propriedades estáticas finais.

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import android.util.Log;

public class Logger {

    public enum Level {
        error, warn, info, debug, trace
    }

    private static final String DEFAULT_TAG = "Project";

    private static final Level CURRENT_LEVEL = BuildConfig.DEBUG ? Level.trace : Level.info;

    private static boolean isEnabled(Level l) {
        return CURRENT_LEVEL.compareTo(l) >= 0;
    }

    static {
        Log.i(DEFAULT_TAG, "log level: " + CURRENT_LEVEL.name());
    }

    private String classname = DEFAULT_TAG;

    public void setClassName(Class<?> c) {
        classname = c.getSimpleName();
    }

    public String getClassname() {
        return classname;
    }

    public boolean isError() {
        return isEnabled(Level.error);
    }

    public boolean isWarn() {
        return isEnabled(Level.warn);
    }

    public boolean isInfo() {
        return isEnabled(Level.info);
    }

    public boolean isDebug() {
        return isEnabled(Level.debug);
    }

    public boolean isTrace() {
        return isEnabled(Level.trace);
    }

    public void error(Object... args) {
        if (isError()) Log.e(buildTag(), build(args));
    }

    public void warn(Object... args) {
        if (isWarn()) Log.w(buildTag(), build(args));
    }

    public void info(Object... args) {
        if (isInfo()) Log.i(buildTag(), build(args));
    }

    public void debug(Object... args) {
        if (isDebug()) Log.d(buildTag(), build(args));
    }

    public void trace(Object... args) {
        if (isTrace()) Log.v(buildTag(), build(args));
    }

    public void error(String msg, Throwable t) {
        if (isError()) error(buildTag(), msg, stackToString(t));
    }

    public void warn(String msg, Throwable t) {
        if (isWarn()) warn(buildTag(), msg, stackToString(t));
    }

    public void info(String msg, Throwable t) {
        if (isInfo()) info(buildTag(), msg, stackToString(t));
    }

    public void debug(String msg, Throwable t) {
        if (isDebug()) debug(buildTag(), msg, stackToString(t));
    }

    public void trace(String msg, Throwable t) {
        if (isTrace()) trace(buildTag(), msg, stackToString(t));
    }

    private String buildTag() {
        String tag ;
        if (BuildConfig.DEBUG) {
            StringBuilder b = new StringBuilder(20);
            b.append(getClassname());

            StackTraceElement stackEntry = Thread.currentThread().getStackTrace()[4];
            if (stackEntry != null) {
                b.append('.');
                b.append(stackEntry.getMethodName());
                b.append(':');
                b.append(stackEntry.getLineNumber());
            }
            tag = b.toString();
        } else {
            tag = DEFAULT_TAG;
        }
    }

    private String build(Object... args) {
        if (args == null) {
            return "null";
        } else {
            StringBuilder b = new StringBuilder(args.length * 10);
            for (Object arg : args) {
                if (arg == null) {
                    b.append("null");
                } else {
                    b.append(arg);
                }
            }
            return b.toString();
        }
    }

    private String stackToString(Throwable t) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(500);
        baos.toString();
        t.printStackTrace(new PrintStream(baos));
        return baos.toString();
    }
}

use assim:

Loggor log = new Logger();
Map foo = ...
List bar = ...
log.error("Foo:", foo, "bar:", bar);
// bad example (avoid something like this)
// log.error("Foo:" + " foo.toString() + "bar:" + bar); 
Andreas Mager
fonte
1

Em um cenário de log muito simples, no qual você está literalmente tentando gravar no console durante o desenvolvimento para fins de depuração, pode ser mais fácil fazer uma pesquisa e substituir antes da produção produzir e comentar todas as chamadas para Log ou System. out.println.

Por exemplo, supondo que você não usou o "Log". em qualquer lugar fora de uma chamada para Log.d ou Log.e, etc, você pode simplesmente encontrar e substituir toda a solução para substituir "Log". com "// Log". para comentar todas as suas chamadas de log ou, no meu caso, estou apenas usando System.out.println em todos os lugares, portanto, antes de iniciar a produção, basta fazer uma pesquisa completa e substituir por "System.out.println" e substituir por "// System.out.println".

Eu sei que isso não é o ideal, e seria bom se a capacidade de encontrar e comentar chamadas para Log e System.out.println fosse incorporada ao Eclipse, mas até que isso aconteça, a maneira mais fácil, rápida e melhor de fazer isso é para comentar pesquisando e substituindo. Se você fizer isso, não precisará se preocupar com números de linhas de rastreamento de pilha incompatíveis, porque está editando seu código-fonte e não está adicionando nenhuma sobrecarga verificando algumas configurações no nível do log etc.

Jim
fonte
1

Nos meus aplicativos, tenho uma classe que agrupa a classe Log, que possui uma var booleana estática chamada "state". Em todo o meu código, verifico o valor da variável "state" usando um método estático antes de realmente escrever no Log. Em seguida, tenho um método estático para definir a variável "state" que garante que o valor seja comum em todas as instâncias criadas pelo aplicativo. Isso significa que posso ativar ou desativar todos os registros do aplicativo em uma chamada - mesmo quando o aplicativo estiver em execução. Útil para chamadas de suporte ... Isso significa que você precisa manter suas armas durante a depuração e não voltar a usar a classe Log padrão ...

Também é útil (conveniente) que o Java interprete uma var booleana como falsa se não tiver sido atribuído um valor, o que significa que ela pode ser deixada como falsa até que você precise ativar o log :-)

Darnst
fonte
1

Podemos usar a classe Logem nosso componente local e definir os métodos como v / i / e / d. Com base na necessidade de podermos fazer mais ligações.
exemplo é mostrado abaixo.

    public class Log{
        private static boolean TAG = false;
        public static void d(String enable_tag, String message,Object...args){
            if(TAG)
            android.util.Log.d(enable_tag, message+args);
        }
        public static void e(String enable_tag, String message,Object...args){
            if(TAG)
            android.util.Log.e(enable_tag, message+args);
        }
        public static void v(String enable_tag, String message,Object...args){
            if(TAG)
            android.util.Log.v(enable_tag, message+args);
        }
    }
    if we do not need any print(s), at-all make TAG as false for all else 
    remove the check for type of Log (say Log.d).
    as 
    public static void i(String enable_tag, String message,Object...args){
    //      if(TAG)
            android.util.Log.i(enable_tag, message+args);
    }

aqui a mensagem é para stringee argsé o valor que você deseja imprimir.

Gyanendra Tripathi
fonte
0

Para mim, geralmente é útil poder definir níveis de log diferentes para cada TAG.

Estou usando esta classe de invólucro muito simples:

public class Log2 {

    public enum LogLevels {
        VERBOSE(android.util.Log.VERBOSE), DEBUG(android.util.Log.DEBUG), INFO(android.util.Log.INFO), WARN(
                android.util.Log.WARN), ERROR(android.util.Log.ERROR);

        int level;

        private LogLevels(int logLevel) {
            level = logLevel;
        }

        public int getLevel() {
            return level;
        }
    };

    static private HashMap<String, Integer> logLevels = new HashMap<String, Integer>();

    public static void setLogLevel(String tag, LogLevels level) {
        logLevels.put(tag, level.getLevel());
    }

    public static int v(String tag, String msg) {
        return Log2.v(tag, msg, null);
    }

    public static int v(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.VERBOSE) {
                return -1;
            }
        }
        return Log.v(tag, msg, tr);
    }

    public static int d(String tag, String msg) {
        return Log2.d(tag, msg, null);
    }

    public static int d(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.DEBUG) {
                return -1;
            }
        }
        return Log.d(tag, msg);
    }

    public static int i(String tag, String msg) {
        return Log2.i(tag, msg, null);
    }

    public static int i(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.INFO) {
                return -1;
            }
        }
        return Log.i(tag, msg);
    }

    public static int w(String tag, String msg) {
        return Log2.w(tag, msg, null);
    }

    public static int w(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.WARN) {
                return -1;
            }
        }
        return Log.w(tag, msg, tr);
    }

    public static int e(String tag, String msg) {
        return Log2.e(tag, msg, null);
    }

    public static int e(String tag, String msg, Throwable tr) {
        if (logLevels.containsKey(tag)) {
            if (logLevels.get(tag) > android.util.Log.ERROR) {
                return -1;
            }
        }
        return Log.e(tag, msg, tr);
    }

}

Agora basta definir o nível de log por TAG no início de cada classe:

Log2.setLogLevel(TAG, LogLevels.INFO);
Jack Miller
fonte
0

Outra maneira é usar uma plataforma de registro que tenha os recursos de abrir e fechar registros. Às vezes, isso pode dar muita flexibilidade, mesmo em um aplicativo de produção, quais logs devem ser abertos e quais fechados, dependendo de quais problemas você tem, por exemplo:

Elisha Sterngold
fonte