Android - 设置logcat消息的最大长度

120

默认情况下,似乎logcat会截断任何被认为“过长”的日志消息。不论是在Eclipse内部还是使用adb -d logcat命令行运行logcat时都会发生这种情况,从而截断了一些重要的调试信息。

有没有办法增加logcat支持的最大字符串长度,以使其停止截断调试信息? 官方文档暗示可能没有,但也许logcat支持一些未提及的其他选项?


请仅返回翻译后的文本:类似于:https://dev59.com/32015IYBdhLWcg3w6QDA - Ciro Santilli OurBigBook.com
这个回答解决了你的问题吗?Logcat 的大小限制是多少,如何改变其容量? - Josh Correia
1
@JoshCorreia 我认为那不是一个好的重复问题,因为那个问题是关于总缓冲区大小的,而这个问题是关于每个日志消息的。 - Ryan M
1
@RyanM 啊,我的错,我误解了另一个问题。谢谢,我会取消重复标记的。 - Josh Correia
16个回答

96

好的,有意思。看到答案是“你不能真正地扩展它”,我感到失望。我的第一反应是拆开它以便查看整个内容,因此在这里我与您分享如何做到这一点(虽然这并不是什么高级技巧,也不够高效,但紧急情况下能解决问题):

if (sb.length() > 4000) {
    Log.v(TAG, "sb.length = " + sb.length());
    int chunkCount = sb.length() / 4000;     // integer division
    for (int i = 0; i <= chunkCount; i++) {
        int max = 4000 * (i + 1);
        if (max >= sb.length()) {
            Log.v(TAG, "chunk " + i + " of " + chunkCount + ":" + sb.substring(4000 * i));
        } else {
            Log.v(TAG, "chunk " + i + " of " + chunkCount + ":" + sb.substring(4000 * i, max));
        }
    }
} else {
    Log.v(TAG, sb.toString());
}

编辑后展示最后一个字符串!


没问题!希望它对你有所帮助。 - Travis
我相信这里有一个偏移量错误。我不得不使用“i < chunkCount + 1”来获取最后一块。 - Dan
2
你在以下代码中丢失了最后一个字符串: int chunkCount = sb.length() / 4000; 请使用以下代码替换:int chunkCount = sb.length() / 4000; if (chunkCount * 4000 < sb.length()) chunkCount++; - Timur Gilfanov
2
else { Log.v(TAG, sb); } 添加到代码中,以便在消息长度小于或等于4000个字符时也打印日志。 - Bojan Radivojevic
5
对于非ASCII字符,这个回答是错误的。logcat支持UTF8编码,限制是4k 字节,而不是字符。 - miguel
显示剩余2条评论

74

递归地将其分解成几个部分。

public static void largeLog(String tag, String content) {
   if (content.length() > 4000) {
       Log.d(tag, content.substring(0, 4000));
       largeLog(tag, content.substring(4000));
   } else {
       Log.d(tag, content);
   }
}

3
这绝对是最清晰的解决方案,而且这是我第一次在生产代码中使用递归。 - Aggressor
2
@Aggressor,为什么你需要在生产环境中记录4000多条长消息? - TWiStErRob
2
我的使用情况是输出一个大的JSON数据。文件真的很麻烦。 - Marcel Falliere
1
非常有用,谢谢。我已经发布了一个答案,可以在行结束处分割字符串。 - dazed
1
简洁、易用、清晰、酷炫、美观。鼓掌! - Muhammad Ashfaq
显示剩余5条评论

53

在logcat中,二进制日志(/dev/log/events)有一个固定大小的缓冲区,并且该限制为1024字节。对于非二进制日志,也存在限制:

#define LOGGER_ENTRY_MAX_LEN        (4*1024)
#define LOGGER_ENTRY_MAX_PAYLOAD (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

因此,无论是二进制日志还是非二进制日志的真实消息大小都约为4076字节。 内核记录器接口强制实施这个LOGGER_ENTRY_MAX_PAYLOAD限制。

liblog源代码(被logcat使用)也提到:

  • 该消息可能已经被内核日志驱动程序截断。

我建议您尝试使用nxlog工具,它不使用logcat二进制文件,但由于内核的限制,我怀疑它是否能解决您的问题。不过,这仍然值得一试。(声明:我是该工具的作者。)


7
我该在哪里找到它?它在"logcat"代码中吗?那么,我需要编译自己修改过的logcat吗? - d4Rk
2
什么是二进制/非二进制日志? - fobbymaster
2
由于元数据字段的添加,Android 的较新版本中 LOGGER_ENTRY_MAX_PAYLOAD 已从 4076 减少到 4068(请参见此处)。 - mhsmith

11
for( String line : logMesg.split("\n") ) {
    Log.d( TAG, line );
}

这能如何帮助处理单行长字符串? - user2342558

5
下面的代码是对Mark Buikema发布的内容的改进。它会在换行处断开字符串。对于记录长JSON字符串非常有用。
  public static void dLong(String theMsg)
  {
    final int MAX_INDEX = 4000;
    final int MIN_INDEX = 3000;

    // String to be logged is longer than the max...
    if (theMsg.length() > MAX_INDEX)
    {
      String theSubstring = theMsg.substring(0, MAX_INDEX);
      int    theIndex = MAX_INDEX;

      // Try to find a substring break at a line end.
      theIndex = theSubstring.lastIndexOf('\n');
      if (theIndex >= MIN_INDEX)
      {
        theSubstring = theSubstring.substring(0, theIndex);
      }
      else
      {
        theIndex = MAX_INDEX;
      }

      // Log the substring.
      Log.d(APP_LOG_TAG, theSubstring);

      // Recursively log the remainder.
      dLong(theMsg.substring(theIndex));
    }

    // String to be logged is shorter than the max...
    else
    {
      Log.d(APP_LOG_TAG, theMsg);
    }
  }

不错的选择.... - undefined

5
int i = 3000;
while (sb.length() > i) {
    Log.e(TAG, "Substring: "+ sb.substring(0, i));
    sb = sb.substring(i);
}
Log.e(TAG, "Substring: "+ sb);

5

以下是我使用的代码——它将文本行截断到4000个字符以内,同时在换行符处断开一行,而不是在一行的中间断开。这样可以使日志文件更易读。

用法:

Logger.debugEntire("....");

实现:

package ...;

import android.util.Log;

import java.util.Arrays;

public class Logger {

    private static final String LOG_TAG = "MyRockingApp";

    /** @see <a href="https://dev59.com/52ox5IYBdhLWcg3w6oj0#8899735" /> */
    private static final int ENTRY_MAX_LEN = 4000;

    /**
     * @param args If the last argument is an exception than it prints out the stack trace, and there should be no {}
     *             or %s placeholder for it.
     */
    public static void d(String message, Object... args) {
        log(Log.DEBUG, false, message, args);
    }

    /**
     * Display the entire message, showing multiple lines if there are over 4000 characters rather than truncating it.
     */
    public static void debugEntire(String message, Object... args) {
        log(Log.DEBUG, true, message, args);
    }

    public static void i(String message, Object... args) {
        log(Log.INFO, false, message, args);
    }

    public static void w(String message, Object... args) {
        log(Log.WARN, false, message, args);
    }

    public static void e(String message, Object... args) {
        log(Log.ERROR, false, message, args);
    }

    private static void log(int priority, boolean ignoreLimit, String message, Object... args) {
        String print;
        if (args != null && args.length > 0 && args[args.length-1] instanceof Throwable) {
            Object[] truncated = Arrays.copyOf(args, args.length -1);
            Throwable ex = (Throwable) args[args.length-1];
            print = formatMessage(message, truncated) + '\n' + android.util.Log.getStackTraceString(ex);
        } else {
            print = formatMessage(message, args);
        }
        if (ignoreLimit) {
            while (!print.isEmpty()) {
                int lastNewLine = print.lastIndexOf('\n', ENTRY_MAX_LEN);
                int nextEnd = lastNewLine != -1 ? lastNewLine : Math.min(ENTRY_MAX_LEN, print.length());
                String next = print.substring(0, nextEnd /*exclusive*/);
                android.util.Log.println(priority, LOG_TAG, next);
                if (lastNewLine != -1) {
                    // Don't print out the \n twice.
                    print = print.substring(nextEnd+1);
                } else {
                    print = print.substring(nextEnd);
                }
            }
        } else {
            android.util.Log.println(priority, LOG_TAG, print);
        }
    }

    private static String formatMessage(String message, Object... args) {
        String formatted;
        try {
            /*
             * {} is used by SLF4J so keep it compatible with that as it's easy to forget to use %s when you are
             * switching back and forth between server and client code.
             */
            formatted = String.format(message.replaceAll("\\{\\}", "%s"), args);
        } catch (Exception ex) {
            formatted = message + Arrays.toString(args);
        }
        return formatted;
    }
}

2
请使用这个分页逻辑。
    /*
     * StringBuffer sb - long text which want to show in multiple lines 
     * int lenth - lenth of line need
     */

public static void showInPage(StringBuffer sb, int lenth) {
    System.out.println("sb.length = " + sb.length());
    if (sb.length() > lenth) {

        int chunkCount = sb.length() / lenth; // integer division
        if ((chunkCount % lenth) > 1)
            chunkCount++;
        for (int i = 0; i < chunkCount; i++) {
            int max = lenth * (i + 1);
            if (max >= sb.length()) {
                System.out.println("");
                System.out.println("chunk " + i + " of " + chunkCount + ":"
                        + sb.substring(lenth * i));
            } else {
                System.out.println("");
                System.out.println("chunk " + i + " of " + chunkCount + ":"
                        + sb.substring(lenth * i, max));
            }
        }
    }

}

2

正如@mhsmith所提到的那样,在最近的Android版本中,LOGGER_ENTRY_MAX_PAYLOAD为4068。然而,如果您在其他答案中提供的代码片段中使用4068作为最大消息长度,则消息将被截断。这是因为Android会在您的消息开头和结尾添加更多字符,这些字符也要计算在内。其他答案使用4000的限制作为解决方法。但是,使用此代码确实可以使用整个限制(该代码从堆栈跟踪生成标记,以显示调用日志的类名和行号,可以随意修改):

private static final int MAX_MESSAGE_LENGTH = 4068;

private enum LogType {
    debug,
    info,
    warning,
    error
}

private static void logMessage(LogType logType, @Nullable String message, @Nullable String tag) {
    logMessage(logType, message, tag, Thread.currentThread().getStackTrace()[4]);
}

private static void logMessage(LogType logType, @Nullable String message, @Nullable String customTag, StackTraceElement stackTraceElement) {
    // don't use expensive String.format
    String tag = "DASHBOARDS(" + stackTraceElement.getFileName() + "." + (!TextUtils.isEmpty(customTag) ? customTag : stackTraceElement.getMethodName()) + ":" + stackTraceElement.getLineNumber() + ")";
    int maxMessageLength = MAX_MESSAGE_LENGTH - (tag.length()) - 4; // minus four because android adds a letter showing the log type before the tag, e. g. "D/" for debug, and a colon and space are added behind it, i. e. ": "
    if (message == null || message.length() <= maxMessageLength) {
        logMessageInternal(logType, message, tag);
    } else {
        maxMessageLength -= 8; // we will add counter to the beginning of the message, e. g. "(12/15) "
        int totalChunks = (int) Math.ceil((float) message.length() / maxMessageLength);
        for (int i = 1; i <= totalChunks; i++) {
            int start = (i - 1) * maxMessageLength;
            logMessageInternal(logType, "(" + i + "/" + totalChunks + ") " + message.substring(start, Math.min(start + maxMessageLength, message.length())), tag);
        }
    }
}

private static void logMessageInternal(LogType logType, String message, String tag) {
    if (message == null) {
        message = "message is null";
    }
    switch (logType) {
        case debug:
            Log.d(tag, message);
            break;
        case info:
            Log.i(tag, message);
            break;
        case warning:
            Log.w(tag, message);
            break;
        case error:
            Log.e(tag, message);
    }
}

public static void d(String debug, String tag) {
    logMessage(LogType.debug, debug, tag);
}

1

这是将长字符串记录到Logcat的最佳解决方案

每个日志的限制为最大4096字节(4KB),并且一些字节(大约40字节)用于每个日志的一般信息,如标签、优先级(assert、debug等)等。

因此,我尝试递归地每次修剪要记录的4056字节的字符串。

优点:

  • 修剪函数执行非常快速,因为它没有任何循环(在1毫秒内执行)
  • 不截断任何日志消息的字符
  • 它使用递归函数将整个消息记录到多个日志条目中。

以下是解决方案:

private static final int MAX_LOG_BYTES = 4056;


public static void log(int priority, String tag, @NonNull String content) {
    int size = content.getBytes(StandardCharsets.UTF_8).length;

    if (size > MAX_LOG_BYTES) {
        String text = trim(content, MAX_LOG_BYTES);
        Log.println(priority, tag, text);
        log(priority, tag, content.substring(text.length()));

    } else {
        Log.println(priority, tag, content);
    }
}


public static String trim(String text, int size) {
    byte[] inputBytes = text.getBytes(StandardCharsets.UTF_8);
    byte[] outputBytes = new byte[size];

    System.arraycopy(inputBytes, 0, outputBytes, 0, size);
    String result = new String(outputBytes, StandardCharsets.UTF_8);

    // check if last character is truncated
    int lastIndex = result.length() - 1;

    if (lastIndex > 0 && result.charAt(lastIndex) != text.charAt(lastIndex)) {
        // last character is truncated so remove the last character
        return result.substring(0, lastIndex);
    }

    return result;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接