Регистрация лучших практик и идей
Я собираюсь сделать некоторые рефакторинги с моего приложения, и я подумал об этой простой, но сложной теме, протоколировании, как это может быть так сложно сделать чистую, эффективную и информативную регистрацию...
Когда вы читаете документацию по регистрации, вы часто видите этот фрагмент
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.
- Вы можете сажать различное дерево для каждого типа сборки
- Все журналы проходят через центральное место в дереве. так что вы обрабатываете их или загружаете их где-нибудь, если это необходимо.