1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Mobile: Add support for showing only lines of log that contain a filter (#9728)

This commit is contained in:
Henry Heino 2024-01-18 03:26:32 -08:00 committed by GitHub
parent d8d0e705f2
commit 33ed7545a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 110 additions and 37 deletions

View File

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

View File

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