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

5

8

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.
Pigeonhearted answered 23/1, 2012 at 11:19 Comment(0)
S
8

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.

Sarette answered 23/1, 2012 at 11:53 Comment(3)
thank you very much user1102206, do you have idea, how can we sent those track which written in file to sever by clicking on button in activity after restarting [reopen application]?. instead of automatically sending report. Req: after you fully tested. pls update the answer. b'coz no good post found regarding this.. so it will more helpful to others too..Pigeonhearted
Thanks baya. About your initial question on ACRA, the first point answer your question : if your app crash, ACRA will deal with the internet connection availability by itself, no need to do it by hand. About sending a report, there is some things available in the acra website (here : code.google.com/p/acra/source/browse/trunk/acra/src/main/java/…). The code above is by the way inspired by one the class. What I mean when I said fully tested is marginal cases: no space left on device for report, no privileges to write on sdcard, acra config not properly set, etc.Sarette
Deprecated solution, please, see the @user2302510 answerSuint
E
6

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

}
Echinoderm answered 19/6, 2014 at 11:34 Comment(1)
Thanks! This worked for me, with a block copy from here, and with the eclipse fixing the missing imports. The written file goes to the root of the sdcard, and is 27k. Highly recommended!Odle
I
1

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

Iliac answered 25/7, 2022 at 21:39 Comment(0)
I
0

I have used ACRA but not in this form (used to send the logs to my own server), so I am not sure how to do this.
But in such a case cant you acquire the system logs as a whole (that will be a detailed one ofcourse) using the other libs/apis and write it to a file.

OR, what you can do is, use the code of the ACRA zip and modify it a little eg.Using the file "CrashReportData.java" or "CrashReporterDialog.java" in its package and get fetch the content from there and saving it to your file.

I am talking about its version 4.2.3.

Inseverable answered 23/1, 2012 at 11:51 Comment(0)
W
0

the solution above works perfectly. There is probably only one thing, If the file is not visible using file explorer, try to add intent broadcast to Intent.ACTION_MEDIA_SCANNER_SCAN_FILE

check this link

Wallsend answered 30/12, 2014 at 13:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.