Read logcat programmatically within application

AndroidLogcatAndroid Logcat

Android Problem Overview


I want to read and react to logcat logs within my application.

I found the following code:

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

This code indeed returns the logcat logs that made until the application was started -

But is it possible to continuously listen to even new logcat logs?

Android Solutions


Solution 1 - Android

You can keep reading the logs, just by removing the "-d" flag in your code above.

The "-d" flag instruct to logcat to show log content and exit. If you remove the flag, logcat will not terminate and keeps sending any new line added to it.

Just have in mind that this may block your application if not correctly designed.

good luck.

Solution 2 - Android

With coroutines and the official lifecycle-livedata-ktx and lifecycle-viewmodel-ktx libraries it's simple like that:

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

Usage

val logCatViewModel by viewModels<LogCatViewModel>()

logCatViewModel.logCatOutput().observe(this, Observer{ logMessage ->
    logMessageTextView.append("$logMessage\n")
})

Solution 3 - Android

You can clear your logcat with this method i'm using to clear after writing logcat to a file to avoid duplicated lines:

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

Solution 4 - Android

Here is a quick put-together/drop-in that can be used for capturing all current, or all new (since a last request) log items.

You should modify/extend this, because you might want to return a continuous-stream rather than a LogCapture.

The Android LogCat "Manual": 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);
  }

}

In your project which performs activity logging:

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

When you want to capture everything you logged through "Logger"

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

When you get hungry and you want to snack on more logs

LogCapture nextCapture = capture.getNextCapture();

You can get the capture as a string with

String captureString = capture.toString();

Or you can get the log items of the capture with

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

There is no exact static method to capture foreign log keys but there is a way none the less

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

Using the above will also permit you to call Logger.this.nextCapture on the foreign capture.

Solution 5 - Android

The "-c" flag clears the buffer.

-c Clears (flushes) the entire log and exits.

Solution 6 - Android

Based on @user1185087's answer, a simple solution without ViewModel could be:

Start the job on an IO thread:

// Custom scope for collecting logs on IO threads.
val scope = CoroutineScope(Job() + Dispatchers.IO)

val job = scope.launch {
    Runtime.getRuntime().exec("logcat -c") // Clear logs
    Runtime.getRuntime().exec("logcat") // Start to capture new logs
        .inputStream 
        .bufferedReader()
        .useLines { lines ->
            // Note that this forEach loop is an infinite loop until this job is cancelled.
            lines.forEach { newLine ->
                // Check whether this job is cancelled, since a coroutine must
                // cooperate to be cancellable.
                ensureActive()  
                // TODO: Write newLine into a file or buffer or anywhere appropriate              
            }
        }
}

Cancel the job from the main thread:

MainScope().launch {
    // Cancel the job and wait for its completion on main thread.
    job.cancelAndJoin()
    job = null // May be necessary
    // TODO: Anything else you may want to clean up
}

This solution should suffice if you want to collect your app's new logs continuously on a background thread.

Solution 7 - Android

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

I am using this in my app and it works . And you can use above code in Timer Task so that it wont stop your main thread

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

Solution 8 - Android

Try to add this permission into the mainfest:

<uses-permission android:name="android.permission.READ_LOGS"/>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavidView Question on Stackoverflow
Solution 1 - AndroidLuisView Answer on Stackoverflow
Solution 2 - Androiduser1185087View Answer on Stackoverflow
Solution 3 - AndroidJan ZiesseView Answer on Stackoverflow
Solution 4 - AndroidHypersoft SystemsView Answer on Stackoverflow
Solution 5 - AndroidAllenView Answer on Stackoverflow
Solution 6 - AndroidDeXterView Answer on Stackoverflow
Solution 7 - AndroidReetpreet BrarView Answer on Stackoverflow
Solution 8 - AndroidCatYoutuber_View Answer on Stackoverflow