Explore: Add switch to restructure logs for better readability (#36324)

* Add prettifyLogMessage to select components in test file

* Change entry depending on the value of prettifyLogMessage

* Add prettifyLogMessage to state

* Fix merge conflict

* Fixe bug where the log message wasn't parsed as JSON

* Implement function to restructure all logs

* Change elstic image version back to 7.7.1

* Add showCommonLabels that was missing

* Remove comment

* Put import of getParser together with the other imports

* Logs: fix bug where message isn't restructured if it contains ANSI code

* Logs: change label for switch to Restructure

* Remove unnecessary file

* Logs: added divider before switch component

* Add dividers between the different log options

* Remove unintentional changes

* Explore: remove dividers in log settings

* Explore: refactor for LogRowMessage for better readability

* remove unnecessary change

* Logs: fix bug where logs aren't restructured if they have highlights

* Logs: minor refactoring

* Logs: use memoizeOne to prevent parsing on every re-render

* Logs: calculate needsHilight inside renderLogMessage instead of outside

* Logs: fix bug where logs aren't prettified when wrap logs is disabled

* Explore: change name to prettify

* Remove console.log

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* Dashboards: add switch to prettify log messages to the Logs fields

* Logs: make prettify only work for JSON logs

* Logs: fix bug with tests for logs

* Update public/app/plugins/panel/logs/module.tsx

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
This commit is contained in:
Olof Bourghardt 2021-07-16 15:08:47 +02:00 committed by GitHub
parent 96efbbaed1
commit f4f2c197ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 92 additions and 17 deletions

View File

@ -40,6 +40,7 @@ interface Props extends Themeable {
showLabels: boolean; showLabels: boolean;
showTime: boolean; showTime: boolean;
wrapLogMessage: boolean; wrapLogMessage: boolean;
prettifyLogMessage: boolean;
timeZone: TimeZone; timeZone: TimeZone;
enableLogDetails: boolean; enableLogDetails: boolean;
logsSortOrder?: LogsSortOrder | null; logsSortOrder?: LogsSortOrder | null;
@ -139,6 +140,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
showTime, showTime,
showDetectedFields, showDetectedFields,
wrapLogMessage, wrapLogMessage,
prettifyLogMessage,
theme, theme,
getFieldLinks, getFieldLinks,
forceEscape, forceEscape,
@ -201,6 +203,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
contextIsOpen={showContext} contextIsOpen={showContext}
showContextToggle={showContextToggle} showContextToggle={showContextToggle}
wrapLogMessage={wrapLogMessage} wrapLogMessage={wrapLogMessage}
prettifyLogMessage={prettifyLogMessage}
onToggleContext={this.toggleContext} onToggleContext={this.toggleContext}
/> />
)} )}

View File

@ -3,6 +3,7 @@ import { isEqual } from 'lodash';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { LogRowModel, findHighlightChunksInText, GrafanaTheme } from '@grafana/data'; import { LogRowModel, findHighlightChunksInText, GrafanaTheme } from '@grafana/data';
import memoizeOne from 'memoize-one';
// @ts-ignore // @ts-ignore
import Highlighter from 'react-highlight-words'; import Highlighter from 'react-highlight-words';
@ -23,6 +24,7 @@ interface Props extends Themeable {
hasMoreContextRows?: HasMoreContextRows; hasMoreContextRows?: HasMoreContextRows;
contextIsOpen: boolean; contextIsOpen: boolean;
wrapLogMessage: boolean; wrapLogMessage: boolean;
prettifyLogMessage: boolean;
errors?: LogRowContextQueryErrors; errors?: LogRowContextQueryErrors;
context?: LogRowContextRows; context?: LogRowContextRows;
showContextToggle?: (row?: LogRowModel) => boolean; showContextToggle?: (row?: LogRowModel) => boolean;
@ -47,11 +49,46 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
`, `,
horizontalScroll: css` horizontalScroll: css`
label: verticalScroll; label: verticalScroll;
white-space: nowrap; white-space: pre;
`, `,
}; };
}); });
function renderLogMessage(
hasAnsi: boolean,
entry: string,
highlights: string[] | undefined,
highlightClassName: string
) {
const needsHighlighter =
highlights && highlights.length > 0 && highlights[0] && highlights[0].length > 0 && entry.length < MAX_CHARACTERS;
if (needsHighlighter) {
return (
<Highlighter
textToHighlight={entry}
searchWords={highlights ?? []}
findChunks={findHighlightChunksInText}
highlightClassName={highlightClassName}
/>
);
} else if (hasAnsi) {
return <LogMessageAnsi value={entry} />;
} else {
return entry;
}
}
const restructureLog = memoizeOne((line: string, prettifyLogMessage: boolean): string => {
if (prettifyLogMessage) {
try {
return JSON.stringify(JSON.parse(line), undefined, 2);
} catch (error) {
return line;
}
}
return line;
});
class UnThemedLogRowMessage extends PureComponent<Props> { class UnThemedLogRowMessage extends PureComponent<Props> {
onContextToggle = (e: React.SyntheticEvent<HTMLElement>) => { onContextToggle = (e: React.SyntheticEvent<HTMLElement>) => {
e.stopPropagation(); e.stopPropagation();
@ -70,16 +107,16 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
contextIsOpen, contextIsOpen,
showContextToggle, showContextToggle,
wrapLogMessage, wrapLogMessage,
prettifyLogMessage,
onToggleContext, onToggleContext,
} = this.props; } = this.props;
const style = getLogRowStyles(theme, row.logLevel); const style = getLogRowStyles(theme, row.logLevel);
const { entry, hasAnsi, raw } = row; const { hasAnsi, raw } = row;
const restructuredEntry = restructureLog(raw, prettifyLogMessage);
const previewHighlights = highlighterExpressions?.length && !isEqual(highlighterExpressions, row.searchWords); const previewHighlights = highlighterExpressions?.length && !isEqual(highlighterExpressions, row.searchWords);
const highlights = previewHighlights ? highlighterExpressions : row.searchWords; const highlights = previewHighlights ? highlighterExpressions : row.searchWords;
const needsHighlighter =
highlights && highlights.length > 0 && highlights[0] && highlights[0].length > 0 && entry.length < MAX_CHARACTERS;
const highlightClassName = previewHighlights const highlightClassName = previewHighlights
? cx([style.logsRowMatchHighLight, style.logsRowMatchHighLightPreview]) ? cx([style.logsRowMatchHighLight, style.logsRowMatchHighLightPreview])
: cx([style.logsRowMatchHighLight]); : cx([style.logsRowMatchHighLight]);
@ -103,18 +140,7 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
/> />
)} )}
<span className={cx(styles.positionRelative, { [styles.rowWithContext]: contextIsOpen })}> <span className={cx(styles.positionRelative, { [styles.rowWithContext]: contextIsOpen })}>
{needsHighlighter ? ( {renderLogMessage(hasAnsi, restructuredEntry, highlights, highlightClassName)}
<Highlighter
textToHighlight={entry}
searchWords={highlights ?? []}
findChunks={findHighlightChunksInText}
highlightClassName={highlightClassName}
/>
) : hasAnsi ? (
<LogMessageAnsi value={raw} />
) : (
entry
)}
</span> </span>
{showContextToggle?.(row) && ( {showContextToggle?.(row) && (
<span onClick={this.onContextToggle} className={cx('log-row-context', style.context)}> <span onClick={this.onContextToggle} className={cx('log-row-context', style.context)}>

View File

@ -16,6 +16,7 @@ describe('LogRows', () => {
showLabels={false} showLabels={false}
showTime={false} showTime={false}
wrapLogMessage={true} wrapLogMessage={true}
prettifyLogMessage={true}
timeZone={'utc'} timeZone={'utc'}
enableLogDetails={true} enableLogDetails={true}
/> />
@ -38,6 +39,7 @@ describe('LogRows', () => {
showLabels={false} showLabels={false}
showTime={false} showTime={false}
wrapLogMessage={true} wrapLogMessage={true}
prettifyLogMessage={true}
timeZone={'utc'} timeZone={'utc'}
previewLimit={1} previewLimit={1}
enableLogDetails={true} enableLogDetails={true}
@ -69,6 +71,7 @@ describe('LogRows', () => {
showLabels={false} showLabels={false}
showTime={false} showTime={false}
wrapLogMessage={true} wrapLogMessage={true}
prettifyLogMessage={true}
timeZone={'utc'} timeZone={'utc'}
enableLogDetails={true} enableLogDetails={true}
/> />
@ -90,6 +93,7 @@ describe('LogRows', () => {
showLabels={false} showLabels={false}
showTime={false} showTime={false}
wrapLogMessage={true} wrapLogMessage={true}
prettifyLogMessage={true}
timeZone={'utc'} timeZone={'utc'}
enableLogDetails={true} enableLogDetails={true}
/> />
@ -112,6 +116,7 @@ describe('LogRows', () => {
showLabels={false} showLabels={false}
showTime={false} showTime={false}
wrapLogMessage={true} wrapLogMessage={true}
prettifyLogMessage={true}
timeZone={'utc'} timeZone={'utc'}
logsSortOrder={LogsSortOrder.Ascending} logsSortOrder={LogsSortOrder.Ascending}
enableLogDetails={true} enableLogDetails={true}
@ -136,6 +141,7 @@ describe('LogRows', () => {
showLabels={false} showLabels={false}
showTime={false} showTime={false}
wrapLogMessage={true} wrapLogMessage={true}
prettifyLogMessage={true}
timeZone={'utc'} timeZone={'utc'}
logsSortOrder={LogsSortOrder.Descending} logsSortOrder={LogsSortOrder.Descending}
enableLogDetails={true} enableLogDetails={true}

View File

@ -20,6 +20,7 @@ export interface Props extends Themeable {
showLabels: boolean; showLabels: boolean;
showTime: boolean; showTime: boolean;
wrapLogMessage: boolean; wrapLogMessage: boolean;
prettifyLogMessage: boolean;
timeZone: TimeZone; timeZone: TimeZone;
enableLogDetails: boolean; enableLogDetails: boolean;
logsSortOrder?: LogsSortOrder | null; logsSortOrder?: LogsSortOrder | null;
@ -84,6 +85,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
showLabels, showLabels,
showTime, showTime,
wrapLogMessage, wrapLogMessage,
prettifyLogMessage,
logRows, logRows,
deduplicatedRows, deduplicatedRows,
highlighterExpressions, highlighterExpressions,
@ -135,6 +137,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
showTime={showTime} showTime={showTime}
showDetectedFields={showDetectedFields} showDetectedFields={showDetectedFields}
wrapLogMessage={wrapLogMessage} wrapLogMessage={wrapLogMessage}
prettifyLogMessage={prettifyLogMessage}
timeZone={timeZone} timeZone={timeZone}
enableLogDetails={enableLogDetails} enableLogDetails={enableLogDetails}
onClickFilterLabel={onClickFilterLabel} onClickFilterLabel={onClickFilterLabel}
@ -160,6 +163,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
showTime={showTime} showTime={showTime}
showDetectedFields={showDetectedFields} showDetectedFields={showDetectedFields}
wrapLogMessage={wrapLogMessage} wrapLogMessage={wrapLogMessage}
prettifyLogMessage={prettifyLogMessage}
timeZone={timeZone} timeZone={timeZone}
enableLogDetails={enableLogDetails} enableLogDetails={enableLogDetails}
onClickFilterLabel={onClickFilterLabel} onClickFilterLabel={onClickFilterLabel}

View File

@ -42,6 +42,7 @@ const SETTINGS_KEYS = {
showLabels: 'grafana.explore.logs.showLabels', showLabels: 'grafana.explore.logs.showLabels',
showTime: 'grafana.explore.logs.showTime', showTime: 'grafana.explore.logs.showTime',
wrapLogMessage: 'grafana.explore.logs.wrapLogMessage', wrapLogMessage: 'grafana.explore.logs.wrapLogMessage',
prettifyLogMessage: 'grafana.explore.logs.prettifyLogMessage',
}; };
interface Props { interface Props {
@ -74,6 +75,7 @@ interface State {
showLabels: boolean; showLabels: boolean;
showTime: boolean; showTime: boolean;
wrapLogMessage: boolean; wrapLogMessage: boolean;
prettifyLogMessage: boolean;
dedupStrategy: LogsDedupStrategy; dedupStrategy: LogsDedupStrategy;
hiddenLogLevels: LogLevel[]; hiddenLogLevels: LogLevel[];
logsSortOrder: LogsSortOrder | null; logsSortOrder: LogsSortOrder | null;
@ -91,6 +93,7 @@ export class UnthemedLogs extends PureComponent<Props, State> {
showLabels: store.getBool(SETTINGS_KEYS.showLabels, false), showLabels: store.getBool(SETTINGS_KEYS.showLabels, false),
showTime: store.getBool(SETTINGS_KEYS.showTime, true), showTime: store.getBool(SETTINGS_KEYS.showTime, true),
wrapLogMessage: store.getBool(SETTINGS_KEYS.wrapLogMessage, true), wrapLogMessage: store.getBool(SETTINGS_KEYS.wrapLogMessage, true),
prettifyLogMessage: store.getBool(SETTINGS_KEYS.prettifyLogMessage, false),
dedupStrategy: LogsDedupStrategy.none, dedupStrategy: LogsDedupStrategy.none,
hiddenLogLevels: [], hiddenLogLevels: [],
logsSortOrder: null, logsSortOrder: null,
@ -166,6 +169,17 @@ export class UnthemedLogs extends PureComponent<Props, State> {
} }
}; };
onChangePrettifyLogMessage = (event?: React.SyntheticEvent) => {
const target = event && (event.target as HTMLInputElement);
if (target) {
const prettifyLogMessage = target.checked;
this.setState({
prettifyLogMessage,
});
store.set(SETTINGS_KEYS.prettifyLogMessage, prettifyLogMessage);
}
};
onToggleLogLevel = (hiddenRawLevels: string[]) => { onToggleLogLevel = (hiddenRawLevels: string[]) => {
const hiddenLogLevels = hiddenRawLevels.map((level) => LogLevel[level as LogLevel]); const hiddenLogLevels = hiddenRawLevels.map((level) => LogLevel[level as LogLevel]);
this.setState({ hiddenLogLevels }); this.setState({ hiddenLogLevels });
@ -260,6 +274,7 @@ export class UnthemedLogs extends PureComponent<Props, State> {
showLabels, showLabels,
showTime, showTime,
wrapLogMessage, wrapLogMessage,
prettifyLogMessage,
dedupStrategy, dedupStrategy,
hiddenLogLevels, hiddenLogLevels,
logsSortOrder, logsSortOrder,
@ -305,6 +320,9 @@ export class UnthemedLogs extends PureComponent<Props, State> {
<InlineField label="Wrap lines" transparent> <InlineField label="Wrap lines" transparent>
<InlineSwitch value={wrapLogMessage} onChange={this.onChangewrapLogMessage} transparent /> <InlineSwitch value={wrapLogMessage} onChange={this.onChangewrapLogMessage} transparent />
</InlineField> </InlineField>
<InlineField label="Prettify JSON" transparent>
<InlineSwitch value={prettifyLogMessage} onChange={this.onChangePrettifyLogMessage} transparent />
</InlineField>
<InlineField label="Dedup" transparent> <InlineField label="Dedup" transparent>
<RadioButtonGroup <RadioButtonGroup
options={Object.keys(LogsDedupStrategy).map((dedupType: LogsDedupStrategy) => ({ options={Object.keys(LogsDedupStrategy).map((dedupType: LogsDedupStrategy) => ({
@ -356,6 +374,7 @@ export class UnthemedLogs extends PureComponent<Props, State> {
enableLogDetails={true} enableLogDetails={true}
forceEscape={forceEscape} forceEscape={forceEscape}
wrapLogMessage={wrapLogMessage} wrapLogMessage={wrapLogMessage}
prettifyLogMessage={prettifyLogMessage}
timeZone={timeZone} timeZone={timeZone}
getFieldLinks={getFieldLinks} getFieldLinks={getFieldLinks}
logsSortOrder={logsSortOrder} logsSortOrder={logsSortOrder}

View File

@ -12,7 +12,16 @@ interface LogsPanelProps extends PanelProps<Options> {}
export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
data, data,
timeZone, timeZone,
options: { showLabels, showTime, wrapLogMessage, showCommonLabels, sortOrder, dedupStrategy, enableLogDetails }, options: {
showLabels,
showTime,
wrapLogMessage,
showCommonLabels,
prettifyLogMessage,
sortOrder,
dedupStrategy,
enableLogDetails,
},
title, title,
}) => { }) => {
const style = useStyles2(getStyles(title)); const style = useStyles2(getStyles(title));
@ -57,6 +66,7 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
showLabels={showLabels} showLabels={showLabels}
showTime={showTime} showTime={showTime}
wrapLogMessage={wrapLogMessage} wrapLogMessage={wrapLogMessage}
prettifyLogMessage={prettifyLogMessage}
timeZone={timeZone} timeZone={timeZone}
getFieldLinks={getFieldLinks} getFieldLinks={getFieldLinks}
logsSortOrder={sortOrder} logsSortOrder={sortOrder}

View File

@ -28,6 +28,12 @@ export const plugin = new PanelPlugin<Options>(LogsPanel).setPanelOptions((build
description: '', description: '',
defaultValue: false, defaultValue: false,
}) })
.addBooleanSwitch({
path: 'prettifyLogMessage',
name: 'Prettify JSON',
description: '',
defaultValue: false,
})
.addBooleanSwitch({ .addBooleanSwitch({
path: 'enableLogDetails', path: 'enableLogDetails',
name: 'Enable log details', name: 'Enable log details',

View File

@ -5,6 +5,7 @@ export interface Options {
showCommonLabels: boolean; showCommonLabels: boolean;
showTime: boolean; showTime: boolean;
wrapLogMessage: boolean; wrapLogMessage: boolean;
prettifyLogMessage: boolean;
enableLogDetails: boolean; enableLogDetails: boolean;
sortOrder: LogsSortOrder; sortOrder: LogsSortOrder;
dedupStrategy: LogsDedupStrategy; dedupStrategy: LogsDedupStrategy;