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

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

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

在设备电话上部署这个应用程序时,我想关闭可以启用/禁用日志记录的详细日志记录。

224963 次浏览

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

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

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

返回文章页面 关于日志级别,Android 文档说明如下:

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

因此,您可能需要考虑删除日志详细日志语句 可能像另一个答案中建议的那样使用 ProGuard

根据文档,您可以使用系统属性在开发设备上配置日志记录。要设置的属性是 log.tag.<YourTag>,应该将其设置为以下值之一: VERBOSEDEBUGINFOWARNERRORASSERTSUPPRESS关于这一点的更多信息可以在 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()以编程方式设置它们。

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

你应该用

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

我采用了一种简单的方法——创建一个包装器类,它也利用了变量参数列表。

 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

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

Log4j 或 slf4j 也可以与 logcat 一起用作 Android 中的日志框架。参见项目 Android-log-log4jAndroid 中的 log4j 支持

更好的方法是使用 SLF4JAPI + 的一些实现。

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

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

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

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

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

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

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

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

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

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

如果布尔变量没有被赋值,Java 会将其解释为 false,这也很有用(方便) ,这意味着它可以被保留为 false,直到您需要打开日志记录: -)

我创建了一个实用程序/包装器来解决这个问题和日志记录的其他常见问题。

具有以下特性的调试实用程序:

  • 日志模式包装的 Log 类提供的常见特性。
  • 方法进入-退出日志: 可以通过开关关闭
  • 选择性调试: 调试特定类。
  • 方法执行时间测量: 测量单个方法的执行时间以及类的所有方法的总时间。

如何使用?

  • 在项目中包含该类。
  • 首先,像使用 android.util.Log 方法一样使用它。
  • 通过在应用程序中方法的开始和结束处放置对 entry _ log ()-exit _ log ()方法的调用,使用 Entry-Exit log 特性。

我已经尝试使文档自我充分。

欢迎提出改进此工具的建议。

免费使用/分享。

GitHub下载。

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

有一个很小的下拉式替代标准的 android 日志类 -https://github.com/zserge/log

基本上,您所要做的就是替换从 android.util.Logtrikita.log.Log的导入。然后在您的 Application.onCreate()或一些静态初始化检查的 BuilConfig.DEBUG或任何其他标志,并使用 Log.level(Log.D)Log.level(Log.E)来改变最小的日志级别。您完全可以使用 Log.useLog(false)来禁用日志记录。

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

这里的消息是 string,而 args是要打印的值。

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

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

请阅读本文,其中提供完整的实现:

  1. 对于调试版本,将输出所有日志;
  2. 对于发布版本,默认情况下只输出级别高于 DEBUG (排除)的日志。同时,可以通过 setprop log.tag.<YOUR_LOG_TAG> <LEVEL>在运行时启用 DEBUG 和 VERBOSE 日志。