mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
* Move log's typings into grafana/ui * Update the way context is retrieved for Loki Major changes: 1. getLogRowContext expects row to be of LogRowModel type 2. getLogRowContext accepts generic options object, specific to a datasource of interest. limit option has been removed, and now it's a part of Loki's context query options (see below) 3. LogRowContextProvider performs two queries now. Before, it was Loki ds that performed queries in both directions when getLogRowContext. 4. Loki's getLogRowContext accepts options object of a type: interface LokiContextQueryOptions { direction?: 'BACKWARD' | 'FORWARD'; limit?: number; } This will enable querying in either direction independently and also slightly simplifies the way query errors are handled. LogRowContextProvider maps the results to a Loki specific context types, basically string[][], as raw log lines are displayed in first version.
317 lines
9.4 KiB
TypeScript
317 lines
9.4 KiB
TypeScript
import _ from 'lodash';
|
|
import React, { PureComponent } from 'react';
|
|
|
|
import * as rangeUtil from '@grafana/ui/src/utils/rangeutil';
|
|
import {
|
|
RawTimeRange,
|
|
Switch,
|
|
LogLevel,
|
|
TimeZone,
|
|
TimeRange,
|
|
AbsoluteTimeRange,
|
|
LogsMetaKind,
|
|
LogsModel,
|
|
LogsDedupStrategy,
|
|
LogRowModel,
|
|
} from '@grafana/ui';
|
|
import TimeSeries from 'app/core/time_series2';
|
|
|
|
import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup';
|
|
|
|
import Graph from './Graph';
|
|
import { LogLabels } from './LogLabels';
|
|
import { LogRow } from './LogRow';
|
|
import { LogsDedupDescription } from 'app/core/logs_model';
|
|
|
|
const PREVIEW_LIMIT = 100;
|
|
|
|
const graphOptions = {
|
|
series: {
|
|
stack: true,
|
|
bars: {
|
|
show: true,
|
|
lineWidth: 5,
|
|
// barWidth: 10,
|
|
},
|
|
// stack: true,
|
|
},
|
|
yaxis: {
|
|
tickDecimals: 0,
|
|
},
|
|
};
|
|
|
|
function renderMetaItem(value: any, kind: LogsMetaKind) {
|
|
if (kind === LogsMetaKind.LabelsMap) {
|
|
return (
|
|
<span className="logs-meta-item__labels">
|
|
<LogLabels labels={value} plain />
|
|
</span>
|
|
);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
interface Props {
|
|
data?: LogsModel;
|
|
dedupedData?: LogsModel;
|
|
width: number;
|
|
exploreId: string;
|
|
highlighterExpressions: string[];
|
|
loading: boolean;
|
|
range: TimeRange;
|
|
timeZone: TimeZone;
|
|
scanning?: boolean;
|
|
scanRange?: RawTimeRange;
|
|
dedupStrategy: LogsDedupStrategy;
|
|
hiddenLogLevels: Set<LogLevel>;
|
|
onChangeTime?: (range: AbsoluteTimeRange) => void;
|
|
onClickLabel?: (label: string, value: string) => void;
|
|
onStartScanning?: () => void;
|
|
onStopScanning?: () => void;
|
|
onDedupStrategyChange: (dedupStrategy: LogsDedupStrategy) => void;
|
|
onToggleLogLevel: (hiddenLogLevels: Set<LogLevel>) => void;
|
|
getRowContext?: (row: LogRowModel, options?: any) => Promise<any>;
|
|
}
|
|
|
|
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) => {
|
|
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);
|
|
};
|
|
|
|
onClickScan = (event: React.SyntheticEvent) => {
|
|
event.preventDefault();
|
|
this.props.onStartScanning();
|
|
};
|
|
|
|
onClickStopScan = (event: React.SyntheticEvent) => {
|
|
event.preventDefault();
|
|
this.props.onStopScanning();
|
|
};
|
|
|
|
render() {
|
|
const {
|
|
data,
|
|
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;
|
|
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 ? [...data.meta] : [];
|
|
|
|
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);
|
|
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
|
|
? data.series.map(series => new TimeSeries(series))
|
|
: [new TimeSeries({ datapoints: [] })];
|
|
const absRange = {
|
|
from: range.from.valueOf(),
|
|
to: range.to.valueOf(),
|
|
};
|
|
|
|
return (
|
|
<div className="logs-panel">
|
|
<div className="logs-panel-graph">
|
|
<Graph
|
|
data={timeSeries}
|
|
height={100}
|
|
width={width}
|
|
range={absRange}
|
|
timeZone={timeZone}
|
|
id={`explore-logs-graph-${exploreId}`}
|
|
onChangeTime={this.props.onChangeTime}
|
|
onToggleSeries={this.onToggleLogLevel}
|
|
userOptions={graphOptions}
|
|
/>
|
|
</div>
|
|
<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) => (
|
|
<ToggleButton
|
|
key={i}
|
|
value={dedupType}
|
|
onChange={this.onChangeDedup}
|
|
selected={dedupStrategy === dedupType}
|
|
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>
|
|
)}
|
|
|
|
<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}
|
|
getRowContext={this.props.getRowContext}
|
|
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}
|
|
getRowContext={this.props.getRowContext}
|
|
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>
|
|
)}
|
|
|
|
{scanning && (
|
|
<div className="logs-panel-nodata">
|
|
<span>{scanText}</span>
|
|
<a className="link" onClick={this.onClickStopScan}>
|
|
Stop scan
|
|
</a>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
}
|