Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ACRA : How can I write ACRA report to file (in SD Card)?

I can use ACRA library to manage force close error by handling uncaught exception. The report can sent to google doc., email and custom web service successfully..

But what I want..

  • How can I write the report to file [ex. sdcard/myapp/myLog.txt] ?

why I want this..

  • My app user may not have internet connection while force close occurs.. if so then I will miss the report, if i write the report to file then I can sent to my server when the internet connection available.
like image 780
Jayabal Avatar asked Jan 23 '12 11:01

Jayabal


3 Answers

I think what you want to achieve is already done by ACRA. Here's what I see in my abd logcat :

01-23 12:15:28.056: D/ACRA(614): Writing crash report file.
01-23 12:15:28.136: D/ACRA(614): Mark all pending reports as approved.
01-23 12:15:28.136: D/ACRA(614): Looking for error files in /data/data/com.ybi/files
01-23 12:15:28.136: V/ACRA(614): About to start ReportSenderWorker from #handleException
01-23 12:15:28.146: D/ACRA(614): Add user comment to null
01-23 12:15:28.146: D/ACRA(614): #checkAndSendReports - start
01-23 12:15:28.146: D/ACRA(614): Looking for error files in /data/data/com.ybi/files

First thing that ACRA does is creating a report on a file on the inner storage of your app. Then if you're online and the errorreporter is correctly initialized, it sends the report. Otherwise, the reports are kept in the data storage (for a later sending).

I didn't look into the data but I'm currently working on a custom logger. So if you want to do the same things than ACRA, it's easy :

    ACRA.init(this);

    // a custom reporter for your very own purposes
    ErrorReporter.getInstance().setReportSender(new LocalReportSender(this));

And then :

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.acra.ACRA;
import org.acra.CrashReportData;
import org.acra.ReportField;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;

import android.content.Context;

import de.akquinet.android.androlog.Log;

public class LocalReportSender implements ReportSender {

private final Map<ReportField, String> mMapping = new HashMap<ReportField, String>() ;
private FileOutputStream crashReport = null; 

public LocalReportSender(Context ctx) {
    // the destination
    try {
        crashReport = ctx.openFileOutput("crashReport", Context.MODE_WORLD_READABLE);
    } catch (FileNotFoundException e) {
        Log.e("TAG", "IO ERROR",e);
    }
}

@Override
public void send(CrashReportData report) throws ReportSenderException {

    final Map<String, String> finalReport = remap(report);

    try {
        OutputStreamWriter osw = new OutputStreamWriter(crashReport);

        Set set = finalReport.entrySet();
        Iterator i = set.iterator();

        while (i.hasNext()) {
            Map.Entry<String,String> me = (Map.Entry) i.next();
            osw.write("[" + me.getKey() + "]=" + me.getValue());
        }

        osw.flush();
        osw.close();
    } catch (IOException e) {
        Log.e("TAG", "IO ERROR",e);
    }

}

private static boolean isNull(String aString) {
    return aString == null || ACRA.NULL_VALUE.equals(aString);
}

private Map<String, String> remap(Map<ReportField, String> report) {

    ReportField[] fields = ACRA.getConfig().customReportContent();
    if (fields.length == 0) {
        fields = ACRA.DEFAULT_REPORT_FIELDS;
    }

    final Map<String, String> finalReport = new HashMap<String, String>(
            report.size());
    for (ReportField field : fields) {
        if (mMapping == null || mMapping.get(field) == null) {
            finalReport.put(field.toString(), report.get(field));
        } else {
            finalReport.put(mMapping.get(field), report.get(field));
        }
    }
    return finalReport;
}

}

I didn't fully tested it yet but you get the idea. Hope it helps.

like image 137
Gomoku7 Avatar answered Oct 14 '22 11:10

Gomoku7


I guess the answer from @Gomoku7 contains some deprecated code so I'll just post the solution that I used:

Call this in onCreate():

ACRA.init(this);
ACRA.getErrorReporter().setReportSender(new LocalReportSender(this));

Here I have basically changed the code to use BufferedWriter so that I could write directly to SD card which was not possible with openFileOutput(). Therefore only method send() and constructor LocalReportSender() are slightly changed.

Note: Be aware that the logfile grows quite quickly so be sure you don't take up MBs of space of your user's SDcard because of a log file :)

private class LocalReportSender implements ReportSender {

    private final Map<ReportField, String> mMapping = new HashMap<ReportField, String>();
    private FileWriter crashReport = null;

    public LocalReportSender(Context ctx) {
        // the destination
        File logFile = new File(Environment.getExternalStorageDirectory(), "log.txt");

        try {
            crashReport = new FileWriter(logFile, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void send(CrashReportData report) throws ReportSenderException {
        final Map<String, String> finalReport = remap(report);

        try {
            BufferedWriter buf = new BufferedWriter(crashReport);

            Set<Entry<String, String>> set = finalReport.entrySet();
            Iterator<Entry<String, String>> i = set.iterator();

            while (i.hasNext()) {
                Map.Entry<String, String> me = (Entry<String, String>) i.next();
                buf.append("[" + me.getKey() + "]=" + me.getValue());
            }

            buf.flush();
            buf.close();
        } catch (IOException e) {
            Log.e("TAG", "IO ERROR", e);
        }
    }

    private boolean isNull(String aString) {
        return aString == null || ACRAConstants.NULL_VALUE.equals(aString);
    }

    private Map<String, String> remap(Map<ReportField, String> report) {

        ReportField[] fields = ACRA.getConfig().customReportContent();
        if (fields.length == 0) {
            fields = ACRAConstants.DEFAULT_REPORT_FIELDS;
        }

        final Map<String, String> finalReport = new HashMap<String, String>(
                report.size());
        for (ReportField field : fields) {
            if (mMapping == null || mMapping.get(field) == null) {
                finalReport.put(field.toString(), report.get(field));
            } else {
                finalReport.put(mMapping.get(field), report.get(field));
            }
        }
        return finalReport;
    }

}
like image 6
user1071762 Avatar answered Oct 14 '22 12:10

user1071762


The answer from @Gomoku7 and @user1071762 contains some deprecated code so and are very old and ACRA has been updated at lot that I thought I'll just post the an updated solution that I created based on the previous answers as I could not get them to work.

This is using ACRA 5.9.5 and has been tested on Android API 21 to 32, it also uses the standard report output methods

Dependencies

dependencies {
    def acraVersion = '5.9.5'
    implementation "ch.acra:acra-toast:$acraVersion"
    annotationProcessor("com.google.auto.service:auto-service:1.0.1")
    compileOnly("com.google.auto.service:auto-service-annotations:1.0.1")
}

LocalReportSender.java

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


import androidx.annotation.NonNull;

import java.io.File;
import java.io.FileWriter;

import com.google.auto.service.AutoService;
import org.acra.config.CoreConfiguration;
import org.acra.data.CrashReportData;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;
import org.acra.sender.ReportSenderFactory;
import org.jetbrains.annotations.NotNull;

public class LocalReportSender implements ReportSender {
   CoreConfiguration config;

   public LocalReportSender(CoreConfiguration coreConfiguration) {
      config = coreConfiguration;
   }

   @Override
   public void send(@NotNull Context context, @NotNull CrashReportData errorContent) 
        throws ReportSenderException {
      // the destination
      // This usually appear as:-
      // Internal shared storage\Android\data\{packagename}\files\Documents
      // on USB connection or Google files App
      File dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);

      String state = Environment.getExternalStorageState();

      File logFile;
      if(Environment.MEDIA_MOUNTED.equals(state)) {
         logFile = new File(dir, "crash_report.txt");
      } else {
         // backup if external storage is not available
         logFile = new File(context.getCacheDir(),"crash_report.txt");
      }

      try {
         // Use the core ReportFormat configuration
         String reportText = config.getReportFormat()
           .toFormattedString(errorContent,
             config.getReportContent(), "\n", "\n\t", false);

         // Overwrite last report
         FileWriter writer = new FileWriter(logFile, false);
         writer.append(reportText);
         writer.flush();
         writer.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
      Log.d("[LocalReportSender]", "Report Saved");
   }

   @AutoService(ReportSenderFactory.class)
   public static class LocalReportFactory implements ReportSenderFactory {
      @NotNull
      @Override
      public ReportSender create(@NotNull Context context,
         @NotNull CoreConfiguration coreConfiguration) {
         Log.d("[LocalReportSender]", "LocalReportSender created!");
         return new LocalReportSender(coreConfiguration);
      }

      @Override
      public boolean enabled(@NonNull CoreConfiguration coreConfig) {
         Log.d("[LocalReportSender]", "LocalReportSender enabled!");
         return true;
      }
   }
}

The key point on the LocalReportSender to get it loaded in to the ACRA config is the @AutoService annotation

Application class

import android.app.Application;
import android.content.Context;

import org.acra.ACRA;
import org.acra.config.CoreConfigurationBuilder;
import org.acra.config.ToastConfigurationBuilder;
import org.acra.data.StringFormat;

public class MyApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        ACRA.init(this, new CoreConfigurationBuilder()
                //core configuration:
                .withBuildConfigClass(BuildConfig.class)
                .withReportFormat(StringFormat.KEY_VALUE_LIST)
                .withPluginConfigurations(
                        //each plugin you chose above can be configured
                        //with its builder like this:
                        new ToastConfigurationBuilder()
                                //required
                                .withText(getString(R.string.crash_toast_text))
                                .build()
                )
        );
    }
}

like image 1
Andrew Avatar answered Oct 14 '22 11:10

Andrew