2

Goal

Collect periodic updates of the LogCat and save (append) those chunks of text to a file on the SDcard

Problem

The Log class doesn't provide updates since a specific time-stamp

Possible solution

My plan is to periodically run code that is similar to: http://www.helloandroid.com/tutorials/reading-logs-programatically or https://stackoverflow.com/a/9039352/550471

However, with one notable difference: use the -v time parameter to ensure that each line is time-stamped.

After each time the LogCat data is collected, the app will store the time-stamp of the last Log entry. The next time the LogCat data is collected the app will search through the text to find the time-stamp and then save the chunk of data to sdcard that was added to the Log since the specified time-stamp.

Possible problem

If the LogCat data is collected at too short periods then the CPU is busy processing a lot of 'old' data. If the Logcat data is collected at too long periods then some data could be missed.

Is there a better way ?

Community
  • 1
  • 1
Someone Somewhere
  • 23,475
  • 11
  • 118
  • 166
  • Bear in mind that starting with Jelly Bean, you cannot hold `READ_LOGS`, and so you will only be able to get log messages logged by your own app via your techniques. – CommonsWare Jul 19 '12 at 23:54
  • that's OK - that's all I want anyway. Google should update the Log class (whilst they're making changes to logcat) to encompass the functionality that I have planned - it would be so much more efficient. – Someone Somewhere Jul 20 '12 at 19:51
  • Is it possible to use `android.util.Log` to read the LogCat ? I noticed that `Runtime.getRuntime().exec("")` can freeze-up the app... – Someone Somewhere Jul 27 '12 at 22:56
  • "Is it possible to use android.util.Log to read the LogCat ?" -- no. There never was an officially documented and supported means of reading LogCat. – CommonsWare Jul 27 '12 at 23:12

1 Answers1

1

This is what I came up with - it works very well when it doesn't freeze up.

As you might know, Runtime.getRuntime().exec("") has a pretty good chance of causing an ANR in Android earlier than Jelly Bean. If someone has a solution to overcome the ANR, then please share.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

import android.os.Environment;
import android.util.Log;

/*
 * For (compressed) buffer sizes, see: http://elinux.org/Android_Logging_System
 * buffer:main = 64KB
 * buffer:radio = 64KB
 * buffer:system = 64KB
 * buffer:event = 256KB
 * 
 * NOTE: the 'command' must include "-d -v time" !!
 * to switch buffers, use "-b <buffer>"
 */
public class LogCatReader {
    // constants
    private static final String CR = "\r\n";
    private static final String END_OF_DATE_TIME = "): ";
    private static final int DEFAULT_SEARCH_START_INDEX = 0;

    // member variables
    private StringBuilder mLog;
    private LogThread mLogThread = null;
    private String mLastLogReadToken = "";
    private String mLogCommand = "";
    private int mStringCapacity;
    private File mFileTarget = null;

    // constructor
    public LogCatReader(String command, int capacity) {
        mLogCommand = command;
        mStringCapacity = capacity;
    }

    // returns complete logcat buffer
    // note: takes about 1.5sec to finish
    synchronized public StringBuilder getLogComplete() {
        try {
            // capacity should be about 25% bigger than buffer size since the
            // buffer is compressed
            mLog = new StringBuilder(mStringCapacity);

            // command to capture log
            Process process = Runtime.getRuntime().exec(mLogCommand);
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));

            String line;
            while ((line = bufferedReader.readLine()) != null) {
                // append() is costly if capacity needs to be increased, be sure
                // to reserve enough in the first place
                mLog.append(line + CR);
            }
        } catch (IOException e) {
        }

        return mLog;
    }

    public String getLogUpdatesOnly() {
        String strReturn = "";

        StringBuilder sbLog = getLogComplete();

        try {
            int iStartindex = DEFAULT_SEARCH_START_INDEX;

            // if there exists a token from a previous search then use that
            if (mLastLogReadToken.length() > 0) {
                iStartindex = sbLog.indexOf(mLastLogReadToken);

                // if string not found then start at beginning
                if (iStartindex == -1) {
                    // start search at beginning of log
                    iStartindex = DEFAULT_SEARCH_START_INDEX;
                }
            }

            int iEndindex = sbLog.length();

            // if token is found then move index to the next line
            if (iStartindex > DEFAULT_SEARCH_START_INDEX) {
                iStartindex = sbLog.indexOf(CR, iStartindex);

                if (iStartindex != -1) {
                    iStartindex += CR.length();
                } else {
                    // return an empty string
                    iStartindex = iEndindex;
                }
            }

            // grab the data between the start and end indices
            strReturn = sbLog.substring(iStartindex, iEndindex);

            // grab date/time token for next search
            iStartindex = sbLog.lastIndexOf(END_OF_DATE_TIME);

            if (iStartindex != -1) {
                iEndindex = iStartindex;
                iStartindex = sbLog.lastIndexOf(CR, iEndindex);
                iStartindex += CR.length();

                if (iStartindex == -1) {
                    // read from beginning
                    iStartindex = 0;
                }

                mLastLogReadToken = sbLog.substring(iStartindex, iEndindex);
            }
        } catch (Exception e) {
            strReturn = "";
        }

        return strReturn;
    }

    public void startPeriodicLogCatReader(int timePeriod, String logfilename) {
        if (mLogThread == null) {
            mLogThread = new LogThread(timePeriod, logfilename);
            mLogThread.start();
        }
    }

    public void stopPeriodicLogCatReader() {
        if (mLogThread != null) {
            mLogThread.interrupt();
            mLogThread = null;
        }
    }

    private class LogThread extends Thread {
        private boolean mInterrupted;
        private int mTimePeriod;// in seconds
        private String mLogref;
        private BufferedWriter mBuffWriter = null;
        public boolean mPauseLogCollection = false;

        // constructor: logfilename is optional - pass null to not use
        public LogThread(int timePeriod, String logfilename) {
            mTimePeriod = timePeriod;

            if (logfilename != null) {
                File fLogFolder = new File(
                        Environment.getExternalStorageDirectory() + "/logfiles");
                if (fLogFolder.exists() == false) {
                    if (fLogFolder.mkdirs() == false) {
                        Log.e("LogCatReader",
                                "Could not create "
                                        + fLogFolder.getAbsolutePath());
                    }
                }

                mFileTarget = new File(
                        Environment.getExternalStorageDirectory() + "/logfiles",
                        logfilename);

                if (mFileTarget.exists() == false) {
                    try {
                        // file doesn't yet exist - create a fresh one !
                        mFileTarget.createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                        mFileTarget = null;
                    }
                }
            }
        }

        @Override
        public void interrupt() {
            mInterrupted = true;
            super.interrupt();
        }

        @Override
        public void run() {
            super.run();

            // initialization
            mInterrupted = false;

            // set up storage
            if (mFileTarget != null) {
                try {
                    mBuffWriter = new BufferedWriter(new FileWriter(
                            mFileTarget, true), 10240);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            while ((mInterrupted == false) && (mBuffWriter != null)) {
                if (mPauseLogCollection == false) {
                    // read log updates
                    mLogref = getLogUpdatesOnly();

                    // save log updates to file
                    try {
                        mBuffWriter.append(mLogref);
                        mBuffWriter.flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if (!mInterrupted) {
                    try {
                        sleep(mTimePeriod * 1000);
                    } catch (InterruptedException e) {
                    }
                }
            }

            if (mBuffWriter != null) {
                try {
                    mBuffWriter.close();
                    mBuffWriter = null;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }// end of inner class
}// end of outer class

The procedure I used to find only the updates is to capture the date and time of the very last line of a logcat and use that as the search token next time around.

To use this class, the following is an example:

        LogCatReader logcatPeriodicReader = new LogCatReader("logcat -b main -d -v time", 80 * 1024);//collect "main" buffer, exit after reading logcat
        logcatPeriodicReader.startPeriodicLogReader(90, "log.txt");//read logcat every 90 secs
Someone Somewhere
  • 23,475
  • 11
  • 118
  • 166
  • I am having the very same issue. Did you check you don't get duplicates in log.txt? Also - 1.5 sec is prettly long time for the task. I am looking for a solution which "Streams" logs to both logcat AND logs.txt. Maybe "logcat -f" ? – Henry Aloni Feb 12 '15 at 06:42