Android: establece la longitud máxima de los mensajes de logcat

101

De forma predeterminada, parece que logcat truncará cualquier mensaje de registro que considere "demasiado largo". Esto sucede tanto dentro de Eclipse como cuando se ejecuta logcat en la línea de comando usando adb -d logcat, y está truncando algunos mensajes de depuración importantes.

¿Hay alguna forma de aumentar la longitud de cadena máxima admitida por logcat para que deje de truncar la información de depuración? La documentación oficial implica que puede que no los haya, pero ¿quizás logcat admita algunas opciones adicionales que no se mencionan allí?

aroth
fuente
Similar: stackoverflow.com/questions/6321555/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@JoshCorreia No creo que sea un buen duplicado, ya que se refiere al tamaño total del búfer, y esto es por mensaje de registro.
Ryan M
1
@RyanM Ah, mi mal, entendí mal la otra pregunta. Gracias, quitando Mark como dupe.
Josh Correia

Respuestas:

45

Hay un búfer de tamaño fijo en logcat para registros binarios ( /dev/log/events) y este límite es de 1024 bytes. Para los registros no binarios también hay un límite:

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

Por tanto, el tamaño real del mensaje para los registros binarios y no binarios es de ~ 4076 bytes. La interfaz del registrador del kernel impone este LOGGER_ENTRY_MAX_PAYLOADlímite.

Las fuentes de liblog (utilizadas por logcat) también dicen:

  • El mensaje puede haber sido truncado por el controlador de registro del kernel.

Le recomendaría la herramienta nxlog que no usa el binario logcat, pero debido a las limitaciones del kernel dudo que resuelva su problema. Sin embargo, podría valer la pena intentarlo. (descargo de responsabilidad: soy el autor).

b0ti
fuente
6
¿Dónde lo encuentro? ¿Está en el código "logcat"? Entonces, ¿tendría que compilar mi propio logcat modificado?
d4Rk
2
¿Qué es logaritmo binario / no binario?
fobbymaster
2
Debido a que se agregaron campos de metadatos, LOGGER_ENTRY_MAX_PAYLOADse ha reducido de 4076 a 4068 en las versiones más recientes de Android (ver aquí ).
mhsmith
87

Ok, interesante. Me decepcionó ver que la respuesta fue "realmente no se puede expandir". Mi pensamiento inicial fue dividirlo para poder verlo todo, así que aquí comparto con ustedes cómo lo hago (no es nada elegante ni eficiente, pero hace el trabajo en un apuro):

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

¡Editado para mostrar la última cadena!

Travis
fuente
¡No hay problema! Espero que te haya ayudado
Travis
Estoy bastante seguro de que aquí hay un error de uno. Tuve que usar "i <chunkCount + 1" para obtener el último fragmento
Dan
2
int chunkCount = sb.length() / 4000;int chunkCount = sb.length() / 4000; if (chunkCount * 4000 < sb.length()) chunkCount++;
Perdiste la
2
agregar else { Log.v(TAG, sb); }para imprimir también el registro cuando el mensaje tiene <= 4000 caracteres de largo
Bojan Radivojevic Bomber
4
Esta respuesta es incorrecta para caracteres no ASCII. logcat admite UTF8 y el límite es de 4 k bytes , no de caracteres.
miguel
58

Divídalo en varias partes de forma recursiva.

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
fuente
3
Esta es, con mucho, la solución más limpia y la primera vez que utilizo la recursividad en el código de producción.
Agresor
2
@Aggressor, ¿por qué necesita registrar más de 4000 mensajes largos en producción?
TWiStErRob
1
Mi caso de uso es generar un gran material json. Los archivos son simplemente una molestia.
Marcel Falliere
1
Gracias muy útiles. Publiqué una respuesta que rompe la cadena al final de las líneas.
aturdido
1
Awesome Simpler Cleaner Fresco y hermoso. Claps
Muhammad Ashfaq
11
for( String line : logMesg.split("\n") ) {
    Log.d( TAG, line );
}
usuario2085092
fuente
5

Aquí está el código que uso: trunca las líneas en el límite de 4000 mientras que también rompe la línea en nuevas líneas en lugar de en la mitad de la línea. Hace que el archivo de registro sea más fácil de leer.

Uso:

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

Implementación:

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
fuente
4

El siguiente código es un refinamiento de lo publicado por Mark Buikema. Rompe la cuerda en nuevas líneas. Útil para registrar cadenas JSON largas.

  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);
    }
  }
aturdido
fuente
3
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
fuente
2

nosotros esta lógica de paginación

    /*
     * 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
fuente
1

proporcionando mi propia opinión sobre la solución de Travis,

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

Aproveche el hecho de que Log.println()devuelve el número de bytes escritos para evitar la codificación "4000". luego, llámese de forma recursiva en la parte del mensaje que no pudo registrarse hasta que no quede nada.

Jeffrey Blattman
fuente
Desafortunadamente, println devuelve # de bytes escritos y caracteres! = Bytes.
gnuf
1
bueno, funciona. Supongo que porque solo estoy registrando texto ASCII.
Jeffrey Blattman
1

Si su registro es muy largo (por ejemplo, registrando el volcado completo de su base de datos por motivos de depuración, etc.) puede suceder que logcat evite un registro excesivo. Para evitar esto, puede agregar un tiempo de espera evry x milisegundos.

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

Tenga cuidado, use esto solo con fines de depuración, ya que puede detener los bloques del hilo principal.

Patrick Favre
fuente
1

Como mencionó @mhsmith, el LOGGER_ENTRY_MAX_PAYLOADes 4068 en las versiones recientes de Android. Sin embargo, si usa 4068 como la longitud máxima del mensaje en los fragmentos de código que se ofrecen en otras respuestas, los mensajes se truncarán. Esto se debe a que Android agrega más caracteres al principio y al final de su mensaje, que también cuentan. Otras respuestas usan el límite de 4000 como solución. Sin embargo, es posible usar realmente todo el límite con este código (el código genera una etiqueta a partir del seguimiento de la pila para mostrar el nombre de la clase y el número de línea que llamó al registro, no dude en modificarlo):

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ý
fuente
0

No conozco ninguna opción para aumentar la longitud de logcat, pero podemos encontrar los diferentes registros como el registro principal, el registro de eventos, etc. El registro principal generalmente contiene todo lo que su longitud va hasta 4Mb. Así que es posible que pueda obtener lo que perdió en la terminal de registro. La ruta es: \ data \ logger.

Vins
fuente
0

Aunque las otras soluciones proporcionadas fueron útiles, no me satisfizo porque no cubrieron los casos en que el registro es más largo que el doble que el LOGGER_ENTRY_MAX_LEN mencionado por @ b0ti. Además, incluso mi siguiente solución no es perfecta ya que LOGGER_ENTRY_MAX_LEN no se recupera dinámicamente. Si alguien conoce una forma de hacer esto, ¡me encantaría escucharlo en los comentarios! De todos modos, esta es la solución que uso en mi código en este momento:

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
fuente