Commit 9afb8b64 by David Committed by GitHub

Merge pull request #14176 from grafana/davkal/explore-logging-level-filter

Explore: Filter logs by log level
parents 15637af1 ae5ec441
...@@ -3,25 +3,26 @@ import { TimeSeries } from 'app/core/core'; ...@@ -3,25 +3,26 @@ import { TimeSeries } from 'app/core/core';
import colors from 'app/core/utils/colors'; import colors from 'app/core/utils/colors';
export enum LogLevel { export enum LogLevel {
crit = 'crit', crit = 'critical',
warn = 'warn', critical = 'critical',
warn = 'warning',
warning = 'warning',
err = 'error', err = 'error',
error = 'error', error = 'error',
info = 'info', info = 'info',
debug = 'debug', debug = 'debug',
trace = 'trace', trace = 'trace',
none = 'none', unkown = 'unkown',
} }
export const LogLevelColor = { export const LogLevelColor = {
[LogLevel.crit]: colors[7], [LogLevel.critical]: colors[7],
[LogLevel.warn]: colors[1], [LogLevel.warning]: colors[1],
[LogLevel.err]: colors[4],
[LogLevel.error]: colors[4], [LogLevel.error]: colors[4],
[LogLevel.info]: colors[0], [LogLevel.info]: colors[0],
[LogLevel.debug]: colors[3], [LogLevel.debug]: colors[5],
[LogLevel.trace]: colors[3], [LogLevel.trace]: colors[2],
[LogLevel.none]: '#eee', [LogLevel.unkown]: '#ddd',
}; };
export interface LogSearchMatch { export interface LogSearchMatch {
...@@ -119,6 +120,24 @@ export function dedupLogRows(logs: LogsModel, strategy: LogsDedupStrategy): Logs ...@@ -119,6 +120,24 @@ export function dedupLogRows(logs: LogsModel, strategy: LogsDedupStrategy): Logs
}; };
} }
export function filterLogLevels(logs: LogsModel, hiddenLogLevels: Set<LogLevel>): LogsModel {
if (hiddenLogLevels.size === 0) {
return logs;
}
const filteredRows = logs.rows.reduce((result: LogRow[], row: LogRow, index, list) => {
if (!hiddenLogLevels.has(row.logLevel)) {
result.push(row);
}
return result;
}, []);
return {
...logs,
rows: filteredRows,
};
}
export function makeSeriesForLogs(rows: LogRow[], intervalMs: number): TimeSeries[] { export function makeSeriesForLogs(rows: LogRow[], intervalMs: number): TimeSeries[] {
// Graph time series by log level // Graph time series by log level
const seriesByLevel = {}; const seriesByLevel = {};
......
...@@ -83,6 +83,7 @@ interface GraphProps { ...@@ -83,6 +83,7 @@ interface GraphProps {
size?: { width: number; height: number }; size?: { width: number; height: number };
userOptions?: any; userOptions?: any;
onChangeTime?: (range: RawTimeRange) => void; onChangeTime?: (range: RawTimeRange) => void;
onToggleSeries?: (alias: string, hiddenSeries: Set<string>) => void;
} }
interface GraphState { interface GraphState {
...@@ -178,27 +179,30 @@ export class Graph extends PureComponent<GraphProps, GraphState> { ...@@ -178,27 +179,30 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
onToggleSeries = (series: TimeSeries, exclusive: boolean) => { onToggleSeries = (series: TimeSeries, exclusive: boolean) => {
this.setState((state, props) => { this.setState((state, props) => {
const { data } = props; const { data, onToggleSeries } = props;
const { hiddenSeries } = state; const { hiddenSeries } = state;
const hidden = hiddenSeries.has(series.alias);
// Deduplicate series as visibility tracks the alias property // Deduplicate series as visibility tracks the alias property
const oneSeriesVisible = hiddenSeries.size === new Set(data.map(d => d.alias)).size - 1; const oneSeriesVisible = hiddenSeries.size === new Set(data.map(d => d.alias)).size - 1;
let nextHiddenSeries = new Set();
if (exclusive) { if (exclusive) {
return { if (hiddenSeries.has(series.alias) || !oneSeriesVisible) {
hiddenSeries: nextHiddenSeries = new Set(data.filter(d => d.alias !== series.alias).map(d => d.alias));
!hidden && oneSeriesVisible
? new Set()
: new Set(data.filter(d => d.alias !== series.alias).map(d => d.alias)),
};
} }
} else {
// Prune hidden series no longer part of those available from the most recent query // Prune hidden series no longer part of those available from the most recent query
const availableSeries = new Set(data.map(d => d.alias)); const availableSeries = new Set(data.map(d => d.alias));
const nextHiddenSeries = intersect(new Set(hiddenSeries), availableSeries); nextHiddenSeries = intersect(new Set(hiddenSeries), availableSeries);
if (nextHiddenSeries.has(series.alias)) { if (nextHiddenSeries.has(series.alias)) {
nextHiddenSeries.delete(series.alias); nextHiddenSeries.delete(series.alias);
} else { } else {
nextHiddenSeries.add(series.alias); nextHiddenSeries.add(series.alias);
} }
}
if (onToggleSeries) {
onToggleSeries(series.alias, nextHiddenSeries);
}
return { return {
hiddenSeries: nextHiddenSeries, hiddenSeries: nextHiddenSeries,
}; };
......
...@@ -2,7 +2,7 @@ import React, { Fragment, PureComponent } from 'react'; ...@@ -2,7 +2,7 @@ import React, { Fragment, PureComponent } from 'react';
import Highlighter from 'react-highlight-words'; import Highlighter from 'react-highlight-words';
import { RawTimeRange } from 'app/types/series'; import { RawTimeRange } from 'app/types/series';
import { LogsDedupStrategy, LogsModel, dedupLogRows } from 'app/core/logs_model'; import { LogsDedupStrategy, LogsModel, dedupLogRows, filterLogLevels, LogLevel } 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';
...@@ -33,6 +33,7 @@ interface LogsProps { ...@@ -33,6 +33,7 @@ interface LogsProps {
interface LogsState { interface LogsState {
dedup: LogsDedupStrategy; dedup: LogsDedupStrategy;
hiddenLogLevels: Set<LogLevel>;
showLabels: boolean; showLabels: boolean;
showLocalTime: boolean; showLocalTime: boolean;
showUtc: boolean; showUtc: boolean;
...@@ -41,6 +42,7 @@ interface LogsState { ...@@ -41,6 +42,7 @@ interface LogsState {
export default class Logs extends PureComponent<LogsProps, LogsState> { export default class Logs extends PureComponent<LogsProps, LogsState> {
state = { state = {
dedup: LogsDedupStrategy.none, dedup: LogsDedupStrategy.none,
hiddenLogLevels: new Set(),
showLabels: true, showLabels: true,
showLocalTime: true, showLocalTime: true,
showUtc: false, showUtc: false,
...@@ -76,11 +78,17 @@ export default class Logs extends PureComponent<LogsProps, LogsState> { ...@@ -76,11 +78,17 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
}); });
}; };
onToggleLogLevel = (rawLevel: string, hiddenRawLevels: Set<string>) => {
const hiddenLogLevels: Set<LogLevel> = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level]));
this.setState({ hiddenLogLevels });
};
render() { render() {
const { className = '', data, loading = false, position, range } = this.props; const { className = '', data, loading = false, position, range } = this.props;
const { dedup, showLabels, showLocalTime, showUtc } = this.state; const { dedup, hiddenLogLevels, showLabels, showLocalTime, showUtc } = this.state;
const hasData = data && data.rows && data.rows.length > 0; const hasData = data && data.rows && data.rows.length > 0;
const dedupedData = dedupLogRows(data, dedup); const filteredData = filterLogLevels(data, hiddenLogLevels);
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);
const meta = [...data.meta]; const meta = [...data.meta];
if (dedup !== LogsDedupStrategy.none) { if (dedup !== LogsDedupStrategy.none) {
...@@ -113,6 +121,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> { ...@@ -113,6 +121,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
range={range} range={range}
id={`explore-logs-graph-${position}`} id={`explore-logs-graph-${position}`}
onChangeTime={this.props.onChangeTime} onChangeTime={this.props.onChangeTime}
onToggleSeries={this.onToggleLogLevel}
userOptions={graphOptions} userOptions={graphOptions}
/> />
</div> </div>
...@@ -163,11 +172,11 @@ export default class Logs extends PureComponent<LogsProps, LogsState> { ...@@ -163,11 +172,11 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
<div className="logs-entries" style={logEntriesStyle}> <div className="logs-entries" style={logEntriesStyle}>
{hasData && {hasData &&
dedupedData.rows.map(row => ( dedupedData.rows.map(row => (
<Fragment key={row.key}> <Fragment key={row.key + row.duplicates}>
<div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''}> <div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''}>
{row.duplicates > 0 && ( {row.duplicates > 0 && (
<div className="logs-row-level__duplicates" title={`${row.duplicates} duplicates`}> <div className="logs-row-level__duplicates" title={`${row.duplicates} duplicates`}>
{Array.apply(null, { length: row.duplicates }).map(index => ( {Array.apply(null, { length: row.duplicates }).map((bogus, index) => (
<div className="logs-row-level__duplicate" key={`${index}`} /> <div className="logs-row-level__duplicate" key={`${index}`} />
))} ))}
</div> </div>
......
...@@ -11,11 +11,17 @@ import { ...@@ -11,11 +11,17 @@ import {
describe('getLoglevel()', () => { describe('getLoglevel()', () => {
it('returns no log level on empty line', () => { it('returns no log level on empty line', () => {
expect(getLogLevel('')).toBe(LogLevel.none); expect(getLogLevel('')).toBe(LogLevel.unkown);
}); });
it('returns no log level on when level is part of a word', () => { it('returns no log level on when level is part of a word', () => {
expect(getLogLevel('this is a warning')).toBe(LogLevel.none); expect(getLogLevel('this is information')).toBe(LogLevel.unkown);
});
it('returns same log level for long and short version', () => {
expect(getLogLevel('[Warn]')).toBe(LogLevel.warning);
expect(getLogLevel('[Warning]')).toBe(LogLevel.warning);
expect(getLogLevel('[Warn]')).toBe('warning');
}); });
it('returns log level on line contains a log level', () => { it('returns log level on line contains a log level', () => {
...@@ -102,7 +108,7 @@ describe('mergeStreamsToLogs()', () => { ...@@ -102,7 +108,7 @@ describe('mergeStreamsToLogs()', () => {
entry: 'WARN boooo', entry: 'WARN boooo',
labels: '{foo="bar"}', labels: '{foo="bar"}',
key: 'EK1970-01-01T00:00:00Z{foo="bar"}', key: 'EK1970-01-01T00:00:00Z{foo="bar"}',
logLevel: 'warn', logLevel: 'warning',
uniqueLabels: '', uniqueLabels: '',
}, },
]); ]);
...@@ -141,7 +147,7 @@ describe('mergeStreamsToLogs()', () => { ...@@ -141,7 +147,7 @@ describe('mergeStreamsToLogs()', () => {
{ {
entry: 'WARN boooo', entry: 'WARN boooo',
labels: '{foo="bar", baz="1"}', labels: '{foo="bar", baz="1"}',
logLevel: 'warn', logLevel: 'warning',
uniqueLabels: '{baz="1"}', uniqueLabels: '{baz="1"}',
}, },
{ {
......
...@@ -14,13 +14,13 @@ import { DEFAULT_LIMIT } from './datasource'; ...@@ -14,13 +14,13 @@ import { DEFAULT_LIMIT } from './datasource';
/** /**
* Returns the log level of a log line. * Returns the log level of a log line.
* Parse the line for level words. If no level is found, it returns `LogLevel.none`. * Parse the line for level words. If no level is found, it returns `LogLevel.unknown`.
* *
* Example: `getLogLevel('WARN 1999-12-31 this is great') // LogLevel.warn` * Example: `getLogLevel('WARN 1999-12-31 this is great') // LogLevel.warn`
*/ */
export function getLogLevel(line: string): LogLevel { export function getLogLevel(line: string): LogLevel {
if (!line) { if (!line) {
return LogLevel.none; return LogLevel.unkown;
} }
let level: LogLevel; let level: LogLevel;
Object.keys(LogLevel).forEach(key => { Object.keys(LogLevel).forEach(key => {
...@@ -32,7 +32,7 @@ export function getLogLevel(line: string): LogLevel { ...@@ -32,7 +32,7 @@ export function getLogLevel(line: string): LogLevel {
} }
}); });
if (!level) { if (!level) {
level = LogLevel.none; level = LogLevel.unkown;
} }
return level; return level;
} }
......
...@@ -305,6 +305,7 @@ ...@@ -305,6 +305,7 @@
opacity: 0.8; opacity: 0.8;
} }
.logs-row-level-critical,
.logs-row-level-crit { .logs-row-level-crit {
background-color: #705da0; background-color: #705da0;
} }
...@@ -314,6 +315,7 @@ ...@@ -314,6 +315,7 @@
background-color: #e24d42; background-color: #e24d42;
} }
.logs-row-level-warning,
.logs-row-level-warn { .logs-row-level-warn {
background-color: #eab839; background-color: #eab839;
} }
...@@ -322,11 +324,14 @@ ...@@ -322,11 +324,14 @@
background-color: #7eb26d; background-color: #7eb26d;
} }
.logs-row-level-trace,
.logs-row-level-debug { .logs-row-level-debug {
background-color: #1f78c1; background-color: #1f78c1;
} }
.logs-row-level-trace {
background-color: #6ed0e0;
}
.logs-row-level__duplicates { .logs-row-level__duplicates {
position: absolute; position: absolute;
width: 9px; width: 9px;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment