Регистрация лучших практик и идей

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

Когда вы читаете документацию по регистрации, вы часто видите этот фрагмент

if (BuildConfig.DEBUG) {
    Log.d(TAG + "message");
}

и это заставляет меня задаться вопросом, что с ним связано? Согласно документации, Android Developer - Журнал, сообщения журнала отладки компилируются, но удаляются во время выполнения, поэтому вам не нужно будет log вызов внутри этого оператора if. Или я ничего не понимаю?

Затем я также задаюсь вопросом, какова реальная польза от использования любых других вызовов Log.x(), кроме отладки, поскольку записи журнала не будут видны пользователю или не войдут в какой-либо файл ошибок, поэтому они будут скомпилированы в и выполняются в производственной среде без какой-либо цели? Возможно, это вариант использования для инструкции if?

Ранее я упоминал, что запись в журнал не записывается в файл. Почему это не встроенная функция в Android? Это из-за проблем с производительностью, ненужного использования разрешений или чего-то еще? Я реализовал эту функцию в своем собственном классе ведения журнала, но теперь мне интересно, не плохо ли это? Но также хорошо иметь журналы с важными записями журнала?

Итак, чтобы завершить его, внедрить чистую, эффективную и информативную регистрацию, как во время разработки, так и в процессе производства. Какие лучшие практики?

Ответы

Ответ 1

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

Ранее я упоминал, что запись в журнал не записывается в файл. Зачем это не встроенная функция в Android?

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

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

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

Я считаю, что наилучшей практикой является удаление всех журналов, как только модуль или функция полностью реализована и тщательно протестирована, прежде чем развертывать ее на производстве. Представление состояния if (BuildConfig.DEBUG) помогает обеспечить это.

Ответ 2

Это создаст чистые отладочные теги с этим форматом ClasssName[MethodName] - LineNumber с отражением.

Полный код с встроенными комментариями доступен здесь здесь.

import android.util.Log;

public class Logger {

    public enum LOGGER_DEPTH {
        ACTUAL_METHOD(4),
        LOGGER_METHOD(3),
        STACK_TRACE_METHOD(1),
        JVM_METHOD(0);

        private final int value;

        private LOGGER_DEPTH(final int newValue) {
            value = newValue;
        }

        public int getValue() {
            return value;
        }
    }

    private static final String personalTAG = "Logger";

    private StringBuilder sb;

    private Logger() {
        if (LoggerLoader.instance != null) {
            Log.e(personalTAG, "Error: Logger already instantiated");
            throw new IllegalStateException("Already Instantiated");
        } else {
            this.sb = new StringBuilder(255);
        }
    }

    public static Logger getLogger() {
        return LoggerLoader.instance;
    }

    private String getTag(LOGGER_DEPTH depth) {
        try {
            String className = Thread.currentThread().getStackTrace()[depth.getValue()].getClassName();
            sb.append(className.substring(className.lastIndexOf(".") + 1));
            sb.append("[");
            sb.append(Thread.currentThread().getStackTrace()[depth.getValue()].getMethodName());
            sb.append("] - ");
            sb.append(Thread.currentThread().getStackTrace()[depth.getValue()].getLineNumber());
            return sb.toString();
        } catch (Exception ex) {
            ex.printStackTrace();
            Log.d(personalTAG, ex.getMessage());
        } finally {
            sb.setLength(0);
        }
        return null;
    }

    public void d(String msg) {
        try {
            Log.d(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void d(String msg, LOGGER_DEPTH depth) {
        try {
            Log.d(getTag(depth), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void d(String msg, Throwable t, LOGGER_DEPTH depth) {
        try {
            Log.d(getTag(depth), msg, t);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void e(String msg) {
        try {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void e(String msg, LOGGER_DEPTH depth) {
        try {
            Log.e(getTag(depth), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void e(String msg, Throwable t, LOGGER_DEPTH depth) {
        try {
            Log.e(getTag(depth), msg, t);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void w(String msg) {
        try {
            Log.w(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void w(String msg, LOGGER_DEPTH depth) {
        try {
            Log.w(getTag(depth), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void w(String msg, Throwable t, LOGGER_DEPTH depth) {
        try {
            Log.w(getTag(depth), msg, t);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void v(String msg) {
        try {
            Log.v(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void v(String msg, LOGGER_DEPTH depth) {
        try {
            Log.v(getTag(depth), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void v(String msg, Throwable t, LOGGER_DEPTH depth) {
        try {
            Log.v(getTag(depth), msg, t);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void i(String msg) {
        try {
            Log.i(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void i(String msg, LOGGER_DEPTH depth) {
        try {
            Log.i(getTag(depth), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void i(String msg, Throwable t, LOGGER_DEPTH depth) {
        try {
            Log.i(getTag(depth), msg, t);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void wtf(String msg) {
        try {
            Log.wtf(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void wtf(String msg, LOGGER_DEPTH depth) {
        try {
            Log.wtf(getTag(depth), msg);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    public void wtf(String msg, Throwable t, LOGGER_DEPTH depth) {
        try {
            Log.wtf(getTag(depth), msg, t);
        } catch (Exception exception) {
            Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
        }
    }

    private static class LoggerLoader {
        private static final Logger instance = new Logger();
    }
}

Ответ 3

Я столкнулся с той же самой проблемой, так как я начал работать в Android, и я создал этот проект с открытым исходным кодом (Android Studio Macros), который позволяет вам делать то, что вы хотите, плюс некоторые более сложные вещи, используя " теги //& lt; #DEBUG_AREA> и //& lt; #/DEBUG_AREA> "в вашем коде, основная идея состоит в том, что все, что находится внутри этих тегов, будет прокомментировано, когда вы измените варианты сборки, например, если у вас есть что-то подобное в для цикла:

       //=========This piece of code is only for logging purposes...=========
        Log.e("LogUserInfo", "Name: " + name);
        Log.e("LogUserInfo", "Id: " + user.getId());
        Log.e("LogUserInfo", "Id: " + user.getDistance());
        //====================================================================

Вместо этого:

if(DEBUG){
      Log.e("LogginUserInfo", "Name: " + name);
      Log.e("LogginUserInfo", "Id: " + user.getId());
      Log.e("LogginUserInfo", "Id: " + user.getDistance());
 }

С помощью этого макроса вы можете сделать это (полный метод):

private List<String> getNamesOfUsersNearMe(String zipCode){
    List<User> users = mBusinessLogic.getUsersByZipcode(zipCode);
    if(users == null || users.size() < 1){
        return null;
    }

    List<String> names = new ArrayList<String>();
    int totalUsers = users.size();
    for(int i = 0; i < totalUsers; i++){
        User user = users.get(i);
        String name = user.getName();
        names.add(name);
        //<#DEBUG_AREA>
        Log.e("LogginUserInfo", "Name: " + name);
        Log.e("LogginUserInfo", "Id: " + user.getId());
        Log.e("LogginUserInfo", "Id: " + user.getDistance());
        //</#DEBUG_AREA>
    }
    return names;
}

И когда вы меняете свой вариант сборки на релиз, он будет выглядеть примерно так:

private List<String> getNamesOfUsersNearMe(String zipCode){
    List<User> users = mBusinessLogic.getUsersByZipcode(zipCode);
    if(users == null || users.size() < 1){
        return null;
    }

    List<String> names = new ArrayList<String>();
    int totalUsers = users.size();
    for(int i = 0; i < totalUsers; i++){
        User user = users.get(i);
        String name = user.getName();
        names.add(name);
        /*<#DEBUG_OFF>
            Log.e("LogginUserInfo", "Name: " + name);
            Log.e("LogginUserInfo", "Id: " + user.getId());
            Log.e("LogginUserInfo", "Id: " + user.getDistance());
        </#DEBUG_OFF>*/
    }

    return names;
}

Что намного лучше по производительности для длинных циклов и делает ваш код чище, избавляясь от ненужного кода в режиме "выпуска", конечно, если вы вернетесь к "отладке", это раскомментирует область и оставит ее как это было изначально с тегами "& lt; #DEBUG_AREA>"...

Также пытаясь соответствовать наиболее распространенным сценариям, кажется, что бывают случаи, когда вам не нужна полная область, чтобы избавиться от нее, а вместо этого - только один единственный Log, поэтому для этого случая проект также имеет класс-оболочку Log, который вы можете использовать как следующим образом:

if(users == null || users.size() < 1){
    ASDebuggerMacroLog.e("LogUserInfo", "There no users available near me...");
    return null;
}

Строка кода, используемая классом "ASDebuggerMacroLog", будет прокомментирована после перехода в режим "релиз" в Android Studio.

Надеюсь, это поможет!

С уважением!

Ответ 4

Стандартные выходные данные не должны использоваться напрямую для регистрации чего-либо (squid: S106)

При регистрации сообщения есть несколько важных требований, которые должны быть выполнены:

  • Пользователь должен иметь возможность легко получать журналы
  • Формат всех зарегистрированных сообщений должен быть единообразным, чтобы пользователь мог легко читать журнал
  • Фактически зарегистрированные данные должны быть записаны
  • Конфиденциальные данные должны быть зарегистрированы только безопасно

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

Источник: Sonarcloud

Ответ 5

Я настоятельно рекомендую использовать библиотеку Timber: https://github.com/JakeWharton/timber

Это очень маленькая библиотека поверх класса Android Log, которая легко справляется со всеми требованиями к журналам. некоторые функции:

  • Он автоматически определяет, какой класс вызывается, и использует его имя как TAG.
  • Вы можете сажать различное дерево для каждого типа сборки
  • Все журналы проходят через центральное место в дереве. так что вы обрабатываете их или загружаете их где-нибудь, если это необходимо.