Logs Panel: Refactor style generation to improve rendering performance (#62599)

* Log row: move level styles to its own provider

* Log row message: remove unnecessary extra param from styles

* Log rows: parse and pass styles to children

* Log row: receive parsed styles props from parent

* Log details: receive styles from parent

* Revert "Log details: receive styles from parent"

This reverts commit 8487482a6f4fdcf5e26896182c5ad3982774eea2.

* Log row message: receive styles from parent

* Chore: remove unnecessary comment

* Log level styles: move common styles out of getLogLevelStyles

* Chore: fix TimeZone deprecated import

* Log Details: inverse ternary operator for readability

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Log Details: inverse ternary operator for readability

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Chore: apply prettier formatting

---------

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
pull/62700/head
Matias Chomicki 2 years ago committed by GitHub
parent 42f8f5a9ea
commit c139768e3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      public/app/features/logs/components/LogDetails.tsx
  2. 51
      public/app/features/logs/components/LogRow.tsx
  3. 102
      public/app/features/logs/components/LogRowMessage.tsx
  4. 6
      public/app/features/logs/components/LogRows.tsx
  5. 127
      public/app/features/logs/components/getLogRowStyles.ts

@ -7,11 +7,9 @@ import { Themeable2, withTheme2 } from '@grafana/ui';
import { calculateLogsLabelStats, calculateStats } from '../utils';
import { LogDetailsRow } from './LogDetailsRow';
import { getLogRowStyles } from './getLogRowStyles';
import { getLogLevelStyles, getLogRowStyles } from './getLogRowStyles';
import { getAllFields } from './logParser';
//Components
export interface Props extends Themeable2 {
row: LogRowModel;
showDuplicates: boolean;
@ -66,7 +64,8 @@ class UnThemedLogDetails extends PureComponent<Props> {
getFieldLinks,
wrapLogMessage,
} = this.props;
const style = getLogRowStyles(theme, row.logLevel);
const rowStyles = getLogRowStyles(theme);
const levelStyles = getLogLevelStyles(theme, row.logLevel);
const styles = getStyles(theme);
const labels = row.labels ? row.labels : {};
const labelsAvailable = Object.keys(labels).length > 0;
@ -77,19 +76,21 @@ class UnThemedLogDetails extends PureComponent<Props> {
const linksAvailable = links && links.length > 0;
// If logs with error, we are not showing the level color
const levelClassName = cx(!hasError && [style.logsRowLevel, styles.logsRowLevelDetails]);
const levelClassName = hasError
? ''
: `${levelStyles.logsRowLevelColor} ${rowStyles.logsRowLevel} ${styles.logsRowLevelDetails}`;
return (
<tr className={cx(className, styles.logDetails)}>
{showDuplicates && <td />}
<td className={levelClassName} aria-label="Log level" />
<td colSpan={4}>
<div className={style.logDetailsContainer}>
<table className={style.logDetailsTable}>
<div className={rowStyles.logDetailsContainer}>
<table className={rowStyles.logDetailsTable}>
<tbody>
{(labelsAvailable || fieldsAvailable) && (
<tr>
<td colSpan={100} className={style.logDetailsHeading} aria-label="Fields">
<td colSpan={100} className={rowStyles.logDetailsHeading} aria-label="Fields">
Fields
</td>
</tr>
@ -138,7 +139,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
{linksAvailable && (
<tr>
<td colSpan={100} className={style.logDetailsHeading} aria-label="Data Links">
<td colSpan={100} className={rowStyles.logDetailsHeading} aria-label="Data Links">
Links
</td>
</tr>

@ -1,4 +1,4 @@
import { cx, css } from '@emotion/css';
import { cx } from '@emotion/css';
import React, { PureComponent } from 'react';
import {
@ -6,16 +6,15 @@ import {
LinkModel,
LogRowModel,
LogsSortOrder,
TimeZone,
DataQueryResponse,
dateTimeFormat,
GrafanaTheme2,
CoreApp,
DataFrame,
DataSourceWithLogsContextSupport,
} from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { styleMixins, withTheme2, Themeable2, Icon, Tooltip } from '@grafana/ui';
import { TimeZone } from '@grafana/schema';
import { withTheme2, Themeable2, Icon, Tooltip } from '@grafana/ui';
import { checkLogsError, escapeUnescapedString } from '../utils';
@ -30,9 +29,7 @@ import {
} from './LogRowContextProvider';
import { LogRowMessage } from './LogRowMessage';
import { LogRowMessageDisplayedFields } from './LogRowMessageDisplayedFields';
import { getLogRowStyles } from './getLogRowStyles';
//Components
import { getLogLevelStyles, LogRowStyles } from './getLogRowStyles';
interface Props extends Themeable2 {
row: LogRowModel;
@ -61,6 +58,7 @@ interface Props extends Themeable2 {
onClickHideField?: (key: string) => void;
onLogRowHover?: (row?: LogRowModel) => void;
toggleContextIsOpen?: () => void;
styles: LogRowStyles;
}
interface State {
@ -68,24 +66,6 @@ interface State {
showDetails: boolean;
}
const getStyles = (theme: GrafanaTheme2) => {
return {
topVerticalAlign: css`
label: topVerticalAlign;
margin-top: -${theme.spacing(0.9)};
margin-left: -${theme.spacing(0.25)};
`,
detailsOpen: css`
&:hover {
background-color: ${styleMixins.hoverColor(theme.colors.background.primary, theme)};
}
`,
errorLogRow: css`
label: erroredLogRow;
color: ${theme.colors.text.secondary};
`,
};
};
/**
* Renders a log line.
*
@ -171,14 +151,14 @@ class UnThemedLogRow extends PureComponent<Props, State> {
onLogRowHover,
app,
scrollElement,
styles,
} = this.props;
const { showDetails, showContext } = this.state;
const style = getLogRowStyles(theme, row.logLevel);
const styles = getStyles(theme);
const levelStyles = getLogLevelStyles(theme, row.logLevel);
const { errorMessage, hasError } = checkLogsError(row);
const logRowBackground = cx(style.logsRow, {
const logRowBackground = cx(styles.logsRow, {
[styles.errorLogRow]: hasError,
[style.contextBackground]: showContext,
[styles.contextBackground]: showContext,
});
const processedRow =
@ -199,25 +179,25 @@ class UnThemedLogRow extends PureComponent<Props, State> {
}}
>
{showDuplicates && (
<td className={style.logsRowDuplicates}>
<td className={styles.logsRowDuplicates}>
{processedRow.duplicates && processedRow.duplicates > 0 ? `${processedRow.duplicates + 1}x` : null}
</td>
)}
<td className={cx({ [style.logsRowLevel]: !hasError })}>
<td className={hasError ? '' : `${levelStyles.logsRowLevelColor} ${styles.logsRowLevel}`}>
{hasError && (
<Tooltip content={`Error: ${errorMessage}`} placement="right" theme="error">
<Icon className={style.logIconError} name="exclamation-triangle" size="xs" />
<Icon className={styles.logIconError} name="exclamation-triangle" size="xs" />
</Tooltip>
)}
</td>
{enableLogDetails && (
<td title={showDetails ? 'Hide log details' : 'See log details'} className={style.logsRowToggleDetails}>
<td title={showDetails ? 'Hide log details' : 'See log details'} className={styles.logsRowToggleDetails}>
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
</td>
)}
{showTime && <td className={style.logsRowLocalTime}>{this.renderTimeStamp(row.timeEpochMs)}</td>}
{showTime && <td className={styles.logsRowLocalTime}>{this.renderTimeStamp(row.timeEpochMs)}</td>}
{showLabels && processedRow.uniqueLabels && (
<td className={style.logsRowLabels}>
<td className={styles.logsRowLabels}>
<LogLabels labels={processedRow.uniqueLabels} />
</td>
)}
@ -247,6 +227,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
app={app}
scrollElement={scrollElement}
logsSortOrder={logsSortOrder}
styles={styles}
/>
)}
</tr>

@ -1,27 +1,25 @@
import { css, cx } from '@emotion/css';
import { cx } from '@emotion/css';
import memoizeOne from 'memoize-one';
import React, { PureComponent } from 'react';
import Highlighter from 'react-highlight-words';
import tinycolor from 'tinycolor2';
import {
LogRowModel,
findHighlightChunksInText,
GrafanaTheme2,
LogsSortOrder,
CoreApp,
DataSourceWithLogsContextSupport,
} from '@grafana/data';
import { withTheme2, Themeable2, IconButton, Tooltip } from '@grafana/ui';
import { IconButton, Tooltip } from '@grafana/ui';
import { LogMessageAnsi } from './LogMessageAnsi';
import { LogRowContext } from './LogRowContext';
import { LogRowContextQueryErrors, HasMoreContextRows, LogRowContextRows } from './LogRowContextProvider';
import { getLogRowStyles } from './getLogRowStyles';
import { LogRowStyles } from './getLogRowStyles';
export const MAX_CHARACTERS = 100000;
interface Props extends Themeable2 {
interface Props {
row: LogRowModel;
hasMoreContextRows?: HasMoreContextRows;
contextIsOpen: boolean;
@ -39,67 +37,9 @@ interface Props extends Themeable2 {
updateLimit?: () => void;
runContextQuery?: () => void;
logsSortOrder?: LogsSortOrder | null;
styles: LogRowStyles;
}
const getStyles = (theme: GrafanaTheme2, showContextButton: boolean, isInExplore: boolean) => {
const outlineColor = tinycolor(theme.components.dashboard.background).setAlpha(0.7).toRgbString();
return {
positionRelative: css`
label: positionRelative;
position: relative;
`,
rowWithContext: css`
label: rowWithContext;
z-index: 1;
outline: 9999px solid ${outlineColor};
display: inherit;
`,
horizontalScroll: css`
label: horizontalScroll;
white-space: pre;
`,
contextNewline: css`
display: block;
margin-left: 0px;
`,
rowMenu: css`
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-end;
justify-content: space-evenly;
align-items: center;
position: absolute;
top: 0;
bottom: auto;
height: ${theme.spacing(4.5)};
background: ${theme.colors.background.primary};
box-shadow: ${theme.shadows.z3};
padding: ${theme.spacing(0, 0, 0, 0.5)};
z-index: 100;
visibility: hidden;
width: ${showContextButton ? theme.spacing(10) : theme.spacing(5)};
`,
logRowMenuCell: css`
position: absolute;
right: ${!isInExplore ? '40px' : `calc(75px + ${theme.spacing()} + ${showContextButton ? '80px' : '40px'})`};
margin-top: -${theme.spacing(0.125)};
`,
logLine: css`
background-color: transparent;
border: none;
diplay: inline;
font-family: ${theme.typography.fontFamilyMonospace};
font-size: ${theme.typography.bodySmall.fontSize};
letter-spacing: ${theme.typography.bodySmall.letterSpacing};
text-align: left;
padding: 0;
user-select: text;
`,
};
};
function renderLogMessage(
hasAnsi: boolean,
entry: string,
@ -137,7 +77,7 @@ const restructureLog = memoizeOne((line: string, prettifyLogMessage: boolean): s
return line;
});
class UnThemedLogRowMessage extends PureComponent<Props> {
export class LogRowMessage extends PureComponent<Props> {
logRowRef: React.RefObject<HTMLTableCellElement> = React.createRef();
onContextToggle = (e: React.SyntheticEvent<HTMLElement>) => {
@ -159,7 +99,6 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
render() {
const {
row,
theme,
errors,
hasMoreContextRows,
updateLimit,
@ -174,24 +113,23 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
logsSortOrder,
showContextToggle,
getLogRowContextUi,
styles,
} = this.props;
const style = getLogRowStyles(theme, row.logLevel);
const { hasAnsi, raw } = row;
const restructuredEntry = restructureLog(raw, prettifyLogMessage);
const shouldShowContextToggle = showContextToggle ? showContextToggle(row) : false;
const styles = getStyles(theme, shouldShowContextToggle, app === CoreApp.Explore);
const inExplore = app === CoreApp.Explore;
return (
<>
{
// When context is open, the position has to be NOT relative. // Setting the postion as inline-style to
// overwrite the more sepecific style definition from `style.logsRowMessage`.
// overwrite the more sepecific style definition from `styles.logsRowMessage`.
}
<td
ref={this.logRowRef}
style={contextIsOpen ? { position: 'unset' } : undefined}
className={style.logsRowMessage}
className={styles.logsRowMessage}
>
<div
className={cx(
@ -218,13 +156,24 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
/>
)}
<button className={cx(styles.logLine, styles.positionRelative, { [styles.rowWithContext]: contextIsOpen })}>
{renderLogMessage(hasAnsi, restructuredEntry, row.searchWords, style.logsRowMatchHighLight)}
{renderLogMessage(hasAnsi, restructuredEntry, row.searchWords, styles.logsRowMatchHighLight)}
</button>
</div>
</td>
{showRowMenu && (
<td className={cx('log-row-menu-cell', styles.logRowMenuCell)}>
<span className={cx('log-row-menu', styles.rowMenu)} onClick={(e) => e.stopPropagation()}>
<td
className={cx('log-row-menu-cell', styles.logRowMenuCell, {
[styles.logRowMenuCellDefaultPosition]: !inExplore,
[styles.logRowMenuCellExplore]: inExplore && !shouldShowContextToggle,
[styles.logRowMenuCellExploreWithContextButton]: inExplore && shouldShowContextToggle,
})}
>
<span
className={cx('log-row-menu', styles.rowMenu, {
[styles.rowMenuWithContextButton]: shouldShowContextToggle,
})}
onClick={(e) => e.stopPropagation()}
>
{shouldShowContextToggle && (
<Tooltip placement="top" content={'Show context'}>
<IconButton size="md" name="gf-show-context" onClick={this.onShowContextClick} />
@ -240,6 +189,3 @@ class UnThemedLogRowMessage extends PureComponent<Props> {
);
}
}
export const LogRowMessage = withTheme2(UnThemedLogRowMessage);
LogRowMessage.displayName = 'LogRowMessage';

@ -133,7 +133,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
getLogRowContextUi,
} = this.props;
const { renderAll, contextIsOpen } = this.state;
const { logsRowsTable } = getLogRowStyles(theme);
const styles = getLogRowStyles(theme);
const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows;
const hasData = logRows && logRows.length > 0;
const dedupCount = dedupedRows
@ -151,7 +151,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
const getRowContext = this.props.getRowContext ? this.props.getRowContext : () => Promise.resolve([]);
return (
<table className={logsRowsTable}>
<table className={styles.logsRowsTable}>
<tbody>
{hasData &&
firstRows.map((row, index) => (
@ -182,6 +182,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
onLogRowHover={onLogRowHover}
app={app}
scrollElement={scrollElement}
styles={styles}
/>
))}
{hasData &&
@ -214,6 +215,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
onLogRowHover={onLogRowHover}
app={app}
scrollElement={scrollElement}
styles={styles}
/>
))}
{hasData && !renderAll && (

@ -1,12 +1,11 @@
import { css } from '@emotion/css';
import tinycolor from 'tinycolor2';
import { GrafanaTheme2, LogLevel } from '@grafana/data';
import { styleMixins } from '@grafana/ui';
export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
export const getLogLevelStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
let logColor = theme.isLight ? theme.v1.palette.gray5 : theme.v1.palette.gray2;
const hoverBgColor = styleMixins.hoverColor(theme.colors.background.secondary, theme);
switch (logLevel) {
case LogLevel.crit:
case LogLevel.critical:
@ -32,6 +31,32 @@ export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
}
return {
logsRowLevelColor: css`
&::after {
background-color: ${logColor};
}
`,
};
};
export const getLogRowStyles = (theme: GrafanaTheme2) => {
const hoverBgColor = styleMixins.hoverColor(theme.colors.background.secondary, theme);
const contextOutlineColor = tinycolor(theme.components.dashboard.background).setAlpha(0.7).toRgbString();
return {
logsRowLevel: css`
label: logs-row__level;
max-width: ${theme.spacing(1.25)};
cursor: default;
&::after {
content: '';
display: block;
position: absolute;
top: 1px;
bottom: 1px;
width: 3px;
left: ${theme.spacing(0.5)};
}
`,
logsRowMatchHighLight: css`
label: logs-row__match-highlight;
background: inherit;
@ -81,21 +106,6 @@ export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
width: 4em;
cursor: default;
`,
logsRowLevel: css`
label: logs-row__level;
max-width: ${theme.spacing(1.25)};
cursor: default;
&::after {
content: '';
display: block;
position: absolute;
top: 1px;
bottom: 1px;
width: 3px;
left: ${theme.spacing(0.5)};
background-color: ${logColor};
}
`,
logIconError: css`
color: ${theme.colors.warning.main};
`,
@ -174,5 +184,86 @@ export const getLogRowStyles = (theme: GrafanaTheme2, logLevel?: LogLevel) => {
background-color: ${hoverBgColor};
}
`,
// Log row
topVerticalAlign: css`
label: topVerticalAlign;
margin-top: -${theme.spacing(0.9)};
margin-left: -${theme.spacing(0.25)};
`,
detailsOpen: css`
&:hover {
background-color: ${styleMixins.hoverColor(theme.colors.background.primary, theme)};
}
`,
errorLogRow: css`
label: erroredLogRow;
color: ${theme.colors.text.secondary};
`,
// Log Row Message
positionRelative: css`
label: positionRelative;
position: relative;
`,
rowWithContext: css`
label: rowWithContext;
z-index: 1;
outline: 9999px solid ${contextOutlineColor};
display: inherit;
`,
horizontalScroll: css`
label: horizontalScroll;
white-space: pre;
`,
contextNewline: css`
display: block;
margin-left: 0px;
`,
rowMenu: css`
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-end;
justify-content: space-evenly;
align-items: center;
position: absolute;
top: 0;
bottom: auto;
height: ${theme.spacing(4.5)};
background: ${theme.colors.background.primary};
box-shadow: ${theme.shadows.z3};
padding: ${theme.spacing(0, 0, 0, 0.5)};
z-index: 100;
visibility: hidden;
width: ${theme.spacing(5)};
`,
rowMenuWithContextButton: css`
width: ${theme.spacing(10)};
`,
logRowMenuCell: css`
position: absolute;
margin-top: -${theme.spacing(0.125)};
`,
logRowMenuCellDefaultPosition: css`
right: 40px;
`,
logRowMenuCellExplore: css`
right: calc(115px + ${theme.spacing(1)});
`,
logRowMenuCellExploreWithContextButton: css`
right: calc(155px + ${theme.spacing(1)});
`,
logLine: css`
background-color: transparent;
border: none;
diplay: inline;
font-family: ${theme.typography.fontFamilyMonospace};
font-size: ${theme.typography.bodySmall.fontSize};
letter-spacing: ${theme.typography.bodySmall.letterSpacing};
text-align: left;
padding: 0;
user-select: text;
`,
};
};
export type LogRowStyles = ReturnType<typeof getLogRowStyles>;

Loading…
Cancel
Save