mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 11:47:54 +01:00
add debug logging
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user