mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #14176 from grafana/davkal/explore-logging-level-filter
Explore: Filter logs by log level
This commit is contained in:
commit
9afb8b64ed
@ -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
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
|||||||
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,26 +179,29 @@ 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)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Prune hidden series no longer part of those available from the most recent query
|
|
||||||
const availableSeries = new Set(data.map(d => d.alias));
|
|
||||||
const nextHiddenSeries = intersect(new Set(hiddenSeries), availableSeries);
|
|
||||||
if (nextHiddenSeries.has(series.alias)) {
|
|
||||||
nextHiddenSeries.delete(series.alias);
|
|
||||||
} else {
|
} else {
|
||||||
nextHiddenSeries.add(series.alias);
|
// Prune hidden series no longer part of those available from the most recent query
|
||||||
|
const availableSeries = new Set(data.map(d => d.alias));
|
||||||
|
nextHiddenSeries = intersect(new Set(hiddenSeries), availableSeries);
|
||||||
|
if (nextHiddenSeries.has(series.alias)) {
|
||||||
|
nextHiddenSeries.delete(series.alias);
|
||||||
|
} else {
|
||||||
|
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';
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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> {
|
|||||||
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> {
|
|||||||
<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 {
|
|||||||
|
|
||||||
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()', () => {
|
|||||||
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()', () => {
|
|||||||
{
|
{
|
||||||
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';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!level) {
|
if (!level) {
|
||||||
level = LogLevel.none;
|
level = LogLevel.unkown;
|
||||||
}
|
}
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
@ -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 @@
|
|||||||
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 @@
|
|||||||
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;
|
||||||
|
Loading…
Reference in New Issue
Block a user