119

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?

aroth
  • 54,026
  • 20
  • 135
  • 176
  • Similar: http://stackoverflow.com/questions/6321555/what-is-the-size-limit-for-logcat – Ciro Santilli OurBigBook.com Feb 01 '16 at 15:36
  • Similar: [how to display long messages in logcat](https://stackoverflow.com/questions/7606077/how-to-display-long-messages-in-logcat) – chancyWu Aug 15 '17 at 06:32
  • Does this answer your question? [What is the size limit for Logcat and how to change its capacity?](https://stackoverflow.com/questions/6321555/what-is-the-size-limit-for-logcat-and-how-to-change-its-capacity) – Josh Correia Jul 08 '20 at 18:33
  • 1
    @JoshCorreia I don't think that's a good duplicate, as that refers to the total buffer size, and this is per log message. – Ryan M Jul 08 '20 at 23:47
  • 1
    @RyanM Ah my bad, I misunderstood the other question. Thanks, removing mark as dupe. – Josh Correia Jul 09 '20 at 03:34

16 Answers16

95

Ok, interesting. I was disappointed to see that the answer was "you can't really expand it". My initial thought was to break it up so I could view the whole thing, so here I share with you how I do just that (not that it's anything fancy nor is it near efficient, but it gets the job done in a pinch):

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

Edited to show the last string!

Travis
  • 3,737
  • 1
  • 24
  • 24
  • No problem! Hope it helped you out – Travis Jul 03 '12 at 17:50
  • I'm pretty sure there's an off by one error here. I had to use "i < chunkCount + 1" to get the last chunk – Dan Feb 12 '13 at 19:25
  • 2
    You lost last string in: `int chunkCount = sb.length() / 4000;` Use `int chunkCount = sb.length() / 4000; if (chunkCount * 4000 < sb.length()) chunkCount++;` – Timur Gilfanov Feb 20 '13 at 16:55
  • 2
    add `else { Log.v(TAG, sb); }` to also print the log when the message is <= 4000 chars long – Bojan Radivojevic Apr 26 '14 at 21:45
  • 5
    This answer is wrong for non-ASCII characters. logcat supports UTF8 and the limit is 4k *bytes*, not characters. – miguel Apr 14 '15 at 23:46
  • A coworker of mine noticed that this code logs an erroneous log if sb.length() / 4000 is evenly divisible. The last 'chunk' will be empty. At the very least, you won't lose information, but you'll get an empty chunk. Now, this probably won't happen very often, but it was brought up during PR, for what it is worth. – Danedo Aug 31 '20 at 19:28
  • Here is a better solution: stackoverflow.com/a/74171156/5128831 – Homayoon Ahmadi Oct 23 '22 at 12:45
74

Break it up in several pieces recursively.

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);
   }
}
Mark Buikema
  • 2,483
  • 30
  • 53
53

There is a fixed size buffer in logcat for binary logs (/dev/log/events) and this limit is 1024 bytes. For the non-binary logs there is also a limit:

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

So the real message size for both binary and non-binary logs is ~4076 bytes. The kernel logger interface imposes this LOGGER_ENTRY_MAX_PAYLOAD limit.

The liblog sources (used by logcat) also say:

  • The message may have been truncated by the kernel log driver.

I would recommend you the nxlog tool which does not use the logcat binary, but due to the limitations in the kernel I doubt that it will solve your problem. Nevertheless, it might be worth a try. (disclaimer: I'm the author.)

Sky Kelsey
  • 19,192
  • 5
  • 36
  • 77
b0ti
  • 2,319
  • 1
  • 18
  • 18
  • 7
    Where do I find this? Is it in the "logcat" code? So, would I have to compile my own modified logcat? – d4Rk Jul 02 '15 at 15:38
  • 2
    What is binary / non-binary log? – fobbymaster Nov 09 '16 at 18:17
  • 2
    Due to metadata fields being added, `LOGGER_ENTRY_MAX_PAYLOAD` has been reduced from 4076 to 4068 in more recent versions of Android (see [here](https://android.googlesource.com/platform/system/core.git/+/master/liblog/include/log/log_read.h)). – mhsmith Feb 14 '18 at 16:10
11
for( String line : logMesg.split("\n") ) {
    Log.d( TAG, line );
}
user2085092
  • 127
  • 1
  • 2
5

Here is the code I use--it truncates the lines at the 4000 limit while also breaking the line at new lines rather than in the middles of the line. Makes for an easier to read log file.

Usage:

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

Implementation:

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;
    }
}
enl8enmentnow
  • 933
  • 7
  • 14
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);
Rizki Sunaryo
  • 468
  • 1
  • 5
  • 14
4

The code below is a refinement of what was posted by Mark Buikema. It breaks the string at new lines. Useful for logging long JSON strings.

  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);
    }
  }
dazed
  • 352
  • 3
  • 9
2

us this paging logic

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

}
neeraj t
  • 4,654
  • 2
  • 27
  • 30
2

As @mhsmith mentioned, the LOGGER_ENTRY_MAX_PAYLOAD is 4068 in the recent Android versions. However, if you use 4068 as the max message length in the code snippets offered in other answers, the messages will be truncated. This is because Android adds more characters to the beginning and end of your message, which also count. Other answers use the 4000 limit as a workaround. However, it is possible to really use the whole limit with this code (the code generates a tag from the stack trace to show the class name and line number which called the log, feel free to modify that):

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);
}
Miloš Černilovský
  • 3,846
  • 1
  • 28
  • 30
1

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

take advantage of the fact that Log.println() returns the number of bytes written to avoid hardcoding "4000". then, recursively call yourself on the part of the message that couldn't be logged until there's nothing left.

Jeffrey Blattman
  • 22,176
  • 9
  • 79
  • 134
1

If your log is very long (eg. logging entire dump of your database for debugging reasons etc.) it may happen that logcat prevents excessive logging. To work around this you can add a timeout evry x milliseconds.

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

Beware, only use this for debugging purpose as it may halts blocks main thread.

Patrick
  • 33,984
  • 10
  • 106
  • 126
1

Though the other provided solutions were helpful, I wasn't satisfied by them cause they did not cover cases when the log is longer than twice as long as the LOGGER_ENTRY_MAX_LEN mentioned by @b0ti. Furthermore even my following solution is not perfect as the LOGGER_ENTRY_MAX_LEN is not fetched dynamically. If someone knows a way to do this, I would love to hear about it in the comments! Anyway, this is the solution I use in my code right now:

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)
        )
);
Nico Feulner
  • 185
  • 3
  • 14
1

Here is the best solution for logging long strings into Logcat

Each log has a limit of maximum 4096 bytes (4KB), and some bytes (about 40 bytes) are used for general information of each log like tag, priority(assert, debug, ...), etc.

So I tried to recursively trim 4056 bytes of the string to be logged every time.

Advantages:

  • The trim function executes very fast because it doesn't have any loops (executes in 1 ms)
  • It doesn't truncate any characters of the log message
  • It logs entire message using a recursive function into multiple log entries.

Here is the solution:

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;
}
Homayoon Ahmadi
  • 1,181
  • 1
  • 12
  • 24
0

You can make simply a Log extension function to split long message into parts here is the code snippet below in kotlin:

import android.util.Log

fun Log.dLong(tag: String, message: String) {
val maxLength = 4000 // Maximum length of each part

if (message.length <= maxLength) {
    Log.d(tag, message)
} else {
    var startIndex = 0
    var endIndex = maxLength

    while (startIndex < message.length) {
        // Ensure endIndex does not exceed the message length
        if (endIndex > message.length) {
            endIndex = message.length
        }

        val part = message.substring(startIndex, endIndex)
        Log.d(tag, part)

        startIndex = endIndex
        endIndex += maxLength
    }
}

}

0

(I don't have the reputation to comment on individual answers, but I have read through them all and here is a small review on why they shouldn't be used)

user2085092's answer is not reliable at all.

Muzammal Abdul Ghafoor's answer / Miloš Černilovský's answer / Jeffrey Blattman's answer / Rizki Sunaryo's answer / dazed's answer / Travis' answer / Mark Buikema's answer / enl8enmentnow's answer / neeraj t's answer does not consider non-ASCII character sizes, we could still use them but we will have to lower the partitioned string length to ~1000 to support non-ASCII messages.

(Miloš Černilovský's answer is actually really considerate and nice with tag length determination logic)

Homayoon Ahmadi's answer works with non-ASCII and is much better than the rest, but I dislike the trim function where the "truncated character determination" logic creates extra String objects, I believe there is a better way of doing it


My solution (chunkUtf8StringBySize):

static void chunkUtf8StringBySize(
        String string,
        int chunkSizeInBytes,
        Consumer<String> chunkedStringConsumer
){
    chunkUtf8BytesBySize(string.getBytes(), 0, chunkSizeInBytes, byteArray -> {
        chunkedStringConsumer.accept(new String(byteArray));
    });
    //or
    /*chunkUtf8BytesBySize(string.getBytes(StandardCharsets.UTF_8), 0, chunkSizeInBytes, byteArray -> {
        stringConsumer.accept(new String(byteArray, StandardCharsets.UTF_8));
    });*/
}

static void chunkUtf8BytesBySize(
        byte[] utf8bytes,
        int startingIndex,
        int chunkSizeInBytes,
        ByteArrayConsumer chunkedUtf8BytesConsumer
) {
    assert startingIndex >= 0 : "`startingIndex` must be at least 0!";
    assert chunkSizeInBytes >= 4 : "`chunkSizeInBytes` must be at least 4 bytes!";

    if (utf8bytes.length <= chunkSizeInBytes) {
        chunkedUtf8BytesConsumer.accept(utf8bytes);
        return;
    }

    int i = 0;
    while (i < utf8bytes.length) {
        int j = i + chunkSizeInBytes - 1;
        if (j > utf8bytes.length) {
            j = utf8bytes.length - 1;
        }
        else if (-64 <= utf8bytes[j] && utf8bytes[j] <= -9) {
            //j is pointing at the first byte of a code point that requires more than one byte
            j--;
        }
        else if (-128 <= utf8bytes[j] && utf8bytes[j] <= -65) {
            //j is pointing at byte 2-4 of a code point
            do {
                j--;
            } while (-128 <= utf8bytes[j] && utf8bytes[j] <= -65);
            j--;
        }

        byte[] ba = new byte[j - i + 1];
        System.arraycopy(utf8bytes, i, ba, 0, j - i + 1);
        chunkedUtf8BytesConsumer.accept(ba);

        i = j + 1;
    }
}

interface ByteArrayConsumer {
    void accept(byte[] byteArray);
}
interface Consumer<T> {
    void accept(T t);
}

Usage example:

class LongLog{
    public static void i(String tag, String msg) {
        chunkUtf8StringBySize(msg, 4000, s -> Log.i(tag, s));
    }
    
    public static void d(String tag, String msg) {
        chunkUtf8StringBySize(msg, 4000, s -> Log.d(tag, s));
    }
    
    public static void e(String tag, String msg) {
        chunkUtf8StringBySize(msg, 4000, s -> Log.e(tag, s));
    }
    
    /* add more logging functions if needed */
}

Explanation:

My solution is very similar to Homayoon Ahmadi's solution, except it works with UTF-8 encoding directly instead of creating new String instances and comparing them.

Also unlike some other answers my solution takes in consumer functions. This gives it more reusability and flexibility, and it avoids the creation of additional arrays/lists.

Thomas
  • 90
  • 6
0

I dont know any option to increase the length of logcat , but we can find the different logs like main log , event log etc..The main log usually contains everything its length goes upto 4Mb.. So you may able to get what you lost in log terminal. Path is: \data\logger.

Vins
  • 4,089
  • 2
  • 35
  • 50