Wie aktiviere / deaktiviere ich Protokollstufen in Android?

149

Ich habe zum Beispiel viele Protokollierungsanweisungen zum Debuggen.

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

Während der Bereitstellung dieser Anwendung auf dem Gerätetelefon möchte ich die ausführliche Protokollierung deaktivieren, von wo aus ich die Protokollierung aktivieren / deaktivieren kann.

D-Mann
quelle
Mögliches Duplikat der Android-Protokollierungsstufen

Antworten:

80

Eine übliche Methode besteht darin, ein int mit dem Namen loglevel zu erstellen und dessen Debug-Level basierend auf loglevel zu definieren.

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

Später können Sie einfach die LOGLEVEL für alle Debug-Ausgabepegel ändern.

Cytown
quelle
1
schön, aber wie würden Sie DEBUG in Ihrem Beispiel deaktivieren, aber immer noch Warnungen
Andre Bossard
1
Würden die if-Anweisungen nicht im APK-Bytecode enden? Ich dachte, wir wollten (im Allgemeinen) die Protokollierung deaktivieren, wenn die Anwendung bereitgestellt wurde, aber die if-Anweisung würde nicht entfernt.
Schachofnerd
2
In Ihrem Beispiel werden DEBUG-Meldungen angezeigt, WARNs nicht? Würdest du normalerweise nicht das Gegenteil wollen?
Sam
15
Verwenden Sie BuildConfig.DEBUG anstelle von benutzerdefinierten Variablen
hB0
1
@chessofnerd "In Java ist der Code im if nicht einmal Teil des kompilierten Codes. Er muss kompiliert werden, wird aber nicht in den kompilierten Bytecode geschrieben." stackoverflow.com/questions/7122723/…
stoooops
197

In der Android-Dokumentation wird Folgendes zu Protokollstufen angegeben :

Verbose sollte nur während der Entwicklung in eine Anwendung kompiliert werden. Debug-Protokolle werden kompiliert, aber zur Laufzeit entfernt. Fehler-, Warn- und Infoprotokolle werden immer geführt.

Daher sollten Sie in Betracht ziehen, das Protokoll zu entfernen. Ausführliche Protokollierungsanweisungen, möglicherweise unter Verwendung von ProGuard, wie in einer anderen Antwort vorgeschlagen .

Gemäß der Dokumentation können Sie die Protokollierung auf einem Entwicklungsgerät mithilfe der Systemeigenschaften konfigurieren. Die Eigenschaft Satz ist , log.tag.<YourTag>und es sollte auf einem der folgenden Werte gesetzt werden: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, oder SUPPRESS. Weitere Informationen hierzu finden Sie in der Dokumentation zur isLoggable()Methode.

Mit dem setpropBefehl können Sie Eigenschaften vorübergehend festlegen . Beispielsweise:

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

Alternativ können Sie sie in der Datei '/data/local.prop' wie folgt angeben:

log.tag.MyAppTag=WARN

Spätere Versionen von Android erfordern anscheinend, dass /data/local.prop schreibgeschützt ist . Diese Datei wird beim Booten gelesen, sodass Sie sie nach dem Aktualisieren neu starten müssen. Wenn /data/local.propdie Welt beschreibbar ist, wird sie wahrscheinlich ignoriert.

Schließlich können Sie sie mithilfe der System.setProperty()Methode programmgesteuert festlegen .

Dave Webb
quelle
4
Ich habe die gleiche Erfahrung gemacht; Die API-Dokumente sind ziemlich unklar, wie dies genau funktionieren soll, und scheinen sogar die meisten android.util.ConfigKonstanten zu erwähnen, die veraltet sind. Die in den API-Dokumenten angegebenen fest codierten Werte sind nutzlos, da diese (angeblich) je nach Build variieren. Daher schien die ProGuard-Route die beste Lösung für uns zu sein.
Christopher Orr
3
Haben Sie Glück gehabt, die Android-Protokollierung entweder mit der Datei /data/local.prop, der setprop-Methode oder mit einer System.setProperty zu konfigurieren? Ich habe einige Probleme damit, ein Log.isLoggable (TAG, VERBOSE) für mich als wahr zurückzugeben.
Seanoshea
2
Ich habe Android Debugging zum Laufen gebracht. Der Trick ist, dass beim Aufrufen von Log.d ("xyz") die Nachricht in logcat geschrieben wird, auch wenn das Debuggen für den Logger deaktiviert ist. Dies bedeutet, dass die Filterung im Allgemeinen nach dem Schreiben erfolgt. Um vor etwas wie Log.isLoggable (TAG, Log.VERBOSE) zu filtern) {Log.v (TAG, "my log message"); } wird gebraucht. Das ist im Allgemeinen ziemlich lästig. Ich benutze eine modifizierte Version von slf4j-android, um zu bekommen, was ich will.
phreed
2
@ Dave war jemals in der Lage, die local.prop-Methode zum Funktionieren zu bringen. Ich kann dies auch nicht zum Laufen bringen. Ich habe einen Eintrag log.tag.test = INFO erstellt und dann versucht, ihn mit setprop log.tag.test SUPPRESS in der ADB-Shell zu ändern, und er ändert nichts. Auch die Verwendung von System.getProperty und System.setProperty führt zu nichts. Wollte ein Update von dir bekommen. Vielen Dank.
JJNford
2
+1 für Kommentar "Die API-Dokumente sind ziemlich unklar, wie genau dies funktionieren soll".
Alan
90

Der einfachste Weg ist wahrscheinlich, Ihre kompilierte JAR vor der Bereitstellung über ProGuard auszuführen. Die Konfiguration lautet wie folgt :

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

Dadurch werden - abgesehen von allen anderen ProGuard-Optimierungen - alle ausführlichen Protokollanweisungen direkt aus dem Bytecode entfernt.

Christopher Orr
quelle
do it enthält eine log.property-Datei, in der wir Einstellungen definieren können.
D-Mann
1
Das Entfernen von Zeilen mit Proguard bedeutet, dass Ihre Stapelspuren aus der Produktion möglicherweise nicht mit Ihrem Code übereinstimmen.
Larham1
3
@ larham1: ProGuard wirkt auf den Bytecode, daher würde ich mir vorstellen, dass das Entfernen der Protokollierungsaufrufe die Metadaten der eingebetteten Zeilennummer nicht ändert.
Christopher Orr
19
Beachten Sie Folgendes: Auch wenn der eigentliche Aufruf von Log.v () entfernt wird, werden seine Argumente weiterhin ausgewertet. Wenn Sie also einen kostspieligen Methodenaufruf im Inneren haben, z. B. Log.v (TAG, generateLog ()), kann dies Ihre Leistung beeinträchtigen, wenn es sich in einem Hot-Code-Pfad befindet. Sogar Dinge wie toString () oder String.format () können von Bedeutung sein.
Błażej Czapp
4
@ GaneshKrishnan Nein, das stimmt nicht. Der Aufruf von Log.v () wird entfernt, aber standardmäßig werden Methodenaufrufe zum Erstellen der Zeichenfolge nicht entfernt. Siehe diese Antwort vom Autor von ProGuard: stackoverflow.com/a/6023505/234938
Christopher Orr
18

Ich habe einen einfachen Weg eingeschlagen - eine Wrapper-Klasse zu erstellen, die auch variable Parameterlisten verwendet.

 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
quelle
1
Wie ich oben erwähnt habe. Ich habe eine modifizierte Version von slf4j-android verwendet, um diese Technik zu implementieren.
phreed
3
Es gibt große Bedenken, siehe stackoverflow.com/questions/2446248/…
OneWorld
10

Der bessere Weg ist die Verwendung der SLF4J-API + eines Teils ihrer Implementierung.

Für Android-Anwendungen können Sie Folgendes verwenden:

  1. Android Logger ist die leichte, aber einfach zu konfigurierende SLF4J-Implementierung (<50 KB).
  2. LOGBack ist die leistungsstärkste und optimierteste Implementierung, hat jedoch eine Größe von ca. 1 MB.
  3. Alle anderen nach Ihrem Geschmack: slf4j-android, slf4android.
Fortess Nsk
quelle
2
Unter Android müssten Sie verwenden logback-android(da logbackrichtig nicht kompatibel ist). logback-android-1.0.10-1.jarist 429 KB, was angesichts der bereitgestellten Funktionen nicht schlecht ist, aber die meisten Entwickler würden Proguard verwenden, um ihre Anwendung trotzdem zu optimieren.
Tony19
Dies erspart Ihnen nicht die Verwendung von if-Anweisungen zum Überprüfen der Protokollebene vor der Protokollierung. Siehe stackoverflow.com/questions/4958860/…
OneWorld
8

Du solltest benutzen

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "my log message");
    }
Diego Torres Milano
quelle
2
Wie konfiguriere ich die Ausgabe von isLoggable? Sind Debug und Verbose nicht protokollierbar, wenn isDebugable im Manifest auf false gesetzt ist?
OneWorld
5

Das Entfernen der Protokollierung mit Proguard (siehe Antwort von @Christopher) war einfach und schnell, führte jedoch dazu, dass Stapelspuren aus der Produktion nicht mit der Quelle übereinstimmten, wenn in der Datei eine Debug-Protokollierung vorhanden war.

Stattdessen wird hier eine Technik verwendet, die unterschiedliche Protokollierungsstufen in der Entwicklung und in der Produktion verwendet, vorausgesetzt, Proguard wird nur in der Produktion verwendet. Es erkennt die Produktion, indem es prüft, ob proguard einen bestimmten Klassennamen umbenannt hat (im Beispiel verwende ich "com.foo.Bar" - Sie würden dies durch einen vollständig qualifizierten Klassennamen ersetzen, von dem Sie wissen, dass er von proguard umbenannt wird).

Diese Technik verwendet die Commons-Protokollierung.

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

Es gibt einen winzigen Ersatz für die Standard-Android-Log-Klasse - https://github.com/zserge/log

Grundsätzlich müssen Sie nur die Importe von android.util.Lognach ersetzen trikita.log.Log. Application.onCreate()Suchen Sie dann in Ihrem oder einem statischen Initializer nach dem BuilConfig.DEBUGoder einem anderen Flag und verwenden Log.level(Log.D)oder Log.level(Log.E)ändern Sie die minimale Protokollstufe. Sie können die Log.useLog(false)Protokollierung überhaupt deaktivieren.

Eric Weyant
quelle
2

Möglicherweise können Sie diese Protokollerweiterungsklasse sehen: https://github.com/dbauduin/Android-Tools/tree/master/logs .

Damit können Sie die Protokolle genau steuern. Sie können beispielsweise alle Protokolle oder nur die Protokolle einiger Pakete oder Klassen deaktivieren.

Darüber hinaus werden einige nützliche Funktionen hinzugefügt (z. B. müssen Sie nicht für jedes Protokoll ein Tag übergeben).

Esel
quelle
2

Ich habe ein Dienstprogramm / Wrapper erstellt, das dieses Problem und andere häufig auftretende Probleme im Zusammenhang mit der Protokollierung löst.

Ein Debugging-Dienstprogramm mit folgenden Funktionen:

  • Die üblichen Funktionen der Log-Klasse, die von LogMode s umschlossen werden .
  • Methode Entry-Exit-Protokolle: Kann durch einen Schalter ausgeschaltet werden
  • Selektives Debuggen: Debuggen Sie bestimmte Klassen.
  • Messung der Methodenausführungszeit: Messen Sie die Ausführungszeit für einzelne Methoden sowie die Gesamtzeit, die für alle Methoden einer Klasse aufgewendet wurde.

Wie benutzt man?

  • Nehmen Sie die Klasse in Ihr Projekt auf.
  • Verwenden Sie es zunächst so, wie Sie die Methoden android.util.Log verwenden.
  • Verwenden Sie die Funktion "Entry-Exit-Protokolle", indem Sie die Methoden entry_log () - exit_log () am Anfang und Ende der Methoden in Ihrer App aufrufen.

Ich habe versucht, die Dokumentation autark zu machen.

Vorschläge zur Verbesserung dieses Dienstprogramms sind willkommen.

Kostenlos zu nutzen / zu teilen.

Laden Sie es von GitHub herunter .

Vinay W.
quelle
2

Hier ist eine komplexere Lösung. Sie erhalten eine vollständige Stapelverfolgung und die Methode toString () wird nur bei Bedarf aufgerufen (Leistung). Das Attribut BuildConfig.DEBUG ist im Produktionsmodus falsch, sodass alle Trace- und Debug-Protokolle entfernt werden. Der Hot-Spot-Compiler hat die Möglichkeit, die Aufrufe aufgrund der endgültigen statischen Eigenschaften zu entfernen.

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

Verwendung wie folgt:

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

In einem sehr einfachen Protokollierungsszenario, in dem Sie während der Entwicklung buchstäblich nur versuchen, zu Debugging-Zwecken in die Konsole zu schreiben, ist es möglicherweise am einfachsten, vor dem Erstellen der Produktion einfach zu suchen und zu ersetzen und alle Aufrufe von Log oder System zu kommentieren. out.println.

Angenommen, Sie haben das "Protokoll" nicht verwendet. Überall außerhalb eines Aufrufs von Log.d oder Log.e usw. können Sie einfach die gesamte Lösung suchen und ersetzen, um "Log" zu ersetzen. mit "// Log." Um alle Ihre Protokollierungsaufrufe zu kommentieren, oder in meinem Fall verwende ich nur System.out.println überall. Bevor ich zur Produktion gehe, mache ich einfach eine vollständige Suche und ersetze nach "System.out.println" und ersetze durch "//System.out.println".

Ich weiß, dass dies nicht ideal ist, und es wäre schön, wenn die Möglichkeit, Aufrufe von Log und System.out.println zu finden und zu kommentieren, in Eclipse integriert wäre. Bis dahin ist dies jedoch der einfachste, schnellste und beste Weg durch Suchen und Ersetzen auskommentieren. Wenn Sie dies tun, müssen Sie sich keine Gedanken über nicht übereinstimmende Stack-Trace-Zeilennummern machen, da Sie Ihren Quellcode bearbeiten und keinen Overhead hinzufügen, indem Sie die Konfiguration auf Protokollebene usw. überprüfen.

Jim
quelle
1

In meinen Apps habe ich eine Klasse, die die Log-Klasse umschließt, die eine statische boolesche Variable namens "state" hat. Während meines gesamten Codes überprüfe ich den Wert der Variablen "state" mit einer statischen Methode, bevor ich tatsächlich in das Protokoll schreibe. Ich habe dann eine statische Methode, um die Variable "state" festzulegen, die sicherstellt, dass der Wert für alle von der App erstellten Instanzen gleich ist. Dies bedeutet, dass ich die gesamte Protokollierung für die App in einem Aufruf aktivieren oder deaktivieren kann - auch wenn die App ausgeführt wird. Nützlich für Supportanrufe ... Es bedeutet, dass Sie sich beim Debuggen an Ihre Waffen halten müssen und nicht auf die Verwendung der Standard-Log-Klasse zurückgreifen müssen ...

Es ist auch nützlich (praktisch), dass Java eine boolesche Variable als false interpretiert, wenn ihr kein Wert zugewiesen wurde. Dies bedeutet, dass sie als false belassen werden kann, bis Sie die Protokollierung aktivieren müssen :-)

Darnst
quelle
1

Wir können die Klasse Login unserer lokalen Komponente verwenden und die Methoden als v / i / e / d definieren. Je nach Bedarf können wir weiter telefonieren.
Beispiel ist unten gezeigt.

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

Hier ist die Nachricht für stringund und argsist der Wert, den Sie drucken möchten.

Gyanendra Tripathi
quelle
0

Für mich ist es oft nützlich, für jeden TAG unterschiedliche Protokollstufen festlegen zu können.

Ich benutze diese sehr einfache Wrapper-Klasse:

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

}

Stellen Sie jetzt einfach die Protokollstufe pro TAG am Anfang jeder Klasse ein:

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

Eine andere Möglichkeit besteht darin, eine Protokollierungsplattform zu verwenden, mit der Protokolle geöffnet und geschlossen werden können. Dies kann manchmal sogar in einer Produktions-App viel Flexibilität bieten, welche Protokolle geöffnet und welche geschlossen sein sollten, je nachdem, welche Probleme Sie beispielsweise haben:

Elisha Sterngold
quelle