Как включить / отключить уровни журнала в Android?

149

У меня есть много операторов записи для отладки, например.

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

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

D-Man
источник
Возможное дублирование уровней входа

Ответы:

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 для всех уровней вывода отладки.

Cytown
источник
1
хорошо, но как бы вы отключили DEBUG в вашем примере, но по-прежнему показывать предупреждения ....
Andre Bossard
1
Разве операторы if не окажутся в байтовом коде .apk? Я думал, что мы хотели (вообще) отключить ведение журнала при развертывании приложения, но оператор if не будет удален.
chessofnerd
2
в вашем примере сообщения DEBUG будут отображаться, а WARNs - нет? разве вы не хотели бы наоборот?
Сэм
15
Используйте BuildConfig.DEBUG вместо пользовательских переменных
hB0
1
@chessofnerd "в Java код внутри if даже не будет частью скомпилированного кода. Он должен компилироваться, но не будет записан в скомпилированный байт-код." stackoverflow.com/questions/7122723/…
stoooops
197

Android Документация говорит следующее о Log Levels :

Подробно никогда не следует компилировать в приложение, кроме как во время разработки. Журналы отладки компилируются, но удаляются во время выполнения. Журналы ошибок, предупреждений и информации всегда сохраняются.

Таким образом, вы можете захотеть убрать из журнала подробные записи журналирования, возможно, используя ProGuard, как предложено в другом ответе .

Согласно документации, вы можете настроить ведение журнала на устройстве разработки, используя Свойства системы. Свойство множества , 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), чтобы вернуть true для меня.
seanoshea
2
Я получил отладку Android работает. Хитрость в том, что когда вы вызываете что-то вроде Log.d ("xyz"), сообщение записывается в logcat, даже если отладка отключена для регистратора. Это означает, что фильтрация обычно происходит после записи. Для того, чтобы отфильтровать перед чем-то вроде Log.isLoggable (TAG, Log.VERBOSE)) {Log.v (TAG, «мое сообщение журнала»); } нужно. Это вообще довольно утомительно. Я использую модифицированную версию slf4j-android, чтобы получить то, что я хочу.
phreed
2
@ Дэйв, где ты когда-нибудь смог правильно настроить метод 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, generateLog ()), это может ухудшить вашу производительность, если он находится в некотором горячем коде. Даже такие вещи, как toString () или String.format () могут быть значительными.
Блажей Чапп
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
kdahlhaus
источник
1
Как я уже говорил выше. Я использовал модифицированную версию slf4j-android для реализации этой техники.
phreed
3
Это вызывает серьезную обеспокоенность, см. Stackoverflow.com/questions/2446248/…
OneWorld,
10

Лучший способ - использовать SLF4J API + некоторые его реализации.

Для приложений Android вы можете использовать следующее:

  1. Android Logger - это легковесная, но простая в настройке реализация SLF4J (<50 Кб).
  2. LOGBack - самая мощная и оптимизированная реализация, но ее размер составляет около 1 Мб.
  3. Любой другой на ваш вкус: slf4j-android, slf4android.
Fortess Nsk
источник
2
На Android вам придется использовать logback-android(так как logbackправильное несовместимо). logback-android-1.0.10-1.jarсоставляет 429 КБ, что не так уж и плохо, учитывая предоставленные функции, но большинство разработчиков в любом случае используют Proguard для оптимизации своего приложения.
tony19
Это не спасает вас от использования операторов if для проверки уровня журнала перед регистрацией. См. Stackoverflow.com/questions/4958860/…
OneWorld,
8

Вы должны использовать

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "my log message");
    }
Диего Торрес Милано
источник
2
Как настроить вывод isLoggable? Отладка и подробные данные не регистрируются, когда isDebugable имеет значение false в манифесте?
OneWorld,
5

Удаление журналирования с помощью proguard (см. Ответ от @Christopher) было простым и быстрым, но это приводило к тому, что трассировки стека из производства не соответствовали источнику, если в файле была какая-либо запись отладки.

Вместо этого, вот техника, которая использует разные уровни ведения журнала в разработке и производстве, предполагая, что Proguard используется только в производстве. Он распознает производственный процесс, видя, переименовал ли 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);
    }
}
larham1
источник
3

Log4j или slf4j также могут быть использованы в качестве каркасов ведения журналов в Android вместе с logcat. Смотрите поддержку проекта android-logging-log4j или log4j в android

Рольф Кулеманн
источник
3

Существует небольшая замена стандартного класса журнала Android - 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

Я создал Utility / Wrapper, который решает эту проблему + другие распространенные проблемы, связанные с ведением журнала.

Утилита отладки со следующими функциями:

  • Обычные функции, предоставляемые классом Log, обернуты в LogMode s.
  • Журналы метода входа-выхода: можно отключить с помощью переключателя
  • Выборочная отладка: отладка определенных классов.
  • Измерение времени выполнения метода: измерение времени выполнения для отдельных методов, а также коллективного времени, потраченного на все методы класса.

Как пользоваться?

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

Я попытался сделать документацию самодостаточной.

Предложения по улучшению этой утилиты приветствуются.

Бесплатно использовать / поделиться.

Загрузите его с GitHub .

Vinay W
источник
2

Вот более сложное решение. Вы получите полную трассировку стека, и метод toString () будет вызываться только при необходимости (Performance). Атрибут BuildConfig.DEBUG будет иметь значение false в рабочем режиме, поэтому все журналы трассировки и отладки будут удалены. Компилятор горячих точек имеет возможность удалить вызовы из-за конечных статических свойств.

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

В очень простом сценарии ведения журнала, когда вы буквально просто пытаетесь записать данные на консоль во время разработки для целей отладки, может быть проще всего выполнить поиск и замену перед сборкой и закомментировать все обращения к журналу или системе. out.println.

Например, если вы не использовали «Журнал». В любом месте, кроме вызова Log.d или Log.e и т. д., вы можете просто найти и заменить все решение, чтобы заменить «Журнал». с "// Log." чтобы закомментировать все ваши звонки в журнале, или в моем случае я просто использую System.out.println везде, поэтому перед тем, как перейти к производству, я просто сделаю полный поиск и заменит «System.out.println» и заменит на "//System.out.println".

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

Джим
источник
1

В моих приложениях у меня есть класс, который оборачивает класс Log, который имеет статическую логическую переменную, называемую «state». Во всем моем коде я проверяю значение переменной «state» статическим методом, прежде чем записывать его в журнал. Затем у меня есть статический метод для установки переменной «состояние», которая гарантирует, что значение является общим для всех экземпляров, созданных приложением. Это означает, что я могу включить или отключить все журналы для приложения в один вызов - даже когда приложение работает. Полезно для звонков в службу поддержки ... Это означает, что вы должны придерживаться своего оружия при отладке и не возвращаться к использованию стандартного класса Log, хотя ...

Также полезно (удобно), что Java интерпретирует логическое значение var как false, если ему не было присвоено значение, что означает, что его можно оставить равным false до тех пор, пока вам не потребуется включить ведение журнала :-)

Darnst
источник
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);
    }

}

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

Log2.setLogLevel(TAG, LogLevels.INFO);
Джек миллер
источник
0

Другой способ - использовать платформу регистрации, которая имеет возможность открывать и закрывать журналы. Это может дать большую гибкость иногда даже для производственного приложения, журналы которого должны быть открыты, а какие закрыты, в зависимости от того, какие у вас проблемы, например:

Элиша Штернголд
источник