Android - Set max length of logcat messages

By default, it seems that logcat will truncate any log message that it considers to be "too long". This happens both inside of Eclipse and when running logcat on the command line using adb -d logcat, and is truncating some important debugging messages.

Is there any way to increase the maximum string length supported by logcat to get it to stop truncating the debug information? The official documentation implies that there may not be, but maybe logcat supports some additional options not mentioned there?

80659 次浏览

我不知道任何选项来增加 logcat 的长度,但我们可以找到不同的日志,如主日志,事件日志等。主日志通常包含长度高达4Mb 的所有内容。.这样你就可以得到你在日志终端中丢失的东西。路径是: 数据记录器。

There is a fixed size buffer in logcat for binary logs (/dev/log/events) and this limit is 1024 bytes. 对于非二进制日志也有一个限制:

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

因此,二进制和非二进制日志的实际消息大小都是 ~ 4076字节。 The kernel logger interface imposes this LOGGER_ENTRY_MAX_PAYLOAD limit.

Liblog 源(logcat 使用)还说:

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

我建议您使用不使用 logcat 二进制文件的 Nxlog工具,但是由于内核中的限制,我怀疑它能否解决您的问题。尽管如此,还是值得一试。(免责声明: 我是作者)

好吧,有意思。我失望地看到,答案是“你不能真正扩大它”。我最初的想法是打破它,这样我就可以看到整个事情,所以在这里我与你分享我是如何做到这一点(并不是说它是任何花哨的东西,也不是接近高效,但它可以在必要时完成工作) :

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

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

providing my own take on Travis's solution,

void d(String msg) {
println(Log.DEBUG, msg);
}


private void println(int priority, String msg) {
int l = msg.length();
int c = Log.println(priority, TAG, msg);
if (c < l) {
return c + println(priority, TAG, msg.substring(c+1));
} else {
return c;
}
}

利用 Log.println()返回写入的字节数以避免硬编码“4000”这一事实。然后,递归地在消息中不能被记录的部分调用自己,直到没有任何东西剩下。

我们这个寻呼逻辑

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


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

下面是我使用的代码——它在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="http://stackoverflow.com/a/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;
}
}

递归地把它分成几块。

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

如果您的日志非常长(例如,出于调试等原因记录数据库的整个转储) ,那么 logcat 可能会防止过度的日志记录。为了解决这个问题,您可以每 x 毫秒添加一个超时。

/**
* Used for very long messages, splits it into equal chunks and logs each individual to
* work around the logcat max message length. Will log with {@link Log#d(String, String)}.
*
* @param tag     used in for logcat
* @param message long message to log
*/
public static void longLogDebug(final String tag, @NonNull String message) {
int i = 0;


final int maxLogLength = 1000;
while (message.length() > maxLogLength) {
Log.d(tag, message.substring(0, maxLogLength));
message = message.substring(maxLogLength);
i++;


if (i % 100 == 0) {
StrictMode.noteSlowCall("wait to flush logcat");
SystemClock.sleep(32);
}
}
Log.d(tag, message);
}

请注意,仅用于调试目的,因为它可能会停止主线程。

下面的代码是 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);
}
}
int i = 3000;
while (sb.length() > i) {
Log.e(TAG, "Substring: "+ sb.substring(0, i));
sb = sb.substring(i);
}
Log.e(TAG, "Substring: "+ sb);

正如@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);
}

尽管其他提供的解决方案很有帮助,但我对它们并不满意,因为它们没有涵盖日志长度超过@b0ti 提到的 LOGGER _ ENTRY _ MAX _ LEN 两倍的情况。此外,即使我的以下解决方案也不完美,因为 LOGGER _ ENTRY _ MAX _ LEN 不是动态获取的。如果有人知道这样做的方法,我很乐意在评论中听到它!无论如何,这是我现在在代码中使用的解决方案:

final int loggerEntryMaxLength = 4000;
int logLength = loggerEntryMaxLength - 2 - TAG.length();
int i = 0;
while (output.length() / logLength > i) {
int startIndex = i++ * logLength;
int endIndex = i * logLength;
Log.d(TAG, output.substring(startIndex, endIndex));
}
int startIndex = i * logLength;
Log.d(
TAG,
output.substring(
startIndex,
startIndex + (output.length() % logLength)
)
);

每个日志都有 最大4096字节(4KB)的限制,一些字节(大约40个字节)用于每个日志的一般信息,比如标记、优先级(断言、调试、 ...)等等。

So I tried to trim 4056字节 of the string to be logged.

优点:

  • 装饰函数执行 very fast,因为它没有任何循环(在 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 length) {
byte[] inputBytes = text.getBytes(StandardCharsets.UTF_8);
byte[] outputBytes = new byte[length];


System.arraycopy(inputBytes, 0, outputBytes, 0, length);
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;
}