记录最佳实践和想法

时间:2014-09-29 21:15:19

标签: android logging logcat

我即将对我的应用程序进行一些重构,然后我开始考虑这个简单而复杂的主题,日志记录,如何做到干净,有效和信息丰富的日志记录......

当您阅读有关记录的文档时,您经常会看到此代码段

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

它让我想知道它的目的是什么?根据文档Android Developer - Log,调试日志消息在运行时被编译但被剥离,因此您不需要在该if语句中进行日志调用。或者我想知道什么?

然后我还想知道使用除调试之外的任何其他Log.x()调用的真正好处是什么,因为用户不会看到日志条目或登录到某个错误文件,因此他们将在生产环境中编译并执行是否完全没有用处?这可能是之前if语句的用例吗?

我之前提到过,日志条目没有登录到文件中。为什么这不是Android的内置功能?是因为性能问题,不必要的权限使用还是别的?我已经在我自己的日志记录类中实现了这个功能,但现在我想知道它是不是很糟糕的做法?但是拥有重要日志条目的日志也很好吗?

所以要将其包装起来,在开发和生产过程中实现干净,有效和信息丰富的日志记录。什么是最佳做法?

5 个答案:

答案 0 :(得分:9)

仅在开发期间调试应用程序所需的日志,以确保该功能按预期工作并产生所需的结果。我相信,最佳做法是以最方便的方式进行日志记录,并尽可能快速有效地查找和解决问题

  

我前面提到日志条目没有记录到文件中。为什么   这不是Android中的内置功能吗?

谁(开发阶段的程序员除外)希望应用程序在一个设备上浪费有限的存储空间和无用的数据?用户看不到,不看,不使用日志。他们不需要这种垃圾。生产中的应用程序不得生成任何日志,当然,不得将它们保存到文件中。

应该在已发布的应用程序中实现的唯一日志记录是未处理的异常日志记录。此外,应用程序负责处理这些日志,如果它建议发送崩溃报告,并在报告发送后删除它们。

发布的应用程序不应创建日志的另一个原因是它们可能包含需要用户授权的敏感数据和输出,从而引入安全漏洞。

我认为最佳做法是在部署到生产之前,在模块或功能完全实施并经过全面测试后立即删除所有日志。引入if (BuildConfig.DEBUG)条件有助于确保实现这一目标。

答案 1 :(得分:8)

这将使用带有反射的格式ClasssName[MethodName] - LineNumber生成干净的调试标记。

带有内嵌注释的完整代码以gist here提供。

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

答案 2 :(得分:5)

自从我开始在Android工作以来,我发现了同样的问题我创建了这个开源项目(Android Studio Macros),它可以让你做你想做的事情以及一些更复杂的事情" //<#DEBUG_AREA>和//<#/ DEBUG_AREA>"代码中的标签,基本的想法是,当您更改构建变体时,这些标记中的任何内容都会被注释,例如,如果您在for循环中有类似的内容:

       //=========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;
}

这对于长循环的性能更好,并且通过在&#34; release&#34;中删除不必要的代码来使代码更清晰。模式,当然如果你回到&#34;调试&#34;它会取消对该区域的注释,并使其保持原样与&#34;&lt; #DEBUG_AREA&gt;&#34;标签...

同样尝试适应最常见的情况,似乎有时候你不需要一个完整的区域来摆脱而只需要一个Log,所以对于这种情况,项目也有一个日志包装类,您可以按如下方式使用:

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

该类使用的代码行&#34; ASDebuggerMacroLog&#34;将在更改为&#34;发布&#34;之后进行评论Android Studio中的模式。

希望它有帮助!

问候!

答案 3 :(得分:0)

标准输出不应直接用于记录任何内容(鱿鱼:S106)

记录消息时,必须满足几个重要要求:

  • 用户必须能够轻松检索日志
  • 所有记录的消息的格式必须统一,以便用户轻松阅读 日志
  • 实际上必须记录记录的数据
  • 敏感数据只能安全记录

如果程序直接写入标准输出,则绝对没有办法满足这些要求。因此,强烈建议定义和使用专用记录仪。

来源:Sonarcloud

答案 4 :(得分:0)

我强烈建议使用Timber库:https://github.com/JakeWharton/timber

这是Android Log类之上的一个很小的库,可轻松满足所有日志记录要求。一些功能:

  • 它会自动找出正在调用哪个类,并将其名称用作TAG
  • 您可以为每种构建类型种植不同的树
  • 所有通过Tree中心位置的日志。因此您可以对其进行处理,或者在需要时将其上传到某个地方。