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