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


149

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

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

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


Answers:


80

一种常见的方法是创建一个名为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。


1
很好,但是您如何在示例中禁用DEBUG,但仍然显示警告...
Andre Bossard 2012年

1
if语句不会以.apk字节码结尾吗?我以为我们想(通常)在部署应用程序时关闭日志记录,但是不会删除if语句。
12

2
在您的示例中,将显示DEBUG消息,而不会显示WARN?您通常不想要相反吗?
山姆

15
使用BuildConfig.DEBUG代替自定义变量
hB0

1
@chessofnerd“在Java中,if内的代码甚至都不是已编译代码的一部分。它必须进行编译,但不会写入已编译的字节码中。” stackoverflow.com/questions/7122723/…–
stoooops,

197

Android文档说,有关日志级别如下

除非在开发期间,否则切勿将Verbose编译到应用程序中。调试日志在其中编译,但在运行时剥离。错误,警告和信息日志始终保留。

因此,您可能要考虑剥离日志Verbose日志记录语句,可以按照其他答案中的建议使用ProGuard

根据文档,您可以使用“系统属性”在开发设备上配置日志记录。要设置的属性是log.tag.<YourTag>,它应该被设置为以下值中的一个:VERBOSEDEBUGINFOWARNERRORASSERT,或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路线对我们来说似乎是最佳的解决方案。
Christopher Orr 2010年

3
使用/data/local.prop文件,setprop方法或System.setProperty配置Android日志记录时,您是否碰运气?我在获取Log.isLoggable(TAG,VERBOSE)为我返回true时遇到了很多麻烦。
seanoshea 2011年

2
我已经进行了android调试。诀窍是,当您调用Log.d(“ xyz”)之类的消息时,即使为记录器禁用了调试,消息也会写入logcat。这意味着过滤通常在写入后发生。为了在类似Log.isLoggable(TAG,Log.VERBOSE))之类的内容之前进行过滤,{Log.v(TAG,“ my log message”); }是必需的。通常这很累。我使用slf4j-android的修改版来获取我想要的。
2011年

2
@Dave是否能够使local.prop方法正常工作。我也无法完成这项工作,我创建了一个条目log.tag.test = INFO,然后尝试从adb shell运行setprop log.tag.test SUPPRESS对其进行更改,并且它没有任何改变。同时使用System.getProperty和System.setProperty不会执行任何操作。希望获得您的更新。谢谢。
jjNford

2
+1表示评论“ API文档尚不清楚它应该如何工作”。
艾伦(Alan)

90

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

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

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


它包含我们可以在其中定义设置的任何log.property文件吗?
d-man 2010年

1
使用proguard删除行意味着从生产中获取的堆栈跟踪可能与您的代码不一致。
larham1 2011年

3
@ larham1:ProGuard对字节码起作用,因此我可以想象删除记录调用不会改变嵌入式行号元数据。
Christopher Orr

19
记住这一点-即使剥离了对Log.v()的实际调用,其参数仍将被求值。因此,如果您内部有一些昂贵的方法调用,例如Log.v(TAG,generateLog()),则在某些热代码路径中可能会损害您的性能。甚至toString()或String.format()之类的东西也可能很重要。
BłażejCzapp

4
@GaneshKrishnan不,那不是真的。Log.v()的调用已剥离,但默认情况下,不会删除用于构建字符串的方法调用。请参阅ProGuard作者的以下答案:stackoverflow.com/a/6023505/234938
Christopher Orr

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

1
正如我上面提到的。我使用了slf4j-android的修改版来实现此技术。
2011年

3
对此有很大的担忧,请参见stackoverflow.com/questions/2446248/…–
OneWorld

10

更好的方法是使用SLF4J API及其一些实现。

对于Android应用程序,您可以使用以下命令:

  1. Android Logger是轻量级但易于配置的SLF4J实现(<50 Kb)。
  2. LOGBack是最强大,最优化的实现,但其大小约为1 Mb。
  3. 您喜欢的其他任何产品:slf4j-android,slf4android。

2
在Android上,您必须使用logback-android(因为logback不兼容)。logback-android-1.0.10-1.jar是429 KB,考虑到提供的功能,还算不错,但是大多数开发人员还是会使用Proguard来优化其应用程序。
tony19年

这不会使您免于使用if语句在记录之前检查日志级别。见stackoverflow.com/questions/4958860/...
寰宇一家

8

你应该用

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

2
如何配置isLoggable的输出?在清单中将isDebugable设置为false时,debug和详细记录是否不可记录?
OneWorld 2013年

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


3

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


2

可能您可以看到此Log扩展类:https : //github.com/dbauduin/Android-Tools/tree/master/logs

它使您可以更好地控制日志。例如,您可以禁用所有日志,或者仅禁用某些软件包或类的日志。

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


2

我创建了一个工具程序/包装程序,可以解决此问题以及其他与日志记录有关的常见问题。

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

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

如何使用?

  • 将类包括在您的项目中。
  • 像使用android.util.Log方法一样使用它。
  • 通过在应用程序中方法的开头和结尾处放置对entry_log()-exit_log()方法的调用来使用Entry-Exit日志功能。

我试图使文档自给自足。

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

免费使用/共享。

GitHub下载。


2

这是一个更复杂的解决方案。您将获得完整的堆栈跟踪,并且仅在需要时才调用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); 

1

在非常简单的日志记录场景中,您实际上只是在尝试在开发过程中编写控制台以进行调试,因此最简单的方法是在产品构建并注释掉所有对Log或System的调用之前进行搜索和替换。印刷品

例如,假设您没有使用“日志”。在调用Log.d或Log.e等之外的任何位置,您只需查找并替换整个解决方案即可替换“ Log”。用“ // Log”。注释掉您所有的记录调用,或者就我而言,我只是在各处使用System.out.println,因此在投入生产之前,我将简单地进行全面搜索并替换为“ System.out.println”,并替换为“ //System.out.println”。

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


1

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

Java将布尔值var赋值为值(如果尚未为其赋值)也很有用(方便),这意味着在您需要打开日志记录之前可以将其保留为false :-)


1

我们可以Log在本地组件中使用class 并将方法定义为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);
    }

}

现在,只需在每个课程的开头设置每个TAG的日志级别:

Log2.setLogLevel(TAG, LogLevels.INFO);

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.