[android] Refactor logging

- more reliable fallback to system log if file logging is not available
- fix DEBUG core messages not logged after file logging is switched off
- add log messages to facilitate debugging logging issues

Signed-off-by: Konstantin Pastbin <konstantin.pastbin@gmail.com>
This commit is contained in:
Konstantin Pastbin 2022-06-01 20:26:54 +03:00 committed by Viktor Govako
parent 3e4e7f4ec4
commit 48a8d2aba5
6 changed files with 81 additions and 86 deletions

View file

@ -44,7 +44,7 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
{
@SuppressWarnings("NotNullFieldNotInitialized")
@NonNull
private Logger mLogger;
private final Logger mLogger = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
public final static String TAG = "MwmApplication";
private AppBackgroundTracker mBackgroundTracker;
@ -100,8 +100,7 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
@NonNull
public static SharedPreferences prefs(@NonNull Context context)
{
String prefFile = context.getString(R.string.pref_file_name);
return context.getSharedPreferences(prefFile, MODE_PRIVATE);
return context.getSharedPreferences(context.getString(R.string.pref_file_name), MODE_PRIVATE);
}
@Override
@ -116,13 +115,13 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
public void onCreate()
{
super.onCreate();
LoggerFactory.INSTANCE.initialize(this);
mLogger = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
getLogger().d(TAG, "Application is created");
mLogger.i(TAG, "Initializing application");
LoggerFactory.INSTANCE.initFileLogging(this);
// Set configuration directory as early as possible.
// Other methods may explicitly use Config, which requires settingsDir to be set.
final String settingsPath = StorageUtils.getSettingsPath(this);
getLogger().d(TAG, "Settings path = " + settingsPath);
mLogger.d(TAG, "Settings path = " + settingsPath);
try
{
StorageUtils.createDirectory(settingsPath);
@ -168,16 +167,15 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
if (mPlatformInitialized)
return;
final Logger log = getLogger();
final String apkPath = StorageUtils.getApkPath(this);
log.d(TAG, "Apk path = " + apkPath);
mLogger.d(TAG, "Apk path = " + apkPath);
// Note: StoragePathManager uses Config, which requires initConfig() to be called.
final String writablePath = StoragePathManager.findMapsStorage(this);
log.d(TAG, "Writable path = " + writablePath);
mLogger.d(TAG, "Writable path = " + writablePath);
final String privatePath = StorageUtils.getPrivatePath(this);
log.d(TAG, "Private path = " + privatePath);
mLogger.d(TAG, "Private path = " + privatePath);
final String tempPath = StorageUtils.getTempPath(this);
log.d(TAG, "Temp path = " + tempPath);
mLogger.d(TAG, "Temp path = " + tempPath);
// If platform directories are not created it means that native part of app will not be able
// to work at all. So, we just ignore native part initialization in this case, e.g. when the
@ -195,7 +193,7 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
Editor.init(this);
mPlatformInitialized = true;
log.i(TAG, "Platform initialized");
mLogger.i(TAG, "Platform initialized");
}
private void createPlatformDirectories(@NonNull String writablePath,
@ -231,7 +229,7 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
IsolinesManager.from(this).initialize(null);
mBackgroundTracker.addListener(this);
getLogger().i(TAG, "Framework initialized");
mLogger.i(TAG, "Framework initialized");
mFrameworkInitialized = true;
}
@ -297,12 +295,6 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
private static native void nativeAddLocalization(String name, String value);
private static native void nativeOnTransit(boolean foreground);
@NonNull
public Logger getLogger()
{
return mLogger;
}
public boolean isFirstLaunch()
{
return mFirstLaunch;

View file

@ -21,7 +21,6 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.mapswithme.maps.Framework;
import com.mapswithme.maps.R;
import com.mapswithme.maps.downloader.MapManager;
@ -481,16 +480,11 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment
if (pref == null)
return;
final boolean isLoggingEnabled = LoggerFactory.INSTANCE.isFileLoggingEnabled();
((TwoStatePreference) pref).setChecked(isLoggingEnabled);
pref.setOnPreferenceChangeListener(
(preference, newValue) ->
{
boolean newVal = (Boolean) newValue;
if (isLoggingEnabled != newVal)
LoggerFactory.INSTANCE.setFileLoggingEnabled(newVal);
return true;
});
((TwoStatePreference) pref).setChecked(LoggerFactory.INSTANCE.isFileLoggingEnabled);
pref.setOnPreferenceChangeListener((preference, newValue) -> {
LoggerFactory.INSTANCE.setFileLoggingEnabled((Boolean) newValue);
return true;
});
}
private void initEmulationBadStorage()

View file

@ -66,8 +66,7 @@ public class StorageUtils
if (dir != null)
return dir.getAbsolutePath();
Log.e(StorageUtils.class.getSimpleName(),
"Cannot get the external files directory for some reasons", new Throwable());
Log.e(TAG, "Cannot get the external files directory for some reasons", new Throwable());
return null;
}

View file

@ -9,11 +9,9 @@ import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import com.mapswithme.maps.BuildConfig;
import com.mapswithme.util.StorageUtils;
import com.mapswithme.util.Utils;
import net.jcip.annotations.Immutable;
import java.io.File;
@ -36,8 +34,8 @@ class FileLoggerStrategy implements LoggerStrategy
@NonNull
private final Application mApplication;
FileLoggerStrategy(@NonNull Application application, @NonNull String filePath,
@NonNull Executor executor)
public FileLoggerStrategy(@NonNull Application application, @NonNull String filePath,
@NonNull Executor executor)
{
mApplication = application;
mFilePath = filePath;

View file

@ -1,39 +1,43 @@
package com.mapswithme.util.log;
import android.util.Log;
import com.mapswithme.maps.BuildConfig;
import net.jcip.annotations.Immutable;
@Immutable
class LogCatStrategy implements LoggerStrategy
{
private final boolean mIsDebug;
public LogCatStrategy(final boolean isDebug)
{
mIsDebug = isDebug;
}
@Override
public void v(String tag, String msg)
{
if (canLog())
if (mIsDebug)
Log.v(tag, msg);
}
@Override
public void v(String tag, String msg, Throwable tr)
{
if (canLog())
if (mIsDebug)
Log.v(tag, msg, tr);
}
@Override
public void d(String tag, String msg)
{
if (canLog())
if (mIsDebug)
Log.d(tag, msg);
}
@Override
public void d(String tag, String msg, Throwable tr)
{
if (canLog())
if (mIsDebug)
Log.d(tag, msg, tr);
}
@ -78,9 +82,4 @@ class LogCatStrategy implements LoggerStrategy
{
Log.e(tag, msg, tr);
}
private static boolean canLog()
{
return BuildConfig.DEBUG;
}
}

View file

@ -7,13 +7,10 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.mapswithme.maps.BuildConfig;
import com.mapswithme.maps.MwmApplication;
import com.mapswithme.maps.R;
import com.mapswithme.util.CrashlyticsUtils;
import com.mapswithme.util.StorageUtils;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
@ -23,6 +20,10 @@ import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* By default uses Android's system logger.
* After an initFileLogging() call can use a custom file logging implementation.
*/
@ThreadSafe
public class LoggerFactory
{
@ -45,50 +46,63 @@ public class LoggerFactory
}
public final static LoggerFactory INSTANCE = new LoggerFactory();
public boolean isFileLoggingEnabled = false;
@NonNull
@GuardedBy("this")
private final EnumMap<Type, BaseLogger> mLoggers = new EnumMap<>(Type.class);
private final static String CORE_TAG = "OMapsCore";
private final static String TAG = LoggerFactory.class.getSimpleName();
private final static String CORE_TAG = "OMcore";
@Nullable
@GuardedBy("this")
private ExecutorService mFileLoggerExecutor;
@Nullable
private Application mApplication;
private String mLogsFolder;
private LoggerFactory()
{
Log.i(LoggerFactory.class.getSimpleName(), "Logging started");
}
public void initialize(@NonNull Application application)
public void initFileLogging(@NonNull Application application)
{
getLogger(Type.MISC).i(TAG, "Init file logging");
mApplication = application;
mLogsFolder = StorageUtils.getLogsFolder(mApplication);
final SharedPreferences prefs = MwmApplication.prefs(mApplication);
// File logging is enabled by default for beta builds.
isFileLoggingEnabled = prefs.getBoolean(mApplication.getString(R.string.pref_enable_logging),
BuildConfig.BUILD_TYPE.equals("beta"));
getLogger(Type.MISC).i(TAG, "Logging config: isFileLoggingEnabled: " + isFileLoggingEnabled +
"; logs folder: " + mLogsFolder);
// Set native logging level, save into shared preferences, update already created loggers if any.
switchFileLoggingEnabled(isFileLoggingEnabled);
}
public boolean isFileLoggingEnabled()
private void switchFileLoggingEnabled(boolean enabled)
{
if (mApplication == null)
{
if (BuildConfig.DEBUG)
throw new IllegalStateException("Application is not created," +
"but logger is used!");
return false;
}
SharedPreferences prefs = MwmApplication.prefs(mApplication);
String enableLoggingKey = mApplication.getString(R.string.pref_enable_logging);
//noinspection ConstantConditions
return prefs.getBoolean(enableLoggingKey, BuildConfig.BUILD_TYPE.equals("beta"));
getLogger(Type.MISC).i(TAG, "Switch isFileLoggingEnabled to " + enabled);
isFileLoggingEnabled = enabled;
nativeToggleCoreDebugLogs(enabled || BuildConfig.DEBUG);
MwmApplication.prefs(mApplication)
.edit()
.putBoolean(mApplication.getString(R.string.pref_enable_logging), enabled)
.apply();
updateLoggers();
getLogger(Type.MISC).i(TAG, "File logging " + (enabled ? " started to " + mLogsFolder : "stopped"));
}
/**
* Throws a NullPointerException if initFileLogging() was not called before.
*/
public void setFileLoggingEnabled(boolean enabled)
{
Objects.requireNonNull(mApplication);
nativeToggleCoreDebugLogs(enabled);
SharedPreferences prefs = MwmApplication.prefs(mApplication);
SharedPreferences.Editor editor = prefs.edit();
String enableLoggingKey = mApplication.getString(R.string.pref_enable_logging);
editor.putBoolean(enableLoggingKey, enabled).apply();
updateLoggers();
if (isFileLoggingEnabled != enabled)
switchFileLoggingEnabled(enabled);
}
@NonNull
@ -112,21 +126,22 @@ public class LoggerFactory
}
}
/**
* Does nothing if initFileLogging() was not called before.
*/
public synchronized void zipLogs(@Nullable OnZipCompletedListener listener)
{
if (mApplication == null)
return;
String logsFolder = StorageUtils.getLogsFolder(mApplication);
if (TextUtils.isEmpty(logsFolder))
if (TextUtils.isEmpty(mLogsFolder))
{
if (listener != null)
listener.onCompleted(false);
return;
}
Runnable task = new ZipLogsTask(mApplication, logsFolder, logsFolder + ".zip", listener);
Runnable task = new ZipLogsTask(mApplication, mLogsFolder, mLogsFolder + ".zip", listener);
getFileLoggerExecutor().execute(task);
}
@ -140,16 +155,15 @@ public class LoggerFactory
@NonNull
private LoggerStrategy createLoggerStrategy(@NonNull Type type)
{
if (isFileLoggingEnabled() && mApplication != null)
if (isFileLoggingEnabled && mApplication != null)
{
nativeToggleCoreDebugLogs(true);
String logsFolder = StorageUtils.getLogsFolder(mApplication);
if (!TextUtils.isEmpty(logsFolder))
return new FileLoggerStrategy(mApplication,logsFolder + File.separator
+ type.name().toLowerCase() + ".log", getFileLoggerExecutor());
if (!TextUtils.isEmpty(mLogsFolder))
{
return new FileLoggerStrategy(mApplication, mLogsFolder + File.separator + type.name()
.toLowerCase() + ".log", getFileLoggerExecutor());
}
}
return new LogCatStrategy();
return new LogCatStrategy(isFileLoggingEnabled || BuildConfig.DEBUG);
}
@NonNull
@ -182,7 +196,6 @@ public class LoggerFactory
default:
logger.v(CORE_TAG, msg);
}
CrashlyticsUtils.INSTANCE.log(level, CORE_TAG, msg);
}
private static native void nativeToggleCoreDebugLogs(boolean enabled);