mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Migrated logging module from Java to Kotlin (#5972)
* Migrated logging module from Java to Kotlin * Rename .java to .kt --------- Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
parent
1afff73c24
commit
d6c4cab207
11 changed files with 500 additions and 508 deletions
|
|
@ -1,105 +0,0 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import android.os.Bundle;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.utils.DeviceInfoUtil;
|
||||
import org.acra.data.CrashReportData;
|
||||
import org.acra.sender.ReportSenderException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
private SessionManager sessionManager;
|
||||
private Context context;
|
||||
|
||||
@Inject
|
||||
public CommonsLogSender(SessionManager sessionManager,
|
||||
Context context) {
|
||||
super(sessionManager);
|
||||
|
||||
this.sessionManager = sessionManager;
|
||||
this.context = context;
|
||||
boolean isBeta = ConfigUtils.isBetaFlavour();
|
||||
this.logFileName = isBeta ? "CommonsBetaAppLogs.zip" : "CommonsAppLogs.zip";
|
||||
String emailSubjectFormat = isBeta ? BETA_LOGS_PRIVATE_EMAIL_SUBJECT : LOGS_PRIVATE_EMAIL_SUBJECT;
|
||||
this.emailSubject = String.format(emailSubjectFormat, sessionManager.getUserName());
|
||||
this.emailBody = getExtraInfo();
|
||||
this.mailTo = LOGS_PRIVATE_EMAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach any extra meta information about user or device that might help in debugging
|
||||
* @return String with extra meta information useful for debugging
|
||||
*/
|
||||
@Override
|
||||
public String getExtraInfo() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
// Getting API Level
|
||||
builder.append("API level: ")
|
||||
.append(DeviceInfoUtil.getAPILevel())
|
||||
.append("\n");
|
||||
|
||||
// Getting Android Version
|
||||
builder.append("Android version: ")
|
||||
.append(DeviceInfoUtil.getAndroidVersion())
|
||||
.append("\n");
|
||||
|
||||
// Getting Device Manufacturer
|
||||
builder.append("Device manufacturer: ")
|
||||
.append(DeviceInfoUtil.getDeviceManufacturer())
|
||||
.append("\n");
|
||||
|
||||
// Getting Device Model
|
||||
builder.append("Device model: ")
|
||||
.append(DeviceInfoUtil.getDeviceModel())
|
||||
.append("\n");
|
||||
|
||||
// Getting Device Name
|
||||
builder.append("Device: ")
|
||||
.append(DeviceInfoUtil.getDevice())
|
||||
.append("\n");
|
||||
|
||||
// Getting Network Type
|
||||
builder.append("Network type: ")
|
||||
.append(DeviceInfoUtil.getConnectionType(context))
|
||||
.append("\n");
|
||||
|
||||
// Getting App Version
|
||||
builder.append("App version name: ")
|
||||
.append(ConfigUtils.getVersionNameWithSha(context))
|
||||
.append("\n");
|
||||
|
||||
// Getting Username
|
||||
builder.append("User name: ")
|
||||
.append(sessionManager.getUserName())
|
||||
.append("\n");
|
||||
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresForeground() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(@NotNull Context context, @NotNull CrashReportData crashReportData,
|
||||
@NotNull Bundle bundle) throws ReportSenderException {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package fr.free.nrw.commons.logging
|
||||
|
||||
import android.content.Context
|
||||
|
||||
import android.os.Bundle
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.utils.ConfigUtils
|
||||
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
|
||||
import fr.free.nrw.commons.utils.DeviceInfoUtil
|
||||
import org.acra.data.CrashReportData
|
||||
|
||||
|
||||
/**
|
||||
* Class responsible for sending logs to developers
|
||||
*/
|
||||
@Singleton
|
||||
class CommonsLogSender @Inject constructor(
|
||||
private val sessionManager: SessionManager,
|
||||
private val context: Context
|
||||
) : LogsSender(sessionManager) {
|
||||
|
||||
|
||||
companion object {
|
||||
private const val LOGS_PRIVATE_EMAIL = "commons-app-android-private@googlegroups.com"
|
||||
private const val LOGS_PRIVATE_EMAIL_SUBJECT = "Commons Android App (%s) Logs"
|
||||
private const val BETA_LOGS_PRIVATE_EMAIL_SUBJECT = "Commons Beta Android App (%s) Logs"
|
||||
}
|
||||
|
||||
init {
|
||||
val isBeta = ConfigUtils.isBetaFlavour
|
||||
logFileName = if (isBeta) "CommonsBetaAppLogs.zip" else "CommonsAppLogs.zip"
|
||||
val emailSubjectFormat = if (isBeta)
|
||||
BETA_LOGS_PRIVATE_EMAIL_SUBJECT
|
||||
else
|
||||
LOGS_PRIVATE_EMAIL_SUBJECT
|
||||
emailSubject = emailSubjectFormat.format(sessionManager.userName)
|
||||
emailBody = getExtraInfo()
|
||||
mailTo = LOGS_PRIVATE_EMAIL
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach any extra meta information about the user or device that might help in debugging.
|
||||
* @return String with extra meta information useful for debugging.
|
||||
*/
|
||||
public override fun getExtraInfo(): String {
|
||||
return buildString {
|
||||
// Getting API Level
|
||||
append("API level: ")
|
||||
.append(DeviceInfoUtil.getAPILevel())
|
||||
.append("\n")
|
||||
|
||||
// Getting Android Version
|
||||
append("Android version: ")
|
||||
.append(DeviceInfoUtil.getAndroidVersion())
|
||||
.append("\n")
|
||||
|
||||
// Getting Device Manufacturer
|
||||
append("Device manufacturer: ")
|
||||
.append(DeviceInfoUtil.getDeviceManufacturer())
|
||||
.append("\n")
|
||||
|
||||
// Getting Device Model
|
||||
append("Device model: ")
|
||||
.append(DeviceInfoUtil.getDeviceModel())
|
||||
.append("\n")
|
||||
|
||||
// Getting Device Name
|
||||
append("Device: ")
|
||||
.append(DeviceInfoUtil.getDevice())
|
||||
.append("\n")
|
||||
|
||||
// Getting Network Type
|
||||
append("Network type: ")
|
||||
.append(DeviceInfoUtil.getConnectionType(context))
|
||||
.append("\n")
|
||||
|
||||
// Getting App Version
|
||||
append("App version name: ")
|
||||
.append(context.getVersionNameWithSha())
|
||||
.append("\n")
|
||||
|
||||
// Getting Username
|
||||
append("User name: ")
|
||||
.append(sessionManager.userName)
|
||||
.append("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the log sending process requires the app to be in the foreground.
|
||||
* @return False as it does not require foreground execution.
|
||||
*/
|
||||
override fun requiresForeground(): Boolean = false
|
||||
|
||||
/**
|
||||
* Sends logs to developers. Implementation can be extended.
|
||||
*/
|
||||
override fun send(
|
||||
context: Context,
|
||||
errorContent: CrashReportData,
|
||||
extras: Bundle) {
|
||||
// Add logic here if needed.
|
||||
}
|
||||
}
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
133
app/src/main/java/fr/free/nrw/commons/logging/FileLoggingTree.kt
Normal file
133
app/src/main/java/fr/free/nrw/commons/logging/FileLoggingTree.kt
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package fr.free.nrw.commons.logging
|
||||
|
||||
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.
|
||||
*/
|
||||
class FileLoggingTree(
|
||||
private var logLevel: Int,
|
||||
private val logFileName: String,
|
||||
logDirectory: String,
|
||||
private val fileSizeInKb: Int,
|
||||
private val executor: Executor
|
||||
) : Timber.DebugTree(), LogLevelSettableTree {
|
||||
|
||||
private val logger: Logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)
|
||||
private lateinit var rollingPolicy: FixedWindowRollingPolicy
|
||||
|
||||
init {
|
||||
configureLogger(logDirectory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden to change the file's log level.
|
||||
* @param logLevel The new log level.
|
||||
*/
|
||||
override fun setLogLevel(logLevel: Int) {
|
||||
this.logLevel = logLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and logs any message.
|
||||
* @param priority The priority of the log message.
|
||||
* @param tag The tag associated with the log message.
|
||||
* @param message The log message.
|
||||
* @param t An optional throwable.
|
||||
*/
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
executor.execute {
|
||||
logMessage(priority, tag.orEmpty(), message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message based on the priority.
|
||||
* @param priority The priority of the log message.
|
||||
* @param tag The tag associated with the log message.
|
||||
* @param message The log message.
|
||||
*/
|
||||
private fun logMessage(priority: Int, tag: String, message: String) {
|
||||
val messageWithTag = "[$tag] : $message"
|
||||
when (priority) {
|
||||
Log.VERBOSE -> logger.trace(messageWithTag)
|
||||
Log.DEBUG -> logger.debug(messageWithTag)
|
||||
Log.INFO -> logger.info(messageWithTag)
|
||||
Log.WARN -> logger.warn(messageWithTag)
|
||||
Log.ERROR, Log.ASSERT -> logger.error(messageWithTag)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a particular log line should be logged in the file or not.
|
||||
* @param priority The priority of the log message.
|
||||
* @return True if the log message should be logged, false otherwise.
|
||||
*/
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun isLoggable(priority: Int): Boolean {
|
||||
return priority >= logLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the logger with a file size rolling policy (SizeBasedTriggeringPolicy).
|
||||
* https://github.com/tony19/logback-android/wiki
|
||||
* @param logDir The directory where logs should be stored.
|
||||
*/
|
||||
private fun configureLogger(logDir: String) {
|
||||
val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext
|
||||
loggerContext.reset()
|
||||
|
||||
val rollingFileAppender = RollingFileAppender<ILoggingEvent>().apply {
|
||||
context = loggerContext
|
||||
file = "$logDir/$logFileName.0.log"
|
||||
}
|
||||
|
||||
rollingPolicy = FixedWindowRollingPolicy().apply {
|
||||
context = loggerContext
|
||||
minIndex = 1
|
||||
maxIndex = 4
|
||||
setParent(rollingFileAppender)
|
||||
fileNamePattern = "$logDir/$logFileName.%i.log"
|
||||
start()
|
||||
}
|
||||
|
||||
val triggeringPolicy = SizeBasedTriggeringPolicy<ILoggingEvent>().apply {
|
||||
context = loggerContext
|
||||
maxFileSize = "$fileSizeInKb"
|
||||
start()
|
||||
}
|
||||
|
||||
val encoder = PatternLayoutEncoder().apply {
|
||||
context = loggerContext
|
||||
pattern = "%-27(%date{ISO8601}) [%-5level] [%thread] %msg%n"
|
||||
start()
|
||||
}
|
||||
|
||||
rollingFileAppender.apply {
|
||||
this.encoder = encoder
|
||||
rollingPolicy = rollingPolicy
|
||||
this.triggeringPolicy = triggeringPolicy
|
||||
start()
|
||||
}
|
||||
|
||||
val rootLogger = LoggerFactory.getLogger(
|
||||
Logger.ROOT_LOGGER_NAME
|
||||
) as ch.qos.logback.classic.Logger
|
||||
rootLogger.addAppender(rollingFileAppender)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
/**
|
||||
* Can be implemented to set the log level for file tree
|
||||
*/
|
||||
public interface LogLevelSettableTree {
|
||||
void setLogLevel(int logLevel);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package fr.free.nrw.commons.logging
|
||||
|
||||
/**
|
||||
* Can be implemented to set the log level for file tree
|
||||
*/
|
||||
interface LogLevelSettableTree {
|
||||
fun setLogLevel(logLevel: Int)
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import fr.free.nrw.commons.upload.FileUtils;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
|
||||
/**
|
||||
* Returns the log directory
|
||||
*/
|
||||
public final class LogUtils {
|
||||
private LogUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory for saving logs on the device
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getLogDirectory() {
|
||||
String dirPath;
|
||||
if (ConfigUtils.isBetaFlavour()) {
|
||||
dirPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/beta";
|
||||
} else {
|
||||
dirPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/prod";
|
||||
}
|
||||
|
||||
FileUtils.recursivelyCreateDirs(dirPath);
|
||||
return dirPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory for saving logs on the device
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getLogZipDirectory() {
|
||||
String dirPath;
|
||||
if (ConfigUtils.isBetaFlavour()) {
|
||||
dirPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/beta/zip";
|
||||
} else {
|
||||
dirPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/prod/zip";
|
||||
}
|
||||
|
||||
FileUtils.recursivelyCreateDirs(dirPath);
|
||||
return dirPath;
|
||||
}
|
||||
}
|
||||
57
app/src/main/java/fr/free/nrw/commons/logging/LogUtils.kt
Normal file
57
app/src/main/java/fr/free/nrw/commons/logging/LogUtils.kt
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package fr.free.nrw.commons.logging
|
||||
|
||||
import android.os.Environment
|
||||
|
||||
import fr.free.nrw.commons.upload.FileUtils
|
||||
import fr.free.nrw.commons.utils.ConfigUtils
|
||||
|
||||
|
||||
/**
|
||||
* Returns the log directory
|
||||
*/
|
||||
object LogUtils {
|
||||
|
||||
/**
|
||||
* Returns the directory for saving logs on the device.
|
||||
*
|
||||
* @return The path to the log directory.
|
||||
*/
|
||||
fun getLogDirectory(): String {
|
||||
val dirPath = if (ConfigUtils.isBetaFlavour) {
|
||||
"${Environment
|
||||
.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
)}/logs/beta"
|
||||
} else {
|
||||
"${Environment
|
||||
.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
)}/logs/prod"
|
||||
}
|
||||
|
||||
FileUtils.recursivelyCreateDirs(dirPath)
|
||||
return dirPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory for saving zipped logs on the device.
|
||||
*
|
||||
* @return The path to the zipped log directory.
|
||||
*/
|
||||
fun getLogZipDirectory(): String {
|
||||
val dirPath = if (ConfigUtils.isBetaFlavour) {
|
||||
"${Environment
|
||||
.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
)}/logs/beta/zip"
|
||||
} else {
|
||||
"${Environment
|
||||
.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS
|
||||
)}/logs/prod/zip"
|
||||
}
|
||||
|
||||
FileUtils.recursivelyCreateDirs(dirPath)
|
||||
return dirPath
|
||||
}
|
||||
}
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
package fr.free.nrw.commons.logging;
|
||||
|
||||
import static org.acra.ACRA.getErrorReporter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import org.acra.data.CrashReportData;
|
||||
import org.acra.sender.ReportSender;
|
||||
|
||||
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.nio.charset.Charset;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
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;
|
||||
|
||||
LogsSender(SessionManager sessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
} else {
|
||||
getErrorReporter().handleSilentException(null);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
* 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_SEND);
|
||||
emailIntent.setType("message/rfc822");
|
||||
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailTo});
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, logFileUri);
|
||||
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
context.startActivity(Intent.createChooser(emailIntent, context.getString(R.string.share_logs_using)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI for the zipped log file
|
||||
*
|
||||
* @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(Charset.forName("UTF-8"));
|
||||
File zipFile = new File(LogUtils.getLogZipDirectory(), logFileName);
|
||||
writeLogToZipFile(metaData, zipFile);
|
||||
return FileProvider
|
||||
.getUriForFile(context,
|
||||
context.getApplicationContext().getPackageName() + ".provider", 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());
|
||||
|
||||
if (!logDir.exists() || logDir.listFiles().length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
for (File file : logDir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
193
app/src/main/java/fr/free/nrw/commons/logging/LogsSender.kt
Normal file
193
app/src/main/java/fr/free/nrw/commons/logging/LogsSender.kt
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package fr.free.nrw.commons.logging
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
import androidx.core.content.FileProvider
|
||||
|
||||
import org.acra.data.CrashReportData
|
||||
import org.acra.sender.ReportSender
|
||||
|
||||
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.R
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import org.acra.ACRA.errorReporter
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class that implements Acra's log sender.
|
||||
*/
|
||||
abstract class LogsSender(
|
||||
private val sessionManager: SessionManager
|
||||
): ReportSender {
|
||||
|
||||
var mailTo: String? = null
|
||||
var logFileName: String? = null
|
||||
var emailSubject: String? = null
|
||||
var emailBody: String? = null
|
||||
|
||||
/**
|
||||
* Overrides the send method of ACRA's ReportSender to send logs.
|
||||
*
|
||||
* @param context The context in which to send the logs.
|
||||
* @param report The crash report data, if any.
|
||||
*/
|
||||
fun sendWithNullable(context: Context, report: CrashReportData?) {
|
||||
if (report == null) {
|
||||
errorReporter.handleSilentException(null)
|
||||
return
|
||||
}
|
||||
send(context, report)
|
||||
}
|
||||
|
||||
override fun send(context: Context, report: CrashReportData) {
|
||||
sendLogs(context, report)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets zipped log files and sends them via email. Can be modified to change the send
|
||||
* log mechanism.
|
||||
*
|
||||
* @param context The context in which to send the logs.
|
||||
* @param report The crash report data, if any.
|
||||
*/
|
||||
private fun sendLogs(context: Context, report: CrashReportData?) {
|
||||
val logFileUri = getZippedLogFileUri(context, report)
|
||||
if (logFileUri != null) {
|
||||
sendEmail(context, logFileUri)
|
||||
} else {
|
||||
errorReporter.handleSilentException(null)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides any extra information that you want to send. The return value will be
|
||||
* delivered inside the report verbatim.
|
||||
*
|
||||
* @return A string containing the extra information.
|
||||
*/
|
||||
protected abstract fun getExtraInfo(): String
|
||||
|
||||
/**
|
||||
* Fires an intent to send an email with logs.
|
||||
*
|
||||
* @param context The context in which to send the email.
|
||||
* @param logFileUri The URI of the zipped log file.
|
||||
*/
|
||||
private fun sendEmail(context: Context, logFileUri: Uri) {
|
||||
val emailIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "message/rfc822"
|
||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(mailTo))
|
||||
putExtra(Intent.EXTRA_SUBJECT, emailSubject)
|
||||
putExtra(Intent.EXTRA_TEXT, emailBody)
|
||||
putExtra(Intent.EXTRA_STREAM, logFileUri)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
context.startActivity(Intent.createChooser(emailIntent, context.getString(R.string.share_logs_using)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI for the zipped log file.
|
||||
*
|
||||
* @param context The context for file URI generation.
|
||||
* @param report The crash report data, if any.
|
||||
* @return The URI of the zipped log file or null if an error occurs.
|
||||
*/
|
||||
private fun getZippedLogFileUri(context: Context, report: CrashReportData?): Uri? {
|
||||
return try {
|
||||
val builder = StringBuilder().apply {
|
||||
report?.let { attachCrashInfo(it, this) }
|
||||
attachUserInfo(this)
|
||||
attachExtraInfo(this)
|
||||
}
|
||||
val metaData = builder.toString().toByteArray(Charsets.UTF_8)
|
||||
val zipFile = File(LogUtils.getLogZipDirectory(), logFileName ?: "logs.zip")
|
||||
writeLogToZipFile(metaData, zipFile)
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.applicationContext.packageName}.provider",
|
||||
zipFile
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
Timber.w(e, "Error in generating log file")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any pending crash reports and attaches them to the logs.
|
||||
*
|
||||
* @param report The crash report data, if any.
|
||||
* @param builder The string builder to append crash info.
|
||||
*/
|
||||
private fun attachCrashInfo(report: CrashReportData?, builder: StringBuilder) {
|
||||
if(report != null) {
|
||||
builder.append(report)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the username to the metadata file.
|
||||
*
|
||||
* @param builder The string builder to append user info.
|
||||
*/
|
||||
private fun attachUserInfo(builder: StringBuilder) {
|
||||
builder.append("MediaWiki Username = ").append(sessionManager.userName).append("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets any extra metadata information to be attached with the log files.
|
||||
*
|
||||
* @param builder The string builder to append extra info.
|
||||
*/
|
||||
private fun attachExtraInfo(builder: StringBuilder) {
|
||||
builder.append(getExtraInfo()).append("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Zips the logs and metadata information.
|
||||
*
|
||||
* @param metaData The metadata to be added to the zip file.
|
||||
* @param zipFile The zip file to write to.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun writeLogToZipFile(metaData: ByteArray, zipFile: File) {
|
||||
val logDir = File(LogUtils.getLogDirectory())
|
||||
if (!logDir.exists() || logDir.listFiles().isNullOrEmpty()) return
|
||||
|
||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use { zos ->
|
||||
val buffer = ByteArray(1024)
|
||||
logDir.listFiles()?.forEach { file ->
|
||||
if (file.isDirectory) return@forEach
|
||||
FileInputStream(file).use { fis ->
|
||||
BufferedInputStream(fis).use { bis ->
|
||||
zos.putNextEntry(ZipEntry(file.name))
|
||||
var length: Int
|
||||
while (bis.read(buffer).also { length = it } > 0) {
|
||||
zos.write(buffer, 0, length)
|
||||
}
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach metadata as a separate file.
|
||||
zos.putNextEntry(ZipEntry("meta_data.txt"))
|
||||
zos.write(metaData)
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.settings
|
||||
|
||||
import android.Manifest.permission
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
|
|
@ -527,7 +528,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
PermissionUtils.PERMISSIONS_STORAGE
|
||||
)
|
||||
) {
|
||||
commonsLogSender.send(requireActivity(), null)
|
||||
commonsLogSender.sendWithNullable(requireActivity(), null)
|
||||
} else {
|
||||
requestExternalStoragePermissions()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue