add debug logging

This commit is contained in:
ammarahm-ed
2022-07-19 13:36:45 +05:00
parent eb952dec67
commit 0b6aa7a762
7 changed files with 307 additions and 172 deletions

View File

@@ -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;

View File

@@ -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 ? (
<>
<Header />
<DelayLayout animated={false} wait={loading} />
<SafeAreaView
style={{
flex: 1,
backgroundColor: colors.bg
}}
>
<Header />
<DelayLayout animated={false} wait={loading} />
</SafeAreaView>
</>
) : (
<>

View File

@@ -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}

View File

@@ -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 (
<TouchableOpacity
@@ -50,24 +93,31 @@ export default function DebugLogs() {
paddingHorizontal: 12,
paddingVertical: 12,
backgroundColor: background,
flexShrink: 1
flexShrink: 1,
borderBottomWidth: 1,
borderBottomColor: colors.nav
}}
>
<Paragraph
style={{
flexShrink: 1,
flexWrap: 'wrap',
fontFamily: 'monospace'
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace'
}}
size={12}
color={color}
>
{item.error}
[{getLevelString(item.level)}] {item.message}
</Paragraph>
</TouchableOpacity>
);
};
return (
<View>
<View
style={{
flex: 1
}}
>
<View
style={{
padding: 12
@@ -77,104 +127,154 @@ export default function DebugLogs() {
text="If you are facing an issue in the app. You can send us the logs from here to help us investigate the issue."
type="information"
/>
<View
style={{
flexDirection: 'row',
width: '100%',
alignItems: 'flex-end',
justifyContent: 'flex-end',
marginTop: 12
}}
>
<IconButton
onPress={() => {
Clipboard.setString('All logs copied');
ToastEvent.show({
heading: 'Debug log copied!',
context: 'global',
type: 'success'
});
}}
size={20}
name="clipboard"
color={colors.gray}
/>
<IconButton
onPress={async () => {
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}
/>
<IconButton
onPress={() => {
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}
/>
</View>
</View>
<FlatList
inverted
data={[
{
type: 'error',
error: 'Error: file not found and jt aksdj adksj kdajkd jsakdjs kjda kdjsak djaks dkas.'
},
{
type: 'warn',
error: 'Warning: file found with invalid name'
},
{
type: 'success',
error: 'Success: file found'
},
{
type: 'error',
error: 'Error: file not found'
{currentLog && (
<FlatList
ListHeaderComponent={
<View
style={{
paddingHorizontal: 12,
marginBottom: 10,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.bg,
justifyContent: 'space-between'
}}
>
<View
style={{
flexDirection: 'row',
alignItems: 'center'
}}
>
<Paragraph>{currentLog.key}</Paragraph>
<IconButton
customStyle={{
width: 30,
height: 30,
marginHorizontal: 5
}}
onPress={() => {
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}
/>
<IconButton
customStyle={{
width: 30,
height: 30
}}
onPress={() => {
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}
/>
</View>
<View
style={{
flexDirection: 'row'
}}
>
<IconButton
onPress={() => {
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}
/>
<IconButton
onPress={async () => {
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}
/>
</View>
</View>
}
]}
renderItem={renderItem}
/>
style={{
flex: 1,
width: '100%'
}}
stickyHeaderIndices={[0]}
ListFooterComponent={
<View
style={{
height: 200
}}
/>
}
data={currentLog.logs}
renderItem={renderItem}
/>
)}
</View>
);
}

View File

@@ -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'
// }
]
},
{

View File

@@ -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,

View File

@@ -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}