Skip to content

Commit

Permalink
Mobile: Add support for showing only lines of log that contain a filt…
Browse files Browse the repository at this point in the history
…er (#9728)
  • Loading branch information
personalizedrefrigerator committed Jan 18, 2024
1 parent d8d0e70 commit 33ed754
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 37 deletions.
122 changes: 89 additions & 33 deletions packages/app-mobile/components/screens/LogScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => {
Expand All @@ -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;
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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];
Expand All @@ -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,
});
}
Expand All @@ -138,29 +175,48 @@ class LogScreenComponent extends BaseScreenComponent {
return `${time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss')}: ${item.message}`;
}

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>
);
};
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;

// `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39
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 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' }}>
Expand Down Expand Up @@ -190,6 +246,6 @@ const LogScreen = connect((state: AppState) => {
return {
themeId: state.settings.theme,
};
})(LogScreenComponent as any);
})(LogScreenComponent);

export default LogScreen;
25 changes: 21 additions & 4 deletions packages/utils/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -196,17 +201,29 @@ 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 [];

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 [];
Expand Down

0 comments on commit 33ed754

Please sign in to comment.