Reputation: 54806
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?
Upvotes: 121
Views: 94134
Reputation: 422
Work around the limit by breaking messages into chunks before passing them to Logcat.
inline fun chunkUtf8StringBySize(
string: String,
chunkSizeInBytes: Int,
chunkedStringConsumer: (String) -> Unit
) {
chunkUtf8BytesBySize(string.toByteArray(), 0, chunkSizeInBytes) {
chunkedStringConsumer(String(it))
}
}
inline fun chunkUtf8BytesBySize(
utf8bytes: ByteArray,
startingIndex: Int,
chunkSizeInBytes: Int,
chunkedUtf8BytesConsumer: (ByteArray) -> Unit
) {
assert(startingIndex >= 0) { "`startingIndex` must be at least 0!" }
assert(chunkSizeInBytes >= 4) { "`chunkSizeInBytes` must be at least 4 bytes!" }
if (utf8bytes.size <= chunkSizeInBytes) {
chunkedUtf8BytesConsumer(utf8bytes)
return
}
var i = 0
while (i < utf8bytes.size) {
var j = i + chunkSizeInBytes - 1
if (j > utf8bytes.size) {
j = utf8bytes.size - 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 (utf8bytes[j] <= -65) {
//j is pointing at byte 2-4 of a code point
do {
j--
} while (utf8bytes[j] <= -65)
j--
}
val ba = ByteArray(j - i + 1)
System.arraycopy(utf8bytes, i, ba, 0, j - i + 1)
chunkedUtf8BytesConsumer(ba)
i = j + 1
}
}
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 (utf8bytes[j] <= -65) {
//j is pointing at byte 2-4 of a code point
do {
j--;
} while (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);
}
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 */
}
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.
Upvotes: 0
Reputation: 2833
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:
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;
}
Upvotes: 1
Reputation: 149
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
}
}
}
Upvotes: 0
Reputation: 185
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)
)
);
Upvotes: 1
Reputation: 4112
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);
}
Upvotes: 2
Reputation: 486
int i = 3000;
while (sb.length() > i) {
Log.e(TAG, "Substring: "+ sb.substring(0, i));
sb = sb.substring(i);
}
Log.e(TAG, "Substring: "+ sb);
Upvotes: 5
Reputation: 372
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);
}
}
Upvotes: 5
Reputation: 35234
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.
Upvotes: 1
Reputation: 3757
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!
Upvotes: 96
Reputation: 2329
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.)
Upvotes: 53
Reputation: 2540
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);
}
}
Upvotes: 78
Reputation: 943
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;
}
}
Upvotes: 5
Reputation: 127
for( String line : logMesg.split("\n") ) {
Log.d( TAG, line );
}
Upvotes: 11
Reputation: 4754
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));
}
}
}
}
Upvotes: 2
Reputation: 22637
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.
Upvotes: 1
Reputation: 4119
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.
Upvotes: 0