¿Cómo habilito / deshabilito los niveles de registro en Android?

149

Tengo muchas declaraciones de registro para depurar, por ejemplo.

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

Al implementar esta aplicación en el teléfono del dispositivo, quiero desactivar el registro detallado desde donde puedo habilitar / deshabilitar el registro.

d-man
fuente
Posible duplicado de los niveles de registro

Respuestas:

80

Una forma común es crear un nivel de registro con nombre int y definir su nivel de depuración en función del nivel de registro.

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

Más tarde, puede cambiar el LOGLEVEL para todos los niveles de salida de depuración.

Cytown
fuente
1
bien, pero ¿cómo deshabilitarías DEBUG en tu ejemplo, pero aún mostrando advertencias ...?
Andre Bossard
1
¿No terminarían las declaraciones if en el código de bytes .apk? Pensé que queríamos (en general) desactivar el registro cuando se implementó la aplicación, pero la instrucción if no se eliminaría.
chessofnerd
2
en su ejemplo, se mostrarán los mensajes de DEPURACIÓN, mientras que los WARN no. ¿normalmente no te gustaría oponer?
Sam
15
Use BuildConfig.DEBUG en lugar de variables personalizadas
hB0
1
@chessofnerd "en Java, el código dentro del if ni siquiera formará parte del código compilado. Debe compilarse, pero no se escribirá en el bytecode compilado". stackoverflow.com/questions/7122723/…
stoooops
197

La documentación de Android dice lo siguiente sobre los niveles de registro :

Verbose nunca debe compilarse en una aplicación, excepto durante el desarrollo. Los registros de depuración se compilan pero se eliminan en tiempo de ejecución. Los registros de errores, advertencias e información siempre se mantienen.

Por lo tanto, es posible que desee considerar eliminar el registro de las declaraciones de registro detalladas, posiblemente utilizando ProGuard como se sugiere en otra respuesta .

De acuerdo con la documentación, puede configurar el inicio de sesión en un dispositivo de desarrollo utilizando las Propiedades del sistema. La propiedad de conjunto es log.tag.<YourTag>y debe ser ajustado a uno de los siguientes valores: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, o SUPPRESS. Más información sobre esto está disponible en la documentación para elisLoggable() método.

Puede establecer propiedades temporalmente usando el setpropcomando. Por ejemplo:

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

Alternativamente, puede especificarlos en el archivo '/data/local.prop' de la siguiente manera:

log.tag.MyAppTag=WARN

Parece que las versiones posteriores de Android requieren que /data/local.prop sea de solo lectura . Este archivo se lee en el momento del arranque, por lo que deberá reiniciarlo después de actualizarlo. Si /data/local.propse puede escribir en el mundo, es probable que se ignore.

Por último, se puede establecer mediante programación utilizando el System.setProperty()método .

Dave Webb
fuente
44
He tenido la misma experiencia; los documentos de la API no son claros acerca de cómo debería funcionar exactamente esto e incluso parecen mencionar la mayoría de las android.util.Configconstantes en desuso. Los valores codificados especificados en los documentos de API son inútiles ya que estos (supuestamente) varían según la compilación. Por lo tanto, la ruta ProGuard nos pareció la mejor solución.
Christopher Orr
3
¿Ha tenido suerte al configurar el registro de Android usando el archivo /data/local.prop, el método setprop o con un System.setProperty? Tengo muchos problemas para que Log.isLoggable (TAG, VERBOSE) me resulte verdadero.
seanoshea
2
He conseguido que la depuración de Android funcione. El truco es que cuando llamas a algo como Log.d ("xyz") el mensaje se escribe en logcat incluso si la depuración está desactivada para el registrador. Esto significa que el filtrado generalmente ocurre después de ser escrito. Para filtrar antes de algo como Log.isLoggable (TAG, Log.VERBOSE)) {Log.v (TAG, "mi mensaje de registro"); } es necesario. Esto es generalmente bastante cansador. Utilizo una versión modificada de slf4j-android para obtener lo que quiero.
Phreed
2
@Dave, ¿alguna vez pudiste hacer que el método local.prop funcionara correctamente? Tampoco puedo hacer que esto funcione, he creado una entrada log.tag.test = INFO y luego intenté cambiarla ejecutando setprop log.tag.test SUPPRESS desde el shell adb y no cambia nada. También usar System.getProperty y System.setProperty no hace nada. Quería recibir una actualización de usted. Gracias.
jjNford
2
+1 para comentar "los documentos de la API no son claros acerca de cómo debería funcionar exactamente".
Alan
90

Probablemente, la forma más fácil es ejecutar su JAR compilado a través de ProGuard antes de la implementación, con una configuración como:

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

Eso, aparte de todas las otras optimizaciones de ProGuard, eliminará cualquier declaración de registro detallada directamente del código de bytes.

Christopher Orr
fuente
contiene cualquier archivo log.property donde podamos definir la configuración.
d-man el
1
eliminar líneas con proguard significa que sus trazas de pila de producción pueden no alinearse con su código.
larham1
3
@ larham1: ProGuard actúa sobre el código de bytes, por lo que me imagino que eliminar las llamadas de registro no alteraría los metadatos del número de línea incrustado.
Christopher Orr
19
Tenga en cuenta esto: incluso si la llamada real a Log.v () se está eliminando, sus argumentos aún se evalúan. Entonces, si tiene alguna llamada de método costosa en el interior, por ejemplo, Log.v (TAG, generateLog ()) puede dañar su rendimiento si está en alguna ruta de código activo. Incluso cosas como toString () o String.format () pueden ser significativas.
Błażej Czapp
44
@GaneshKrishnan No, eso no es cierto. La llamada a Log.v () se elimina pero, de forma predeterminada, no se eliminarán las llamadas a métodos para construir la cadena. Vea esta respuesta del autor de ProGuard: stackoverflow.com/a/6023505/234938
Christopher Orr
18

Tomé una ruta simple: crear una clase de contenedor que también utiliza listas de parámetros variables.

 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
fuente
1
Como mencioné anteriormente. He usado una versión modificada de slf4j-android para implementar esta técnica.
Phreed
3
Hay una gran preocupación al respecto, consulte stackoverflow.com/questions/2446248/…
OneWorld
10

La mejor manera es usar SLF4J API + parte de su implementación.

Para las aplicaciones de Android, puede usar lo siguiente:

  1. Android Logger es la implementación de SLF4J ligera pero fácil de configurar (<50 Kb).
  2. LOGBack es la implementación más potente y optimizada, pero su tamaño es de aproximadamente 1 Mb.
  3. Cualquier otro según su gusto: slf4j-android, slf4android.
Fortess Nsk
fuente
2
En Android, tendría que usar logback-android(ya que lo logbackcorrecto es incompatible). logback-android-1.0.10-1.jares de 429 KB, lo que no está tan mal teniendo en cuenta las características proporcionadas, pero la mayoría de los desarrolladores usarían Proguard para optimizar su aplicación de todos modos.
tony19
Esto no le ahorra el uso de sentencias if para verificar el nivel de registro antes de iniciar sesión. Ver stackoverflow.com/questions/4958860/…
OneWorld
8

Deberías usar

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "my log message");
    }
Diego Torres Milano
fuente
2
¿Cómo configurar la salida de isLoggable? ¿La depuración y el detalle no son registrables cuando isDebugable se configura como falso en manifiesto?
OneWorld
5

Eliminar el registro con proguard (ver respuesta de @Christopher) fue fácil y rápido, pero provocó que los rastros de la pila de la producción no coincidan con la fuente si hubo algún registro de depuración en el archivo.

En cambio, aquí hay una técnica que utiliza diferentes niveles de registro en el desarrollo frente a la producción, suponiendo que proguard se use solo en la producción. Reconoce la producción al ver si proguard ha cambiado el nombre de un nombre de clase dado (en el ejemplo, uso "com.foo.Bar"; usted reemplazaría esto con un nombre de clase completamente calificado que sabe que cambiará el nombre de proguard).

Esta técnica hace uso del registro de bienes comunes.

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
fuente
3

Hay un pequeño reemplazo directo para la clase de registro de Android estándar: https://github.com/zserge/log

Básicamente, todo lo que tiene que hacer es reemplazar las importaciones de android.util.Loga trikita.log.Log. Luego, en su Application.onCreate()o en algún inicializador estático, verifique el BuilConfig.DEBUGindicador o cualquier otro y use Log.level(Log.D)o Log.level(Log.E)para cambiar el nivel mínimo de registro. Puede usar Log.useLog(false)para deshabilitar el registro en absoluto.

Eric Weyant
fuente
2

Puede ser que pueda ver esta clase de extensión de registro: https://github.com/dbauduin/Android-Tools/tree/master/logs .

Le permite tener un control preciso sobre los registros. Por ejemplo, puede deshabilitar todos los registros o solo los registros de algunos paquetes o clases.

Además, agrega algunas funcionalidades útiles (por ejemplo, no tiene que pasar una etiqueta para cada registro).

Burro
fuente
2

Creé una utilidad / envoltorio que resuelve este problema + otros problemas comunes relacionados con el registro.

Una utilidad de depuración con las siguientes características:

  • Las características habituales proporcionadas por la clase Log envueltas por LogMode s.
  • Método Registros de entrada y salida: se puede apagar con un interruptor
  • Depuración selectiva: depuración de clases específicas.
  • Método de medición del tiempo de ejecución: Mida el tiempo de ejecución de los métodos individuales y el tiempo colectivo dedicado a todos los métodos de una clase.

¿Cómo utilizar?

  • Incluye la clase en tu proyecto.
  • Úselo como si usara los métodos android.util.Log, para empezar.
  • Utilice la función de registros de Entrada-Salida realizando llamadas a los métodos entry_log () - exit_log () al principio y al final de los métodos en su aplicación.

He intentado que la documentación sea autosuficiente.

Las sugerencias para mejorar esta utilidad son bienvenidas.

Gratis para usar / compartir.

Descárgalo desde GitHub .

Vinay W
fuente
2

Aquí hay una solución más compleja. Obtendrá un seguimiento completo de la pila y se llamará al método toString () solo si es necesario (Rendimiento). El atributo BuildConfig.DEBUG será falso en el modo de producción, por lo que se eliminarán todos los registros de rastreo y depuración. El compilador de hot spot tiene la posibilidad de eliminar las llamadas debido a las propiedades estáticas finales.

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();
    }
}

usar así:

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
fuente
1

En un escenario de registro muy simple, donde literalmente solo está tratando de escribir en la consola durante el desarrollo con fines de depuración, podría ser más fácil simplemente hacer una búsqueda y reemplazar antes de su producción, y comentar todas las llamadas al Registro o al Sistema. out.println.

Por ejemplo, suponiendo que no haya usado el "Registro". en cualquier lugar fuera de una llamada a Log.d o Log.e, etc., simplemente puede buscar y reemplazar toda la solución para reemplazar "Log". con "// Registro". para comentar todas sus llamadas de registro, o en mi caso solo estoy usando System.out.println en todas partes, así que antes de pasar a producción simplemente haré una búsqueda completa y reemplazaré por "System.out.println" y lo reemplazaré con "//System.out.println".

Sé que esto no es ideal, y sería bueno si la capacidad de encontrar y comentar llamadas a Log and System.out.println se integrara en Eclipse, pero hasta que eso ocurra, la forma más fácil, rápida y mejor de hacerlo es comentar mediante búsqueda y reemplazo. Si hace esto, no tiene que preocuparse por los números de línea de rastreo de pila que no coinciden, porque está editando su código fuente y no está agregando ninguna sobrecarga al verificar alguna configuración de nivel de registro, etc.

Jim
fuente
1

En mis aplicaciones tengo una clase que envuelve la clase Log que tiene una var booleana estática llamada "estado". A lo largo de mi código verifico el valor de la variable "estado" usando un método estático antes de escribir en el Log. Luego tengo un método estático para establecer la variable "estado" que garantiza que el valor sea común en todas las instancias creadas por la aplicación. Esto significa que puedo habilitar o deshabilitar todos los registros de la aplicación en una sola llamada, incluso cuando la aplicación se está ejecutando. Útil para llamadas de soporte ... Sin embargo, significa que debe atenerse a sus armas al depurar y no retroceder al uso de la clase de registro estándar ...

También es útil (conveniente) que Java interprete una var booleana como falsa si no se le ha asignado un valor, lo que significa que se puede dejar como falsa hasta que necesite activar el registro :-)

Darnst
fuente
1

Podemos usar la clase Logen nuestro componente local y definir los métodos como v / i / e / d. En función de la necesidad de podemos hacer más llamadas.
El ejemplo se muestra a continuación.

    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);
    }

aquí el mensaje es para stringy argses el valor que desea imprimir.

Gyanendra Tripathi
fuente
0

Para mí, a menudo es útil poder establecer diferentes niveles de registro para cada TAG.

Estoy usando esta clase de contenedor muy simple:

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);
    }

}

Ahora solo establezca el nivel de registro por TAG al comienzo de cada clase:

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

Otra forma es utilizar una plataforma de registro que tenga la capacidad de abrir y cerrar registros. Esto puede dar mucha flexibilidad a veces, incluso en una aplicación de producción cuyos registros deberían estar abiertos y cerrados, dependiendo de los problemas que tenga, por ejemplo:

Elisha Sterngold
fuente