mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 05:43:55 +01:00
Fix log reporting for release builds (#1916)
* Fix log reporting for release builds * Fix logs for release builds * wip * Clean up the branch to exclude unrelated changes * With java docs * Uncomment quiz checker * Check for external storage permissions before sending logs * With more java docs * Fix crash while zipping log files * Do not log token and cookies * Add instruction to restart app
This commit is contained in:
parent
02fe0044a6
commit
b0b4b08100
28 changed files with 761 additions and 136 deletions
|
|
@ -0,0 +1,45 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
|
||||
/**
|
||||
* Class responsible for sending logs to developers
|
||||
*/
|
||||
@Singleton
|
||||
public class CommonsLogSender extends LogsSender {
|
||||
private static final String LOGS_PRIVATE_EMAIL = "commons-app-android-private@googlegroups.com";
|
||||
private static final String LOGS_PRIVATE_EMAIL_SUBJECT = "Commons Android App (%s) Logs";
|
||||
private static final String BETA_LOGS_PRIVATE_EMAIL_SUBJECT = "Commons Beta Android App (%s) Logs";
|
||||
|
||||
@Inject
|
||||
public CommonsLogSender(SessionManager sessionManager,
|
||||
@Named("isBeta") boolean isBeta) {
|
||||
super(sessionManager, isBeta);
|
||||
|
||||
this.logFileName = isBeta ? "CommonsBetaAppLogs.zip" : "CommonsAppLogs.zip";
|
||||
String emailSubjectFormat = isBeta ? BETA_LOGS_PRIVATE_EMAIL_SUBJECT : LOGS_PRIVATE_EMAIL_SUBJECT;
|
||||
String message = String.format(emailSubjectFormat, sessionManager.getUserName());
|
||||
this.emailSubject = message;
|
||||
this.emailBody = message;
|
||||
this.mailTo = LOGS_PRIVATE_EMAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach any extra meta information about user or device that might help in debugging
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected String getExtraInfo() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("App Version Name: ")
|
||||
.append(BuildConfig.VERSION_NAME)
|
||||
.append("\n");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
|
||||
import ch.qos.logback.core.rolling.RollingFileAppender;
|
||||
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Extends Timber's debug tree to write logs to a file
|
||||
*/
|
||||
public class FileLoggingTree extends Timber.DebugTree implements LogLevelSettableTree {
|
||||
private final Logger logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
private int logLevel;
|
||||
private final String logFileName;
|
||||
private int fileSize;
|
||||
private FixedWindowRollingPolicy rollingPolicy;
|
||||
private final Executor executor;
|
||||
|
||||
public FileLoggingTree(int logLevel,
|
||||
String logFileName,
|
||||
String logDirectory,
|
||||
int fileSizeInKb,
|
||||
Executor executor) {
|
||||
this.logLevel = logLevel;
|
||||
this.logFileName = logFileName;
|
||||
this.fileSize = fileSizeInKb;
|
||||
configureLogger(logDirectory);
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden to change file's log level
|
||||
* @param logLevel
|
||||
*/
|
||||
@Override
|
||||
public void setLogLevel(int logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and log any message
|
||||
* @param priority
|
||||
* @param tag
|
||||
* @param message
|
||||
* @param t
|
||||
*/
|
||||
@Override
|
||||
protected void log(final int priority, final String tag, @NonNull final String message, Throwable t) {
|
||||
executor.execute(() -> logMessage(priority, tag, message));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Log any message based on the priority
|
||||
* @param priority
|
||||
* @param tag
|
||||
* @param message
|
||||
*/
|
||||
private void logMessage(int priority, String tag, String message) {
|
||||
String messageWithTag = String.format("[%s] : %s", tag, message);
|
||||
switch (priority) {
|
||||
case Log.VERBOSE:
|
||||
logger.trace(messageWithTag);
|
||||
break;
|
||||
case Log.DEBUG:
|
||||
logger.debug(messageWithTag);
|
||||
break;
|
||||
case Log.INFO:
|
||||
logger.info(messageWithTag);
|
||||
break;
|
||||
case Log.WARN:
|
||||
logger.warn(messageWithTag);
|
||||
break;
|
||||
case Log.ERROR:
|
||||
logger.error(messageWithTag);
|
||||
break;
|
||||
case Log.ASSERT:
|
||||
logger.error(messageWithTag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a particular log line should be logged in the file or not
|
||||
* @param priority
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLoggable(int priority) {
|
||||
return priority >= logLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the logger with a file size rolling policy (SizeBasedTriggeringPolicy)
|
||||
* https://github.com/tony19/logback-android/wiki
|
||||
* @param logDir
|
||||
*/
|
||||
private void configureLogger(String logDir) {
|
||||
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
loggerContext.reset();
|
||||
|
||||
RollingFileAppender<ILoggingEvent> rollingFileAppender = new RollingFileAppender<>();
|
||||
rollingFileAppender.setContext(loggerContext);
|
||||
rollingFileAppender.setFile(logDir + "/" + logFileName + ".0.log");
|
||||
|
||||
rollingPolicy = new FixedWindowRollingPolicy();
|
||||
rollingPolicy.setContext(loggerContext);
|
||||
rollingPolicy.setMinIndex(1);
|
||||
rollingPolicy.setMaxIndex(4);
|
||||
rollingPolicy.setParent(rollingFileAppender);
|
||||
rollingPolicy.setFileNamePattern(logDir + "/" + logFileName + ".%i.log");
|
||||
rollingPolicy.start();
|
||||
|
||||
SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = new SizeBasedTriggeringPolicy<>();
|
||||
triggeringPolicy.setContext(loggerContext);
|
||||
triggeringPolicy.setMaxFileSize(String.format(Locale.ENGLISH, "%dKB", fileSize));
|
||||
triggeringPolicy.start();
|
||||
|
||||
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
|
||||
encoder.setContext(loggerContext);
|
||||
encoder.setPattern("%-27(%date{ISO8601}) [%-5level] [%thread] %msg%n");
|
||||
encoder.start();
|
||||
|
||||
rollingFileAppender.setEncoder(encoder);
|
||||
rollingFileAppender.setRollingPolicy(rollingPolicy);
|
||||
rollingFileAppender.setTriggeringPolicy(triggeringPolicy);
|
||||
rollingFileAppender.start();
|
||||
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger)
|
||||
LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
logger.addAppender(rollingFileAppender);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
/**
|
||||
* Can be implemented to set the log level for file tree
|
||||
*/
|
||||
public interface LogLevelSettableTree {
|
||||
void setLogLevel(int logLevel);
|
||||
}
|
||||
25
app/src/main/java/fr/free/nrw/commons/logging/LogUtils.java
Normal file
25
app/src/main/java/fr/free/nrw/commons/logging/LogUtils.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
/**
|
||||
* Returns the log directory
|
||||
*/
|
||||
public final class LogUtils {
|
||||
private LogUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory for saving logs on the device
|
||||
* @param isBeta
|
||||
* @return
|
||||
*/
|
||||
public static String getLogDirectory(boolean isBeta) {
|
||||
if(isBeta) {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/beta";
|
||||
} else {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/prod";
|
||||
}
|
||||
}
|
||||
}
|
||||
183
app/src/main/java/fr/free/nrw/commons/logging/LogsSender.java
Normal file
183
app/src/main/java/fr/free/nrw/commons/logging/LogsSender.java
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.acra.collector.CrashReportData;
|
||||
import org.acra.sender.ReportSender;
|
||||
import org.apache.commons.codec.Charsets;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Abstract class that implements Acra's log sender
|
||||
*/
|
||||
public abstract class LogsSender implements ReportSender {
|
||||
|
||||
String mailTo;
|
||||
String logFileName;
|
||||
String emailSubject;
|
||||
String emailBody;
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private final boolean isBeta;
|
||||
|
||||
LogsSender(SessionManager sessionManager,
|
||||
boolean isBeta) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.isBeta = isBeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides send method of ACRA's ReportSender to send logs
|
||||
* @param context
|
||||
* @param report
|
||||
*/
|
||||
@Override
|
||||
public void send(@NonNull final Context context, @Nullable CrashReportData report) {
|
||||
sendLogs(context, report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets zipped log files and sends it via email. Can be modified to change the send log mechanism
|
||||
* @param context
|
||||
* @param report
|
||||
*/
|
||||
private void sendLogs(Context context, CrashReportData report) {
|
||||
final Uri logFileUri = getZippedLogFileUri(context, report);
|
||||
if (logFileUri != null) {
|
||||
sendEmail(context, logFileUri);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* Provides any extra information that you want to send. The return value will be
|
||||
* delivered inside the report verbatim
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract String getExtraInfo();
|
||||
|
||||
/**
|
||||
* Fires an intent to send email with logs
|
||||
* @param context
|
||||
* @param logFileUri
|
||||
*/
|
||||
private void sendEmail(Context context, Uri logFileUri) {
|
||||
String subject = emailSubject;
|
||||
String body = emailBody;
|
||||
|
||||
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
|
||||
emailIntent.setData(Uri.fromParts("mailto", mailTo, null));
|
||||
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, logFileUri);
|
||||
context.startActivity(emailIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI for the zipped log file
|
||||
* @param context
|
||||
* @param report
|
||||
* @return
|
||||
*/
|
||||
private Uri getZippedLogFileUri(Context context, CrashReportData report) {
|
||||
try {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (report != null) {
|
||||
attachCrashInfo(report, builder);
|
||||
}
|
||||
attachUserInfo(builder);
|
||||
attachExtraInfo(builder);
|
||||
byte[] metaData = builder.toString().getBytes(Charsets.UTF_8);
|
||||
File zipFile = new File(context.getExternalFilesDir(null), logFileName);
|
||||
writeLogToZipFile(metaData, zipFile);
|
||||
return Uri.fromFile(zipFile);
|
||||
} catch (IOException e) {
|
||||
Timber.w(e, "Error in generating log file");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any pending crash reports and attaches them to the logs
|
||||
* @param report
|
||||
* @param builder
|
||||
*/
|
||||
private void attachCrashInfo(CrashReportData report, StringBuilder builder) {
|
||||
if (report == null) {
|
||||
return;
|
||||
}
|
||||
builder.append(report);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches username to the the meta_data file
|
||||
* @param builder
|
||||
*/
|
||||
private void attachUserInfo(StringBuilder builder) {
|
||||
builder.append("MediaWiki Username = ").append(sessionManager.getUserName()).append("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets any extra meta information to be attached with the log files
|
||||
* @param builder
|
||||
*/
|
||||
private void attachExtraInfo(StringBuilder builder) {
|
||||
String infoToBeAttached = getExtraInfo();
|
||||
builder.append(infoToBeAttached);
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Zips the logs and meta information
|
||||
* @param metaData
|
||||
* @param zipFile
|
||||
* @throws IOException
|
||||
*/
|
||||
private void writeLogToZipFile(byte[] metaData, File zipFile) throws IOException {
|
||||
FileOutputStream fos = new FileOutputStream(zipFile);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(fos);
|
||||
ZipOutputStream zos = new ZipOutputStream(bos);
|
||||
File logDir = new File(LogUtils.getLogDirectory(isBeta));
|
||||
|
||||
if (!logDir.exists() || logDir.listFiles().length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
for (File file : logDir.listFiles()) {
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
zos.putNextEntry(new ZipEntry(file.getName()));
|
||||
int length;
|
||||
while ((length = bis.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, length);
|
||||
}
|
||||
zos.closeEntry();
|
||||
bis.close();
|
||||
}
|
||||
|
||||
//attach metadata as a separate file
|
||||
zos.putNextEntry(new ZipEntry("meta_data.txt"));
|
||||
zos.write(metaData);
|
||||
zos.closeEntry();
|
||||
|
||||
zos.flush();
|
||||
zos.close();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue