Czytaj logcat programowo w aplikacji

116

Chcę czytać dzienniki logcat w mojej aplikacji i reagować na nie.

Znalazłem następujący kod:

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

Ten kod rzeczywiście zwraca dzienniki logcat, które powstały do ​​momentu uruchomienia aplikacji -

Ale czy możliwe jest ciągłe słuchanie nawet nowych dzienników logcat?

David
źródło
1
Opcja -d zrzuci dziennik i zakończy działanie. Po prostu usuń opcję -d, a logcat nie wyjdzie.
Frohnzie
1
Jak mogę to wyczyścić? - Muszę znaleźć konkretną linię dotyczącą mojej aplikacji
David
1
Nie jest wymagany root.
Luis
2
Jeśli root nie jest wymagany, tylko kompilacja deweloperska może uzyskać dostęp do własnego dziennika? A gdzie dokładnie ten kod jest wykonywany? z w aplikacji apk?
Ayyappa,
2
Spróbowałem testu. Mogłem odczytać tylko zdarzenia mojego własnego procesu. Myślę, że potrzebujesz roota, aby odczytać zdarzenia innych procesów.
Yetti99

Odpowiedzi:

55

Możesz dalej czytać dzienniki, usuwając flagę „-d” w powyższym kodzie.

Flaga „-d” nakazuje logcatowi wyświetlenie zawartości dziennika i wyjście. Jeśli usuniesz flagę, logcat nie zakończy działania i będzie wysyłać każdą dodaną do niego nową linię.

Pamiętaj tylko, że może to zablokować twoją aplikację, jeśli nie zostanie poprawnie zaprojektowana.

powodzenia.

Luis
źródło
Jeśli usunę „-d”, aplikacja utknęła i pojawia się okno dialogowe wymuszonego zamknięcia.
David
21
Jak powiedziałem powyżej, wymagałoby to starannego zaprojektowania aplikacji. Musisz mieć powyższy kod działający w oddzielnym wątku (aby uniknąć blokowania interfejsu użytkownika), a ponieważ chcesz zaktualizować widok tekstu w interfejsie użytkownika za pomocą informacji dziennika, musisz użyć programu obsługi, aby wysłać informacje z powrotem do interfejsu użytkownika.
Luis
2
Cześć Luis, czy możesz przesłać przykładowy kod oddzielnego wątku?
img.simone
Zobacz odpowiedź stackoverflow.com/a/59511458/1185087 używa ona programów, które nie blokują wątku interfejsu użytkownika.
user1185087
8

Możesz wyczyścić swój logcat za pomocą tej metody, której używam do czyszczenia po zapisaniu logcata do pliku, aby uniknąć zduplikowanych wierszy:

public void clearLog(){
     try {
         Process process = new ProcessBuilder()
         .command("logcat", "-c")
         .redirectErrorStream(true)
         .start();
    } catch (IOException e) {
    }
}
Jan Ziesse
źródło
Właśnie odkryłem, że wyczyszczenie dziennika może zająć trochę czasu. Jeśli więc wyczyścisz dziennik, a następnie natychmiast go przeczytasz, niektóre stare wpisy mogą jeszcze nie zostać wyczyszczone. Ponadto niektóre nowo dodane wpisy dziennika można usunąć, ponieważ zostały dodane przed zakończeniem czyszczenia. Nie ma to znaczenia, nawet jeśli wywołasz process.waitfor ().
Tom Rutchik
6

Z coroutines i oficjalnymi bibliotekami lifecycle-livesata-ktx i lifecycle -viewmodel-ktx jest to proste:

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

Stosowanie

val logCatViewModel by viewModels<LogCatViewModel>()

logCatViewModel.logCatOutput().observe(this, Observer{ logMessage ->
    logMessageTextView.append("$logMessage\n")
})
user1185087
źródło
Świetna sugestia. Zastanawiam się, czy jest sposób, aby to zadziałało WebViewzamiast TextView.
Fatih
4

Oto szybkie zestawianie / upuszczanie, które można wykorzystać do przechwycenia wszystkich bieżących lub wszystkich nowych (od ostatniego żądania) elementów dziennika.

Powinieneś to zmodyfikować / rozszerzyć, ponieważ możesz chcieć zwrócić ciągły strumień zamiast LogCapture.

„Instrukcja” 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);
  }

}

W Twoim projekcie, który rejestruje aktywność:

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

Gdy chcesz uchwycić wszystko, co zalogowałeś się przez „Logger”

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

Kiedy zgłodniejesz i chcesz zjeść więcej kłód

LogCapture nextCapture = capture.getNextCapture();

Możesz uzyskać przechwytywanie jako ciąg z

String captureString = capture.toString();

Możesz też pobrać pozycje dziennika przechwytywania za pomocą

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

Nie ma dokładnej statycznej metody przechwytywania kluczy dzienników obcych, ale jest na to sposób

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

Skorzystanie z powyższego pozwoli ci również wezwać Logger.this.nextCapturezagraniczne przechwytywanie.

Systemy Hypersoft
źródło
Jest to ogólnie najlepsza metoda wykonywania rejestrowania i analizy ze względu na strategię niskiego narzutu [w przetwarzaniu]. W tym kodzie może występować błąd lub nie. metoda logcat wyboru czasu musi być większa niż czas podany dla prawidłowego dopasowania. Brak prawidłowego algorytmu wyboru czasu spowoduje zduplikowany wpis w dzienniku w pierwszym elemencie nextCapture.
Hypersoft Systems
Brak dokumentacji na temat formatów czasu i ustawień regionalnych związanych z opcją selektora czasu android-logcat mógł spowodować powstanie błędów, które będą wymagały modyfikacji interpolacji formatu czasu.
Hypersoft Systems
Cześć ... Użyłem twojego kodu, ale wynik jest zawsze pusty. Jest ciągle ważny?
img.simone
@ img.simone; Zaktualizowałem to swoimi wersjami kodu. Logcat Androida jest uszkodzony na kilka sposobów. Pierwszą jest to, że dane wyjściowe logcat w formacie daty nie zawierają składnika roku, co powoduje, że porównanie dat jest cofane do roku 1970; po drugie opcja -t i -T z datą nie zaczyna w rzeczywistości wypluwać logów o określonej dacie, więc musimy przeanalizować datę i porównać ją z datą numeryczną zsynchronizowaną z rokiem 1970. Nie mogę tego przetestować aktualizacja dla Ciebie, ale zdecydowanie powinna działać; ponieważ kod pochodzi z działającego repozytorium z poprawkami specyficznymi dla tego kontekstu.
Hypersoft Systems
1
Możesz znaleźć działający kod tutaj pod git.hsusa.core.log.controller.AndroidLogController.java; Możesz użyć mojej biblioteki hscore zamiast tego "szybkiego i brudnego" rozwiązania. Aby zarejestrować się za pomocą hscore, użyłbyś: public final static SmartLogContext log = SmartLog.getContextFor("MyLogContext");aby rozpocząć. Działa prawie tak samo z lepszym interfejsem API. Możesz skorzystać z mojego narzędzia do śledzenia problemów w git hub, jeśli potrzebujesz JAKIEJKOLWIEK pomocy.
Hypersoft Systems
3

Flaga „-c” czyści bufor.

-c Czyści (opróżnia) cały dziennik i wychodzi.

Allen
źródło
jak użyć -c w powyższym kodzie. jeśli znalazłem coś w dzienniku, chcę to wyczyścić
yuva ツ
yuva, po prostu zrób: 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"))
              {
              }
            }

Używam tego w mojej aplikacji i działa. I możesz użyć powyższego kodu w zadaniu timera, aby nie zatrzymywał twojego głównego wątku

        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
źródło