mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-24 08:12:24 +02:00
Mobile: Add support for showing only lines of log that contain a filter (#9728)
This commit is contained in:
parent
d8d0e705f2
commit
33ed7545a9
@ -7,33 +7,47 @@ import { ScreenHeader } from '../ScreenHeader';
|
||||
import time from '@joplin/lib/time';
|
||||
const { themeStyle } = require('../global-style.js');
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
const { BaseScreenComponent } = require('../base-screen.js');
|
||||
import { BaseScreenComponent } from '../base-screen';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { MenuOptionType } from '../ScreenHeader';
|
||||
import { AppState } from '../../utils/types';
|
||||
import Share from 'react-native-share';
|
||||
import { writeTextToCacheFile } from '../../utils/ShareUtils';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import { TextInput } from 'react-native-paper';
|
||||
|
||||
const logger = Logger.create('LogScreen');
|
||||
|
||||
class LogScreenComponent extends BaseScreenComponent {
|
||||
private readonly menuOptions: MenuOptionType[];
|
||||
interface Props {
|
||||
themeId: number;
|
||||
navigation: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
logEntries: any[];
|
||||
showErrorsOnly: boolean;
|
||||
filter: string|undefined;
|
||||
}
|
||||
|
||||
class LogScreenComponent extends BaseScreenComponent<Props, State> {
|
||||
private readonly menuOptions_: MenuOptionType[];
|
||||
private styles_: any;
|
||||
|
||||
public static navigationOptions(): any {
|
||||
return { header: null };
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
logEntries: [],
|
||||
showErrorsOnly: false,
|
||||
filter: undefined,
|
||||
};
|
||||
this.styles_ = {};
|
||||
|
||||
this.menuOptions = [
|
||||
this.menuOptions_ = [
|
||||
{
|
||||
title: _('Share'),
|
||||
onPress: () => {
|
||||
@ -43,10 +57,36 @@ class LogScreenComponent extends BaseScreenComponent {
|
||||
];
|
||||
}
|
||||
|
||||
private refreshLogTimeout: any = null;
|
||||
public override componentDidUpdate(_prevProps: Props, prevState: State) {
|
||||
if ((prevState?.filter ?? '') !== (this.state.filter ?? '')) {
|
||||
// We refresh the log only after a brief delay -- this prevents the log from updating
|
||||
// with every keystroke in the filter input.
|
||||
if (this.refreshLogTimeout) {
|
||||
clearTimeout(this.refreshLogTimeout);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.refreshLogTimeout = null;
|
||||
void this.resfreshLogEntries();
|
||||
}, 600);
|
||||
}
|
||||
}
|
||||
|
||||
public override componentDidMount() {
|
||||
void this.resfreshLogEntries();
|
||||
|
||||
if (this.props.navigation.state.defaultFilter) {
|
||||
this.setState({ filter: this.props.navigation.state.defaultFilter });
|
||||
}
|
||||
}
|
||||
|
||||
private async getLogEntries(showErrorsOnly: boolean, limit: number|null = null) {
|
||||
const levels = this.getLogLevels(showErrorsOnly);
|
||||
return await reg.logger().lastEntries(limit, { levels, filter: this.state.filter });
|
||||
}
|
||||
|
||||
private async onSharePress() {
|
||||
const limit: number|null = null; // no limit
|
||||
const levels = this.getLogLevels(this.state.showErrorsOnly);
|
||||
const allEntries: any[] = await reg.logger().lastEntries(limit, { levels });
|
||||
const allEntries: any[] = await this.getLogEntries(this.state.showErrorsOnly);
|
||||
const logData = allEntries.map(entry => this.formatLogEntry(entry)).join('\n');
|
||||
|
||||
let fileToShare;
|
||||
@ -74,11 +114,11 @@ class LogScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
|
||||
public styles() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
|
||||
this.styles_ = {};
|
||||
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const styles: any = {
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
@ -108,10 +148,6 @@ class LogScreenComponent extends BaseScreenComponent {
|
||||
return this.styles_[this.props.themeId];
|
||||
}
|
||||
|
||||
public UNSAFE_componentWillMount() {
|
||||
void this.resfreshLogEntries();
|
||||
}
|
||||
|
||||
private getLogLevels(showErrorsOnly: boolean) {
|
||||
let levels = [Logger.LEVEL_DEBUG, Logger.LEVEL_INFO, Logger.LEVEL_WARN, Logger.LEVEL_ERROR];
|
||||
if (showErrorsOnly) levels = [Logger.LEVEL_WARN, Logger.LEVEL_ERROR];
|
||||
@ -122,10 +158,11 @@ class LogScreenComponent extends BaseScreenComponent {
|
||||
private async resfreshLogEntries(showErrorsOnly: boolean = null) {
|
||||
if (showErrorsOnly === null) showErrorsOnly = this.state.showErrorsOnly;
|
||||
|
||||
const levels = this.getLogLevels(showErrorsOnly);
|
||||
const limit = 1000;
|
||||
const logEntries = await this.getLogEntries(showErrorsOnly, limit);
|
||||
|
||||
this.setState({
|
||||
logEntries: await reg.logger().lastEntries(1000, { levels: levels }),
|
||||
logEntries: logEntries,
|
||||
showErrorsOnly: showErrorsOnly,
|
||||
});
|
||||
}
|
||||
@ -138,29 +175,48 @@ class LogScreenComponent extends BaseScreenComponent {
|
||||
return `${time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss')}: ${item.message}`;
|
||||
}
|
||||
|
||||
private onRenderLogRow = ({ item }: any) => {
|
||||
let textStyle = this.styles().rowText;
|
||||
if (item.level === Logger.LEVEL_WARN) textStyle = this.styles().rowTextWarn;
|
||||
if (item.level === Logger.LEVEL_ERROR) textStyle = this.styles().rowTextError;
|
||||
|
||||
return (
|
||||
<View style={this.styles().row}>
|
||||
<Text style={textStyle}>{this.formatLogEntry(item)}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
private onFilterUpdated = (newFilter: string) => {
|
||||
this.setState({ filter: newFilter });
|
||||
};
|
||||
|
||||
private onToggleFilterInput = () => {
|
||||
const filter = this.state.filter === undefined ? '' : undefined;
|
||||
this.setState({ filter });
|
||||
};
|
||||
|
||||
public render() {
|
||||
const renderRow = ({ item }: any) => {
|
||||
let textStyle = this.styles().rowText;
|
||||
if (item.level === Logger.LEVEL_WARN) textStyle = this.styles().rowTextWarn;
|
||||
if (item.level === Logger.LEVEL_ERROR) textStyle = this.styles().rowTextError;
|
||||
|
||||
return (
|
||||
<View style={this.styles().row}>
|
||||
<Text style={textStyle}>{this.formatLogEntry(item)}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
// `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39
|
||||
const filterInput = (
|
||||
<TextInput
|
||||
value={this.state.filter}
|
||||
onChangeText={this.onFilterUpdated}
|
||||
label={_('Filter')}
|
||||
placeholder={_('Filter')}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={this.rootStyle(this.props.themeId).root}>
|
||||
<ScreenHeader
|
||||
title={_('Log')}
|
||||
menuOptions={this.menuOptions}/>
|
||||
menuOptions={this.menuOptions_}
|
||||
showSearchButton={true}
|
||||
onSearchButtonPress={this.onToggleFilterInput}/>
|
||||
{this.state.filter !== undefined ? filterInput : null}
|
||||
<FlatList
|
||||
data={this.state.logEntries}
|
||||
renderItem={renderRow}
|
||||
renderItem={this.onRenderLogRow}
|
||||
keyExtractor={item => { return `${item.id}`; }}
|
||||
/>
|
||||
<View style={{ flexDirection: 'row' }}>
|
||||
@ -190,6 +246,6 @@ const LogScreen = connect((state: AppState) => {
|
||||
return {
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
})(LogScreenComponent as any);
|
||||
})(LogScreenComponent);
|
||||
|
||||
export default LogScreen;
|
||||
|
@ -36,6 +36,11 @@ interface Target extends TargetOptions {
|
||||
type: TargetType;
|
||||
}
|
||||
|
||||
interface LastEntriesOptions {
|
||||
levels?: LogLevel[];
|
||||
filter?: string;
|
||||
}
|
||||
|
||||
export interface LoggerWrapper {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
|
||||
debug: Function;
|
||||
@ -196,7 +201,7 @@ class Logger {
|
||||
}
|
||||
|
||||
// Only for database at the moment
|
||||
public async lastEntries(limit = 100, options: any = null) {
|
||||
public async lastEntries(limit = 100, options: LastEntriesOptions|null = null) {
|
||||
if (options === null) options = {};
|
||||
if (!options.levels) options.levels = [LogLevel.Debug, LogLevel.Info, LogLevel.Warn, LogLevel.Error];
|
||||
if (!options.levels.length) return [];
|
||||
@ -204,9 +209,21 @@ class Logger {
|
||||
for (let i = 0; i < this.targets_.length; i++) {
|
||||
const target = this.targets_[i];
|
||||
if (target.type === 'database') {
|
||||
let sql = `SELECT * FROM logs WHERE level IN (${options.levels.join(',')}) ORDER BY timestamp DESC`;
|
||||
if (limit !== null) sql += ` LIMIT ${limit}`;
|
||||
return await target.database.selectAll(sql);
|
||||
const sql = [`SELECT * FROM logs WHERE level IN (${options.levels.join(',')})`];
|
||||
const sqlParams = [];
|
||||
|
||||
if (options.filter) {
|
||||
sql.push('AND message LIKE ?');
|
||||
sqlParams.push(`%${options.filter}%`);
|
||||
}
|
||||
|
||||
sql.push('ORDER BY timestamp DESC');
|
||||
if (limit !== null) {
|
||||
sql.push('LIMIT ?');
|
||||
sqlParams.push(limit);
|
||||
}
|
||||
|
||||
return await target.database.selectAll(sql.join(' '), sqlParams);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
|
Loading…
Reference in New Issue
Block a user