Yo! I’m launching an email newsletter @ PuppetHero.com! If you are into articles, tutorials, and news about Puppeteer, the PuppetHero Digest is for you.

I’ve got an Android project going that uses FFmpeg to build short video files (more on this at a later date). Normally, you run FFmpeg in a terminal window or command prompt.  It updates you on the status of a task in that terminal window.  I’ve instrumented FFmpeg on Android with some C code and a JNI interface.  When I execute FFmpeg from an Android app those status updates are written to logcat.

There are some important details that I was to surface in my app, primarily the progress of an encoding process.  In order to do this, I needed a way to read my Android apps logging statements.  This is possible on Android and without needing to request the scary READ_LOGS Android permission. Note: All of my examples are written in Kotlin because I ❤ Kotlin. You can execute a process with the Runtime API:

val process = Runtime.getRuntime().exec("some\_command\_here")

You can then get an InputStream to the Process’s output:

val process = Runtime.getRuntime().exec("some\_command\_here")
val inputStream = process.inputStream

Putting it all together, this is how one would get your apps logging statements from logcat, within the scope of your app:

val process = Runtime.getRuntime().exec("logcat")
reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?

do {
    line = reader.readLine()

    if (line != null) {
        // do something with this logging statement
    }
} while (line != null)

reader.close()

This code essentially runs forever because, as far as I’ve seen, reader doesn’t return a null line. I’m close, but I still needed to add some sophistication. I needed to read and parse logs while FFmpeg was running so I can update the user on the status of their job. I needed to start parsing logs when I wanted to update the user while FFmpeg was running and end parsing when FFmpeg was complete. This problem is perfectly solved using RxJava:

fun readLogs(): Observable = 
  return Observable.create ({ emitter ->
    var reader: BufferedReader? = null

    try {
      val process = Runtime.getRuntime().exec("logcat")
      reader = BufferedReader(InputStreamReader(process.inputStream))
      var line: String?

      do {
          line = reader.readLine()

          if (line.isNotBlank()) {
              emitter.onNext(line)
          }
      } while (line != null)
      emitter.onComplete()

    } catch (e: Exception) {
      emitter.onError(e)
    } finally {
      reader?.close()
    }
  })

With RxJava, I can emit a logging statement as it’s received by logcat. Usage:

val disposable = readLogs()
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe({ logLine -> 
    logTextView.setText(logLine)
  }, { error -> throw error })
  
// later, when you no longer need to subscribe to your logs
disposable.dispose()

Because it’s all based on RxJava, you can do more advanced things like filtering

val disposable = readLogs()
  .filter({ line -> line.startsWith("foo") })
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe({ logLine -> 
    logTextView.setText(logLine)
  }, { error -> throw error })

This allows you to “subscribe” to your apps logs!