import _ from 'lodash'; import React, { PureComponent } from 'react'; import Highlighter from 'react-highlight-words'; import classnames from 'classnames'; import * as rangeUtil from 'app/core/utils/rangeutil'; import { RawTimeRange } from 'app/types/series'; import { LogsDedupStrategy, LogsModel, dedupLogRows, filterLogLevels, LogLevel, LogsMetaKind, LogRow, } from 'app/core/logs_model'; import { findHighlightChunksInText } from 'app/core/utils/text'; import { Switch } from 'app/core/components/Switch/Switch'; import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; import Graph from './Graph'; import LogLabels from './LogLabels'; const PREVIEW_LIMIT = 100; const graphOptions = { series: { stack: true, bars: { show: true, lineWidth: 5, // barWidth: 10, }, // stack: true, }, yaxis: { tickDecimals: 0, }, }; interface RowProps { allRows: LogRow[]; highlighterExpressions?: string[]; row: LogRow; showDuplicates: boolean; showLabels: boolean | null; // Tristate: null means auto showLocalTime: boolean; showUtc: boolean; onClickLabel?: (label: string, value: string) => void; } function Row({ allRows, highlighterExpressions, onClickLabel, row, showDuplicates, showLabels, showLocalTime, showUtc, }: RowProps) { const previewHighlights = highlighterExpressions && !_.isEqual(highlighterExpressions, row.searchWords); const highlights = previewHighlights ? highlighterExpressions : row.searchWords; const needsHighlighter = highlights && highlights.length > 0; const highlightClassName = classnames('logs-row__match-highlight', { 'logs-row__match-highlight--preview': previewHighlights, }); return (
{showDuplicates && (
{row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
)}
{showUtc && (
{row.timestamp}
)} {showLocalTime && (
{row.timeLocal}
)} {showLabels && (
)}
{needsHighlighter ? ( ) : ( row.entry )}
); } function renderMetaItem(value: any, kind: LogsMetaKind) { if (kind === LogsMetaKind.LabelsMap) { return ( ); } return value; } interface LogsProps { data: LogsModel; highlighterExpressions: string[]; loading: boolean; position: string; range?: RawTimeRange; scanning?: boolean; scanRange?: RawTimeRange; onChangeTime?: (range: RawTimeRange) => void; onClickLabel?: (label: string, value: string) => void; onStartScanning?: () => void; onStopScanning?: () => void; } interface LogsState { dedup: LogsDedupStrategy; deferLogs: boolean; hiddenLogLevels: Set; renderAll: boolean; showLabels: boolean | null; // Tristate: null means auto showLocalTime: boolean; showUtc: boolean; } export default class Logs extends PureComponent { deferLogsTimer: NodeJS.Timer; renderAllTimer: NodeJS.Timer; state = { dedup: LogsDedupStrategy.none, deferLogs: true, hiddenLogLevels: new Set(), renderAll: false, showLabels: null, showLocalTime: true, showUtc: false, }; componentDidMount() { // Staged rendering if (this.state.deferLogs) { const { data } = this.props; const rowCount = data && data.rows ? data.rows.length : 0; // Render all right away if not too far over the limit const renderAll = rowCount <= PREVIEW_LIMIT * 2; this.deferLogsTimer = setTimeout(() => this.setState({ deferLogs: false, renderAll }), rowCount); } } componentDidUpdate(prevProps, prevState) { // Staged rendering if (prevState.deferLogs && !this.state.deferLogs && !this.state.renderAll) { this.renderAllTimer = setTimeout(() => this.setState({ renderAll: true }), 2000); } } componentWillUnmount() { clearTimeout(this.deferLogsTimer); clearTimeout(this.renderAllTimer); } onChangeDedup = (dedup: LogsDedupStrategy) => { this.setState(prevState => { if (prevState.dedup === dedup) { return { dedup: LogsDedupStrategy.none }; } return { dedup }; }); }; onChangeLabels = (event: React.SyntheticEvent) => { const target = event.target as HTMLInputElement; this.setState({ showLabels: target.checked, }); }; onChangeLocalTime = (event: React.SyntheticEvent) => { const target = event.target as HTMLInputElement; this.setState({ showLocalTime: target.checked, }); }; onChangeUtc = (event: React.SyntheticEvent) => { const target = event.target as HTMLInputElement; this.setState({ showUtc: target.checked, }); }; onToggleLogLevel = (rawLevel: string, hiddenRawLevels: Set) => { const hiddenLogLevels: Set = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level])); this.setState({ hiddenLogLevels }); }; onClickScan = (event: React.SyntheticEvent) => { event.preventDefault(); this.props.onStartScanning(); }; onClickStopScan = (event: React.SyntheticEvent) => { event.preventDefault(); this.props.onStopScanning(); }; render() { const { data, highlighterExpressions, loading = false, onClickLabel, position, range, scanning, scanRange, } = this.props; const { dedup, deferLogs, hiddenLogLevels, renderAll, showLocalTime, showUtc } = this.state; let { showLabels } = this.state; const hasData = data && data.rows && data.rows.length > 0; const showDuplicates = dedup !== LogsDedupStrategy.none; // Filtering const filteredData = filterLogLevels(data, hiddenLogLevels); const dedupedData = dedupLogRows(filteredData, dedup); const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0); const meta = [...data.meta]; if (dedup !== LogsDedupStrategy.none) { meta.push({ label: 'Dedup count', value: dedupCount, kind: LogsMetaKind.Number, }); } // Staged rendering const processedRows = dedupedData.rows; const firstRows = processedRows.slice(0, PREVIEW_LIMIT); const lastRows = processedRows.slice(PREVIEW_LIMIT); // Check for labels if (showLabels === null) { if (hasData) { showLabels = data.rows.some(row => _.size(row.uniqueLabels) > 0); } else { showLabels = true; } } // Grid options // const cssColumnSizes = []; // if (showDuplicates) { // cssColumnSizes.push('max-content'); // } // // Log-level indicator line // cssColumnSizes.push('3px'); // if (showUtc) { // cssColumnSizes.push('minmax(220px, max-content)'); // } // if (showLocalTime) { // cssColumnSizes.push('minmax(140px, max-content)'); // } // if (showLabels) { // cssColumnSizes.push('fit-content(20%)'); // } // cssColumnSizes.push('1fr'); // const logEntriesStyle = { // gridTemplateColumns: cssColumnSizes.join(' '), // }; const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...'; return (
Object.keys(LogsDedupStrategy).map((dedupType, i) => ( {dedupType} )) } /> {hasData && meta && (
{meta.map(item => (
{item.label}: {renderMetaItem(item.value, item.kind)}
))}
)}
{hasData && !deferLogs && // Only inject highlighterExpression in the first set for performance reasons firstRows.map(row => ( ))} {hasData && !deferLogs && renderAll && lastRows.map(row => ( ))} {hasData && deferLogs && Rendering {dedupedData.rows.length} rows...}
{!loading && !hasData && !scanning && (
No logs found. Scan for older logs
)} {scanning && (
{scanText} Stop scan
)}
); } }