diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 21f518a682b..2485dbda40a 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -3,25 +3,26 @@ import { TimeSeries } from 'app/core/core'; import colors from 'app/core/utils/colors'; export enum LogLevel { - crit = 'crit', - warn = 'warn', + crit = 'critical', + critical = 'critical', + warn = 'warning', + warning = 'warning', err = 'error', error = 'error', info = 'info', debug = 'debug', trace = 'trace', - none = 'none', + unkown = 'unkown', } export const LogLevelColor = { - [LogLevel.crit]: colors[7], - [LogLevel.warn]: colors[1], - [LogLevel.err]: colors[4], + [LogLevel.critical]: colors[7], + [LogLevel.warning]: colors[1], [LogLevel.error]: colors[4], [LogLevel.info]: colors[0], - [LogLevel.debug]: colors[3], - [LogLevel.trace]: colors[3], - [LogLevel.none]: '#eee', + [LogLevel.debug]: colors[5], + [LogLevel.trace]: colors[2], + [LogLevel.unkown]: '#ddd', }; export interface LogSearchMatch { @@ -119,6 +120,24 @@ export function dedupLogRows(logs: LogsModel, strategy: LogsDedupStrategy): Logs }; } +export function filterLogLevels(logs: LogsModel, hiddenLogLevels: Set): 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[] { // Graph time series by log level const seriesByLevel = {}; diff --git a/public/app/features/explore/Graph.tsx b/public/app/features/explore/Graph.tsx index 01e9d5e7921..10f3faa1267 100644 --- a/public/app/features/explore/Graph.tsx +++ b/public/app/features/explore/Graph.tsx @@ -83,6 +83,7 @@ interface GraphProps { size?: { width: number; height: number }; userOptions?: any; onChangeTime?: (range: RawTimeRange) => void; + onToggleSeries?: (alias: string, hiddenSeries: Set) => void; } interface GraphState { @@ -178,26 +179,29 @@ export class Graph extends PureComponent { onToggleSeries = (series: TimeSeries, exclusive: boolean) => { this.setState((state, props) => { - const { data } = props; + const { data, onToggleSeries } = props; const { hiddenSeries } = state; - const hidden = hiddenSeries.has(series.alias); + // Deduplicate series as visibility tracks the alias property const oneSeriesVisible = hiddenSeries.size === new Set(data.map(d => d.alias)).size - 1; + + let nextHiddenSeries = new Set(); if (exclusive) { - return { - hiddenSeries: - !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); + if (hiddenSeries.has(series.alias) || !oneSeriesVisible) { + nextHiddenSeries = new Set(data.filter(d => d.alias !== series.alias).map(d => d.alias)); + } } 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 { hiddenSeries: nextHiddenSeries, diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 9eee5c31376..9d875458024 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -2,7 +2,7 @@ import React, { Fragment, PureComponent } from 'react'; import Highlighter from 'react-highlight-words'; 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 { Switch } from 'app/core/components/Switch/Switch'; @@ -33,6 +33,7 @@ interface LogsProps { interface LogsState { dedup: LogsDedupStrategy; + hiddenLogLevels: Set; showLabels: boolean; showLocalTime: boolean; showUtc: boolean; @@ -41,6 +42,7 @@ interface LogsState { export default class Logs extends PureComponent { state = { dedup: LogsDedupStrategy.none, + hiddenLogLevels: new Set(), showLabels: true, showLocalTime: true, showUtc: false, @@ -76,11 +78,17 @@ export default class Logs extends PureComponent { }); }; + onToggleLogLevel = (rawLevel: string, hiddenRawLevels: Set) => { + const hiddenLogLevels: Set = new Set(Array.from(hiddenRawLevels).map(level => LogLevel[level])); + this.setState({ hiddenLogLevels }); + }; + render() { 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 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 meta = [...data.meta]; if (dedup !== LogsDedupStrategy.none) { @@ -113,6 +121,7 @@ export default class Logs extends PureComponent { range={range} id={`explore-logs-graph-${position}`} onChangeTime={this.props.onChangeTime} + onToggleSeries={this.onToggleLogLevel} userOptions={graphOptions} /> @@ -163,11 +172,11 @@ export default class Logs extends PureComponent {
{hasData && dedupedData.rows.map(row => ( - +
{row.duplicates > 0 && (
- {Array.apply(null, { length: row.duplicates }).map(index => ( + {Array.apply(null, { length: row.duplicates }).map((bogus, index) => (
))}
diff --git a/public/app/plugins/datasource/logging/result_transformer.test.ts b/public/app/plugins/datasource/logging/result_transformer.test.ts index 6f88c301fcd..c57190d927d 100644 --- a/public/app/plugins/datasource/logging/result_transformer.test.ts +++ b/public/app/plugins/datasource/logging/result_transformer.test.ts @@ -11,11 +11,17 @@ import { describe('getLoglevel()', () => { 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', () => { - 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', () => { @@ -102,7 +108,7 @@ describe('mergeStreamsToLogs()', () => { entry: 'WARN boooo', labels: '{foo="bar"}', key: 'EK1970-01-01T00:00:00Z{foo="bar"}', - logLevel: 'warn', + logLevel: 'warning', uniqueLabels: '', }, ]); @@ -141,7 +147,7 @@ describe('mergeStreamsToLogs()', () => { { entry: 'WARN boooo', labels: '{foo="bar", baz="1"}', - logLevel: 'warn', + logLevel: 'warning', uniqueLabels: '{baz="1"}', }, { diff --git a/public/app/plugins/datasource/logging/result_transformer.ts b/public/app/plugins/datasource/logging/result_transformer.ts index 3d04deb7ed2..a6b9ef70abb 100644 --- a/public/app/plugins/datasource/logging/result_transformer.ts +++ b/public/app/plugins/datasource/logging/result_transformer.ts @@ -14,13 +14,13 @@ import { DEFAULT_LIMIT } from './datasource'; /** * 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` */ export function getLogLevel(line: string): LogLevel { if (!line) { - return LogLevel.none; + return LogLevel.unkown; } let level: LogLevel; Object.keys(LogLevel).forEach(key => { @@ -32,7 +32,7 @@ export function getLogLevel(line: string): LogLevel { } }); if (!level) { - level = LogLevel.none; + level = LogLevel.unkown; } return level; } diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index edb637e5e22..23c6fbf0916 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -305,6 +305,7 @@ opacity: 0.8; } + .logs-row-level-critical, .logs-row-level-crit { background-color: #705da0; } @@ -314,6 +315,7 @@ background-color: #e24d42; } + .logs-row-level-warning, .logs-row-level-warn { background-color: #eab839; } @@ -322,11 +324,14 @@ background-color: #7eb26d; } - .logs-row-level-trace, .logs-row-level-debug { background-color: #1f78c1; } + .logs-row-level-trace { + background-color: #6ed0e0; + } + .logs-row-level__duplicates { position: absolute; width: 9px;