mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
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:
parent
96efbbaed1
commit
f4f2c197ae
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
@ -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)}>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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',
|
||||
|
@ -5,6 +5,7 @@ export interface Options {
|
||||
showCommonLabels: boolean;
|
||||
showTime: boolean;
|
||||
wrapLogMessage: boolean;
|
||||
prettifyLogMessage: boolean;
|
||||
enableLogDetails: boolean;
|
||||
sortOrder: LogsSortOrder;
|
||||
dedupStrategy: LogsDedupStrategy;
|
||||
|
Loading…
Reference in New Issue
Block a user