Jak włączyć / wyłączyć poziomy logowania w systemie Android?


149

Na przykład mam wiele instrukcji logowania do debugowania.

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

podczas wdrażania tej aplikacji na telefonie urządzenia chcę wyłączyć szczegółowe rejestrowanie, z którego mogę włączyć / wyłączyć rejestrowanie.


Możliwe duplikaty poziomów logowania Androida

Odpowiedzi:


80

Typowym sposobem jest utworzenie int o nazwie loglevel i zdefiniowanie jego poziomu debugowania w oparciu o 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

Później możesz po prostu zmienić LOGLEVEL dla wszystkich poziomów wyjściowych debugowania.


1
fajnie, ale jak byś wyłączył DEBUG w swoim przykładzie, ale nadal wyświetla ostrzeżenia ...
Andre Bossard

1
Czy instrukcje if nie trafiłyby do kodu bajtowego .apk? Pomyślałem, że chcieliśmy (ogólnie) wyłączyć rejestrowanie po wdrożeniu aplikacji, ale instrukcja if nie zostanie usunięta.
chessofnerd

2
w twoim przykładzie komunikaty DEBUG zostaną wyświetlone, a WARN nie? czy normalnie nie chciałbyś czegoś przeciwnego?
Sam

15
Użyj BuildConfig.DEBUG zamiast zmiennych niestandardowych
hB0

1
@chessofnerd "w Javie, kod wewnątrz if nie będzie nawet częścią skompilowanego kodu. Musi się skompilować, ale nie zostanie zapisany w skompilowanym kodzie bajtowym." stackoverflow.com/questions/7122723/ ...
stoooops

197

Android Dokumentacja mówi o następujące poziomy rejestrowania :

Informacje szczegółowe nigdy nie powinny być kompilowane do aplikacji, chyba że podczas programowania. Dzienniki debugowania są kompilowane, ale usuwane w czasie wykonywania. Dzienniki błędów, ostrzeżeń i informacji są zawsze przechowywane.

Możesz więc rozważyć usunięcie z dziennika informacji o szczegółowym wylogowaniu, być może przy użyciu ProGuard, jak sugeruje inna odpowiedź .

Zgodnie z dokumentacją możesz skonfigurować logowanie na urządzeniu deweloperskim za pomocą Właściwości systemu. Nieruchomość do zestawu jest log.tag.<YourTag>i powinien być ustawiony na jedną z następujących wartości: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, lub SUPPRESS. Więcej informacji na ten temat można znaleźć w dokumentacji programuisLoggable() metody.

Możesz tymczasowo ustawić właściwości za pomocą setproppolecenia. Na przykład:

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

Alternatywnie możesz określić je w pliku „/data/local.prop” w następujący sposób:

log.tag.MyAppTag=WARN

Wydaje się, że późniejsze wersje Androida wymagają, aby plik /data/local.prop był tylko do odczytu . Ten plik jest odczytywany podczas rozruchu, więc po zaktualizowaniu należy go ponownie uruchomić. Jeśli /data/local.propświat jest zapisywalny, prawdopodobnie zostanie zignorowany.

Na koniec możesz ustawić je programowo za pomocą System.setProperty()metody .


4
Miałem to samo doświadczenie; Dokumentacja API jest dość niejasna, jak dokładnie to powinno działać, a nawet wydaje się, że wspominają o większości android.util.Configwycofywanych stałych. Zakodowane wartości określone w dokumentacji API są bezużyteczne, ponieważ (podobno) różnią się w zależności od kompilacji. Stąd trasa ProGuard wydawała się dla nas najlepszym rozwiązaniem.
Christopher Orr,

3
Czy miałeś jakieś szczęście w konfigurowaniu rejestrowania w systemie Android przy użyciu pliku /data/local.prop, metody setprop lub System.setProperty? Mam spore problemy ze znalezieniem Log.isLoggable (TAG, VERBOSE), aby zwrócić prawdę.
seanoshea

2
Mam działające debugowanie Androida. Sztuczka polega na tym, że gdy wywołujesz coś takiego jak Log.d ("xyz"), komunikat jest zapisywany w logcat, nawet jeśli debugowanie jest wyłączone dla rejestratora. Oznacza to, że filtrowanie zwykle następuje po napisaniu. Aby filtrować przed czymś takim jak Log.isLoggable (TAG, Log.VERBOSE)) {Log.v (TAG, "mój komunikat dziennika"); } jest potrzebne. Generalnie jest to dość męczące. Używam zmodyfikowanej wersji slf4j-android, aby uzyskać to, czego chcę.
wysłano

2
@Dave czy kiedykolwiek udało Ci się sprawić, aby metoda local.prop działała poprawnie. Ja też nie jestem w stanie tego zrobić, utworzyłem wpis log.tag.test = INFO, a następnie próbowałem go zmienić, uruchamiając setprop log.tag.test SUPPRESS z powłoki adb i nic to nie zmienia. Również użycie System.getProperty i System.setProperty nic nie robi. Chciałem uzyskać od ciebie aktualizację. Dzięki.
jjNford,

2
+1 dla komentarza „Dokumentacja API jest dość niejasna, jak to powinno działać”.
Alan

90

Najłatwiejszym sposobem jest prawdopodobnie uruchomienie skompilowanego JARa przez ProGuard przed wdrożeniem, z konfiguracją taką jak:

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

Spowoduje to - poza wszystkimi innymi optymalizacjami ProGuard - usunięcie wszelkich szczegółowych instrukcji dziennika bezpośrednio z kodu bajtowego.


do it zawiera dowolny plik log.property, w którym możemy zdefiniować ustawienia.
d-man,

1
usunięcie wierszy za pomocą programu proguard oznacza, że ​​ślady stosu z produkcji mogą nie zgadzać się z kodem.
larham1

3
@ larham1: ProGuard działa na kodzie bajtowym, więc wyobrażam sobie, że usunięcie wywołań rejestrowania nie zmieniłoby osadzonych metadanych numeru linii.
Christopher Orr

19
Należy pamiętać - nawet jeśli rzeczywiste wywołanie Log.v () jest usuwane, jego argumenty są nadal oceniane. Więc jeśli masz wewnątrz jakieś kosztowne wywołanie metody, np. Log.v (TAG, generationLog ()), może to zaszkodzić wydajności, jeśli znajduje się w jakiejś gorącej ścieżce kodu. Nawet rzeczy takie jak toString () lub String.format () mogą mieć znaczenie.
Błażej Czapp,

4
@GaneshKrishnan Nie, to nieprawda. Wywołanie Log.v () jest usuwane, ale domyślnie wywołania metod w celu zbudowania ciągu nie zostaną usunięte. Zobacz tę odpowiedź od autora ProGuard: stackoverflow.com/a/6023505/234938
Christopher Orr,

18

Obrałem prostą drogę - tworząc klasę opakowania, która również korzysta z list parametrów zmiennych.

 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

1
Jak wspomniałem powyżej. Do zaimplementowania tej techniki użyłem zmodyfikowanej wersji slf4j-android.
wysłano

3
Jest to duży problem, patrz stackoverflow.com/questions/2446248/ ...
OneWorld

10

Lepszym sposobem jest użycie SLF4J API + część jego implementacji.

W przypadku aplikacji na Androida możesz użyć:

  1. Android Logger to lekka, ale łatwa w konfiguracji implementacja SLF4J (<50 Kb).
  2. LOGBack to najpotężniejsza i zoptymalizowana implementacja, ale jej rozmiar to około 1 Mb.
  3. Każdy inny według twojego gustu: slf4j-android, slf4android.

2
Na Androidzie musiałbyś użyć logback-android(ponieważ logbackwłaściwy jest niekompatybilny). logback-android-1.0.10-1.jarto 429 KB, co nie jest takie złe, biorąc pod uwagę dostarczone funkcje, ale większość programistów i tak używałaby Proguard do optymalizacji swojej aplikacji.
tony19

Nie chroni to użytkownika przed używaniem instrukcji if do sprawdzania poziomu dziennika przed zalogowaniem. Zobacz stackoverflow.com/questions/4958860/ ...
OneWorld

8

Powinieneś użyć

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "my log message");
    }

2
Jak skonfigurować wyjście isLoggable? Czy debugowanie i rozwiązywanie problemów nie są rejestrowane, gdy isDebugable ma wartość false w manifeście?
OneWorld

5

Usunięcie logowania za pomocą programu proguard (patrz odpowiedź od @Christopher) było łatwe i szybkie, ale powodowało, że ślady stosu z produkcji powodowały niezgodność źródła, jeśli w pliku było jakiekolwiek logowanie debugowania.

Zamiast tego mamy do czynienia z techniką, która wykorzystuje różne poziomy rejestrowania w rozwoju w porównaniu z produkcją, przy założeniu, że proguard jest używany tylko w produkcji. Rozpoznaje produkcję, sprawdzając, czy program proguard zmienił nazwę danej klasy (w tym przykładzie używam „com.foo.Bar” - należy ją zastąpić w pełni kwalifikowaną nazwą klasy, o której wiadomo, że zostanie zmieniona przez program proguard).

Ta technika wykorzystuje wspólne logowanie.

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


3

Istnieje niewielki zamiennik standardowej klasy Android Log - https://github.com/zserge/log

Zasadniczo wszystko, co musisz zrobić, to zastąpić import z android.util.Logdo trikita.log.Log. Następnie w swoim Application.onCreate()lub w jakimś statycznym inicjatorze sprawdź, BuilConfig.DEBUGczy nie ma flagi lub innej flagi i użyj Log.level(Log.D)lub, Log.level(Log.E)aby zmienić minimalny poziom dziennika. Możesz użyć, Log.useLog(false)aby w ogóle wyłączyć logowanie.


2

Być może możesz zobaczyć tę klasę rozszerzenia dziennika: https://github.com/dbauduin/Android-Tools/tree/master/logs .

Umożliwia precyzyjną kontrolę dzienników. Możesz na przykład wyłączyć wszystkie dzienniki lub tylko dzienniki niektórych pakietów lub klas.

Ponadto dodaje kilka przydatnych funkcjonalności (na przykład nie musisz podawać tagu dla każdego dziennika).


2

Stworzyłem narzędzie / opakowanie, które rozwiązuje ten problem + inne typowe problemy związane z logowaniem.

Narzędzie do debugowania z następującymi funkcjami:

  • Zwykłe funkcje zapewniane przez klasę Log owiniętą przez LogMode s.
  • Dzienniki wejścia-wyjścia metody: można wyłączyć za pomocą przełącznika
  • Selektywne debugowanie: debuguj określone klasy.
  • Pomiar czasu wykonania metody: pomiar Czas wykonania poszczególnych metod, jak również zbiorczy czas spędzony na wszystkich metodach klasy.

Jak używać?

  • Uwzględnij klasę w swoim projekcie.
  • Na początek używaj go tak samo, jak metod android.util.Log.
  • Użyj funkcji dzienników wejścia-wyjścia, umieszczając wywołania metod entry_log () - exit_log () na początku i na końcu metod w aplikacji.

Starałem się, aby dokumentacja była samowystarczalna.

Sugestie dotyczące ulepszenia tego narzędzia są mile widziane.

Do bezpłatnego użytku / udostępniania.

Pobierz go z GitHub .


2

Oto bardziej złożone rozwiązanie. Otrzymasz pełny ślad stosu, a metoda toString () zostanie wywołana tylko w razie potrzeby (Performance). Atrybut BuildConfig.DEBUG będzie miał wartość false w trybie produkcyjnym, więc wszystkie dzienniki śledzenia i debugowania zostaną usunięte. Kompilator punktu aktywnego ma szansę usunąć wywołania, ponieważ wyłącza końcowe właściwości statyczne.

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

użyj w ten sposób:

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

1

W bardzo prostym scenariuszu rejestrowania, w którym dosłownie po prostu próbujesz pisać na konsoli podczas programowania w celu debugowania, najłatwiej będzie po prostu przeprowadzić wyszukiwanie i zamianę przed kompilacją produkcyjną i skomentować wszystkie wywołania dziennika lub systemu. out.println.

Na przykład zakładając, że nie korzystałeś z „Dziennika”. Gdziekolwiek poza wywołaniem Log.d lub Log.e, itp., możesz po prostu znaleźć i zamienić w całym rozwiązaniu, aby zastąpić „Log”. z „// Log”. aby zakomentować wszystkie wywołania logowania lub w moim przypadku wszędzie używam System.out.println, więc przed przejściem do produkcji po prostu przeprowadzę pełne wyszukiwanie i zastąpię „System.out.println” i zastąpię ciągiem „//System.out.println”.

Wiem, że to nie jest idealne i byłoby miło, gdyby możliwość znajdowania i komentowania wywołań Log i System.out.println została wbudowana w Eclipse, ale dopóki to się nie stanie, najłatwiejszym, najszybszym i najlepszym sposobem na to jest do komentowania przez wyszukiwanie i zamianę. Jeśli to zrobisz, nie musisz się martwić o niezgodność numerów wierszy śledzenia stosu, ponieważ edytujesz kod źródłowy i nie dodajesz żadnych narzutów, sprawdzając konfigurację na poziomie dziennika itp.


1

W moich aplikacjach mam klasę, która otacza klasę Log, która ma statyczną zmienną logiczną o nazwie „stan”. W całym kodzie sprawdzam wartość zmiennej „stan” za pomocą metody statycznej, zanim zacznę pisać do dziennika. Następnie mam statyczną metodę ustawiania zmiennej „stan”, która zapewnia, że ​​wartość jest wspólna dla wszystkich wystąpień utworzonych przez aplikację. Oznacza to, że mogę włączyć lub wyłączyć wszystkie rejestrowanie aplikacji w jednym wywołaniu - nawet gdy aplikacja jest uruchomiona. Przydatne w przypadku połączeń z pomocą techniczną ... Oznacza to, że podczas debugowania musisz trzymać się swojej broni i nie cofać się do korzystania ze standardowej klasy Log ...

Przydatne (wygodne) jest również to, że Java interpretuje zmienną logiczną jako fałsz, jeśli nie została przypisana wartość, co oznacza, że ​​można ją pozostawić jako fałsz do momentu włączenia logowania :-)


1

Możemy użyć klasy Logw naszym komponencie lokalnym i zdefiniować metody jako v / i / e / d. W zależności od potrzeby możemy zadzwonić dalej.
przykład pokazano poniżej.

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

tutaj komunikat jest dla stringi argsjest wartością, którą chcesz wydrukować.


0

Dla mnie często przydatna jest możliwość ustawienia różnych poziomów dziennika dla każdego TAG.

Używam tej bardzo prostej klasy opakowującej:

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

}

Teraz po prostu ustaw poziom dziennika na TAG na początku każdej klasy:

Log2.setLogLevel(TAG, LogLevels.INFO);

0

Innym sposobem jest użycie platformy logowania, która ma możliwość otwierania i zamykania logów. Może to zapewnić dużą elastyczność czasami nawet w aplikacji produkcyjnej, w której dzienniki powinny być otwarte, a które zamknięte w zależności od problemów, na przykład:

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.