diff --git a/ReactNativeClient/lib/components/screen-header.js b/ReactNativeClient/lib/components/screen-header.js index 5fdd97e51..883529db0 100644 --- a/ReactNativeClient/lib/components/screen-header.js +++ b/ReactNativeClient/lib/components/screen-header.js @@ -41,6 +41,13 @@ class ScreenHeaderComponent extends Component { } } + log_press() { + this.props.dispatch({ + type: 'Navigation/NAVIGATE', + routeName: 'Log', + }); + } + render() { let key = 0; let menuOptionComponents = []; @@ -56,10 +63,10 @@ class ScreenHeaderComponent extends Component { menuOptionComponents.push(); } - // menuOptionComponents.push( - // - // {_('Configuration')} - // ); + menuOptionComponents.push( + this.log_press()} key={'menuOption_' + key++}> + {_('Log')} + ); let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName); diff --git a/ReactNativeClient/lib/components/screens/log.js b/ReactNativeClient/lib/components/screens/log.js new file mode 100644 index 000000000..1ee302010 --- /dev/null +++ b/ReactNativeClient/lib/components/screens/log.js @@ -0,0 +1,62 @@ +import React, { Component } from 'react'; +import { ListView, View, Text } from 'react-native'; +import { connect } from 'react-redux' +import { Log } from 'lib/log.js' +import { reg } from 'lib/registry.js' +import { ScreenHeader } from 'lib/components/screen-header.js'; +import { time } from 'lib/time-utils' + +class LogScreenComponent extends React.Component { + + static navigationOptions(options) { + return { header: null }; + } + + constructor() { + super(); + const ds = new ListView.DataSource({ + rowHasChanged: (r1, r2) => { return r1 !== r2; } + }); + this.state = { + dataSource: ds, + }; + } + + componentWillMount() { + reg.logger().lastEntries(1000).then((entries) => { + const newDataSource = this.state.dataSource.cloneWithRows(entries); + this.setState({ dataSource: newDataSource }); + }); + } + + render() { + let renderRow = (item) => { + return ( + + {time.unixMsToIsoSec(item.timestamp) + ': ' + item.message} + + ); + } + + // `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39 + return ( + + + + + ); + } + +} + +const LogScreen = connect( + (state) => { + return {}; + } +)(LogScreenComponent) + +export { LogScreen }; \ No newline at end of file diff --git a/ReactNativeClient/lib/logger.js b/ReactNativeClient/lib/logger.js index 785e1671d..f3af0cacb 100644 --- a/ReactNativeClient/lib/logger.js +++ b/ReactNativeClient/lib/logger.js @@ -52,12 +52,20 @@ class Logger { output = object; } - return output; + return output; + } + + objectsToString(...object) { + let output = []; + for (let i = 0; i < object.length; i++) { + output.push('"' + this.objectToString(object[i]) + '"'); + } + return output.join(', '); } static databaseCreateTableSql() { let output = ` - CREATE TABLE logs ( + CREATE TABLE IF NOT EXISTS logs ( id INTEGER PRIMARY KEY, source TEXT, level INT NOT NULL, @@ -68,14 +76,25 @@ class Logger { return output.split("\n").join(' '); } - log(level, object) { + // Only for database at the moment + async lastEntries(limit = 100) { + for (let i = 0; i < this.targets_.length; i++) { + const target = this.targets_[i]; + if (target.type == 'database') { + return await target.database.selectAll('SELECT * FROM logs ORDER BY timestamp DESC LIMIT ' + limit); + } + } + return []; + } + + log(level, ...object) { if (this.level() < level || !this.targets_.length) return; let levelString = ''; - if (this.level() == Logger.LEVEL_INFO) levelString = '[info] '; - if (this.level() == Logger.LEVEL_WARN) levelString = '[warn] '; - if (this.level() == Logger.LEVEL_ERROR) levelString = '[error] '; - let line = moment().format('YYYY-MM-DD HH:mm:ss') + ': ' + levelString; + let line = moment().format('YYYY-MM-DD HH:mm:ss') + ': '; + + if (level == Logger.LEVEL_WARN) levelString += '[warn] '; + if (level == Logger.LEVEL_ERROR) levelString += '[error] '; for (let i = 0; i < this.targets_.length; i++) { let target = this.targets_[i]; @@ -84,27 +103,23 @@ class Logger { if (level = Logger.LEVEL_ERROR) fn = 'error'; if (level = Logger.LEVEL_WARN) fn = 'warn'; if (level = Logger.LEVEL_INFO) fn = 'info'; - if (typeof object === 'object') { - console[fn](line, object); - } else { - console[fn](line + object); - } + console[fn](line + this.objectsToString(...object)); } else if (target.type == 'file') { - let serializedObject = this.objectToString(object); + let serializedObject = this.objectsToString(...object); Logger.fsDriver().appendFileSync(target.path, line + serializedObject + "\n"); } else if (target.type == 'vorpal') { - target.vorpal.log(object); + target.vorpal.log(...object); } else if (target.type == 'database') { - let msg = this.objectToString(object); + let msg = this.objectsToString(...object); target.database.exec('INSERT INTO logs (`source`, `level`, `message`, `timestamp`) VALUES (?, ?, ?, ?)', [target.source, level, msg, time.unixMs()]); } } } - error(object) { return this.log(Logger.LEVEL_ERROR, object); } - warn(object) { return this.log(Logger.LEVEL_WARN, object); } - info(object) { return this.log(Logger.LEVEL_INFO, object); } - debug(object) { return this.log(Logger.LEVEL_DEBUG, object); } + error(...object) { return this.log(Logger.LEVEL_ERROR, ...object); } + warn(...object) { return this.log(Logger.LEVEL_WARN, ...object); } + info(...object) { return this.log(Logger.LEVEL_INFO, ...object); } + debug(...object) { return this.log(Logger.LEVEL_DEBUG, ...object); } static levelStringToId(s) { if (s == 'none') return Logger.LEVEL_NONE; diff --git a/ReactNativeClient/lib/react-logger.js b/ReactNativeClient/lib/react-logger.js new file mode 100644 index 000000000..d868bb2bc --- /dev/null +++ b/ReactNativeClient/lib/react-logger.js @@ -0,0 +1,9 @@ +import { Logger } from 'lib/logger.js'; + +class ReactLogger extends Logger { + + + +} + +export { ReactLogger } \ No newline at end of file diff --git a/ReactNativeClient/lib/time-utils.js b/ReactNativeClient/lib/time-utils.js index 837ef2bbc..54d55f033 100644 --- a/ReactNativeClient/lib/time-utils.js +++ b/ReactNativeClient/lib/time-utils.js @@ -18,6 +18,10 @@ let time = { return moment.unix(ms / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z'; }, + unixMsToIsoSec(ms) { + return moment.unix(ms / 1000).utc().format('YYYY-MM-DDTHH:mm:ss') + 'Z'; + }, + msleep(ms) { return new Promise((resolve, reject) => { setTimeout(() => { diff --git a/ReactNativeClient/root.js b/ReactNativeClient/root.js index 2ecde8c8c..6cb2e3ff9 100644 --- a/ReactNativeClient/root.js +++ b/ReactNativeClient/root.js @@ -8,6 +8,7 @@ import { StackNavigator } from 'react-navigation'; import { addNavigationHelpers } from 'react-navigation'; import { shim } from 'lib/shim.js'; import { Log } from 'lib/log.js' +import { Logger } from 'lib/logger.js' import { Note } from 'lib/models/note.js' import { Folder } from 'lib/models/folder.js' import { Resource } from 'lib/models/resource.js' @@ -16,6 +17,7 @@ import { NoteTag } from 'lib/models/note-tag.js' import { BaseItem } from 'lib/models/base-item.js' import { BaseModel } from 'lib/base-model.js' import { JoplinDatabase } from 'lib/joplin-database.js' +import { Database } from 'lib/database.js' import { ItemList } from 'lib/components/item-list.js' import { NotesScreen } from 'lib/components/screens/notes.js' import { NotesScreenUtils } from 'lib/components/screens/notes-utils.js' @@ -23,6 +25,7 @@ import { NoteScreen } from 'lib/components/screens/note.js' import { FolderScreen } from 'lib/components/screens/folder.js' import { FoldersScreen } from 'lib/components/screens/folders.js' import { LoginScreen } from 'lib/components/screens/login.js' +import { LogScreen } from 'lib/components/screens/log.js' import { LoadingScreen } from 'lib/components/screens/loading.js' import { OneDriveLoginScreen } from 'lib/components/screens/onedrive-login.js' import { Setting } from 'lib/models/setting.js' @@ -45,7 +48,7 @@ let defaultState = { }; const reducer = (state = defaultState, action) => { - Log.info('Reducer action', action.type); + reg.logger().info('Reducer action', action.type); let newState = state; @@ -58,8 +61,8 @@ const reducer = (state = defaultState, action) => { const currentRoute = r.length ? r[r.length - 1] : null; const currentRouteName = currentRoute ? currentRoute.routeName : ''; - Log.info('Current route name', currentRouteName); - Log.info('New route name', action.routeName); + reg.logger().info('Current route name', currentRouteName); + reg.logger().info('New route name', action.routeName); newState = Object.assign({}, state); @@ -178,7 +181,7 @@ const reducer = (state = defaultState, action) => { } - // Log.info('newState.selectedFolderId', newState.selectedFolderId); + // reg.logger().info('newState.selectedFolderId', newState.selectedFolderId); return newState; } @@ -193,6 +196,7 @@ const AppNavigator = StackNavigator({ Login: { screen: LoginScreen }, Loading: { screen: LoadingScreen }, OneDriveLogin: { screen: OneDriveLoginScreen }, + Log: { screen: LogScreen }, }); class AppComponent extends React.Component { @@ -233,6 +237,16 @@ class AppComponent extends React.Component { } } + Setting.setConstant('appId', 'net.cozic.joplin-android'); + Setting.setConstant('appType', 'mobile'); + + const logDatabase = new Database(new DatabaseDriverReactNative()); + await logDatabase.open({ name: 'log.sqlite' }); + await logDatabase.exec(Logger.databaseCreateTableSql()); + reg.logger().addTarget('database', { database: logDatabase, source: 'm' }); + + reg.logger().info('Starting application' + Setting.value('appId')); + let db = new JoplinDatabase(new DatabaseDriverReactNative()); reg.setDb(db); @@ -247,8 +261,8 @@ class AppComponent extends React.Component { BaseItem.loadClass('NoteTag', NoteTag); try { - await db.open({ name: '/storage/emulated/0/Download/joplin-48.sqlite' }) - Log.info('Database is ready.'); + await db.open({ name: 'joplin-50.sqlite' }) + reg.logger().info('Database is ready.'); //await db.exec('DELETE FROM notes'); //await db.exec('DELETE FROM folders'); @@ -257,14 +271,12 @@ class AppComponent extends React.Component { //await db.exec('DELETE FROM resources'); //await db.exec('DELETE FROM deleted_items'); - Log.info('Loading settings...'); + reg.logger().info('Loading settings...'); await Setting.load(); - Setting.setConstant('appId', 'net.cozic.joplin-android'); - Setting.setConstant('appType', 'mobile'); Setting.setConstant('resourceDir', RNFetchBlob.fs.dirs.DocumentDir); - Log.info('Loading folders...'); + reg.logger().info('Loading folders...'); let folders = await Folder.all();