grafana/public/app/features/explore/Logs.tsx

302 lines
9.1 KiB
TypeScript
Raw Normal View History

import _ from 'lodash';
import React, { PureComponent } from 'react';
import * as rangeUtil from '@grafana/ui/src/utils/rangeutil';
import { RawTimeRange, Switch, LogLevel, TimeZone, TimeRange, AbsoluteTimeRange } from '@grafana/ui';
import TimeSeries from 'app/core/time_series2';
import { LogsDedupDescription, LogsDedupStrategy, LogsModel, LogsMetaKind } from 'app/core/logs_model';
import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
import Graph from './Graph';
import { LogLabels } from './LogLabels';
import { LogRow } from './LogRow';
const PREVIEW_LIMIT = 100;
const graphOptions = {
series: {
stack: true,
bars: {
show: true,
2018-11-06 04:07:12 -06:00
lineWidth: 5,
// barWidth: 10,
},
2018-11-06 04:07:12 -06:00
// stack: true,
},
yaxis: {
tickDecimals: 0,
},
};
function renderMetaItem(value: any, kind: LogsMetaKind) {
if (kind === LogsMetaKind.LabelsMap) {
return (
2018-12-06 01:08:16 -06:00
<span className="logs-meta-item__labels">
<LogLabels labels={value} plain />
</span>
);
}
return value;
}
interface Props {
data?: LogsModel;
dedupedData?: LogsModel;
width: number;
2019-01-11 11:26:56 -06:00
exploreId: string;
highlighterExpressions: string[];
loading: boolean;
range: TimeRange;
timeZone: TimeZone;
scanning?: boolean;
2018-11-27 09:35:37 -06:00
scanRange?: RawTimeRange;
2019-02-07 10:46:33 -06:00
dedupStrategy: LogsDedupStrategy;
hiddenLogLevels: Set<LogLevel>;
onChangeTime?: (range: AbsoluteTimeRange) => void;
onClickLabel?: (label: string, value: string) => void;
2018-11-27 09:35:37 -06:00
onStartScanning?: () => void;
onStopScanning?: () => void;
2019-02-07 10:46:33 -06:00
onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
onToggleLogLevel: (hiddenLogLevels: Set<LogLevel>) => void;
}
interface State {
deferLogs: boolean;
renderAll: boolean;
showLabels: boolean;
showLocalTime: boolean;
showUtc: boolean;
}
export default class Logs extends PureComponent<Props, State> {
deferLogsTimer: NodeJS.Timer;
renderAllTimer: NodeJS.Timer;
state = {
deferLogs: true,
renderAll: false,
showLabels: false,
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) => {
2019-02-07 10:46:33 -06:00
const { onDedupStrategyChange } = this.props;
if (this.props.dedupStrategy === dedup) {
return onDedupStrategyChange(LogsDedupStrategy.none);
}
return onDedupStrategyChange(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<string>) => {
const hiddenLogLevels: Set<LogLevel> = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level]));
this.props.onToggleLogLevel(hiddenLogLevels);
};
2018-11-27 09:35:37 -06:00
onClickScan = (event: React.SyntheticEvent) => {
event.preventDefault();
2018-11-27 09:35:37 -06:00
this.props.onStartScanning();
};
onClickStopScan = (event: React.SyntheticEvent) => {
event.preventDefault();
this.props.onStopScanning();
};
render() {
const {
data,
2019-01-11 11:26:56 -06:00
exploreId,
highlighterExpressions,
loading = false,
onClickLabel,
range,
timeZone,
scanning,
scanRange,
width,
dedupedData,
} = this.props;
if (!data) {
return null;
}
const { deferLogs, renderAll, showLabels, showLocalTime, showUtc } = this.state;
2019-02-07 10:46:33 -06:00
const { dedupStrategy } = this.props;
const hasData = data && data.rows && data.rows.length > 0;
const hasLabel = hasData && dedupedData.hasUniqueLabels;
const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
const showDuplicates = dedupStrategy !== LogsDedupStrategy.none && dedupCount > 0;
const meta = [...data.meta];
2019-02-07 10:46:33 -06:00
if (dedupStrategy !== 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);
2018-11-27 09:35:37 -06:00
const scanText = scanRange ? `Scanning ${rangeUtil.describeTimeRange(scanRange)}` : 'Scanning...';
// React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead
const getRows = () => processedRows;
const timeSeries = data.series.map(series => new TimeSeries(series));
const absRange = {
from: range.from.valueOf(),
to: range.to.valueOf(),
};
return (
2018-12-06 01:08:16 -06:00
<div className="logs-panel">
<div className="logs-panel-graph">
<Graph
data={timeSeries}
height={100}
width={width}
range={absRange}
timeZone={timeZone}
2019-01-11 11:26:56 -06:00
id={`explore-logs-graph-${exploreId}`}
2018-11-05 16:36:58 -06:00
onChangeTime={this.props.onChangeTime}
onToggleSeries={this.onToggleLogLevel}
userOptions={graphOptions}
/>
</div>
2018-12-06 01:08:16 -06:00
<div className="logs-panel-options">
<div className="logs-panel-controls">
<Switch label="Timestamp" checked={showUtc} onChange={this.onChangeUtc} transparent />
<Switch label="Local time" checked={showLocalTime} onChange={this.onChangeLocalTime} transparent />
<Switch label="Labels" checked={showLabels} onChange={this.onChangeLabels} transparent />
<ToggleButtonGroup label="Dedup" transparent={true}>
{Object.keys(LogsDedupStrategy).map((dedupType, i) => (
2018-12-11 03:00:29 -06:00
<ToggleButton
key={i}
value={dedupType}
onChange={this.onChangeDedup}
2019-02-07 10:46:33 -06:00
selected={dedupStrategy === dedupType}
2018-12-11 03:00:29 -06:00
tooltip={LogsDedupDescription[dedupType]}
>
{dedupType}
</ToggleButton>
))}
</ToggleButtonGroup>
</div>
</div>
{hasData && meta && (
<div className="logs-panel-meta">
{meta.map(item => (
<div className="logs-panel-meta__item" key={item.label}>
<span className="logs-panel-meta__label">{item.label}:</span>
<span className="logs-panel-meta__value">{renderMetaItem(item.value, item.kind)}</span>
</div>
))}
</div>
)}
2018-12-07 10:23:35 -06:00
2018-12-05 08:53:20 -06:00
<div className="logs-rows">
{hasData &&
!deferLogs && // Only inject highlighterExpression in the first set for performance reasons
firstRows.map((row, index) => (
<LogRow
key={index}
getRows={getRows}
highlighterExpressions={highlighterExpressions}
row={row}
showDuplicates={showDuplicates}
showLabels={showLabels && hasLabel}
showLocalTime={showLocalTime}
showUtc={showUtc}
onClickLabel={onClickLabel}
/>
))}
{hasData &&
!deferLogs &&
renderAll &&
lastRows.map((row, index) => (
<LogRow
key={PREVIEW_LIMIT + index}
getRows={getRows}
row={row}
showDuplicates={showDuplicates}
showLabels={showLabels && hasLabel}
showLocalTime={showLocalTime}
showUtc={showUtc}
onClickLabel={onClickLabel}
/>
))}
{hasData && deferLogs && <span>Rendering {dedupedData.rows.length} rows...</span>}
</div>
{!loading && !hasData && !scanning && (
<div className="logs-panel-nodata">
No logs found.
<a className="link" onClick={this.onClickScan}>
Scan for older logs
</a>
</div>
)}
2018-11-27 09:35:37 -06:00
{scanning && (
2018-12-06 01:08:16 -06:00
<div className="logs-panel-nodata">
2018-11-27 09:35:37 -06:00
<span>{scanText}</span>
<a className="link" onClick={this.onClickStopScan}>
Stop scan
</a>
</div>
)}
</div>
);
}
}