1

I use LogCat to get feedback about my Android application execution when it runs through my eclipse emulator.

I'm getting some different behavior when the apk runs on one of my real phones. I'm curious if the LogCat generates a file somewhere on the phone, which I can get access too.

Ultimately I would like to get the LogCat results and email them to myself via a support email address. The intent is to allow users who have issues with the program to send me a copy of their LogCat results when they have issues.

Does LogCat create a file I can get access to, and can I email it to myself? If LogCat doesn't do this, is there an alternative way of doing this?

halfer
  • 19,824
  • 17
  • 99
  • 186
James Oravec
  • 19,579
  • 27
  • 94
  • 160
  • To clarify, I see a reference of how to do this with the adb at http://stackoverflow.com/questions/4424544/where-are-android-logcat-files-stored but is there a way to turn this on in production and get the information reported back? – James Oravec Jul 18 '13 at 20:16
  • 1
    "If LogCat doesn't do this, then any suggestions on an alternative way of doing this would be appreciated." -- use ACRA: http://acra.ch/, in conjunction perhaps with your own logging outside of LogCat. – CommonsWare Jul 18 '13 at 20:44

1 Answers1

0

Very difficult task, but hopefully this will help...

(Doing a lot of copying/pasting, so please let me know if I'm missing some important code! I didn't test the max 1MB setting yet - also might make sense to put it in MainActivity.onCreate() instead so we don't call that with ever log message, but this works...)

Some of this from LogCollector, so giving credit where credit is due:(https://code.google.com/p/android-log-collector/)

Relevant method from my LogCollector class: (takes obvious input and emails multiple attachments of logs, if both exist - I have an option to enable a log file as well as logcat. BTW, logcat has a ~64KB memory, so you will NOT get much logging from that, thus the need for me to log to a file)

public boolean sendLog(String email, String subject, String body) {
    Logger.v("LogCollector - sendLog()");
    ArrayList<String> lines = mLastLogs;
    if (lines.size() > 0) {
        Uri emailUri = Uri.parse("mailto:" + email);
        ///////////////////////////////////////////////////////////////////////////////////////     
        // Create and open folder for output file
        Logger.d("LogCollector - Creating folder & file...");
        final String filename = "AppName_logCat.txt";
        File folder = new File(Environment.getExternalStorageDirectory()+"/temp/");
        // Create directory structure if needed
        if(folder.mkdirs()){
            Logger.v("Created temp folder.");  
        }else{
            Logger.v("Did NOT create temp folder - perhaps it already exists");  
        }
        //Create log file
        File logFile = new File(Environment.getExternalStorageDirectory()+"/temp/", filename);
        Logger.v("Log File Path: "+logFile.getAbsolutePath());  
        FileWriter fileWriter;
        //String phoneInfo = collectPhoneInfo();
        //String appInfo = collectAppInfo();
        //Put contents into log file, including phone info
        try {
            Logger.d("LogCollector - Putting info into log file...");
            fileWriter = new FileWriter(logFile, false);        //dont append, clear first
            BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
            //bufferedWriter.write(phoneInfo);
            //bufferedWriter.write("\n\n");
            //bufferedWriter.write(appInfo);
            bufferedWriter.write("\n\n");
            for (String line : lines) {
                    bufferedWriter.write(line);
                    bufferedWriter.newLine();
            }
            bufferedWriter.close();
        } catch (IOException e1) {
            Logger.w("LogCollector - Error putting log info into file: "+e1);
            if(!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
                Logger.w("SD card not present or not accessible");
            }
            return false;
        }
        // Check if log can be read for debugging
        if(!logFile.canRead()){
            Logger.e("Can't read file!");
            return false;
        }
        // Create appLogFile objects
        appLogFile = new File(Environment.getExternalStorageDirectory()+"/temp/", appFilename);

        //Send log file via email
        Logger.d("LogCollector - Emailing Logs...");
        // Need to assemble body this way due to Android bug
        //emailIntent.putExtra(Intent.EXTRA_TEXT, body);                        //Regular method - Causes warning
        //ArrayList<String> extra_text = new ArrayList<String>();               //workaround
        //extra_text.add("See attached CSV files.");                            //workaround
        //emailIntent.putStringArrayListExtra(Intent.EXTRA_TEXT, extra_text);   //causes no error but missing body/text - not a big deal, but pointless to have if doesnt issue a body
        // Put info in email
        Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{emailUri.toString()});
        emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
        emailIntent.setType("text/plain");
        ArrayList<Uri> uris = new ArrayList<Uri>();     
        String[] filePaths;             
        // If appLogFile exists & is valid, attach to email
        if(appLogFile.exists() && appLogFile.isFile() && appLogFile.canRead()) {
            Logger.i("appLogFile exists; attaching to email");
            filePaths = new String[] {logFile.toString(),appLogFile.toString()};
        }else{
            Logger.w("Error finding or reading logfile. Debug disabled?!");
            filePaths = new String[] {logFile.toString()};
        }
        for (String file : filePaths) {
            File fileIn = new File(file);
            Uri u = Uri.fromFile(fileIn);
            uris.add(u);
        }
        emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
        mContext.startActivity(Intent.createChooser(emailIntent, "Email Logs to Developer"));
    }
    return true;
}

Custom Logger class: (handles all my logging - writes to file as well if debug option is enabled)

public class Logger {
private static final String TAG = "AppName";
private static final int MAX_FILESIZE=1;    //in MB
private static File logFolder;
private static File logFile;
private static String filename = TAG+"_logfile.txt";
private static FileWriter fileWriter;
private static BufferedWriter bufferedWriter;
private static SimpleDateFormat sdf;
private static String dateTime;
private static int PID;
private static int TID;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static void v(String message) {
    // Do normal logging to logcat
    Log.v(TAG,message);
    // Log to file
    if(MainActivity.enable_debug()) {
        PID= android.os.Process.myPid();
        TID=android.os.Process.myTid();
        logToFile(PID,TID,"V",message);
    }
}

public static void d(String message) {
    // Do normal logging to logcat
    Log.d(TAG,message);
    // Log to file
    if(MainActivity.enable_debug()) {
        PID= android.os.Process.myPid();
        TID=android.os.Process.myTid();
        logToFile(PID,TID,"D",message);
    }
}
public static void i(String message) {
    // Do normal logging to logcat
    Log.i(TAG,message);
    // Log to file
    if(MainActivity.enable_debug()) {
        PID= android.os.Process.myPid();
        TID=android.os.Process.myTid();
        logToFile(PID,TID,"I",message);
    }
}
public static void w(String message) {
    // Do normal logging to logcat
    Log.w(TAG,message);
    // Log to file
    if(MainActivity.enable_debug()) {
        PID= android.os.Process.myPid();
        TID=android.os.Process.myTid();
        logToFile(PID,TID,"W",message);
    }
}   
public static void e(String message) {
    // Do normal logging to logcat
    Log.e(TAG,message);
    // Log to file
    if(MainActivity.enable_debug()) {
        PID= android.os.Process.myPid();
        TID=android.os.Process.myTid();
        logToFile(PID,TID,"E",message);
    }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@SuppressLint("SimpleDateFormat")
private static void logToFile(int PID,int TID,String LEVEL,String message) {
    //return if there is no SD card, or it's inaccessible
    if(!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
        return;
    }
    // Date - Time - PID - TID - LEVEL - TAG? - Message
    // Create and initialize temp folder for log file if doesn't already exist      
    if(logFolder == null) {
        logFolder = new File(Environment.getExternalStorageDirectory()+"/temp/");
    }
    // Create temp folder if doesn't exist
    if(!logFolder.exists()) {
        //Logger.i("Creating temp folder on SD card root...");
        logFolder.mkdirs();
    }
    // Create log file if doesn't already exist
    if(logFile == null) {
        logFile = new File(Environment.getExternalStorageDirectory()+"/temp/", filename);
        try {
            logFile.createNewFile();
        } catch (IOException e) {
            Logger.e("Error creating new file: "+e);
        }
    }
    // Check log file validity - Error if there's a problem with the file
    // Not sure if this is a performance hit
    if(!logFile.exists() || !logFile.isFile() || !logFile.canRead()) {
        //Logger.e("Problem with logFile! Doesnt exist, isn't a file, or can't read it");
        return;
    }
    //Get Date/Time
    if(sdf == null) {
        sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");    //determines dateTime format
    }
    dateTime = sdf.format(new Date());  //set to current date/time

    // Write log message to file
    try {
        if(fileWriter == null) {
            //if(size of file is > 1MB or whatever, then set below to false to clear file first?  Or need to do something better so we dont wipe mid incoming text) {
            if(logFile.length() > MAX_FILESIZE*1024*1024) {
                Logger.i("logFile is > "+MAX_FILESIZE+" MB, clearing first...");
                fileWriter = new FileWriter(logFile, false);        // true=dont append, clear first
            }else{
                fileWriter = new FileWriter(logFile, true);         // false=append, clear first
            }
        }
        if(bufferedWriter == null) {
            bufferedWriter = new BufferedWriter(fileWriter);
        }
        bufferedWriter.write(dateTime+" "+PID+" "+TID+" "+LEVEL+" "+TAG+": "+message);  //write line to log file
        bufferedWriter.newLine();
        bufferedWriter.flush();     //forces to write to file?
    } catch (IOException e) {
        Logger.e("Error writing to log: ");
        e.printStackTrace();
    }           
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

}

Important methods from my Utilities class:

// EMAIL LOGS
public static void emailLogsDialog(final Context context) {
    final AlertDialog.Builder builder = new AlertDialog.Builder(context); 
    builder.setTitle("Send Logs to Developer");
    builder.setMessage("Do you want to send your system logs to the Developer for troubleshooting?\n\nWarning: The logs may contain personal information; this is beyond the Developer's control.");
    builder.setInverseBackgroundForced(true);
    builder.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog,int which) {
            dialog.dismiss();
            emailLogs(context);
        }
    });
    builder.setNegativeButton("Cancel",new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog,int which) {
            dialog.dismiss();
        }
    });
    AlertDialog alertConfirm = builder.create();
    alertConfirm.show();
}
public static void emailLogs(final Context context) {
    final LogCollector logCollector = new LogCollector(context);
    final AlertDialog.Builder builder = new AlertDialog.Builder(context);  
    new AsyncTask<Void, Void, Boolean>() {
        AlertDialog alert;
        @Override
        protected Boolean doInBackground(Void... params) {
            return logCollector.collect();
        }
        @Override
        protected void onPreExecute() {
            builder.setTitle("Send Logs to Developer");
            builder.setMessage("Collecting Logs & Emailing now...");
            builder.setInverseBackgroundForced(true);
            builder.setNegativeButton("Cancel",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog,int which) {
                            dialog.dismiss();
                            return;
                        }
                    });
            alert = builder.create();
            alert.show();
        }
        @Override
        protected void onPostExecute(Boolean result) {
            alert.dismiss();
            builder.setTitle("Send Logs to Developer");
            builder.setMessage("Logs successfully sent to Developer\n\n (Make sure your email app successfully sent the email.)");
            builder.setInverseBackgroundForced(true);
            builder.setPositiveButton("Ok",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog,int which) {
                            dialog.dismiss();
                        }
                    });
            if (result) {
                Logger.d("Successfully extracted logs.");
                if(logCollector.sendLog("ArnoldSwarteneger@gmail.com", "OnCallPager Error Log", "Error Log\n")) {
                    Toast.makeText(context,"Logs successfully extracted to your default email application",Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(context,"There was a problem extracting the logs.\n\nDo you have an SD card and is it mounted?",Toast.LENGTH_LONG).show();
                }
            }else{
                Logger.e("Failed to extract logs!");
                Toast.makeText(context,"Error acquiring logs!",Toast.LENGTH_LONG).show();
            }
        }    
    }.execute();
}

Call Logs this way:

Logger.v("LogCollector - sendLog()");

Key to sendEmail (with logs): (as used above)

if(logCollector.sendLog("ArnoldSwarteneger@gmail.com", "OnCallPager Error Log", "Error Log\n")) {
                Toast.makeText(context,"Logs successfully extracted to your default email application",Toast.LENGTH_LONG).show();
            }else{
                Toast.makeText(context,"There was a problem extracting the logs.\n\nDo you have an SD card and is it mounted?",Toast.LENGTH_LONG).show();
            }
Kevin
  • 2,296
  • 21
  • 22