|
@@ -1,9 +1,18 @@
|
|
|
|
|
+import _ from 'lodash';
|
|
|
import React, { Fragment, PureComponent } from 'react';
|
|
import React, { Fragment, PureComponent } from 'react';
|
|
|
import Highlighter from 'react-highlight-words';
|
|
import Highlighter from 'react-highlight-words';
|
|
|
|
|
|
|
|
import * as rangeUtil from 'app/core/utils/rangeutil';
|
|
import * as rangeUtil from 'app/core/utils/rangeutil';
|
|
|
import { RawTimeRange } from 'app/types/series';
|
|
import { RawTimeRange } from 'app/types/series';
|
|
|
-import { LogsDedupStrategy, LogsModel, dedupLogRows, filterLogLevels, LogLevel } from 'app/core/logs_model';
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ LogsDedupStrategy,
|
|
|
|
|
+ LogsModel,
|
|
|
|
|
+ dedupLogRows,
|
|
|
|
|
+ filterLogLevels,
|
|
|
|
|
+ LogLevel,
|
|
|
|
|
+ LogsStreamLabels,
|
|
|
|
|
+ LogsMetaKind,
|
|
|
|
|
+} from 'app/core/logs_model';
|
|
|
import { findHighlightChunksInText } from 'app/core/utils/text';
|
|
import { findHighlightChunksInText } from 'app/core/utils/text';
|
|
|
import { Switch } from 'app/core/components/Switch/Switch';
|
|
import { Switch } from 'app/core/components/Switch/Switch';
|
|
|
|
|
|
|
@@ -23,6 +32,51 @@ const graphOptions = {
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+function renderMetaItem(value: any, kind: LogsMetaKind) {
|
|
|
|
|
+ if (kind === LogsMetaKind.LabelsMap) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <span className="logs-meta-item__value-labels">
|
|
|
|
|
+ <Labels labels={value} />
|
|
|
|
|
+ </span>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ return value;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+class Label extends PureComponent<{
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ value: string;
|
|
|
|
|
+ onClickLabel?: (label: string, value: string) => void;
|
|
|
|
|
+}> {
|
|
|
|
|
+ onClickLabel = () => {
|
|
|
|
|
+ const { onClickLabel, label, value } = this.props;
|
|
|
|
|
+ if (onClickLabel) {
|
|
|
|
|
+ onClickLabel(label, value);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ render() {
|
|
|
|
|
+ const { label, value } = this.props;
|
|
|
|
|
+ const tooltip = `${label}: ${value}`;
|
|
|
|
|
+ return (
|
|
|
|
|
+ <span className="logs-label" title={tooltip} onClick={this.onClickLabel}>
|
|
|
|
|
+ {value}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+class Labels extends PureComponent<{
|
|
|
|
|
+ labels: LogsStreamLabels;
|
|
|
|
|
+ onClickLabel?: (label: string, value: string) => void;
|
|
|
|
|
+}> {
|
|
|
|
|
+ render() {
|
|
|
|
|
+ const { labels, onClickLabel } = this.props;
|
|
|
|
|
+ return Object.keys(labels).map(key => (
|
|
|
|
|
+ <Label key={key} label={key} value={labels[key]} onClickLabel={onClickLabel} />
|
|
|
|
|
+ ));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
interface LogsProps {
|
|
interface LogsProps {
|
|
|
className?: string;
|
|
className?: string;
|
|
|
data: LogsModel;
|
|
data: LogsModel;
|
|
@@ -32,6 +86,7 @@ interface LogsProps {
|
|
|
scanning?: boolean;
|
|
scanning?: boolean;
|
|
|
scanRange?: RawTimeRange;
|
|
scanRange?: RawTimeRange;
|
|
|
onChangeTime?: (range: RawTimeRange) => void;
|
|
onChangeTime?: (range: RawTimeRange) => void;
|
|
|
|
|
+ onClickLabel?: (label: string, value: string) => void;
|
|
|
onStartScanning?: () => void;
|
|
onStartScanning?: () => void;
|
|
|
onStopScanning?: () => void;
|
|
onStopScanning?: () => void;
|
|
|
}
|
|
}
|
|
@@ -39,7 +94,7 @@ interface LogsProps {
|
|
|
interface LogsState {
|
|
interface LogsState {
|
|
|
dedup: LogsDedupStrategy;
|
|
dedup: LogsDedupStrategy;
|
|
|
hiddenLogLevels: Set<LogLevel>;
|
|
hiddenLogLevels: Set<LogLevel>;
|
|
|
- showLabels: boolean;
|
|
|
|
|
|
|
+ showLabels: boolean | null; // Tristate: null means auto
|
|
|
showLocalTime: boolean;
|
|
showLocalTime: boolean;
|
|
|
showUtc: boolean;
|
|
showUtc: boolean;
|
|
|
}
|
|
}
|
|
@@ -48,7 +103,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|
|
state = {
|
|
state = {
|
|
|
dedup: LogsDedupStrategy.none,
|
|
dedup: LogsDedupStrategy.none,
|
|
|
hiddenLogLevels: new Set(),
|
|
hiddenLogLevels: new Set(),
|
|
|
- showLabels: true,
|
|
|
|
|
|
|
+ showLabels: null,
|
|
|
showLocalTime: true,
|
|
showLocalTime: true,
|
|
|
showUtc: false,
|
|
showUtc: false,
|
|
|
};
|
|
};
|
|
@@ -99,9 +154,12 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
render() {
|
|
|
- const { className = '', data, loading = false, position, range, scanning, scanRange } = this.props;
|
|
|
|
|
- const { dedup, hiddenLogLevels, showLabels, showLocalTime, showUtc } = this.state;
|
|
|
|
|
|
|
+ const { className = '', data, loading = false, onClickLabel, position, range, scanning, scanRange } = this.props;
|
|
|
|
|
+ const { dedup, hiddenLogLevels, showLocalTime, showUtc } = this.state;
|
|
|
|
|
+ let { showLabels } = this.state;
|
|
|
const hasData = data && data.rows && data.rows.length > 0;
|
|
const hasData = data && data.rows && data.rows.length > 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Filtering
|
|
|
const filteredData = filterLogLevels(data, hiddenLogLevels);
|
|
const filteredData = filterLogLevels(data, hiddenLogLevels);
|
|
|
const dedupedData = dedupLogRows(filteredData, dedup);
|
|
const dedupedData = dedupLogRows(filteredData, dedup);
|
|
|
const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
|
|
const dedupCount = dedupedData.rows.reduce((sum, row) => sum + row.duplicates, 0);
|
|
@@ -109,9 +167,17 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|
|
if (dedup !== LogsDedupStrategy.none) {
|
|
if (dedup !== LogsDedupStrategy.none) {
|
|
|
meta.push({
|
|
meta.push({
|
|
|
label: 'Dedup count',
|
|
label: 'Dedup count',
|
|
|
- value: String(dedupCount),
|
|
|
|
|
|
|
+ value: dedupCount,
|
|
|
|
|
+ kind: LogsMetaKind.Number,
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // Check for labels
|
|
|
|
|
+ if (showLabels === null && hasData) {
|
|
|
|
|
+ showLabels = data.rows.some(row => _.size(row.uniqueLabels) > 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Grid options
|
|
|
const cssColumnSizes = ['3px']; // Log-level indicator line
|
|
const cssColumnSizes = ['3px']; // Log-level indicator line
|
|
|
if (showUtc) {
|
|
if (showUtc) {
|
|
|
cssColumnSizes.push('minmax(100px, max-content)');
|
|
cssColumnSizes.push('minmax(100px, max-content)');
|
|
@@ -177,7 +243,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|
|
{meta.map(item => (
|
|
{meta.map(item => (
|
|
|
<div className="logs-meta-item" key={item.label}>
|
|
<div className="logs-meta-item" key={item.label}>
|
|
|
<span className="logs-meta-item__label">{item.label}:</span>
|
|
<span className="logs-meta-item__label">{item.label}:</span>
|
|
|
- <span className="logs-meta-item__value">{item.value}</span>
|
|
|
|
|
|
|
+ <span className="logs-meta-item__value">{renderMetaItem(item.value, item.kind)}</span>
|
|
|
</div>
|
|
</div>
|
|
|
))}
|
|
))}
|
|
|
</div>
|
|
</div>
|
|
@@ -201,8 +267,8 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|
|
{showUtc && <div title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>{row.timestamp}</div>}
|
|
{showUtc && <div title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>{row.timestamp}</div>}
|
|
|
{showLocalTime && <div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>}
|
|
{showLocalTime && <div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>}
|
|
|
{showLabels && (
|
|
{showLabels && (
|
|
|
- <div className="max-width" title={row.labels}>
|
|
|
|
|
- {row.labels}
|
|
|
|
|
|
|
+ <div className="logs-row-labels">
|
|
|
|
|
+ <Labels labels={row.uniqueLabels} onClickLabel={onClickLabel} />
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
|
<div>
|
|
<div>
|