Як увімкнути / відключити рівні журналу в Android?


149

Наприклад, у мене є безліч операторів реєстрації для відладки.

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

під час розгортання цієї програми на телефоні пристрою, я хочу вимкнути багатослівний журнал, звідки я можу включити / вимкнути журнал.


Можливий дублікат рівнів реєстрації в Android

Відповіді:


80

Поширеним способом є створення int з іменем loglevel та визначення рівня налагодження на основі 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

Пізніше ви можете просто змінити LOGLEVEL для всіх вихідних рівнів налагодження.


1
приємно, але як би ви відключили ДЕБУГ у своєму прикладі, але все ще показували попередження ....
Андре Боссар

1
Чи не, якщо висловлювання закінчуються в байтовому коді .apk? Я думав, що ми хочемо (як правило) вимкнути журнал, коли додаток було розгорнуто, але оператор if не буде видалений.
chessofnerd

2
у вашому прикладі відображатимуться повідомлення DEBUG, тоді як ПОПЕРЕДЖЕННЯ не буде? ти зазвичай не хочеш протилежного?
Сем

15
Використовуйте BuildConfig.DEBUG замість спеціальних змінних
hB0

1
@chessofnerd "У Java код всередині if навіть не буде частиною компільованого коду. Він повинен компілюватися, але він не буде записаний у складений байтовий код." stackoverflow.com/questions/7122723 / ...
stoooops

197

Android Документація говорить наступне про Log Levels :

Дослідний текст ніколи не повинен складатися в додаток, за винятком під час розробки. Журнали налагодження складаються у, але позбавлені під час виконання. Журнали помилок, попереджень та інформації завжди зберігаються.

Тому ви можете розглянути можливість вилучення висловлювань протоколу журналу Verbose, можливо, використовуючи ProGuard, як це запропоновано в іншій відповіді .

Відповідно до документації, ви можете налаштувати ведення журналу на пристрої розробки за допомогою System Properties. Властивість безлічі , log.tag.<YourTag>і він повинен бути встановлений в одне з наступних значень: VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, або SUPPRESS. Більш детальна інформація про це доступна в документації до isLoggable()методу.

Ви можете встановити властивості тимчасово за допомогою setpropкоманди. Наприклад:

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

Ви також можете вказати їх у файлі '/data/local.prop' таким чином:

log.tag.MyAppTag=WARN

Пізніші версії Android вимагають, щоб /data/local.prop був доступним лише для читання . Цей файл читається під час завантаження, тому вам потрібно буде перезапустити його після оновлення. Якщо /data/local.propбуде написано у всьому світі, воно, ймовірно, буде проігноровано.

Нарешті, ви можете встановити їх програмно за допомогою System.setProperty()методу .


4
Я мав той самий досвід; Документи API доволі незрозумілі, як саме це має працювати, і, здається, згадують про більшу частину android.util.Configзастарілих констант. Значення жорсткого коду, вказані в документах API, марні, оскільки вони (нібито) залежать від збірки. Отже маршрут ProGuard здавався найкращим рішенням для нас.
Крістофер Орр

3
Чи пощастило вам налаштувати журнал Android за допомогою файла /data/local.prop, методу setprop або за допомогою System.setProperty? У мене виникають досить багато проблем з тим, щоб Log.isLoggable (TAG, VERBOSE) повернути справжнє для мене.
seanoshea

2
Я налагодив роботу з налагодженням для Android. Хитрість полягає в тому, що коли ви викликаєте щось на зразок Log.d ("xyz"), повідомлення записується в logcat, навіть якщо налагодження вимкнено для реєстратора. Це означає, що фільтрація зазвичай відбувається після написання. Для того, щоб фільтрувати перед тим, як Log.isLoggable (TAG, Log.VERBOSE)) {Log.v (TAG, "моє повідомлення журналу"); } потрібен. Це взагалі досить втомлює. Я використовую модифіковану версію slf4j-android, щоб отримати те, що я хочу.
фразується

2
@Dave ви коли-небудь змогли зробити так, щоб метод local.prop працював правильно. Я також не можу зробити цю роботу, я створив запис log.tag.test = INFO, а потім спробував змінити його, виконавши setprop log.tag.test SUPPRESS з оболонки adb, і це нічого не змінить. Також використання System.getProperty та System.setProperty нічого не робить. Хотіли отримати оновлення від вас. Дякую.
jjNford

2
+1 для коментаря "Документи API доволі незрозумілі, як саме це має працювати".
Алан

90

Найпростіший спосіб - це, мабуть, запустити скомпільований JAR через ProGuard перед розгортанням, з конфігурацією на зразок:

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

Це - окрім всіх інших оптимізацій ProGuard - видалить будь-які вербологічні протоколи журналу безпосередньо з байтового коду.


чи містить він будь-який файл log.property, де ми можемо визначити налаштування.
d-man

1
викреслювання рядків за допомогою proguard означає, що сліди вашого стека від виробництва можуть не співпадати з вашим кодом.
larham1

3
@ larham1: ProGuard діє на байтовий код, тому я вважаю, що видалення журнальних викликів не змінить метадані вбудованого рядка.
Крістофер Орр

19
Майте на увазі це - навіть якщо фактичний виклик Log.v () знімається, його аргументи все ще оцінюються. Тож якщо у вас є якийсь дорогий виклик методу, наприклад Log.v (TAG, generatorLog ()), це може зашкодити вашій продуктивності, якщо він знаходиться в якомусь гарячому кодовому шляху. Навіть такі речі, як toString () або String.format (), можуть бути важливими.
Błażej Czapp

4
@GaneshKrishnan Ні, це неправда. Виклик до Log.v () знімається, але за замовчуванням виклики методу для створення рядка не будуть видалені. Дивіться цю відповідь від автора ProGuard: stackoverflow.com/a/6023505/234938
Крістофер Орр

18

Я взяв простий маршрут - створення класу обгортки, який також використовує перелікові списки параметрів.

 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
Як я вже згадував вище. Я використовував модифіковану версію slf4j-android для реалізації цієї методики.
фразується

3
Про це існує велика стурбованість, дивіться stackoverflow.com/questions/2446248/…
OneWorld

10

Кращим способом є використання API SLF4J + деяка його реалізація.

Для програм Android можна використовувати наступні:

  1. Android Logger - це легкий, але простий у налаштуванні реалізацію SLF4J (<50 Кб).
  2. LOGBack - це найпотужніша та оптимізована реалізація, але її розмір становить близько 1 Мб.
  3. Будь-який інший на ваш смак: slf4j-android, slf4android.

2
На Android вам доведеться користуватися logback-android(оскільки logbackвласне несумісне). logback-android-1.0.10-1.jarскладає 429 Кб, що не так вже й погано, враховуючи надані функції, але більшість розробників використовуватимуть Proguard для оптимізації їх застосування в будь-якому випадку.
tony19

Це не рятує u від використання if операторів для перевірки рівня журналу перед входом у систему. Дивіться stackoverflow.com/questions/4958860/…
OneWorld

8

Ви повинні використовувати

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

2
Як налаштувати вихід isLoggable? Чи не налагоджувати і налагоджувати не можна вхід у систему, коли параметр isDebugable встановлений помилковим?
OneWorld

5

Видалення журналу за допомогою proguard (див. Відповідь від @Christopher) було легко і швидко, але це призвело до того, що сліди стека від виробництва невідповідні джерела, якщо у файлі була якась помилка введення помилок.

Натомість, ось методика, яка використовує різні рівні лісозаготівель у розвитку та виробництві, припускаючи, що прогуар використовується лише у виробництві. Він розпізнає виробництво, бачачи, чи proguard перейменував задане ім’я класу (у прикладі я використовую "com.foo.Bar" - ви б замінили це повністю кваліфіковане ім'я класу, яке, на вашу думку, буде перейменовано на proguard).

Ця методика використовує загальнодоступний журнал.

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

Для стандартного класу android Log є крихітна заміна - https://github.com/zserge/log

В основному все , що вам потрібно зробити , це замінити імпорт з android.util.Logв trikita.log.Log. Потім у своєму Application.onCreate()або в якомусь статичному ініталізаторі перевірте, BuilConfig.DEBUGчи є будь-який інший прапор, і використовуйте Log.level(Log.D)або Log.level(Log.E)для зміни мінімального рівня журналу. Ви можете Log.useLog(false)взагалі вимкнути журнал.


2

Можливо, ви можете побачити цей клас розширення журналу: https://github.com/dbauduin/Android-Tools/tree/master/logs .

Це дає можливість точного контролю над журналами. Наприклад, ви можете відключити всі журнали або лише журнали деяких пакетів або класів.

Крім того, він додає деякі корисні функції (наприклад, вам не потрібно передавати тег для кожного журналу).


2

Я створив утиліту / обгортку, яка вирішує цю проблему + інші поширені проблеми навколо ведення журналу.

Утиліта налагодження з такими функціями:

  • Звичайні функції, що надаються класом Log, обгорнуті LogMode s.
  • Методи входу-виходу журналів: можна вимкнути комутатором
  • Вибірна налагодження: налагодження конкретних класів.
  • Метод Виконання-Вимірювання часу: Час виконання вимірювання для окремих методів, а також колективний час, витрачений на всі методи класу.

Як використовувати?

  • Включіть клас у свій проект.
  • Використовуйте його так, як ви використовуєте методи android.util.Log, для початку.
  • Використовуйте функцію журналів входу-виходу, розміщуючи виклики методам entry_log () - exit_log () на початку та в кінці методів у вашому додатку.

Я намагався зробити документацію самодостатньою.

Пропозиції щодо вдосконалення цієї утиліти вітаються.

Безкоштовно користуватися / ділитися.

Завантажте його з GitHub .


2

Ось більш складне рішення. Ви отримаєте повний слід стека, і метод toString () буде викликаний лише за потреби (Performance). Атрибут BuildConfig.DEBUG буде помилковим у виробничому режимі, тому всі журнали відстеження та налагодження будуть видалені. Компілятор гарячої точки має шанс видалити дзвінки через відсутність кінцевих статичних властивостей.

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

використовувати так:

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

У дуже простому сценарії ведення журналів, де ви буквально просто намагаєтесь писати на консоль під час розробки для цілей налагодження, можливо, найпростіше просто здійснити пошук і заміну, перш ніж виробництво збирає і коментує всі виклики до Log або System. out.println.

Наприклад, якщо ви не використовували "Журнал". в будь-якому місці за межами дзвінка до Log.d або Log.e тощо, ви можете просто знайти і замінити все рішення для заміни "Log". з "// Журналом." щоб прокоментувати всі ваші дзвінки в журнал, або в моєму випадку я просто використовую System.out.println скрізь, тому перед тим, як перейти до виробництва, я просто проведу повний пошук і заміню на "System.out.println" і заміню на "//System.out.println".

Я знаю, що це не ідеально, і було б непогано, якби можливість пошуку та коментування дзвінків до Log та System.out.println була вбудована у Eclipse, але поки це не відбудеться найпростіший і швидкий і найкращий спосіб зробити це коментувати шляхом пошуку та заміни. Якщо ви це зробите, вам не доведеться турбуватися про невідповідність номерів рядків сліду стека, оскільки ви редагуєте свій вихідний код і не додаєте накладних даних, перевіряючи деяку конфігурацію рівня журналу тощо.


1

У своїх додатках у мене є клас, який обгортає клас Log, який має статичний булевий var, який називається "state". Протягом свого коду я перевіряю значення змінної "state" за допомогою статичного методу перед тим, як насправді записатись у Log. Потім у мене є статичний метод встановити змінну "state", яка забезпечує значення загальним для всіх примірників, створених додатком. Це означає, що я можу ввімкнути або вимкнути весь журнал для програми за один дзвінок - навіть коли додаток працює. Корисно для викликів підтримки ... Це означає, що вам доведеться дотримуватися зброї під час налагодження і не регресувати, використовуючи стандартний клас журналу, хоча ...

Також корисно (зручно), що Java трактує булевий var як хибний, якщо йому не було призначено значення, а це означає, що він може залишатися помилковим, поки вам не потрібно буде ввімкнути журнал :-)


1

Ми можемо використовувати клас Logу нашому локальному компоненті та визначати методи як v / i / e / d. Виходячи з потреби, ми можемо телефонувати далі.
Приклад показано нижче.

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

тут повідомлення призначене для stringі argsє значенням, яке ви хочете надрукувати.


0

Для мене часто корисно мати можливість встановлювати різні рівні журналу для кожної TAG.

Я використовую цей дуже простий клас обгортки:

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

}

Тепер просто встановіть рівень журналу для TAG на початку кожного класу:

Log2.setLogLevel(TAG, LogLevels.INFO);

0

Інший спосіб - використовувати платформу для лісозаготівлі, яка має можливості відкривання та закриття журналів. Це може дати велику гнучкість іноді навіть у виробничому додатку, які журнали повинні бути відкритими, а які закритими, залежно від проблем, які у вас є, наприклад:

  • LumberJack
  • Шифр (відмова від відповідальності: я автор цього пакета)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.