diff --git a/android/jni/CMakeLists.txt b/android/jni/CMakeLists.txt index 1fcbd2d784..259d1f8676 100644 --- a/android/jni/CMakeLists.txt +++ b/android/jni/CMakeLists.txt @@ -64,7 +64,7 @@ set(SRC com/mapswithme/util/HttpUploader.cpp com/mapswithme/util/HttpUploaderUtils.cpp com/mapswithme/util/Language.cpp - com/mapswithme/util/LoggerFactory.cpp + com/mapswithme/util/LogsManager.cpp com/mapswithme/util/NetworkPolicy.cpp com/mapswithme/util/StringUtils.cpp com/mapswithme/vulkan/android_vulkan_context_factory.cpp diff --git a/android/jni/com/mapswithme/core/jni_helper.cpp b/android/jni/com/mapswithme/core/jni_helper.cpp index ec7e68fe65..96f06c6281 100644 --- a/android/jni/com/mapswithme/core/jni_helper.cpp +++ b/android/jni/com/mapswithme/core/jni_helper.cpp @@ -21,7 +21,7 @@ jclass g_httpClientClazz; jclass g_httpParamsClazz; jclass g_platformSocketClazz; jclass g_utilsClazz; -jclass g_loggerFactoryClazz; +jclass g_loggerClazz; jclass g_keyValueClazz; jclass g_httpUploaderClazz; jclass g_httpPayloadClazz; @@ -48,7 +48,7 @@ JNI_OnLoad(JavaVM * jvm, void *) g_httpParamsClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/HttpClient$Params"); g_platformSocketClazz = jni::GetGlobalClassRef(env, "com/mapswithme/maps/location/PlatformSocket"); g_utilsClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/Utils"); - g_loggerFactoryClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/log/LoggerFactory"); + g_loggerClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/log/Logger"); g_keyValueClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/KeyValue"); g_httpUploaderClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/HttpUploader"); g_httpPayloadClazz = jni::GetGlobalClassRef(env, "com/mapswithme/util/HttpPayload"); @@ -74,7 +74,7 @@ JNI_OnUnload(JavaVM *, void *) env->DeleteGlobalRef(g_httpParamsClazz); env->DeleteGlobalRef(g_platformSocketClazz); env->DeleteGlobalRef(g_utilsClazz); - env->DeleteGlobalRef(g_loggerFactoryClazz); + env->DeleteGlobalRef(g_loggerClazz); env->DeleteGlobalRef(g_keyValueClazz); env->DeleteGlobalRef(g_httpUploaderClazz); env->DeleteGlobalRef(g_httpPayloadClazz); diff --git a/android/jni/com/mapswithme/core/jni_helper.hpp b/android/jni/com/mapswithme/core/jni_helper.hpp index 11cbb5f0cf..765dfa12c9 100644 --- a/android/jni/com/mapswithme/core/jni_helper.hpp +++ b/android/jni/com/mapswithme/core/jni_helper.hpp @@ -22,7 +22,7 @@ extern jclass g_httpClientClazz; extern jclass g_httpParamsClazz; extern jclass g_platformSocketClazz; extern jclass g_utilsClazz; -extern jclass g_loggerFactoryClazz; +extern jclass g_loggerClazz; extern jclass g_keyValueClazz; extern jclass g_httpUploaderClazz; extern jclass g_httpPayloadClazz; diff --git a/android/jni/com/mapswithme/core/logging.cpp b/android/jni/com/mapswithme/core/logging.cpp index 037cfd80cb..fad6d129ed 100644 --- a/android/jni/com/mapswithme/core/logging.cpp +++ b/android/jni/com/mapswithme/core/logging.cpp @@ -31,12 +31,12 @@ void AndroidMessage(LogLevel level, SrcPoint const & src, std::string const & s) } ScopedEnv env(jni::GetJVM()); - static jmethodID const logCoreMsgMethod = jni::GetStaticMethodID(env.get(), g_loggerFactoryClazz, + static jmethodID const logCoreMsgMethod = jni::GetStaticMethodID(env.get(), g_loggerClazz, "logCoreMessage", "(ILjava/lang/String;)V"); std::string const out = DebugPrint(src) + " " + s; jni::TScopedLocalRef msg(env.get(), jni::ToJavaString(env.get(), out)); - env->CallStaticVoidMethod(g_loggerFactoryClazz, logCoreMsgMethod, pr, msg.get()); + env->CallStaticVoidMethod(g_loggerClazz, logCoreMsgMethod, pr, msg.get()); } void AndroidLogMessage(LogLevel level, SrcPoint const & src, std::string const & s) @@ -69,17 +69,3 @@ void ToggleDebugLogs(bool enabled) g_LogLevel = LINFO; } } - -extern "C" { - -void DbgPrintC(char const * format, ...) -{ - va_list argptr; - va_start(argptr, format); - - __android_log_vprint(ANDROID_LOG_INFO, "OMaps_Debug", format, argptr); - - va_end(argptr); -} - -} diff --git a/android/jni/com/mapswithme/platform/Platform.cpp b/android/jni/com/mapswithme/platform/Platform.cpp index e21744a7ba..3d47c64e31 100644 --- a/android/jni/com/mapswithme/platform/Platform.cpp +++ b/android/jni/com/mapswithme/platform/Platform.cpp @@ -24,17 +24,17 @@ std::string Platform::GetMemoryInfo() const if (env == nullptr) return std::string(); - static std::shared_ptr classMemLogging = jni::make_global_ref(env->FindClass("com/mapswithme/util/log/MemLogging")); - ASSERT(classMemLogging, ()); + static std::shared_ptr classLogsManager = jni::make_global_ref(env->FindClass("com/mapswithme/util/log/LogsManager")); + ASSERT(classLogsManager, ()); jobject context = android::Platform::Instance().GetContext(); static jmethodID const getMemoryInfoId = jni::GetStaticMethodID(env, - static_cast(*classMemLogging), + static_cast(*classLogsManager), "getMemoryInfo", "(Landroid/content/Context;)Ljava/lang/String;"); jstring const memInfoString = static_cast(env->CallStaticObjectMethod( - static_cast(*classMemLogging), getMemoryInfoId, context)); + static_cast(*classLogsManager), getMemoryInfoId, context)); ASSERT(memInfoString, ()); return jni::ToNativeString(env, memInfoString); diff --git a/android/jni/com/mapswithme/util/LoggerFactory.cpp b/android/jni/com/mapswithme/util/LogsManager.cpp similarity index 74% rename from android/jni/com/mapswithme/util/LoggerFactory.cpp rename to android/jni/com/mapswithme/util/LogsManager.cpp index 2bd0dd168e..79efa1224a 100644 --- a/android/jni/com/mapswithme/util/LoggerFactory.cpp +++ b/android/jni/com/mapswithme/util/LogsManager.cpp @@ -3,7 +3,7 @@ extern "C" { JNIEXPORT void JNICALL -Java_com_mapswithme_util_log_LoggerFactory_nativeToggleCoreDebugLogs( +Java_com_mapswithme_util_log_LogsManager_nativeToggleCoreDebugLogs( JNIEnv * /*env*/, jclass /*clazz*/, jboolean enabled) { jni::ToggleDebugLogs(enabled); diff --git a/android/multidex-config.txt b/android/multidex-config.txt index 9201f5f9fa..669246ab7e 100644 --- a/android/multidex-config.txt +++ b/android/multidex-config.txt @@ -13,11 +13,8 @@ com/mapswithme/util/HttpUploader$Result.class com/mapswithme/util/HttpUploader.class com/mapswithme/util/KeyValue.class com/mapswithme/util/SharedPropertiesUtils.class -com/mapswithme/util/log/BaseLogger.class -com/mapswithme/util/log/FileLoggerStrategy.class -com/mapswithme/util/log/LogCatStrategy.class -com/mapswithme/util/log/LoggerFactory.class -com/mapswithme/util/log/ZipLogsTask.class +com/mapswithme/util/log/Logger.class +com/mapswithme/util/log/LogsManager.class com/mapswithme/util/NetworkPolicy.class com/mapswithme/util/sharing/ShareableInfoProvider.class com/mapswithme/util/Utils.class diff --git a/android/src/com/mapswithme/maps/settings/SettingsPrefsFragment.java b/android/src/com/mapswithme/maps/settings/SettingsPrefsFragment.java index d4f47a0406..b708ea3d95 100644 --- a/android/src/com/mapswithme/maps/settings/SettingsPrefsFragment.java +++ b/android/src/com/mapswithme/maps/settings/SettingsPrefsFragment.java @@ -480,7 +480,7 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment if (pref == null) return; - ((TwoStatePreference) pref).setChecked(LoggerFactory.INSTANCE.isFileLoggingEnabled); + ((TwoStatePreference) pref).setChecked(LoggerFactory.INSTANCE.isFileLoggingEnabled()); pref.setOnPreferenceChangeListener((preference, newValue) -> { if (!LoggerFactory.INSTANCE.setFileLoggingEnabled((Boolean) newValue)) { diff --git a/android/src/com/mapswithme/util/log/BaseLogger.java b/android/src/com/mapswithme/util/log/BaseLogger.java deleted file mode 100644 index 12985fb003..0000000000 --- a/android/src/com/mapswithme/util/log/BaseLogger.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.mapswithme.util.log; - -import androidx.annotation.NonNull; - -import net.jcip.annotations.GuardedBy; -import net.jcip.annotations.ThreadSafe; - -@ThreadSafe -class BaseLogger implements Logger -{ - @NonNull - @GuardedBy("this") - private LoggerStrategy mStrategy; - - BaseLogger(@NonNull LoggerStrategy strategy) - { - mStrategy = strategy; - } - - synchronized void setStrategy(@NonNull LoggerStrategy strategy) - { - mStrategy = strategy; - } - - @Override - public synchronized void v(String tag, String msg) - { - mStrategy.v(tag, msg); - } - - @Override - public synchronized void v(String tag, String msg, Throwable tr) - { - mStrategy.v(tag, msg, tr); - } - - @Override - public synchronized void d(String tag, String msg) - { - mStrategy.d(tag, msg); - } - - @Override - public synchronized void d(String tag, String msg, Throwable tr) - { - mStrategy.d(tag, msg, tr); - } - - @Override - public synchronized void i(String tag, String msg) - { - mStrategy.i(tag, msg); - } - - @Override - public synchronized void i(String tag, String msg, Throwable tr) - { - mStrategy.i(tag, msg, tr); - } - - @Override - public synchronized void w(String tag, String msg) - { - mStrategy.w(tag, msg); - } - - @Override - public synchronized void w(String tag, String msg, Throwable tr) - { - mStrategy.w(tag, msg, tr); - } - - @Override - public synchronized void w(String tag, Throwable tr) - { - mStrategy.w(tag, tr); - } - - @Override - public synchronized void e(String tag, String msg) - { - mStrategy.e(tag, msg); - } - - @Override - public synchronized void e(String tag, String msg, Throwable tr) - { - mStrategy.e(tag, msg, tr); - } -} diff --git a/android/src/com/mapswithme/util/log/FileLoggerStrategy.java b/android/src/com/mapswithme/util/log/FileLoggerStrategy.java deleted file mode 100644 index 037b8999e0..0000000000 --- a/android/src/com/mapswithme/util/log/FileLoggerStrategy.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.mapswithme.util.log; - -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; -import net.jcip.annotations.Immutable; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.concurrent.Executor; - -@Immutable -class FileLoggerStrategy implements LoggerStrategy -{ - private static final String TAG = FileLoggerStrategy.class.getSimpleName(); - @NonNull - private final String mFileName; - @NonNull - private final Executor mExecutor; - - public FileLoggerStrategy(@NonNull String fileName, @NonNull Executor executor) - { - mFileName = fileName; - mExecutor = executor; - } - - @Override - public void v(String tag, String msg) - { - write("V/" + tag + ": " + msg); - } - - @Override - public void v(String tag, String msg, Throwable tr) - { - write("V/" + tag + ": " + msg + "\n" + Log.getStackTraceString(tr)); - } - - @Override - public void d(String tag, String msg) - { - write("D/" + tag + ": " + msg); - } - - @Override - public void d(String tag, String msg, Throwable tr) - { - write("D/" + tag + ": " + msg + "\n" + Log.getStackTraceString(tr)); - } - - @Override - public void i(String tag, String msg) - { - write("I/" + tag + ": " + msg); - } - - @Override - public void i(String tag, String msg, Throwable tr) - { - - write("I/" + tag + ": " + msg + "\n" + Log.getStackTraceString(tr)); - } - - @Override - public void w(String tag, String msg) - { - write("W/" + tag + ": " + msg); - } - - @Override - public void w(String tag, String msg, Throwable tr) - { - write("W/" + tag + ": " + msg + "\n" + Log.getStackTraceString(tr)); - } - - @Override - public void w(String tag, Throwable tr) - { - write("D/" + tag + ": " + Log.getStackTraceString(tr)); - } - - @Override - public void e(String tag, String msg) - { - write("E/" + tag + ": " + msg); - } - - @Override - public void e(String tag, String msg, Throwable tr) - { - write("E/" + tag + ": " + msg + "\n" + Log.getStackTraceString(tr)); - } - - private void write(@NonNull final String data) - { - final String logsPath = LoggerFactory.INSTANCE.ensureLogsFolder(); - if (logsPath == null) - Log.e(TAG, "Couldn't log to " + mFileName + ": " + data); - else - mExecutor.execute(new WriteTask(logsPath + File.separator + mFileName, - data, Thread.currentThread().getName())); - } - - private static class WriteTask implements Runnable - { - private static final int MAX_SIZE = 3000000; - @NonNull - private final String mFilePath; - @NonNull - private final String mData; - @NonNull - private final String mCallingThread; - - private WriteTask(@NonNull String filePath, @NonNull String data, @NonNull String callingThread) - { - mFilePath = filePath; - mData = data; - mCallingThread = callingThread; - } - - @Override - public void run() - { - FileWriter fw = null; - try - { - File file = new File(mFilePath); - if (!file.exists() || file.length() > MAX_SIZE) - { - fw = new FileWriter(file, false); - LoggerFactory.INSTANCE.writeSystemInformation(fw); - } - else - { - fw = new FileWriter(mFilePath, true); - } - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); - fw.write(formatter.format(new Date()) + " " + mCallingThread + ": " + mData + "\n"); - } - catch (IOException e) - { - Log.e(TAG, "Failed to log: " + mData, e); - } - finally - { - if (fw != null) - try - { - fw.close(); - } - catch (IOException e) - { - Log.e(TAG, "Failed to close file " + mFilePath, e); - } - } - } - } -} diff --git a/android/src/com/mapswithme/util/log/LogCatStrategy.java b/android/src/com/mapswithme/util/log/LogCatStrategy.java deleted file mode 100644 index 3b0a785980..0000000000 --- a/android/src/com/mapswithme/util/log/LogCatStrategy.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.mapswithme.util.log; - -import android.util.Log; -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 (mIsDebug) - Log.v(tag, msg); - } - - @Override - public void v(String tag, String msg, Throwable tr) - { - if (mIsDebug) - Log.v(tag, msg, tr); - } - - @Override - public void d(String tag, String msg) - { - if (mIsDebug) - Log.d(tag, msg); - } - - @Override - public void d(String tag, String msg, Throwable tr) - { - if (mIsDebug) - Log.d(tag, msg, tr); - } - - @Override - public void i(String tag, String msg) - { - Log.i(tag, msg); - } - - @Override - public void i(String tag, String msg, Throwable tr) - { - Log.i(tag, msg, tr); - } - - @Override - public void w(String tag, String msg) - { - Log.w(tag, msg); - } - - @Override - public void w(String tag, String msg, Throwable tr) - { - Log.w(tag, msg, tr); - } - - @Override - public void w(String tag, Throwable tr) - { - Log.w(tag, tr); - } - - @Override - public void e(String tag, String msg) - { - Log.e(tag, msg); - } - - @Override - public void e(String tag, String msg, Throwable tr) - { - Log.e(tag, msg, tr); - } -} diff --git a/android/src/com/mapswithme/util/log/Logger.java b/android/src/com/mapswithme/util/log/Logger.java index fa85f6187d..212c3e8bea 100644 --- a/android/src/com/mapswithme/util/log/Logger.java +++ b/android/src/com/mapswithme/util/log/Logger.java @@ -1,17 +1,175 @@ package com.mapswithme.util.log; +import android.util.Log; -public interface Logger +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.mapswithme.maps.BuildConfig; +import net.jcip.annotations.ThreadSafe; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +@ThreadSafe +public final class Logger { - void v(String tag, String msg); - void v(String tag, String msg, Throwable tr); - void d(String tag, String msg); - void d(String tag, String msg, Throwable tr); - void i(String tag, String msg); - void i(String tag, String msg, Throwable tr); - void w(String tag, String msg); - void w(String tag, String msg, Throwable tr); - void w(String tag, Throwable tr); - void e(String tag, String msg); - void e(String tag, String msg, Throwable tr); + private static final String TAG = Logger.class.getSimpleName(); + private static final String CORE_TAG = "OMcore"; + private static final String FILENAME = "app.log"; + + public static void v(String tag, String msg) + { + log(Log.VERBOSE, tag, msg, null); + } + + public static void v(String tag, String msg, Throwable tr) + { + log(Log.VERBOSE, tag, msg, tr); + } + + public static void d(String tag, String msg) + { + log(Log.DEBUG, tag, msg, null); + } + + public static void d(String tag, String msg, Throwable tr) + { + log(Log.DEBUG, tag, msg, tr); + } + + public static void i(String tag, String msg) + { + log(Log.INFO, tag, msg, null); + } + + public static void i(String tag, String msg, Throwable tr) + { + log(Log.INFO, tag, msg, tr); + } + + public static void w(String tag, String msg) + { + log(Log.WARN, tag, msg, null); + } + + public static void w(String tag, String msg, Throwable tr) + { + log(Log.WARN, tag, msg, tr); + } + + public static void e(String tag, String msg) + { + log(Log.ERROR, tag, msg, null); + } + + public static void e(String tag, String msg, Throwable tr) + { + log(Log.ERROR, tag, msg, tr); + } + + // Called from JNI to proxy native code logging. + @SuppressWarnings("unused") + @Keep + private static void logCoreMessage(int level, String msg) + { + log(level, CORE_TAG, msg, null); + } + + public static void log(int level, @NonNull String tag, @NonNull String msg, @Nullable Throwable tr) + { + final String logsFolder = LogsManager.INSTANCE.getEnabledLogsFolder(); + if (logsFolder != null) + { + final String data = getLevelChar(level) + "/" + tag + ": " + msg + (tr != null ? '\n' + Log.getStackTraceString(tr) : ""); + LogsManager.EXECUTOR.execute(new WriteTask(logsFolder + File.separator + FILENAME, + data, Thread.currentThread().getName())); + } + else if (BuildConfig.DEBUG || level >= Log.INFO) + { + // Only Debug builds log DEBUG level to Android system log. + if (tr != null) + msg += '\n' + Log.getStackTraceString(tr); + Log.println(level, tag, msg); + } + } + + private static char getLevelChar(int level) + { + switch (level) + { + case Log.VERBOSE: + return 'V'; + case Log.DEBUG: + return 'D'; + case Log.INFO: + return 'I'; + case Log.WARN: + return 'W'; + case Log.ERROR: + return 'E'; + } + assert false : "Unknown log level " + level; + return '_'; + } + + private static class WriteTask implements Runnable + { + private static final int MAX_SIZE = 3000000; + @NonNull + private final String mFilePath; + @NonNull + private final String mData; + @NonNull + private final String mCallingThread; + + private WriteTask(@NonNull String filePath, @NonNull String data, @NonNull String callingThread) + { + mFilePath = filePath; + mData = data; + mCallingThread = callingThread; + } + + @Override + public void run() + { + FileWriter fw = null; + try + { + File file = new File(mFilePath); + if (!file.exists() || file.length() > MAX_SIZE) + { + fw = new FileWriter(file, false); + fw.write(LogsManager.INSTANCE.getSystemInformation()); //todo: write in a separate file and add a timestamp + } + else + { + fw = new FileWriter(mFilePath, true); + } + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); + fw.write(formatter.format(new Date()) + " " + mCallingThread + ": " + mData + "\n"); + } + catch (IOException e) + { + Log.e(TAG, "Failed to write to " + mFilePath + ": " + mData, e); + } + finally + { + if (fw != null) + try + { + fw.close(); + } + catch (IOException e) + { + Log.e(TAG, "Failed to close file " + mFilePath, e); + } + } + } + } } diff --git a/android/src/com/mapswithme/util/log/LoggerFactory.java b/android/src/com/mapswithme/util/log/LoggerFactory.java deleted file mode 100644 index bf10d541cd..0000000000 --- a/android/src/com/mapswithme/util/log/LoggerFactory.java +++ /dev/null @@ -1,299 +0,0 @@ -package com.mapswithme.util.log; - -import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; -import android.location.LocationManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Build; -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.Utils; -import net.jcip.annotations.GuardedBy; -import net.jcip.annotations.ThreadSafe; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.EnumMap; -import java.util.Locale; -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 -{ - public enum Type - { - MISC, LOCATION, TRAFFIC, GPS_TRACKING, TRACK_RECORDER, ROUTING, NETWORK, STORAGE, DOWNLOADER, - CORE, THIRD_PARTY, BILLING - } - - public interface OnZipCompletedListener - { - // Called from the logger thread. - public void onCompleted(final boolean success, @Nullable final String zipPath); - } - - public final static LoggerFactory INSTANCE = new LoggerFactory(); - public boolean isFileLoggingEnabled = false; - - @NonNull - @GuardedBy("this") - private final EnumMap mLoggers = new EnumMap<>(Type.class); - 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 initFileLogging(@NonNull Application application) - { - getLogger(Type.MISC).i(TAG, "Init file logging"); - mApplication = application; - ensureLogsFolder(); - - 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); - } - - /** - * Ensures logs folder exists. - * Tries to create it and/or re-get a path from the system, falling back to the internal storage. - * Switches off file logging if nothing had helped. - * - * Its important to have only system logging here to avoid infinite loop - * (file loggers call ensureLogsFolder() in preparation to write). - * - * @return logs folder path, null if it can't be created - * - * NOTE: initFileLogging() must be called before. - */ - @Nullable - public synchronized String ensureLogsFolder() - { - assert mApplication != null : "mApplication must be initialized first by calling initFileLogging()"; - - if (mLogsFolder != null && createWritableDir(mLogsFolder)) - return mLogsFolder; - - mLogsFolder = null; - mLogsFolder = createLogsFolder(mApplication.getExternalFilesDir(null)); - if (mLogsFolder == null) - mLogsFolder = createLogsFolder(mApplication.getFilesDir()); - - if (mLogsFolder == null) - { - Log.e(TAG, "Can't create any logs folder"); - if (isFileLoggingEnabled) - switchFileLoggingEnabled(false); - } - - return mLogsFolder; - } - - // Only system logging allowed, see ensureLogsFolder(). - private synchronized boolean createWritableDir(@NonNull final String path) - { - final File dir = new File(path); - if (!dir.exists() && !dir.mkdirs()) - { - Log.e(TAG, "Can't create a logs folder " + path); - return false; - } - if (!dir.canWrite()) - { - Log.e(TAG, "Can't write to a logs folder " + path); - return false; - } - return true; - } - - // Only system logging allowed, see ensureLogsFolder(). - @Nullable - private synchronized String createLogsFolder(@Nullable final File dir) - { - if (dir != null) - { - final String path = dir.getPath() + File.separator + "logs"; - if (createWritableDir(path)) - return path; - } - return null; - } - - // Only system logging allowed, see ensureLogsFolder(). - private synchronized void switchFileLoggingEnabled(boolean enabled) - { - enabled = enabled && mLogsFolder != null; - Log.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(); - Log.i(TAG, "File logging " + (enabled ? "started to " + mLogsFolder : "stopped")); - } - - /** - * Returns false if file logging can't be enabled. - * - * NOTE: initFileLogging() must be called before. - */ - public boolean setFileLoggingEnabled(boolean enabled) - { - assert mApplication != null : "mApplication must be initialized first by calling initFileLogging()"; - - if (isFileLoggingEnabled != enabled) - { - if (enabled && ensureLogsFolder() == null) - { - Log.e(TAG, "Can't enable file logging: there is no logs folder."); - return false; - } - else - switchFileLoggingEnabled(enabled); - } - - return true; - } - - @NonNull - public synchronized Logger getLogger(@NonNull Type type) - { - BaseLogger logger = mLoggers.get(type); - if (logger == null) - { - logger = createLogger(type); - mLoggers.put(type, logger); - } - return logger; - } - - private synchronized void updateLoggers() - { - for (Type type : mLoggers.keySet()) - mLoggers.get(type).setStrategy(createLoggerStrategy(type)); - } - - /** - * NOTE: initFileLogging() must be called before. - */ - public synchronized void zipLogs(@NonNull OnZipCompletedListener listener) - { - assert mApplication != null : "mApplication must be initialized first by calling initFileLogging()"; - - if (ensureLogsFolder() == null) - { - Log.e(TAG, "Can't send logs: there is no logs folder."); - listener.onCompleted(false, null); - return; - } - - final Runnable task = new ZipLogsTask(mLogsFolder, mLogsFolder + ".zip", listener); - getFileLoggerExecutor().execute(task); - } - - @NonNull - private BaseLogger createLogger(@NonNull Type type) - { - return new BaseLogger(createLoggerStrategy(type)); - } - - @NonNull - private LoggerStrategy createLoggerStrategy(@NonNull Type type) - { - if (isFileLoggingEnabled) - return new FileLoggerStrategy(type.name().toLowerCase() + ".log", getFileLoggerExecutor()); - - return new LogCatStrategy(BuildConfig.DEBUG); - } - - @NonNull - private synchronized ExecutorService getFileLoggerExecutor() - { - if (mFileLoggerExecutor == null) - mFileLoggerExecutor = Executors.newSingleThreadExecutor(); - return mFileLoggerExecutor; - } - - // Called from JNI. - @SuppressWarnings("unused") - private static void logCoreMessage(int level, String msg) - { - final Logger logger = INSTANCE.getLogger(Type.CORE); - switch (level) - { - case Log.DEBUG: - logger.d(CORE_TAG, msg); - break; - case Log.INFO: - logger.i(CORE_TAG, msg); - break; - case Log.WARN: - logger.w(CORE_TAG, msg); - break; - case Log.ERROR: - logger.e(CORE_TAG, msg); - break; - default: - logger.v(CORE_TAG, msg); - } - } - - /** - * NOTE: initFileLogging() must be called before. - */ - public void writeSystemInformation(@NonNull final FileWriter fw) throws IOException - { - assert mApplication != null : "mApplication must be initialized first by calling initFileLogging()"; - - fw.write("Android version: " + Build.VERSION.SDK_INT); - fw.write("\nDevice: " + Utils.getFullDeviceModel()); - fw.write("\nApp version: " + BuildConfig.APPLICATION_ID + " " + BuildConfig.VERSION_NAME); - fw.write("\nLocale: " + Locale.getDefault()); - fw.write("\nNetworks: "); - final ConnectivityManager manager = (ConnectivityManager) mApplication.getSystemService(Context.CONNECTIVITY_SERVICE); - if (manager != null) - // TODO: getAllNetworkInfo() is deprecated, for alternatives check - // https://stackoverflow.com/questions/32547006/connectivitymanager-getnetworkinfoint-deprecated - for (NetworkInfo info : manager.getAllNetworkInfo()) - fw.write(info.toString()); - fw.write("\nLocation providers: "); - final LocationManager locMngr = (android.location.LocationManager) mApplication.getSystemService(Context.LOCATION_SERVICE); - if (locMngr != null) - for (String provider: locMngr.getProviders(true)) - fw.write(provider + " "); - fw.write("\n\n"); - } - - private static native void nativeToggleCoreDebugLogs(boolean enabled); -} diff --git a/android/src/com/mapswithme/util/log/LoggerStrategy.java b/android/src/com/mapswithme/util/log/LoggerStrategy.java deleted file mode 100644 index 2bbf56d7be..0000000000 --- a/android/src/com/mapswithme/util/log/LoggerStrategy.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.mapswithme.util.log; - - -interface LoggerStrategy -{ - void v(String tag, String msg); - void v(String tag, String msg, Throwable tr); - void d(String tag, String msg); - void d(String tag, String msg, Throwable tr); - void i(String tag, String msg); - void i(String tag, String msg, Throwable tr); - void w(String tag, String msg); - void w(String tag, String msg, Throwable tr); - void w(String tag, Throwable tr); - void e(String tag, String msg); - void e(String tag, String msg, Throwable tr); -} diff --git a/android/src/com/mapswithme/util/log/LogsManager.java b/android/src/com/mapswithme/util/log/LogsManager.java new file mode 100644 index 0000000000..1d93409e50 --- /dev/null +++ b/android/src/com/mapswithme/util/log/LogsManager.java @@ -0,0 +1,278 @@ +package com.mapswithme.util.log; + +import android.app.ActivityManager; +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.location.LocationManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; +import android.os.Debug; +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.Utils; +import net.jcip.annotations.ThreadSafe; + +import java.io.File; +import java.util.Locale; +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. + * + * Its important to have only system logging here to avoid infinite loop + * (file loggers call getEnabledLogsFolder() in preparation to write). + */ +@ThreadSafe +public class LogsManager +{ + public interface OnZipCompletedListener + { + // Called from the logger thread. + public void onCompleted(final boolean success, @Nullable final String zipPath); + } + + private final static String TAG = LogsManager.class.getSimpleName(); + + public final static LogsManager INSTANCE = new LogsManager(); + final static ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(); + + @Nullable + private Application mApplication; + private boolean mIsFileLoggingEnabled = false; + @Nullable + private String mLogsFolder; + + private LogsManager() + { + Log.i(LogsManager.TAG, "Logging started"); + } + + public synchronized void initFileLogging(@NonNull Application application) + { + Log.i(TAG, "Init file logging"); + mApplication = application; + + final SharedPreferences prefs = MwmApplication.prefs(mApplication); + // File logging is enabled by default for beta builds. + mIsFileLoggingEnabled = prefs.getBoolean(mApplication.getString(R.string.pref_enable_logging), + BuildConfig.BUILD_TYPE.equals("beta")); + Log.i(TAG, "isFileLoggingEnabled preference: " + mIsFileLoggingEnabled); + mIsFileLoggingEnabled = mIsFileLoggingEnabled && ensureLogsFolder() != null; + + // Set native logging level, save into shared preferences. + switchFileLoggingEnabled(mIsFileLoggingEnabled); + } + + private void assertFileLoggingInit() + { + assert mApplication != null : "mApplication must be initialized first by calling initFileLogging()"; + } + + /** + * Returns logs folder path if file logging is enabled. + * Switches off file logging if the path doesn't exist and can't be created. + */ + @Nullable + synchronized String getEnabledLogsFolder() + { + if (!mIsFileLoggingEnabled) + return null; + + final String logsFolder = ensureLogsFolder(); + if (logsFolder == null) + switchFileLoggingEnabled(false); + + return logsFolder; + } + + /** + * Ensures logs folder exists. + * Tries to create it and/or re-get a path from the system, falling back to the internal storage. + * NOTE: initFileLogging() must be called before. + * + * @return logs folder path, null if it can't be created + */ + @Nullable + private String ensureLogsFolder() + { + assertFileLoggingInit(); + + if (mLogsFolder != null && createWritableDir(mLogsFolder)) + return mLogsFolder; + + mLogsFolder = createLogsFolder(mApplication.getExternalFilesDir(null)); + if (mLogsFolder == null) + mLogsFolder = createLogsFolder(mApplication.getFilesDir()); + + if (mLogsFolder == null) + Log.e(TAG, "Can't create any logs folder"); + + return mLogsFolder; + } + + private boolean createWritableDir(@NonNull final String path) + { + final File dir = new File(path); + if (!dir.exists()) + { + Log.i(TAG, "Creating logs folder " + path); + if (!dir.mkdirs()) + { + Log.e(TAG, "Can't create a logs folder " + path); + return false; + } + } + if (!dir.canWrite()) + { + Log.e(TAG, "Can't write to a logs folder " + path); + return false; + } + return true; + } + + @Nullable + private String createLogsFolder(@Nullable final File dir) + { + if (dir != null) + { + final String path = dir.getPath() + File.separator + "logs"; + if (createWritableDir(path)) + return path; + } + return null; + } + + private void switchFileLoggingEnabled(boolean enabled) + { + mIsFileLoggingEnabled = enabled; + nativeToggleCoreDebugLogs(enabled || BuildConfig.DEBUG); + MwmApplication.prefs(mApplication) + .edit() + .putBoolean(mApplication.getString(R.string.pref_enable_logging), enabled) + .apply(); + Log.i(TAG, "Logging to " + (enabled ? "logs folder " + mLogsFolder : "system log")); + } + + public synchronized boolean isFileLoggingEnabled() + { + return mIsFileLoggingEnabled; + } + + /** + * Returns false if file logging can't be enabled. + * + * NOTE: initFileLogging() must be called before. + */ + public synchronized boolean setFileLoggingEnabled(boolean enabled) + { + assertFileLoggingInit(); + + if (mIsFileLoggingEnabled != enabled) + { + Log.i(TAG, "Switching isFileLoggingEnabled to " + enabled); + if (enabled && ensureLogsFolder() == null) + { + Log.e(TAG, "Can't enable file logging: no logs folder."); + return false; + } + else + switchFileLoggingEnabled(enabled); + } + + return true; + } + + /** + * NOTE: initFileLogging() must be called before. + */ + public synchronized void zipLogs(@NonNull OnZipCompletedListener listener) + { + assertFileLoggingInit(); + + if (ensureLogsFolder() == null) + { + Log.e(TAG, "Can't zip log files: no logs folder."); + listener.onCompleted(false, null); + return; + } + + Log.i(TAG, "Zipping log files in " + mLogsFolder); + final Runnable task = new ZipLogsTask(mLogsFolder, mLogsFolder + ".zip", listener); + EXECUTOR.execute(task); + } + + /** + * NOTE: initFileLogging() must be called before. + */ + @NonNull + String getSystemInformation() + { + assertFileLoggingInit(); + + String res = "Android version: " + Build.VERSION.SDK_INT + + "\nDevice: " + Utils.getFullDeviceModel() + + "\nApp version: " + BuildConfig.APPLICATION_ID + " " + BuildConfig.VERSION_NAME + + "\nLocale: " + Locale.getDefault() + + "\nNetworks: "; + final ConnectivityManager manager = (ConnectivityManager) mApplication.getSystemService(Context.CONNECTIVITY_SERVICE); + if (manager != null) + // TODO: getAllNetworkInfo() is deprecated, for alternatives check + // https://stackoverflow.com/questions/32547006/connectivitymanager-getnetworkinfoint-deprecated + for (NetworkInfo info : manager.getAllNetworkInfo()) + res += "\n\t" + info.toString(); + res += "\nLocation providers: "; + final LocationManager locMngr = (android.location.LocationManager) mApplication.getSystemService(Context.LOCATION_SERVICE); + if (locMngr != null) + for (String provider : locMngr.getProviders(true)) + res += provider + " "; + + return res + "\n\n"; + } + + // Called from JNI. + @SuppressWarnings("unused") + @NonNull + public static String getMemoryInfo(@NonNull Context context) + { + final Debug.MemoryInfo debugMI = new Debug.MemoryInfo(); + Debug.getMemoryInfo(debugMI); + final ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); + final ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + activityManager.getMemoryInfo(mi); + + StringBuilder log = new StringBuilder("Memory info: "); + log.append(" Debug.getNativeHeapSize() = ") + .append(Debug.getNativeHeapSize() / 1024) + .append("KB; Debug.getNativeHeapAllocatedSize() = ") + .append(Debug.getNativeHeapAllocatedSize() / 1024) + .append("KB; Debug.getNativeHeapFreeSize() = ") + .append(Debug.getNativeHeapFreeSize() / 1024) + .append("KB; debugMI.getTotalPrivateDirty() = ") + .append(debugMI.getTotalPrivateDirty()) + .append("KB; debugMI.getTotalPss() = ") + .append(debugMI.getTotalPss()) + .append("KB; mi.availMem = ") + .append(mi.availMem / 1024) + .append("KB; mi.threshold = ") + .append(mi.threshold / 1024) + .append("KB; mi.lowMemory = ") + .append(mi.lowMemory); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + log.append(" mi.totalMem = ").append(mi.totalMem / 1024).append("KB;"); + } + return log.toString(); + } + + private static native void nativeToggleCoreDebugLogs(boolean enabled); +} diff --git a/android/src/com/mapswithme/util/log/MemLogging.java b/android/src/com/mapswithme/util/log/MemLogging.java deleted file mode 100644 index 5fe16a94c6..0000000000 --- a/android/src/com/mapswithme/util/log/MemLogging.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.mapswithme.util.log; - -import android.app.ActivityManager; -import android.content.Context; -import android.os.Build; -import android.os.Debug; - -import androidx.annotation.NonNull; - -@SuppressWarnings("unused") -public class MemLogging -{ - public static String getMemoryInfo(@NonNull Context context) - { - final Debug.MemoryInfo debugMI = new Debug.MemoryInfo(); - Debug.getMemoryInfo(debugMI); - final ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); - final ActivityManager activityManager = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - activityManager.getMemoryInfo(mi); - - StringBuilder log = new StringBuilder("Memory info: "); - log.append(" Debug.getNativeHeapSize() = ").append(Debug.getNativeHeapSize() / 1024) - .append("KB; Debug.getNativeHeapAllocatedSize() = ").append(Debug.getNativeHeapAllocatedSize() / 1024) - .append("KB; Debug.getNativeHeapFreeSize() = ").append(Debug.getNativeHeapFreeSize() / 1024) - .append("KB; debugMI.getTotalPrivateDirty() = ").append(debugMI.getTotalPrivateDirty()) - .append("KB; debugMI.getTotalPss() = ").append(debugMI.getTotalPss()) - .append("KB; mi.availMem = ").append(mi.availMem / 1024) - .append("KB; mi.threshold = ").append(mi.threshold / 1024) - .append("KB; mi.lowMemory = ").append(mi.lowMemory); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) - { - log.append(" mi.totalMem = ").append(mi.totalMem / 1024).append("KB;"); - } - return log.toString(); - } -} diff --git a/android/src/com/mapswithme/util/log/ZipLogsTask.java b/android/src/com/mapswithme/util/log/ZipLogsTask.java index e586a51ff0..2ac97b27d4 100644 --- a/android/src/com/mapswithme/util/log/ZipLogsTask.java +++ b/android/src/com/mapswithme/util/log/ZipLogsTask.java @@ -1,9 +1,5 @@ package com.mapswithme.util.log; -import android.app.Application; -import android.text.TextUtils; -import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -18,21 +14,20 @@ import java.io.InputStreamReader; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; - class ZipLogsTask implements Runnable { - private final static String TAG = ZipLogsTask.class.getSimpleName(); - private final static Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); + private static final Logger LOGGER = new Logger(Logger.Scope.MAIN, ZipLogsTask.class); + private final static int BUFFER_SIZE = 2048; @NonNull private final String mLogsPath; @NonNull private final String mZipPath; @Nullable - private final LoggerFactory.OnZipCompletedListener mOnCompletedListener; + private final LogsManager.OnZipCompletedListener mOnCompletedListener; ZipLogsTask(@NonNull String logsPath, @NonNull String zipPath, - @NonNull LoggerFactory.OnZipCompletedListener onCompletedListener) + @NonNull LogsManager.OnZipCompletedListener onCompletedListener) { mLogsPath = logsPath; mZipPath = zipPath; @@ -61,7 +56,7 @@ class ZipLogsTask implements Runnable } catch (Exception e) { - Log.e(TAG, "Failed to zip file '" + sourcePath + "' to location '" + toLocation + "'", e); + LOGGER.e("Failed to zip file '" + sourcePath + "' to location '" + toLocation + "'", e); return false; } return true; @@ -101,14 +96,6 @@ class ZipLogsTask implements Runnable } } - private static String getLastPathComponent(String filePath) - { - String[] segments = filePath.split(File.separator); - if (segments.length == 0) - return ""; - return segments[segments.length - 1]; - } - private void saveSystemLogcat(String path) { final String cmd = "logcat -d -v time"; @@ -119,7 +106,7 @@ class ZipLogsTask implements Runnable } catch (IOException e) { - LOGGER.e(TAG, "Failed to get system logcat", e); + LOGGER.e("Failed to get system logcat", e); return; } @@ -129,7 +116,7 @@ class ZipLogsTask implements Runnable InputStreamReader reader = new InputStreamReader(process.getInputStream()); FileWriter writer = new FileWriter(file)) { - LoggerFactory.INSTANCE.writeSystemInformation(writer); + writer.write(LogsManager.INSTANCE.getSystemInformation()); char[] buffer = new char[10000]; do { @@ -141,8 +128,7 @@ class ZipLogsTask implements Runnable } catch (Throwable e) { - LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC); - LOGGER.e(TAG, "Failed to save system logcat to " + path, e); + LOGGER.e("Failed to save system logcat to " + path, e); } } }