From 9d21cf143e99824462ecc3f74372c2149587bdab Mon Sep 17 00:00:00 2001 From: Pedro Pombeiro Date: Sat, 15 Dec 2018 19:57:00 +0100 Subject: [PATCH] Add Send Logs command. Closes #6710 Signed-off-by: Pedro Pombeiro --- android/app/src/main/AndroidManifest.xml | 11 + .../java/im/status/ethereum/MainActivity.java | 1 - .../src/main/res/xml/file_provider_paths.xml | 6 + desktop/CMakeModules/CompleteBundle.cmake | 2 +- desktop/CMakeModules/QtConfiguration.cmake | 4 + desktop/reportApp/CMakeLists.txt | 3 +- .../status/ethereum/module/StatusModule.java | 188 +++++++++++++++++- .../react-native-status/desktop/rctstatus.cpp | 117 +++++++++-- .../react-native-status/desktop/rctstatus.h | 3 +- .../ios/RCTStatus/RCTStatus.m | 10 + src/status_im/events.cljs | 8 + src/status_im/log_level/core.cljs | 13 +- src/status_im/native_module/core.cljs | 3 + src/status_im/native_module/impl/module.cljs | 4 + src/status_im/node/core.cljs | 10 +- .../desktop/main/tabs/profile/styles.cljs | 13 ++ .../desktop/main/tabs/profile/views.cljs | 30 ++- .../ui/screens/profile/user/styles.cljs | 8 + .../ui/screens/profile/user/views.cljs | 9 +- src/status_im/utils/logging/core.cljs | 36 ++++ translations/en.json | 1 + 21 files changed, 429 insertions(+), 51 deletions(-) create mode 100644 android/app/src/main/res/xml/file_provider_paths.xml create mode 100644 src/status_im/utils/logging/core.cljs diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1209366318..520e849b98 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -91,6 +91,17 @@ + + + diff --git a/android/app/src/main/java/im/status/ethereum/MainActivity.java b/android/app/src/main/java/im/status/ethereum/MainActivity.java index 43e1796219..54b9ea8d70 100644 --- a/android/app/src/main/java/im/status/ethereum/MainActivity.java +++ b/android/app/src/main/java/im/status/ethereum/MainActivity.java @@ -30,7 +30,6 @@ import im.status.ethereum.module.StatusThreadPoolExecutor; public class MainActivity extends ReactActivity implements ActivityCompat.OnRequestPermissionsResultCallback{ - private static final int PERMISSION_REQUEST_CAMERA = 0; @Nullable private PermissionListener mPermissionListener; diff --git a/android/app/src/main/res/xml/file_provider_paths.xml b/android/app/src/main/res/xml/file_provider_paths.xml new file mode 100644 index 0000000000..816c9d3f8c --- /dev/null +++ b/android/app/src/main/res/xml/file_provider_paths.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/desktop/CMakeModules/CompleteBundle.cmake b/desktop/CMakeModules/CompleteBundle.cmake index a27268a872..39151c73c4 100644 --- a/desktop/CMakeModules/CompleteBundle.cmake +++ b/desktop/CMakeModules/CompleteBundle.cmake @@ -9,4 +9,4 @@ endif(APPLE) if(SCRIPT AND EXISTS ${CMAKE_SOURCE_DIR}/CMakeModules/${SCRIPT}.cmake.in) configure_file(${CMAKE_SOURCE_DIR}/CMakeModules/${SCRIPT}.cmake.in ${SCRIPT}.cmake @ONLY) include(${CMAKE_CURRENT_BINARY_DIR}/${SCRIPT}.cmake) -endif(SCRIPT) +endif() diff --git a/desktop/CMakeModules/QtConfiguration.cmake b/desktop/CMakeModules/QtConfiguration.cmake index a81a07677b..47e756e20d 100644 --- a/desktop/CMakeModules/QtConfiguration.cmake +++ b/desktop/CMakeModules/QtConfiguration.cmake @@ -14,6 +14,10 @@ macro(import_qt_modules) #message("${mod}_INCLUDE_DIRS: include_directories(${${mod}_INCLUDE_DIRS})") include_directories(${${mod}_INCLUDE_DIRS}) + if (${COMP} STREQUAL "Quick") + # We need to include the private headers for QZipWriter. If in the future we can't use that class anymore, we can always resort to the QuaZIP OSS library + include_directories(${${mod}_PRIVATE_INCLUDE_DIRS}) + endif() list(APPEND QT5_LIBRARIES ${${mod}_LIBRARIES}) list(APPEND QT5_CFLAGS ${${mod}_EXECUTABLE_COMPILE_FLAGS}) diff --git a/desktop/reportApp/CMakeLists.txt b/desktop/reportApp/CMakeLists.txt index 5263b906d1..866cd0804f 100644 --- a/desktop/reportApp/CMakeLists.txt +++ b/desktop/reportApp/CMakeLists.txt @@ -14,7 +14,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11") set(APP_NAME "reportApp") set(MAIN_CPP_SOURCE reportpublisher.cpp - reportpublisher.cpp main.cpp) add_executable( @@ -23,7 +22,7 @@ add_executable( main.qrc ) -set(USED_QT_MODULES Core Qml Quick WebSockets Svg) +set(USED_QT_MODULES Core Qml Quick WebSockets Widgets Svg) qt5_use_modules(${APP_NAME} ${USED_QT_MODULES}) diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java index c6dd2681f1..a93e5bc979 100644 --- a/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java +++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/module/StatusModule.java @@ -1,12 +1,17 @@ package im.status.ethereum.module; import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.*; -import android.view.WindowManager; +import android.support.v4.content.FileProvider ; import android.text.TextUtils; +import android.view.WindowManager; import android.util.Log; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; @@ -16,17 +21,25 @@ import com.facebook.react.bridge.*; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.github.status_im.status_go.Statusgo; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.FileInputStream; -import java.io.OutputStream; import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.text.SimpleDateFormat; +import java.util.Stack; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.json.JSONObject; import org.json.JSONException; @@ -36,11 +49,12 @@ import javax.annotation.Nullable; class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ConnectorHandler { private static final String TAG = "StatusModule"; + private static final String logsZipFileName = "Status-debug-logs.zip"; + private static final String gethLogFileName = "geth.log"; + private static final String statusLogFileName = "Status.log"; private final static int TESTNET_NETWORK_ID = 3; - private HashMap callbacks = new HashMap<>(); - private static StatusModule module; private ServiceConnector status = null; private ExecutorService executor = null; @@ -114,10 +128,15 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params); } - private static String prepareLogsFile() { - String gethLogFileName = "geth.log"; - File pubDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - File logFile = new File(pubDirectory, gethLogFileName); + private File getLogsFile() { + final File pubDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + final File logFile = new File(pubDirectory, gethLogFileName); + + return logFile; + } + + private String prepareLogsFile(final Context context) { + final File logFile = getLogsFile(); try { logFile.setReadable(true); @@ -149,7 +168,8 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL // retrieve parameters from app config, that will be applied onto the Go-side config later on final String absDataDirPath = pathCombine(absRootDirPath, jsonConfig.getString("DataDir")); final Boolean logEnabled = jsonConfig.getBoolean("LogEnabled"); - final String gethLogFilePath = logEnabled ? prepareLogsFile() : null; + final Context context = this.getReactApplicationContext(); + final String gethLogFilePath = logEnabled ? prepareLogsFile(context) : null; jsonConfig.put("DataDir", absDataDirPath); jsonConfig.put("KeyStoreDir", absKeystoreDirPath); @@ -456,6 +476,156 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL StatusThreadPoolExecutor.getInstance().execute(r); } + private Boolean zip(File[] _files, File zipFile, Stack errorList) { + final int BUFFER = 0x8000; + + try { + BufferedInputStream origin = null; + FileOutputStream dest = new FileOutputStream(zipFile); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)); + byte data[] = new byte[BUFFER]; + + for (int i = 0; i < _files.length; i++) { + final File file = _files[i]; + if (file == null || !file.exists()) { + continue; + } + + Log.v("Compress", "Adding: " + file.getAbsolutePath()); + try { + FileInputStream fi = new FileInputStream(file); + origin = new BufferedInputStream(fi, BUFFER); + + ZipEntry entry = new ZipEntry(file.getName()); + out.putNextEntry(entry); + int count; + + while ((count = origin.read(data, 0, BUFFER)) != -1) { + out.write(data, 0, count); + } + origin.close(); + } catch (IOException e) { + Log.e(TAG, e.getMessage()); + errorList.push(e.getMessage()); + } + } + + out.close(); + + return true; + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + e.printStackTrace(); + return false; + } + } + + private void dumpAdbLogsTo(final FileOutputStream statusLogStream) throws IOException { + final String filter = "logcat -d -b main ReactNativeJS:D StatusModule:D StatusService:D StatusNativeLogs:D *:S"; + final java.lang.Process p = Runtime.getRuntime().exec(filter); + final java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream())); + final java.io.BufferedWriter out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(statusLogStream)); + String line; + while ((line = in.readLine()) != null) { + out.write(line); + out.newLine(); + } + out.close(); + in.close(); + } + + private void showErrorMessage(final String message) { + final Activity activity = getCurrentActivity(); + + new AlertDialog.Builder(activity) + .setTitle("Error") + .setMessage(message) + .setNegativeButton("Exit", new DialogInterface.OnClickListener() { + public void onClick(final DialogInterface dialog, final int id) { + dialog.dismiss(); + } + }).show(); + } + + @ReactMethod + public void sendLogs(final String dbJson) { + Log.d(TAG, "sendLogs"); + if (!checkAvailability()) { + return; + } + + final Context context = this.getReactApplicationContext(); + final File logsTempDir = new File(context.getCacheDir(), "logs"); // This path needs to be in sync with android/app/src/main/res/xml/file_provider_paths.xml + logsTempDir.mkdir(); + + final File dbFile = new File(logsTempDir, "db.json"); + try { + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(dbFile)); + outputStreamWriter.write(dbJson); + outputStreamWriter.close(); + } + catch (IOException e) { + Log.e(TAG, "File write failed: " + e.toString()); + showErrorMessage(e.getLocalizedMessage()); + } + + final File zipFile = new File(logsTempDir, logsZipFileName); + final File statusLogFile = new File(logsTempDir, statusLogFileName); + final File gethLogFile = getLogsFile(); + + try { + if (zipFile.exists() || zipFile.createNewFile()) { + final long usableSpace = zipFile.getUsableSpace(); + if (usableSpace < 20 * 1024 * 1024) { + final String message = String.format("Insufficient space available on device (%s) to write logs.\nPlease free up some space.", android.text.format.Formatter.formatShortFileSize(context, usableSpace)); + Log.e(TAG, message); + showErrorMessage(message); + return; + } + } + + dumpAdbLogsTo(new FileOutputStream(statusLogFile)); + + final Stack errorList = new Stack(); + final Boolean zipped = zip(new File[] {dbFile, gethLogFile, statusLogFile}, zipFile, errorList); + if (zipped && zipFile.exists()) { + Log.d(TAG, "Sending " + zipFile.getAbsolutePath() + " file through share intent"); + + final String providerName = context.getPackageName() + ".provider"; + final Activity activity = getCurrentActivity(); + zipFile.setReadable(true, false); + final Uri dbJsonURI = FileProvider.getUriForFile(activity, providerName, zipFile); + + Intent intentShareFile = new Intent(Intent.ACTION_SEND); + + intentShareFile.setType("application/json"); + intentShareFile.putExtra(Intent.EXTRA_STREAM, dbJsonURI); + + SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormatGmt.setTimeZone(java.util.TimeZone.getTimeZone("GMT")); + intentShareFile.putExtra(Intent.EXTRA_SUBJECT, "Status.im logs"); + intentShareFile.putExtra(Intent.EXTRA_TEXT, + String.format("Logs from %s GMT\n\nThese logs have been generated automatically by the user's request for debugging purposes.\n\n%s", + dateFormatGmt.format(new java.util.Date()), + errorList)); + + activity.startActivity(Intent.createChooser(intentShareFile, "Share Debug Logs")); + } else { + Log.d(TAG, "File " + zipFile.getAbsolutePath() + " does not exist"); + } + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + showErrorMessage(e.getLocalizedMessage()); + e.printStackTrace(); + return; + } + finally { + dbFile.delete(); + statusLogFile.delete(); + zipFile.deleteOnExit(); + } + } + @ReactMethod public void addPeer(final String enode, final Callback callback) { Log.d(TAG, "addPeer"); diff --git a/modules/react-native-status/desktop/rctstatus.cpp b/modules/react-native-status/desktop/rctstatus.cpp index 95ba0a69d3..78b11ee8f5 100644 --- a/modules/react-native-status/desktop/rctstatus.cpp +++ b/modules/react-native-status/desktop/rctstatus.cpp @@ -13,6 +13,8 @@ #include "eventdispatcher.h" #include +#include +#include #include #include #include @@ -22,6 +24,9 @@ #include "libstatus.h" +extern QString getDataStoragePath(); +extern QString getLogFilePath(); + namespace { struct RegisterQMLMetaType { RegisterQMLMetaType() { @@ -92,7 +97,7 @@ void RCTStatus::startNode(QString configString) { if (!relativeDataDirPath.startsWith("/")) relativeDataDirPath.prepend("/"); - QString rootDirPath = getRootDirPath(); + QString rootDirPath = getDataStoragePath(); QDir rootDir(rootDirPath); QString absDataDirPath = rootDirPath + relativeDataDirPath; QDir dataDir(absDataDirPath); @@ -100,10 +105,11 @@ void RCTStatus::startNode(QString configString) { dataDir.mkpath("."); } + d_gethLogFilePath = dataDir.absoluteFilePath("geth.log"); configJSON["DataDir"] = absDataDirPath; configJSON["BackupDisabledDataDir"] = absDataDirPath; configJSON["KeyStoreDir"] = rootDir.absoluteFilePath("keystore"); - configJSON["LogFile"] = dataDir.absoluteFilePath("geth.log"); + configJSON["LogFile"] = d_gethLogFilePath; const QJsonDocument& updatedJsonDoc = QJsonDocument::fromVariant(configJSON); qCInfo(RCTSTATUS) << "::startNode updated configString: " << updatedJsonDoc.toVariant().toMap(); @@ -141,6 +147,97 @@ void RCTStatus::notifyUsers(QString token, QString payloadJSON, QString tokensJS } +#include +#include +#include +#include +#include + +void showFileInGraphicalShell(QWidget *parent, const QFileInfo &fileInfo) +{ + // Mac, Windows support folder or file. +#ifdef Q_OS_WIN + const QString explorer = QStandardPaths::findExecutable(QLatin1String("explorer.exe")); + if (explorer.isEmpty()) { + QMessageBox::warning(parent, + QApplication::translate("Core::Internal", + "Launching Windows Explorer Failed"), + QApplication::translate("Core::Internal", + "Could not find explorer.exe in path to launch Windows Explorer.")); + return; + } + QStringList param; + if (!fileInfo.isDir()) + param += QLatin1String("/select,"); + param += QDir::toNativeSeparators(fileInfo.canonicalFilePath()); + QProcess::startDetached(explorer, param); +#elif defined(Q_OS_MAC) + QStringList scriptArgs; + scriptArgs << QLatin1String("-e") + << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"") + .arg(fileInfo.canonicalFilePath()); + QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs); + scriptArgs.clear(); + scriptArgs << QLatin1String("-e") + << QLatin1String("tell application \"Finder\" to activate"); + QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs); +#else + // we cannot select a file here, because no file browser really supports it... + const QString folder = fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.dir().absolutePath(); + QProcess browserProc; + browserProc.setProgram("xdg-open"); + browserProc.setArguments(QStringList(folder)); + bool success = browserProc.startDetached(); + const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError()); + success = success && error.isEmpty(); + if (!success) { + QMessageBox::warning(parent, "Launching Explorer Failed", error); + return; + } +#endif +} + +void RCTStatus::sendLogs(QString dbJSON) { + Q_D(RCTStatus); + + qCDebug(RCTSTATUS) << "::sendLogs call - logFilePath:" << getLogFilePath() + << "d_gethLogFilePath:" << d_gethLogFilePath + << "dbJSON:" << dbJSON; + + QString tmpDirName("Status.im"); + QDir tmpDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); + if (!tmpDir.mkpath(tmpDirName)) { + qCWarning(RCTSTATUS) << "::sendLogs could not create temp dir:" << tmpDirName; + return; + } + + // Check that at least 20MB are available for log generation + QStorageInfo storage(tmpDir); + if (storage.bytesAvailable() < 20 * 1024 * 1024) { + QMessageBox dlg; + dlg.warning(QApplication::activeWindow(), + "Error", QString("Insufficient storage space available in %1 for log generation. Please free up some space.").arg(storage.rootPath()), + QMessageBox::Close); + return; + } + + QFile zipFile(tmpDir.absoluteFilePath(tmpDirName + QDir::separator() + "Status-debug-logs.zip")); + QZipWriter zipWriter(&zipFile); + QFile gethLogFile(d_gethLogFilePath); + QFile logFile(getLogFilePath()); + zipWriter.addFile("db.json", dbJSON.toUtf8()); + if (gethLogFile.exists()) { + zipWriter.addFile(QFileInfo(gethLogFile).fileName(), &gethLogFile); + } + if (logFile.exists()) { + zipWriter.addFile(QFileInfo(logFile).fileName(), &logFile); + } + zipWriter.close(); + + showFileInGraphicalShell(QApplication::activeWindow(), QFileInfo(zipFile)); +} + + void RCTStatus::addPeer(QString enode, double callbackId) { Q_D(RCTStatus); qCDebug(RCTSTATUS) << "::addPeer call - callbackId:" << callbackId; @@ -177,7 +274,7 @@ void RCTStatus::verify(QString address, QString password, double callbackId) { Q_D(RCTStatus); qCInfo(RCTSTATUS) << "::verify call - callbackId:" << callbackId; QtConcurrent::run([&](QString address, QString password, double callbackId) { - QDir rootDir(getRootDirPath()); + QDir rootDir(getDataStoragePath()); QString keystorePath = rootDir.absoluteFilePath("keystore"); const char* result = VerifyAccountPassword(keystorePath.toUtf8().data(), address.toUtf8().data(), password.toUtf8().data()); logStatusGoResult("::verify VerifyAccountPassword", result); @@ -334,17 +431,3 @@ void RCTStatus::updateMailservers(QString enodes, double callbackId) { d->bridge->invokePromiseCallback(callbackId, QVariantList{result}); }, enodes, callbackId); } - -QString RCTStatus::getRootDirPath() { - QString statusDataDir = qgetenv("STATUS_DATA_DIR"); - QString rootDirPath; - if (!statusDataDir.isEmpty()) { - rootDirPath = statusDataDir; - } - else { - rootDirPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); - } - - return rootDirPath; -} - diff --git a/modules/react-native-status/desktop/rctstatus.h b/modules/react-native-status/desktop/rctstatus.h index c33b75f142..3713fe4e98 100644 --- a/modules/react-native-status/desktop/rctstatus.h +++ b/modules/react-native-status/desktop/rctstatus.h @@ -39,6 +39,7 @@ public: Q_INVOKABLE void stopNode(); Q_INVOKABLE void createAccount(QString password, double callbackId); Q_INVOKABLE void notifyUsers(QString token, QString payloadJSON, QString tokensJSON, double callbackId); + Q_INVOKABLE void sendLogs(QString dbJSON); Q_INVOKABLE void addPeer(QString enode, double callbackId); Q_INVOKABLE void recoverAccount(QString passphrase, QString password, double callbackId); Q_INVOKABLE void login(QString address, QString password, double callbackId); @@ -75,9 +76,9 @@ private Q_SLOTS: private: void logStatusGoResult(const char* methodName, const char* result); - QString getRootDirPath(); QScopedPointer d_ptr; + QString d_gethLogFilePath; }; #endif // RCTSTATUS_H diff --git a/modules/react-native-status/ios/RCTStatus/RCTStatus.m b/modules/react-native-status/ios/RCTStatus/RCTStatus.m index 7dc3e6d06b..a5c6d308ed 100644 --- a/modules/react-native-status/ios/RCTStatus/RCTStatus.m +++ b/modules/react-native-status/ios/RCTStatus/RCTStatus.m @@ -196,6 +196,16 @@ RCT_EXPORT_METHOD(notifyUsers:(NSString *)message #endif } +//////////////////////////////////////////////////////////////////// +#pragma mark - SendLogs method +//////////////////////////////////////////////////////////////////// sendLogs +RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson) { + // TODO: Implement SendLogs for iOS +#if DEBUG + NSLog(@"SendLogs() method called, not implemented"); +#endif +} + //////////////////////////////////////////////////////////////////// addPeer RCT_EXPORT_METHOD(addPeer:(NSString *)enode callback:(RCTResponseSenderBlock)callback) { diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 46c831baf9..575004143b 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -24,6 +24,7 @@ [status-im.hardwallet.core :as hardwallet] [status-im.i18n :as i18n] [status-im.init.core :as init] + [status-im.utils.logging.core :as logging] [status-im.log-level.core :as log-level] [status-im.mailserver.core :as mailserver] [status-im.network.core :as network] @@ -543,6 +544,13 @@ (fn [cofx [_ url data modal?]] (extensions.registry/install cofx url data modal?))) +;; logging module + +(handlers/register-handler-fx + :logging.ui/send-logs-pressed + (fn [cofx _] + (logging/send-logs cofx))) + ;; log-level module (handlers/register-handler-fx diff --git a/src/status_im/log_level/core.cljs b/src/status_im/log_level/core.cljs index 1d68e1829f..52df58216b 100644 --- a/src/status_im/log_level/core.cljs +++ b/src/status_im/log_level/core.cljs @@ -2,17 +2,20 @@ (:require [re-frame.core :as re-frame] [status-im.accounts.update.core :as accounts.update] [status-im.react-native.js-dependencies :as rn-dependencies] + [status-im.node.core :as node] [status-im.i18n :as i18n] [status-im.utils.fx :as fx])) (fx/defn save-log-level [{:keys [db now] :as cofx} log-level] - (let [settings (get-in db [:account/account :settings])] + (let [settings (get-in db [:account/account :settings]) + new-settings (if log-level + (assoc settings :log-level log-level) + (dissoc settings :log-level))] (accounts.update/update-settings cofx - (if log-level - (assoc settings :log-level log-level) - (dissoc settings :log-level)) - {:success-event [:accounts.update.callback/save-settings-success]}))) + new-settings + (when (not= (node/get-log-level settings) (node/get-log-level new-settings)) + {:success-event [:accounts.update.callback/save-settings-success]})))) (fx/defn show-change-log-level-confirmation [{:keys [db]} {:keys [name value] :as log-level}] diff --git a/src/status_im/native_module/core.cljs b/src/status_im/native_module/core.cljs index 180eac5876..8839b6ab64 100644 --- a/src/status_im/native_module/core.cljs +++ b/src/status_im/native_module/core.cljs @@ -54,6 +54,9 @@ (defn notify-users [m callback] (native-module/notify-users m callback)) +(defn send-logs [dbJson] + (native-module/send-logs dbJson)) + (defn add-peer [enode callback] (native-module/add-peer enode callback)) diff --git a/src/status_im/native_module/impl/module.cljs b/src/status_im/native_module/impl/module.cljs index d10314966d..c81610d59a 100644 --- a/src/status_im/native_module/impl/module.cljs +++ b/src/status_im/native_module/impl/module.cljs @@ -87,6 +87,10 @@ (when status (call-module #(.notifyUsers status message payload tokens on-result)))) +(defn send-logs [dbJson] + (when status + (call-module #(.sendLogs status dbJson)))) + (defn add-peer [enode on-result] (when (and @node-started status) (call-module #(.addPeer status enode on-result)))) diff --git a/src/status_im/node/core.cljs b/src/status_im/node/core.cljs index 2318d8ffff..7ecbeb545e 100644 --- a/src/status_im/node/core.cljs +++ b/src/status_im/node/core.cljs @@ -72,6 +72,12 @@ [limit nodes] (take limit (shuffle nodes))) +(defn get-log-level + [account-settings] + (or (:log-level account-settings) + (if utils.platform/desktop? "" + config/log-level-status-go))) + (defn- get-account-node-config [db address] (let [accounts (get db :accounts/accounts) current-fleet-key (fleet/current-fleet db address) @@ -85,9 +91,7 @@ :installation-id (get db :accounts/new-installation-id)} (get accounts address)) use-custom-bootnodes (get-in settings [:bootnodes network]) - log-level (or (:log-level settings) - (if utils.platform/desktop? "" - config/log-level-status-go))] + log-level (get-log-level settings)] (cond-> (get-in networks [network :config]) :always (get-base-node-config) diff --git a/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs b/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs index 31fcac0137..2296737090 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/profile/styles.cljs @@ -183,6 +183,19 @@ :margin-bottom 8 :background-color colors/gray-light}) +(defn adv-settings-row-text [color] + {:color color + :font-size 14}) + +(defn adv-settings-row [active?] + {:justify-content :space-between + :flex-direction :row + :padding-horizontal 24 + :height 56 + :align-self :stretch + :align-items :center + :background-color (if active? colors/gray-lighter colors/white)}) + (def pair-button {:margin-left 32}) diff --git a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs index 6171e519b3..69bad0bec1 100644 --- a/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs +++ b/src/status_im/ui/screens/desktop/main/tabs/profile/views.cljs @@ -7,6 +7,8 @@ [status-im.utils.utils :as utils] [status-im.ui.components.colors :as colors] [status-im.i18n :as i18n] + [status-im.utils.logging.core :as logging] + [status-im.utils.platform :as platform] [status-im.ui.components.icons.vector-icons :as vector-icons] [taoensso.timbre :as log] [status-im.utils.gfycat.core :as gfy] @@ -135,15 +137,21 @@ (views/defview logging-display [] (views/letsubs [logging-enabled [:settings/logging-enabled]] - [react/view {:style (styles/profile-row false)} - [react/text {:style (assoc (styles/profile-row-text colors/black) - :font-size 14)} (i18n/label :t/logging-enabled)] - [react/switch {:on-tint-color colors/blue - :value logging-enabled - :on-value-change #(re-frame/dispatch [:log-level.ui/logging-enabled (not logging-enabled)])}]])) + [react/view + [react/view {:style (styles/adv-settings-row false)} + [react/text {:style (assoc (styles/adv-settings-row-text colors/black) + :font-size 14)} (i18n/label :t/logging-enabled)] + [react/switch {:on-tint-color colors/blue + :value logging-enabled + :on-value-change #(re-frame/dispatch [:log-level.ui/logging-enabled (not logging-enabled)])}]] + [react/view {:style (styles/adv-settings-row false)} + [react/touchable-highlight {:on-press #(re-frame/dispatch [:logging.ui/send-logs-pressed])} + [react/text {:style (styles/adv-settings-row-text colors/red)} + (i18n/label :t/send-logs)]]]])) (views/defview advanced-settings [] - (views/letsubs [current-mailserver-id [:mailserver/current-id] + (views/letsubs [installations [:pairing/installations] + current-mailserver-id [:mailserver/current-id] mailservers [:mailserver/fleet-mailservers] mailserver-state [:mailserver/state] node-status [:node-status] @@ -197,21 +205,21 @@ [vector-icons/icon :icons/qr {:style {:tint-color colors/blue}}]]]]) (defn help-item [help-open?] - [react/touchable-highlight {:style (styles/profile-row help-open?) + [react/touchable-highlight {:style (styles/adv-settings-row help-open?) :on-press #(re-frame/dispatch [:navigate-to (if help-open? :home :help-center)])} [react/view {:style styles/adv-settings} - [react/text {:style (styles/profile-row-text colors/black) + [react/text {:style (styles/adv-settings-row-text colors/black) :font (if help-open? :medium :default)} (i18n/label :t/help-center)] [vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]]) (defn advanced-settings-item [adv-settings-open?] - [react/touchable-highlight {:style (styles/profile-row adv-settings-open?) + [react/touchable-highlight {:style (styles/adv-settings-row adv-settings-open?) :on-press #(do (re-frame/dispatch [:navigate-to (if adv-settings-open? :home :advanced-settings)]) (re-frame/dispatch [:load-debug-metrics]))} [react/view {:style styles/adv-settings} - [react/text {:style (styles/profile-row-text colors/black) + [react/text {:style (styles/adv-settings-row-text colors/black) :font (if adv-settings-open? :medium :default)} (i18n/label :t/advanced-settings)] [vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]]) diff --git a/src/status_im/ui/screens/profile/user/styles.cljs b/src/status_im/ui/screens/profile/user/styles.cljs index 5b47fc61d8..102d5d39fe 100644 --- a/src/status_im/ui/screens/profile/user/styles.cljs +++ b/src/status_im/ui/screens/profile/user/styles.cljs @@ -38,6 +38,14 @@ (defstyle my-profile-settings-logout {:min-width "50%"}) +(defstyle my-profile-settings-send-logs-wrapper + {:flex-direction :row + :justify-content :space-between + :align-items :center}) + +(defstyle my-profile-settings-send-logs + {:min-width "50%"}) + (def advanced-button {:margin-top 16 :margin-bottom 12}) diff --git a/src/status_im/ui/screens/profile/user/views.cljs b/src/status_im/ui/screens/profile/user/views.cljs index 43008f9af8..a52735ef5d 100644 --- a/src/status_im/ui/screens/profile/user/views.cljs +++ b/src/status_im/ui/screens/profile/user/views.cljs @@ -170,6 +170,13 @@ {:label-kw :t/log-level :action-fn #(re-frame/dispatch [:navigate-to :log-level-settings]) :accessibility-label :log-level-settings-button}] + (when (and dev-mode? (not platform/ios?)) + [react/view styles/my-profile-settings-send-logs-wrapper + [react/view styles/my-profile-settings-send-logs + [profile.components/settings-item {:label-kw :t/send-logs + :destructive? true + :hide-arrow? true + :action-fn #(re-frame/dispatch [:logging.ui/send-logs-pressed])}]]]) [profile.components/settings-item-separator] [profile.components/settings-item {:label-kw :t/fleet @@ -250,7 +257,7 @@ {:contact current-account :source :public-key :value public-key}]) - :style styles/share-contact-code-button + :style styles/share-contact-code-button :accessibility-label :share-my-profile-button} (i18n/label :t/share-my-profile)]] [react/view styles/my-profile-info-container diff --git a/src/status_im/utils/logging/core.cljs b/src/status_im/utils/logging/core.cljs new file mode 100644 index 0000000000..745b835b9f --- /dev/null +++ b/src/status_im/utils/logging/core.cljs @@ -0,0 +1,36 @@ +(ns status-im.utils.logging.core + (:require [re-frame.core :as re-frame] + [status-im.native-module.core :as status] + [status-im.utils.fx :as fx] + [status-im.utils.types :as types])) + +(fx/defn send-logs + [{:keys [db] :as cofx}] + ;; TODO: Add message explaining db export + (let [db-json (types/clj->json (select-keys db [:app-state + :current-chat-id + :discover-current-dapp + :discover-search-tags + :discoveries + :initial-props + :keyboard-height + :keyboard-max-height + :navigation-stack + :network + :network-status + :peers-count + :peers-summary + :status-module-initialized? + :sync-state + :tab-bar-visible? + :view-id + :chat/cooldown-enabled? + :chat/cooldowns + :chat/last-outgoing-message-sent-at + :chat/spam-messages-frequency + :chats/loading? + :desktop/desktop + :dimensions/window + :my-profile/editing? + :node/status]))] + (status/send-logs db-json))) diff --git a/translations/en.json b/translations/en.json index ef17b95eef..7784a63937 100644 --- a/translations/en.json +++ b/translations/en.json @@ -261,6 +261,7 @@ "logout": "Log out", "status-not-sent-tap": "Not confirmed. Tap for options", "status-not-sent-click": "Not confirmed. Click for options", + "send-logs": "Send logs", "edit-network-config": "Edit network config", "clear-history-confirmation": "Clear history?", "connect": "Connect",