如何在 Android 中启用/禁用日志级别?

Posted

技术标签:

【中文标题】如何在 Android 中启用/禁用日志级别?【英文标题】:How do I enable/disable log levels in Android? 【发布时间】:2011-01-02 08:39:09 【问题描述】:

例如,我有很多日志记录语句要调试。

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

在设备手机上部署此应用程序时,我想关闭详细日志记录,我可以在其中启用/禁用日志记录。

【问题讨论】:

android logging levels的可能重复 【参考方案1】:

Android Documentation says the following about Log Levels:

除非在开发过程中,否则绝不应将详细信息编译到应用程序中。调试日志被编译,但在运行时被剥离。始终保留错误、警告和信息日志。

因此,您可能需要考虑删除日志详细日志记录语句,possibly using ProGuard as suggested in another answer。

根据文档,您可以使用系统属性在开发设备上配置日志记录。要设置的属性是log.tag.<YourTag>,它应该设置为以下值之一:VERBOSEDEBUGINFOWARNERRORASSERTSUPPRESS。 More information on this is available in the documentation for the isLoggable() method.

您可以使用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 appear to require that /data/local.prop be read only。此文件在启动时读取,因此您需要在更新后重新启动。如果/data/local.prop 是全局可写的,它可能会被忽略。

最后,您可以使用System.setProperty() method 以编程方式设置它们。

【讨论】:

我也有过同样的经历; API 文档不清楚这应该如何工作,甚至似乎提到了大部分 android.util.Config 常量被弃用。 API 文档中指定的硬编码值是无用的,因为这些(据说)因构建而异。因此,ProGuard 路线似乎是我们的最佳解决方案。 您在使用 /data/local.prop 文件、setprop 方法或 System.setProperty 配置 Android 日志记录方面有没有运气?我很难让 Log.isLoggable(TAG, VERBOSE) 为我返回 true。 我已经得到了 android 调试工作。诀窍是,当您调用 Log.d("xyz") 之类的内容时,即使对记录器禁用了调试,消息也会写入 logcat。这意味着过滤通常发生在写入之后。为了在 Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "my log message"); 是必需的。这通常很烦人。我使用 slf4j-android 的修改版本来获得我想要的。 @Dave 你曾经能够让 local.prop 方法正常工作吗?我也无法完成这项工作,我创建了一个条目 log.tag.test=INFO,然后尝试从 adb shell 运行 setprop log.tag.test SUPPRESS 对其进行更改,但它没有任何改变。也使用 System.getProperty 和 System.setProperty 什么都不做。想从你那里得到更新。谢谢。 +1 评论“API 文档不清楚这应该如何工作”。【参考方案2】:

最简单的方法可能是在部署之前通过ProGuard 运行已编译的 JAR,配置如下:

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

除了所有其他 ProGuard 优化之外,这将直接从字节码中删除任何冗长的日志语句。

【讨论】:

是否包含任何我们可以定义设置的 log.property 文件。 使用 proguard 剥离行意味着您的生产中的堆栈跟踪可能与您的代码不一致。 @larham1:ProGuard 作用于字节码,所以我想删除日志调用不会改变嵌入的行号元数据。 请注意 - 即使对 Log.v() 的实际调用被剥离,它的参数仍然被评估。因此,如果您内部有一些昂贵的方法调用,例如Log.v(TAG, generateLog()) 如果它在一些热代码路径中,它可能会损害您的性能。甚至像 toString() 或 String.format() 这样的东西也很重要。 @GaneshKrishnan 不,这不是真的。对 Log.v() 的调用被剥离,但默认情况下,构建字符串的方法调用不会被删除。请参阅 ProGuard 作者的回答:***.com/a/6023505/234938【参考方案3】:

一种常见的方法是创建一个名为loglevel的int,并根据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。

【讨论】:

很好,但是如何在示例中禁用 DEBUG,但仍然显示警告...... if 语句不会以 .apk 字节码结尾吗?我认为我们希望(通常)在部署应用程序时关闭日志记录,但不会删除 if 语句。 在您的示例中,将显示 DEBUG 消息,而 WARN 不会?你通常不想反对吗? 使用 BuildConfig.DEBUG 代替自定义变量 @chessofnerd “在 Java 中,if 中的代码甚至不会成为编译代码的一部分。它必须编译,但不会写入编译后的字节码。” ***.com/questions/7122723/…【参考方案4】:

我采取了一条简单的路线——创建一个也使用可变参数列表的包装类。

 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

【讨论】:

如上所述。我使用了 slf4j-android 的修改版本来实现这个技术。 对此有很大的担忧,请参阅***.com/questions/2446248/…【参考方案5】:

更好的方法是使用 SLF4J API + 它的一些实现。

对于 Android 应用程序,您可以使用以下内容:

    Android Logger 是轻量级但易于配置的 SLF4J 实现 ( LOGBack 是最强大和优化的实现,但其大小约为 1 Mb。 任何其他您喜欢的:slf4j-android、slf4android。

【讨论】:

在 Android 上,您必须使用 logback-android(因为 logback 不兼容)。 logback-android-1.0.10-1.jar 是 429 KB,考虑到提供的功能,这还不错,但大多数开发人员还是会使用 Proguard 来优化他们的应用程序。 这并不能避免您在记录之前使用 if 语句检查日志级别。见***.com/questions/4958860/…【参考方案6】:

你应该使用

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

【讨论】:

如何配置isLoggable的输出?当 isDebugable 在 manifest 中设置为 false 时,debug 和 verbose 是否不可记录?【参考方案7】:

使用 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);
    

【讨论】:

【参考方案8】:

Log4j 或 slf4j 也可以与 logcat 一起用作 Android 中的日志框架。查看项目android-logging-log4j 或log4j support in android

【讨论】:

【参考方案9】:

标准的 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) 完全禁用日志记录。

【讨论】:

【参考方案10】:

也许你可以看到这个日志扩展类:https://github.com/dbauduin/Android-Tools/tree/master/logs。

它使您能够对日志进行精细控制。 例如,您可以禁用所有日志或仅禁用某些包或类的日志。

此外,它还添加了一些有用的功能(例如,您不必为每个日志传递标签)。

【讨论】:

【参考方案11】:

我创建了一个实用程序/包装器,它解决了这个问题以及有关日志记录的其他常见问题。

具有以下功能的调试实用程序:

Lo​​g 类提供的常用功能由 LogMode s 包裹。 方法进入-退出日志:可以通过开关关闭 选择性调试:调试特定类。 方法执行时间测量:测量单个方法的执行时间以及花费在一个类的所有方法上的集体时间。

如何使用?

在您的项目中包含该类。 开始使用它就像使用 android.util.Log 方法一样。 通过在应用中方法的开头和结尾调用 entry_log()-exit_log() 方法来使用进入-退出日志功能。

我试图让文档自给自足。

欢迎提出改进此实用程序的建议。

免费使用/分享。

从GitHub下载。

【讨论】:

【参考方案12】:

这是一个更复杂的解决方案。您将获得完整的堆栈跟踪,并且只有在需要时才会调用 toString() 方法(性能)。在生产模式下,属性 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); 

【讨论】:

【参考方案13】:

在一个非常简单的日志记录场景中,您实际上只是在开发期间尝试写入控制台以进行调试,最简单的方法可能是在生产构建之前进行搜索和替换并注释掉对 Log 的所有调用或 System.out.println.

例如,假设您没有使用“日志”。在调用 Log.d 或 Log.e 等之外的任何地方,您都可以简单地在整个解决方案中进行查找和替换以替换“Log”。用“//日志”。注释掉你所有的日志调用,或者就我而言,我只是在任何地方使用 System.out.println,所以在投入生产之前,我将简单地进行完整搜索并替换“System.out.println”并替换为"//System.out.println".

我知道这并不理想,如果在 Eclipse 中内置了查找和注释对 Log 和 System.out.println 调用的功能,那就太好了,但在此之前,这是最简单、最快和最好的方法这样做是通过搜索和替换来注释掉。如果这样做,您不必担心堆栈跟踪行号不匹配,因为您正在编辑源代码,并且不会通过检查某些日志级别配置等来增加任何开销。

【讨论】:

【参考方案14】:

在我的应用程序中,我有一个包装 Log 类的类,该类有一个名为“state”的静态布尔变量。在我的代码中,我在实际写入日志之前使用静态方法检查“状态”变量的值。然后我有一个静态方法来设置“状态”变量,以确保该值在应用程序创建的所有实例中是通用的。这意味着我可以在一次调用中启用或禁用应用程序的所有日志记录 - 即使应用程序正在运行。对支持呼叫很有用...这确实意味着您在调试时必须坚持自己的立场,并且不要退回到使用标准 Log 类...

如果尚未为其分配值,Java 将布尔 var 解释为 false 也很有用(方便),这意味着它可以保留为 false,直到您需要打开日志记录 :-)

【讨论】:

【参考方案15】:

我们可以在本地组件中使用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);
    

这里的消息是针对stringargs 是您要打印的值。

【讨论】:

【参考方案16】:

对我来说,为每个 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);

【讨论】:

【参考方案17】:

另一种方法是使用具有打开和关闭日志功能的日志记录平台。这有时甚至可以在生产应用程序上提供很大的灵活性,哪些日志应该打开,哪些日志应该关闭,具体取决于您遇到的问题 例如:

LumberJack Shipbook(免责声明:我是这个包的作者)

【讨论】:

【参考方案18】:

https://limxtop.blogspot.com/2019/05/app-log.html

请阅读这篇文章,这里提供了完整的工具:

    对于调试版本,将输出所有日志; 对于release版本,默认只输出DEBUG(排除)以上级别的日志。同时,运行时可以通过setprop log.tag.&lt;YOUR_LOG_TAG&gt; &lt;LEVEL&gt;开启DEBUG和VERBOSE日志。

【讨论】:

以上是关于如何在 Android 中启用/禁用日志级别?的主要内容,如果未能解决你的问题,请参考以下文章

如何在代码中启用/禁用 spdlog 日志记录?

如何在根级别禁用导入的模块日志记录

使用 syslog 时禁用/启用日志记录

如何在 Apache2 服务器上启用日志级别调试 [关闭]

如何启用 Hystrix DEBUG 级别的日志记录

未启用 iCloud 时如何存储核心数据日志?