From 0b6aa7a76284325a19c204573ecd4e15120a9a2c Mon Sep 17 00:00:00 2001 From: ammarahm-ed Date: Tue, 19 Jul 2022 13:36:45 +0500 Subject: [PATCH] add debug logging --- apps/mobile/src/components/launcher/index.js | 4 +- apps/mobile/src/navigation/index.js | 14 +- apps/mobile/src/screens/editor/index.tsx | 2 +- apps/mobile/src/screens/settings/debug.tsx | 318 ++++++++++++------ .../src/screens/settings/settingsdata.tsx | 14 +- apps/mobile/src/utils/database/Storage.js | 118 ++++--- apps/mobile/src/utils/database/index.js | 9 +- 7 files changed, 307 insertions(+), 172 deletions(-) diff --git a/apps/mobile/src/components/launcher/index.js b/apps/mobile/src/components/launcher/index.js index 035724597..e519970ae 100644 --- a/apps/mobile/src/components/launcher/index.js +++ b/apps/mobile/src/components/launcher/index.js @@ -17,7 +17,7 @@ import { useNoteStore } from '../../stores/use-notes-store'; import { useSettingStore } from '../../stores/use-setting-store'; import { useThemeStore } from '../../stores/use-theme-store'; import { useUserStore } from '../../stores/use-user-store'; -import { db, loadDatabase } from '../../utils/database'; +import { DatabaseLogger, db, loadDatabase } from '../../utils/database'; import { MMKV } from '../../utils/database/mmkv'; import { eCloseProgressDialog, eOpenAnnouncementDialog } from '../../utils/events'; import { tabBarRef } from '../../utils/global-refs'; @@ -85,6 +85,7 @@ const Launcher = React.memo( await loadDatabase(); db?.eventManager?.subscribe(EVENTS.databaseMigrating, onDatabaseMigratingProgess); db?.eventManager?.subscribe(EVENTS.databaseMigrated, onMigrationCompleted); + DatabaseLogger.info('Initializing database'); await db.init(); dbInitCompleted.current = true; } @@ -139,6 +140,7 @@ const Launcher = React.memo( }; const checkAppUpdateAvailable = async () => { + if (__DEV__) return; try { const version = await checkVersion(); if (!version.needsUpdate) return false; diff --git a/apps/mobile/src/navigation/index.js b/apps/mobile/src/navigation/index.js index a77ee0619..79ec2332c 100644 --- a/apps/mobile/src/navigation/index.js +++ b/apps/mobile/src/navigation/index.js @@ -6,18 +6,28 @@ import { Header } from '../components/header'; import { Toast } from '../components/toast'; import { useNoteStore } from '../stores/use-notes-store'; import { useSettingStore } from '../stores/use-setting-store'; +import { useThemeStore } from '../stores/use-theme-store'; import { TabsHolder } from './tabs-holder'; export const ApplicationHolder = React.memo( () => { const loading = useNoteStore(state => state.loading); const introCompleted = useSettingStore(state => state.settings.introCompleted); + const colors = useThemeStore(state => state.colors); + return ( <> {loading && introCompleted ? ( <> -
- + +
+ + ) : ( <> diff --git a/apps/mobile/src/screens/editor/index.tsx b/apps/mobile/src/screens/editor/index.tsx index 4ccb8091f..aaccdb739 100755 --- a/apps/mobile/src/screens/editor/index.tsx +++ b/apps/mobile/src/screens/editor/index.tsx @@ -139,7 +139,7 @@ const Editor = React.memo( allowUniversalAccessFromFileURLs={true} originWhitelist={['*']} source={{ - uri: __DEV__ ? 'http://localhost:3000/index.html' : EDITOR_URI + uri: __DEV__ ? 'http://192.168.10.6:3000/index.html' : EDITOR_URI }} style={style} autoManageStatusBarEnabled={false} diff --git a/apps/mobile/src/screens/settings/debug.tsx b/apps/mobile/src/screens/settings/debug.tsx index 6f096804d..4c0812cf2 100644 --- a/apps/mobile/src/screens/settings/debug.tsx +++ b/apps/mobile/src/screens/settings/debug.tsx @@ -1,5 +1,5 @@ import Clipboard from '@react-native-clipboard/clipboard'; -import React from 'react'; +import React, { useState } from 'react'; import { FlatList, Platform, TouchableOpacity, View } from 'react-native'; import * as ScopedStorage from 'react-native-scoped-storage'; import RNFetchBlob from 'rn-fetch-blob'; @@ -12,28 +12,71 @@ import { useThemeStore } from '../../stores/use-theme-store'; import { hexToRGBA } from '../../utils/color-scheme/utils'; import Storage from '../../utils/database/storage'; import { sanitizeFilename } from '../../utils/sanitizer'; +import { DatabaseLogger } from '../../utils/database/index'; +import { useEffect } from 'react'; +import useTimer from '../../utils/hooks/use-timer'; +import { LogLevel } from '@streetwriters/notesnook-core/logger'; + +function getLevelString(level: number) { + switch (level) { + case LogLevel.Debug: + return 'DEBUG'; + case LogLevel.Info: + return 'INFO'; + case LogLevel.Log: + return 'LOG'; + case LogLevel.Error: + return 'ERROR'; + case LogLevel.Warn: + return 'WARN'; + case LogLevel.Fatal: + return 'FATAL'; + } +} export default function DebugLogs() { const colors = useThemeStore(state => state.colors); + const { seconds, start } = useTimer('debug_logs_timer'); + const [logs, setLogs] = useState< + { + key: string; + logs: any[]; + }[] + >([]); + const [currentLog, setCurrentLog] = useState<{ + key: string; + logs: any[]; + }>(); + + useEffect(() => { + (async () => { + if (seconds === 0) { + start(5, 'debug_logs_timer'); + let logs = await DatabaseLogger.get(); + if (logs.length > 0 && !currentLog) { + setCurrentLog(logs[0]); + } else { + setCurrentLog(logs[logs.findIndex(l => l.key === currentLog?.key)]); + } + setLogs(logs); + } + })(); + }, [seconds]); const renderItem = ({ item, index }) => { const background = - item.type === 'error' + item.level === LogLevel.Error || item.level === LogLevel.Fatal ? hexToRGBA(colors.red, 0.2) - : item.type === 'success' - ? hexToRGBA(colors.green, 0.2) - : item.type === 'warn' + : item.level === LogLevel.Warn ? hexToRGBA(colors.orange, 0.2) : 'transparent'; const color = - item.type === 'error' + item.level === LogLevel.Error || item.level === LogLevel.Fatal ? colors.red - : item.type === 'success' - ? colors.green - : item.type === 'warn' + : item.level === LogLevel.Warn ? colors.orange - : 'transparent'; + : colors.pri; return ( - {item.error} + [{getLevelString(item.level)}] {item.message} ); }; return ( - + - - - { - Clipboard.setString('All logs copied'); - ToastEvent.show({ - heading: 'Debug log copied!', - context: 'global', - type: 'success' - }); - }} - size={20} - name="clipboard" - color={colors.gray} - /> - { - try { - let path = null; - const fileName = sanitizeFilename(`notesnook_logs_${Date.now()}`); - if (Platform.OS === 'android') { - let file = await ScopedStorage.createDocument( - fileName, - 'text/plain', - 'save data', - 'utf8' - ); - if (!file) return; - path = file.uri; - } else { - path = await Storage.checkAndCreateDir('/'); - await RNFetchBlob.fs.writeFile(path + fileName, 'save data', 'utf8'); - path = path + fileName; - } - - if (path) { - ToastEvent.show({ - heading: 'Debug logs downloaded', - context: 'global', - type: 'success' - }); - } - } catch (e) { - console.log(e); - } - }} - size={20} - name="download" - color={colors.gray} - /> - - { - presentDialog({ - title: 'Clear all logs', - paragraph: 'Are you sure you want to delete alll logs?', - negativeText: 'Cancel', - positiveText: 'Clear', - positivePress: () => {} - }); - }} - size={20} - name="delete" - color={colors.gray} - /> - - + + {currentLog.key} + + { + let index = logs.findIndex(l => l.key === currentLog.key); + if (index === 0) return; + setCurrentLog(logs[index - 1]); + }} + size={20} + name="chevron-left" + color={colors.icon} + /> + + { + let index = logs.findIndex(l => l.key === currentLog.key); + if (index === logs.length - 1) return; + setCurrentLog(logs[index + 1]); + }} + size={20} + name="chevron-right" + color={colors.icon} + /> + + + + { + Clipboard.setString('All logs copied'); + ToastEvent.show({ + heading: 'Debug log copied!', + context: 'global', + type: 'success' + }); + }} + size={20} + customStyle={{ + width: 30, + height: 30, + marginRight: 5 + }} + name="content-copy" + color={colors.gray} + /> + { + try { + let path = null; + const fileName = sanitizeFilename(`notesnook_logs_${Date.now()}`); + const data = currentLog?.logs + .map( + log => + `${new Date(log.timestamp).toUTCString()}: [${getLevelString( + log.level + )}] ${log.message || log.error?.message}${ + log.error?.stack ? '\n' + log.error?.stack : '' + }` + ) + .join(`\n`); + if (!data) return; + if (Platform.OS === 'android') { + let file = await ScopedStorage.createDocument( + fileName + '.txt', + 'text/plain', + data, + 'utf8' + ); + if (!file) return; + path = file.uri; + } else { + path = await Storage.checkAndCreateDir('/'); + await RNFetchBlob.fs.writeFile(path + fileName + '.txt', data, 'utf8'); + path = path + fileName; + } + + if (path) { + ToastEvent.show({ + heading: 'Debug logs downloaded', + context: 'global', + type: 'success' + }); + } + } catch (e) { + console.log(e); + } + }} + customStyle={{ + width: 30, + height: 30, + marginRight: 5 + }} + size={20} + name="download" + color={colors.gray} + /> + + } - ]} - renderItem={renderItem} - /> + style={{ + flex: 1, + width: '100%' + }} + stickyHeaderIndices={[0]} + ListFooterComponent={ + + } + data={currentLog.logs} + renderItem={renderItem} + /> + )} ); } diff --git a/apps/mobile/src/screens/settings/settingsdata.tsx b/apps/mobile/src/screens/settings/settingsdata.tsx index 8472e3a6e..34fab3af7 100644 --- a/apps/mobile/src/screens/settings/settingsdata.tsx +++ b/apps/mobile/src/screens/settings/settingsdata.tsx @@ -749,14 +749,14 @@ export const settingsGroups: SettingSection[] = [ name: 'Debug mode', description: 'Show debug options on items', property: 'devMode' + }, + { + id: 'debug-logs', + type: 'screen', + name: 'Debug logs', + description: 'View debug logs from the app', + component: 'debug-logs' } - // { - // id: 'debug-logs', - // type: 'screen', - // name: 'Debug logs', - // description: 'View debug logs from the app', - // component: 'debug-logs' - // } ] }, { diff --git a/apps/mobile/src/utils/database/Storage.js b/apps/mobile/src/utils/database/Storage.js index 8e1054017..a73d5b1a6 100644 --- a/apps/mobile/src/utils/database/Storage.js +++ b/apps/mobile/src/utils/database/Storage.js @@ -12,53 +12,71 @@ import { } from './encryption'; import { MMKV } from './mmkv'; -async function read(key) { - if (!key) return null; - let data = MMKV.getString(key); - if (!data) return null; - try { - let parse = JSON.parse(data); - return parse; - } catch (e) { - return data; +export class KV { + storage = null; + constructor(storage) { + this.storage = storage; + } + async read(key) { + if (!key) return null; + let data = this.storage.getString(key); + if (!data) return null; + try { + let parse = JSON.parse(data); + return parse; + } catch (e) { + return data; + } + } + + async write(key, data) { + this.storage.setString(key, typeof data === 'string' ? data : JSON.stringify(data)); + return true; + } + + async readMulti(keys) { + if (keys.length <= 0) { + return []; + } else { + let data = await this.storage.getMultipleItemsAsync(keys.slice()); + + return data.map(([key, value]) => { + let obj; + try { + obj = JSON.parse(value); + } catch (e) { + obj = value; + } + + return [key, obj]; + }); + } + } + + async remove(key) { + return await this.storage.removeItem(key); + } + + async clear() { + return await this.storage.clearStore(); + } + + async getAllKeys() { + let keys = (await this.storage.indexer.getKeys()) || []; + keys = keys.filter( + k => + k !== 'stringIndex' && + k !== 'boolIndex' && + k !== 'mapIndex' && + k !== 'arrayIndex' && + k !== 'numberIndex' && + k !== this.storage.instanceID + ); + return keys; } } -async function write(key, data) { - MMKV.setString(key, typeof data === 'string' ? data : JSON.stringify(data)); - return true; -} - -async function readMulti(keys) { - if (keys.length <= 0) { - return []; - } else { - let data = await MMKV.getMultipleItemsAsync(keys.slice()); - - return data.map(([key, value]) => { - let obj; - try { - obj = JSON.parse(value); - } catch (e) { - obj = value; - } - - return [key, obj]; - }); - } -} - -async function remove(key) { - return await MMKV.removeItem(key); -} - -async function clear() { - return await MMKV.clearStore(); -} - -async function getAllKeys() { - return await MMKV.indexer.getKeys(); -} +const DefaultStorage = new KV(MMKV); async function requestPermission() { if (Platform.OS === 'ios') return true; @@ -84,14 +102,14 @@ async function checkAndCreateDir(path) { } export default { - read, - write, - readMulti, - remove, - clear, + read: key => DefaultStorage.read(key), + write: (key, value) => DefaultStorage.write(key, value), + readMulti: keys => DefaultStorage.readMulti(keys), + remove: key => DefaultStorage.remove(key), + clear: () => DefaultStorage.clear(), + getAllKeys: () => DefaultStorage.getAllKeys(), encrypt, decrypt, - getAllKeys, getRandomBytes, checkAndCreateDir, requestPermission, diff --git a/apps/mobile/src/utils/database/index.js b/apps/mobile/src/utils/database/index.js index 902290c41..ae5b2d127 100644 --- a/apps/mobile/src/utils/database/index.js +++ b/apps/mobile/src/utils/database/index.js @@ -1,9 +1,14 @@ +import Database from '@streetwriters/notesnook-core/api/index'; +import { initalize, logger as dbLogger } from '@streetwriters/notesnook-core/logger'; import { Platform } from 'react-native'; +import { MMKVLoader } from 'react-native-mmkv-storage'; import filesystem from '../filesystem'; import EventSource from '../sse/even-source-ios'; import AndroidEventSource from '../sse/event-source'; -import Storage from './storage'; -import Database from '@streetwriters/notesnook-core/api/index'; +import Storage, { KV } from './storage'; +const LoggerStorage = new MMKVLoader().withInstanceID('notesnook_logs').initialize(); +initalize(new KV(LoggerStorage)); +export const DatabaseLogger = dbLogger; /** * @type {import("@streetwriters/notesnook-core/api/index").default}