Leggi logcat a livello di programmazione all'interno dell'applicazione


116

Voglio leggere e reagire ai log di logcat all'interno della mia applicazione.

Ho trovato il codice seguente:

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

Questo codice restituisce effettivamente i log di logcat che hanno fatto fino all'avvio dell'applicazione -

Ma è possibile ascoltare continuamente anche i nuovi log di logcat?


1
L'opzione -d scaricherà il registro e uscirà. Basta rimuovere l'opzione -d e logcat non uscirà.
Frohnzie

1
Come posso cancellarlo? - Devo trovare una riga specifica per quanto riguarda la mia domanda
David

1
Nessuna radice richiesta.
Luis

2
Se root non è richiesto, solo la build di sviluppo può accedere al proprio log? E dove viene eseguito esattamente questo codice? con nell'applicazione apk?
Ayyappa

2
Ho provato un test. Sono stato in grado di leggere solo gli eventi del mio processo. Penso che tu abbia bisogno di root per leggere gli eventi di altri processi.
Yetti99

Risposte:


55

Puoi continuare a leggere i log, semplicemente rimuovendo il flag "-d" nel codice sopra.

Il flag "-d" indica a logcat di mostrare il contenuto del log e di uscire. Se rimuovi il flag, logcat non terminerà e continuerà a inviare qualsiasi nuova riga aggiunta ad esso.

Tieni presente che ciò potrebbe bloccare la tua applicazione se non progettata correttamente.

in bocca al lupo.


Se rimuovo "-d" l'applicazione si blocca e ottengo la finestra di dialogo di chiusura forzata.
David

21
Come ho detto sopra, richiederebbe un'attenta progettazione dell'applicazione. È necessario che il codice precedente sia in esecuzione in un thread separato (per evitare di bloccare l'interfaccia utente) e poiché si desidera aggiornare la visualizzazione del testo nell'interfaccia utente con le informazioni di registro, è necessario utilizzare un gestore per pubblicare le informazioni sull'interfaccia utente.
Luis

2
Ciao Luis, per favore puoi pubblicare un codice di esempio di un thread separato?
img

Vedi la risposta stackoverflow.com/a/59511458/1185087 sta usando coroutine che non bloccano il thread dell'interfaccia utente.
user1185087

8

Puoi cancellare il tuo logcat con questo metodo che sto usando per cancellare dopo aver scritto logcat su un file per evitare righe duplicate:

public void clearLog(){
     try {
         Process process = new ProcessBuilder()
         .command("logcat", "-c")
         .redirectErrorStream(true)
         .start();
    } catch (IOException e) {
    }
}

Ho appena scoperto che la cancellazione del registro può richiedere del tempo. Quindi, se si cancella il registro e poi lo si legge immediatamente, alcune vecchie voci potrebbero non essere state ancora cancellate. Inoltre, alcune voci di registro appena aggiunte possono essere eliminate poiché vengono aggiunte prima del completamento della cancellazione. Non fa differenza anche se chiami process.waitfor ().
Tom Rutchik

6

Con coroutines e le librerie ufficiali lifecycle-livesata-ktx e lifecycle-viewmodel-ktx è semplice così:

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

Ottimo suggerimento. Mi chiedo se ci sia un modo per farlo funzionare WebViewinvece di TextView.
Fatih

4

Di seguito è riportato un rapido assemblaggio / inserimento che può essere utilizzato per acquisire tutti gli elementi di registro correnti o tutti nuovi (dall'ultima richiesta).

Dovresti modificarlo / estenderlo, perché potresti voler restituire un flusso continuo piuttosto che un LogCapture.

Il "Manuale" di 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);
  }

}

Nel tuo progetto che esegue la registrazione delle attività:

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

Quando si desidera acquisire tutto ciò che è stato eseguito l'accesso tramite "Logger"

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

Quando hai fame e vuoi fare uno spuntino con più tronchi

LogCapture nextCapture = capture.getNextCapture();

Puoi ottenere l'acquisizione come stringa con

String captureString = capture.toString();

Oppure puoi ottenere gli elementi di registro dell'acquisizione con

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

Non esiste un metodo statico esatto per acquisire chiavi di registro esterne, ma c'è comunque un modo

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

L'uso di quanto sopra ti consentirà anche di chiamare Logger.this.nextCapturela cattura straniera.


Questo è generalmente il metodo migliore per eseguire la registrazione e l'analisi a causa della strategia di basso overhead [in elaborazione]. Potrebbe esserci o meno un bug in questo codice. Il trattamento logcat della selezione del tempo deve essere maggiore del tempo dato per una corretta corrispondenza. La mancanza di un algoritmo di selezione dell'ora corretto creerà una voce di registro duplicata nel primo elemento di nextCapture.
Hypersoft Systems

La mancanza di documentazione sui formati dell'ora e sulle impostazioni locali associate all'opzione di selezione dell'ora di android-logcat potrebbe aver creato bug che richiederanno modifiche di interpolazione del formato dell'ora.
Hypersoft Systems

Ciao .. ho usato il tuo codice ma il risultato è sempre vuoto. È ancora valido?
img

@ img.simone; L'ho aggiornato con le mie revisioni del codice. Il logcat di Android è danneggiato in alcuni modi. Il primo è che l'output del formato della data di logcat non ha una componente relativa all'anno, il che fa un ripiego del confronto della data all'anno 1970; in secondo luogo, l'opzione -t e -T con una data non inizia effettivamente a sputare i log alla data specificata, quindi dobbiamo analizzare la data e confrontarla con una data numerica sincronizzata con l'anno 1970. Non posso testarlo aggiornamento per te, ma dovrebbe sicuramente funzionare; poiché il codice proviene da un repository funzionante con modifiche specifiche per questo contesto.
Hypersoft Systems

1
Puoi trovare il codice funzionante qui sotto git.hsusa.core.log.controller.AndroidLogController.java; Potresti voler usare la mia libreria hscore invece di questa soluzione "rapida e sporca". Per eseguire il log con hscore dovresti usare: public final static SmartLogContext log = SmartLog.getContextFor("MyLogContext");per iniziare. Funziona più o meno allo stesso modo con un'API migliore. Puoi usare il mio tracker dei problemi dell'hub git se hai bisogno di QUALSIASI aiuto con questo.
Hypersoft Systems

3

Il flag "-c" cancella il buffer.

-c Cancella (scarica) l'intero registro ed esce.


come usare -c nel codice sopra. se ho trovato qualcosa nel registro, voglio cancellarlo
yuva ツ

yuva, basta fare: process = Runtime.getRuntime (). exec ("logcat -c"); bufferedReader = new BufferedReader (new InputStreamReader (process.getInputStream ()), 1024); line = bufferedReader.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"))
              {
              }
            }

Lo sto usando nella mia app e funziona. E puoi usare il codice sopra in Timer Task in modo che non interrompa il tuo thread principale

        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);
      }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.