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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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