Logcat Recorder

Introduction

Logcat is an Android mechanism used to collect and view system debug output. This is extremely useful for debugging and QA testing, as it provides valuable information about what the apps are doing.

Under normal conditions, logcat is visible via Android Device Monitor or directly executed from the ADB shell via the logcat command. The purpose of this tutorial is to present a simple mechanism to programmatically capture and filter logcat entries.

Logcat recorder

First thing’s first, we need a nice way to handle the logcat recorder’s status (recording, new log entry, idle). As a result, we create a simple OnLogcatRecorderListener.java interface to define these events:

OnLogcatRecorderListener .java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

/**

* An interface used to notify the log recording status.

*/

publicinterfaceOnLogcatRecorderListener{

/**

* Called when recording has started.

*/

voidonStartRecording();

/**

* Called when a new log entry is recorded.

*

* @param logEntry Log entry.

*/

voidonNewLogEntry(finalStringlogEntry);

/**

* Called when recording has stopped.

*

* @param log Returns the recorded log.

*/

voidonStopRecording(finalStringlog);

}

Next, we need to define a basic LogcatRecorder.java class and introduce our listener:

Now, we need a mechanism to start() recording the logcat. This will essentially create a new thread (to avoid locking the main UI thread), containing a Runtime Process to execute the logcat shell command and continuously record its output:

start()

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

/**

* Start recording the log output with a specific filter.

*

* @param filter Log filter.

* @throws IllegalStateException When log recording is already in place.

We first need to clear the current log via the “logcat -c” runtime process call. Once the log is clear, we can start recording it Runtime.getRuntime().exec(“logcat“)and, optionally, add a filter to our recorder Runtime.getRuntime().exec(“logcat | grep ” + filter). Whilst recording, we call our interface’s onNewLogEntry() method to pass on each new log entry.

To wrap this up, we also need a method to stop() the recording process:

stop()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

/**

* Stops recording the log output.

*

* @throws IllegalStateException When log recording has already been stopped.

*/

publicvoidstop()throwsIllegalStateException{

if(recording){

if(onLogcatRecorderListener!=null){

if(log==null||log.toString().length()==0){

log=newStringBuilder("n/a");

}

onLogcatRecorderListener.onStopRecording(log.toString());

}

if(continuousLogging!=null){

//Kill the Runtime Process.

continuousLogging.destroy();

}

recording=false;

}else{

thrownewIllegalStateException("Unable to call stop(): Currently not recording.");

}

}

In order to do this, we first check our recorder’s state and simply destroy the logging Runtime Process. Once the recording is done, our interface will serve the full list of recorded log entries via onLogcatRecorderListener.onStopRecording(log.toString());

Finally

We just need to add the LogcatRecorder to a new Activity. First, we define ../res/layout/activity_main.xml:

activity_main.xml

XHTML

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

<?xml version="1.0"encoding="utf-8"?>

<RelativeLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.screechstudios.logcatrecorder.MainActivity">

<ToggleButton

android:id="@+id/toggleButton"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentEnd="true"

android:layout_alignParentRight="true"

android:layout_alignParentTop="true"/>

<TextView

android:id="@+id/textView"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignBottom="@+id/toggleButton"

android:layout_alignParentLeft="true"

android:layout_alignParentStart="true"

android:layout_alignParentTop="true"

android:layout_toLeftOf="@+id/toggleButton"

android:layout_toStartOf="@+id/toggleButton"

android:gravity="center"

android:text="Record Logcat:"

android:textAppearance="?android:attr/textAppearanceLarge"/>

<Button

android:id="@+id/button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignEnd="@+id/toggleButton"

android:layout_alignParentLeft="true"

android:layout_alignParentStart="true"

android:layout_alignRight="@+id/toggleButton"

android:layout_below="@+id/textView"

android:text="Write to console"/>

<ScrollView

android:id="@+id/scrollView"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_alignParentLeft="true"

android:layout_alignParentStart="true"

android:layout_below="@+id/button">

<TextView

android:id="@+id/outputTextView"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:textAppearance="?android:attr/textAppearanceMedium"/>

</ScrollView>

</RelativeLayout>

Finally, we define MainActivity.java and add a new instance of our LogcatRecorder to it: