Leer logcat mediante programación dentro de la aplicación

116

Quiero leer y reaccionar a los registros de logcat dentro de mi aplicación.

Encontré el siguiente código:

try {
  Process process = Runtime.getRuntime().exec("logcat -d");
  BufferedReader bufferedReader = new BufferedReader(
  new InputStreamReader(process.getInputStream()));

  StringBuilder log=new StringBuilder();
  String line = "";
  while ((line = bufferedReader.readLine()) != null) {
    log.append(line);
  }
  TextView tv = (TextView)findViewById(R.id.textView1);
  tv.setText(log.toString());
  } 
catch (IOException e) {}

De hecho, este código devuelve los registros de logcat que se realizaron hasta que se inició la aplicación:

Pero, ¿es posible escuchar continuamente incluso nuevos registros de logcat?

David
fuente
1
La opción -d volcará el registro y saldrá. Simplemente elimine la opción -d y logcat no se cerrará.
Frohnzie
1
¿Cómo puedo borrarlo? - Necesito encontrar una línea específica con respecto a mi solicitud
David
1
No se requiere root.
Luis
2
Si no se requiere root, ¿solo la compilación de desarrollo puede acceder a su propio registro? ¿Y dónde se ejecuta exactamente este código? con en la aplicación apk?
Ayyappa
2
Probé una prueba. Solo pude leer los eventos de mi propio proceso. Creo que necesitas root para leer otros eventos de procesos.
Yetti99

Respuestas:

55

Puede seguir leyendo los registros, simplemente eliminando la marca "-d" en su código anterior.

La bandera "-d" indica a logcat que muestre el contenido del registro y salga. Si quita la bandera, logcat no terminará y seguirá enviando cualquier línea nueva que se le agregue.

Solo tenga en cuenta que esto puede bloquear su aplicación si no está diseñada correctamente.

buena suerte.

Luis
fuente
Si elimino la "-d", la aplicación se atasca y aparece el cuadro de diálogo Forzar cierre.
David
21
Como dije anteriormente, requeriría un diseño de aplicación cuidadoso. Debe tener el código anterior ejecutándose en un hilo separado (para evitar bloquear la interfaz de usuario) y, como desea actualizar la vista de texto en la interfaz de usuario con información de registro, debe usar un controlador para publicar la información en la interfaz de usuario.
Luis
2
Hola Luis, ¿puedes publicar un código de ejemplo de un hilo separado?
img.simone
Vea la respuesta stackoverflow.com/a/59511458/1185087 está usando corrutinas que no bloquean el hilo de la interfaz de usuario.
user1185087
8

Puede borrar su logcat con este método que estoy usando para borrar después de escribir logcat en un archivo para evitar líneas duplicadas:

public void clearLog(){
     try {
         Process process = new ProcessBuilder()
         .command("logcat", "-c")
         .redirectErrorStream(true)
         .start();
    } catch (IOException e) {
    }
}
Jan Ziesse
fuente
Acabo de descubrir que limpiar el registro puede llevar algún tiempo. Entonces, si borra el registro y luego lee el registro inmediatamente, es posible que algunas entradas antiguas aún no se hayan borrado. Además, algunas entradas de registro recién agregadas se pueden eliminar porque se agregan antes de que se complete el borrado. No hace ninguna diferencia incluso si llama a process.waitfor ().
Tom Rutchik
6

Con las corrutinas y las bibliotecas oficiales lifecycle-livedata-ktx y lifecycle-viewmodel-ktx , es así de simple:

class LogCatViewModel : ViewModel() {
    fun logCatOutput() = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
        Runtime.getRuntime().exec("logcat -c")
        Runtime.getRuntime().exec("logcat")
                .inputStream
                .bufferedReader()
                .useLines { lines -> lines.forEach { line -> emit(line) }
        }
    }
}

Uso

val logCatViewModel by viewModels<LogCatViewModel>()

logCatViewModel.logCatOutput().observe(this, Observer{ logMessage ->
    logMessageTextView.append("$logMessage\n")
})
usuario1185087
fuente
Gran sugerencia. Me pregunto si hay alguna manera de hacer que esto funcione con en WebViewlugar de TextView.
Fatih
4

A continuación, se incluye una combinación / introducción rápida que se puede utilizar para capturar todos los elementos de registro actuales o todos los nuevos (desde una última solicitud).

Debe modificar / ampliar esto, porque es posible que desee devolver un flujo continuo en lugar de un LogCapture.

El "Manual" de Android LogCat: https://developer.android.com/studio/command-line/logcat.html

import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Stack;

/**
* Created by triston on 6/30/17.
*/

public class Logger {

  // http://www.java2s.com/Tutorial/Java/0040__Data-Type/SimpleDateFormat.htm
  private static final String ANDROID_LOG_TIME_FORMAT = "MM-dd kk:mm:ss.SSS";
  private static SimpleDateFormat logCatDate = new SimpleDateFormat(ANDROID_LOG_TIME_FORMAT);

  public static String lineEnding = "\n";
  private final String logKey;

  private static List<String> logKeys = new ArrayList<String>();

  Logger(String tag) {
    logKey = tag;
    if (! logKeys.contains(tag)) logKeys.add(logKey);
  }

  public static class LogCapture {
    private String lastLogTime = null;
    public final String buffer;
    public final List<String> log, keys;
    LogCapture(String oLogBuffer, List<String>oLogKeys) {
      this.buffer = oLogBuffer;
      this.keys = oLogKeys;
      this.log = new ArrayList<>();
    }
    private void close() {
      if (isEmpty()) return;
      String[] out = log.get(log.size() - 1).split(" ");
      lastLogTime = (out[0]+" "+out[1]);
    }
    private boolean isEmpty() {
      return log.size() == 0;
    }
    public LogCapture getNextCapture() {
      LogCapture capture = getLogCat(buffer, lastLogTime, keys);
      if (capture == null || capture.isEmpty()) return null;
      return capture;
    }
    public String toString() {
      StringBuilder output = new StringBuilder();
      for (String data : log) {
        output.append(data+lineEnding);
      }
      return output.toString();
    }
  }

  /**
   * Get a list of the known log keys
   * @return copy only
   */
  public static List<String> getLogKeys() {
    return logKeys.subList(0, logKeys.size() - 1);
  }

  /**
   * Platform: Android
   * Get the logcat output in time format from a buffer for this set of static logKeys.
   * @param oLogBuffer logcat buffer ring
   * @return A log capture which can be used to make further captures.
   */
  public static LogCapture getLogCat(String oLogBuffer) { return getLogCat(oLogBuffer, null, getLogKeys()); }

  /**
   * Platform: Android
   * Get the logcat output in time format from a buffer for a set of log-keys; since a specified time.
   * @param oLogBuffer logcat buffer ring
   * @param oLogTime time at which to start capturing log data, or null for all data
   * @param oLogKeys logcat tags to capture
   * @return A log capture; which can be used to make further captures.
   */
  public static LogCapture getLogCat(String oLogBuffer, String oLogTime, List<String> oLogKeys) {
    try {

      List<String>sCommand = new ArrayList<String>();
      sCommand.add("logcat");
      sCommand.add("-bmain");
      sCommand.add("-vtime");
      sCommand.add("-s");
      sCommand.add("-d");

      sCommand.add("-T"+oLogTime);

      for (String item : oLogKeys) sCommand.add(item+":V"); // log level: ALL
      sCommand.add("*:S"); // ignore logs which are not selected

      Process process = new ProcessBuilder().command(sCommand).start();

      BufferedReader bufferedReader = new BufferedReader(
        new InputStreamReader(process.getInputStream()));

      LogCapture mLogCapture = new LogCapture(oLogBuffer, oLogKeys);
      String line = "";

      long lLogTime = logCatDate.parse(oLogTime).getTime();
      if (lLogTime > 0) {
        // Synchronize with "NO YEAR CLOCK" @ unix epoch-year: 1970
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date(oLogTime));
        calendar.set(Calendar.YEAR, 1970);
        Date calDate = calendar.getTime();
        lLogTime = calDate.getTime();
      }

      while ((line = bufferedReader.readLine()) != null) {
        long when = logCatDate.parse(line).getTime();
        if (when > lLogTime) {
          mLogCapture.log.add(line);
          break; // stop checking for date matching
        }
      }

      // continue collecting
      while ((line = bufferedReader.readLine()) != null) mLogCapture.log.add(line);

      mLogCapture.close();
      return mLogCapture;
    } catch (Exception e) {
      // since this is a log reader, there is nowhere to go and nothing useful to do
      return null;
    }
  }

  /**
   * "Error"
   * @param e
   */
  public void failure(Exception e) {
    Log.e(logKey, Log.getStackTraceString(e));
  }

  /**
   * "Error"
   * @param message
   * @param e
   */
  public void failure(String message, Exception e) {
    Log.e(logKey, message, e);
  }

  public void warning(String message) {
    Log.w(logKey, message);
  }

  public void warning(String message, Exception e) {
    Log.w(logKey, message, e);
  }

  /**
   * "Information"
   * @param message
   */
  public void message(String message) {
    Log.i(logKey, message);
  }

  /**
   * "Debug"
   * @param message a Message
   */
  public void examination(String message) {
    Log.d(logKey, message);
  }

  /**
   * "Debug"
   * @param message a Message
   * @param e An failure
   */
  public void examination(String message, Exception e) {
    Log.d(logKey, message, e);
  }

}

En su proyecto que realiza el registro de actividad:

Logger log = new Logger("SuperLog");
// perform logging methods

Cuando desee capturar todo lo que inició sesión a través de "Logger"

LogCapture capture = Logger.getLogCat("main");

Cuando tienes hambre y quieres comer más troncos

LogCapture nextCapture = capture.getNextCapture();

Puede obtener la captura como una cadena con

String captureString = capture.toString();

O puede obtener los elementos de registro de la captura con

String logItem = capture.log.get(itemNumber);

No existe un método estático exacto para capturar claves de registro externas, pero no obstante, existe una forma

LogCapture foreignCapture = Logger.getLogCat("main", null, foreignCaptureKeyList);

El uso de lo anterior también le permitirá solicitar Logger.this.nextCapturela captura extranjera.

Sistemas Hypersoft
fuente
Este es generalmente el mejor método para realizar el registro y el análisis debido a la estrategia de bajo costo [en el procesamiento]. Puede que haya o no un error en este código. El tratamiento de logcat de la selección de tiempo debe ser mayor que el tiempo dado para una coincidencia correcta. La falta de un algoritmo de selección de tiempo correcto creará una entrada de registro duplicada en el primer elemento de nextCapture.
Hypersoft Systems
La falta de documentación sobre formatos de tiempo y configuraciones regionales asociadas con la opción de selector de tiempo de android-logcat puede haber creado errores que requerirán modificaciones de interpolación de formato de tiempo.
Hypersoft Systems
Hola ... Usé tu código pero el resultado siempre está vacío. ¿Aun es válido?
img.simone
@ img.simone; He actualizado esto con mis revisiones de código. El logcat de Android está roto de varias maneras. La primera, es que la salida del formato de fecha de logcat no tiene un componente de año, lo que hace que la comparación de fechas sea un retroceso al año 1970; en segundo lugar, la opción -t y -T con una fecha en realidad no comienza a escupir registros en la fecha especificada, por lo que tenemos que analizar la fecha y compararla con una fecha numérica sincronizada con el año 1970. No puedo probar esto actualizar para usted, pero definitivamente debería funcionar; ya que el código proviene de un repositorio de trabajo con enmiendas específicas para este contexto.
Hypersoft Systems
1
Puede encontrar el código de trabajo aquí debajo git.hsusa.core.log.controller.AndroidLogController.java; Es posible que desee utilizar mi biblioteca hscore en lugar de esta solución "rápida y sucia". Para iniciar sesión con hscore, usaría: public final static SmartLogContext log = SmartLog.getContextFor("MyLogContext");para comenzar. Funciona prácticamente de la misma manera con una mejor API. Puede usar mi rastreador de problemas de git hub si necesita CUALQUIER ayuda con eso.
Hypersoft Systems
3

La bandera "-c" borra el búfer.

-c Borra (vacía) todo el registro y sale.

Allen
fuente
cómo usar -c en el código anterior. Si encontré algo en el registro, quiero borrarlo
Yuva ツ
yuva, haz lo siguiente: process = Runtime.getRuntime (). exec ("logcat -c"); BufferedReader = new BufferedReader (nuevo InputStreamReader (process.getInputStream ()), 1024); línea = búferReader.readLine ();
djdance
1
            //CLEAR LOGS
            Runtime.getRuntime().exec("logcat -c");
            //LISTEN TO NEW LOGS
            Process pq=Runtime.getRuntime().exec("logcat v main");
            BufferedReader brq = new BufferedReader(new InputStreamReader(pq.getInputStream()));
            String sq="";
            while ((sq = brq.readLine()) != null)
            {
              //CHECK YOUR MSG HERE 
              if(sq.contains("send MMS with param"))
              {
              }
            }

Estoy usando esto en mi aplicación y funciona. Y puede usar el código anterior en Timer Task para que no detenga su hilo principal

        Timer t;
        this.t.schedule(new TimerTask()
        {
          public void run()
          {
            try
            {
                ReadMessageResponse.this.startRecord();//ABOVE METHOD HERE

            }
            catch (IOException ex)
            {
              //NEED TO CHECK SOME VARIABLE TO STOP MONITORING LOGS 
              System.err.println("Record Stopped");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            finally
            {
                ReadMessageResponse.this.t.cancel();
            }
          }
        }, 0L);
      }
Reetpreet Brar
fuente