mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Improve Loki logs render with ANSI colors (#15558)
* Improve Loki logs render with ANSI colors * fixup! Improve Loki logs render with ANSI colors * fixup! Improve Loki logs render with ANSI colors * fixup! Improve Loki logs render with ANSI colors
This commit is contained in:
@@ -22,7 +22,7 @@ export enum LogLevel {
|
|||||||
dbug = 'debug',
|
dbug = 'debug',
|
||||||
debug = 'debug',
|
debug = 'debug',
|
||||||
trace = 'trace',
|
trace = 'trace',
|
||||||
unkown = 'unkown',
|
unknown = 'unknown',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LogLevelColor = {
|
export const LogLevelColor = {
|
||||||
@@ -32,7 +32,7 @@ export const LogLevelColor = {
|
|||||||
[LogLevel.info]: colors[0],
|
[LogLevel.info]: colors[0],
|
||||||
[LogLevel.debug]: colors[5],
|
[LogLevel.debug]: colors[5],
|
||||||
[LogLevel.trace]: colors[2],
|
[LogLevel.trace]: colors[2],
|
||||||
[LogLevel.unkown]: getThemeColor('#8e8e8e', '#dde4ed'),
|
[LogLevel.unknown]: getThemeColor('#8e8e8e', '#dde4ed'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface LogSearchMatch {
|
export interface LogSearchMatch {
|
||||||
@@ -44,9 +44,11 @@ export interface LogSearchMatch {
|
|||||||
export interface LogRowModel {
|
export interface LogRowModel {
|
||||||
duplicates?: number;
|
duplicates?: number;
|
||||||
entry: string;
|
entry: string;
|
||||||
|
hasAnsi: boolean;
|
||||||
key: string; // timestamp + labels
|
key: string; // timestamp + labels
|
||||||
labels: LogsStreamLabels;
|
labels: LogsStreamLabels;
|
||||||
logLevel: LogLevel;
|
logLevel: LogLevel;
|
||||||
|
raw: string;
|
||||||
searchWords?: string[];
|
searchWords?: string[];
|
||||||
timestamp: string; // ISO with nanosec precision
|
timestamp: string; // ISO with nanosec precision
|
||||||
timeFromNow: string;
|
timeFromNow: string;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export class LogMessageAnsi extends PureComponent<Props, State> {
|
|||||||
prevValue: '',
|
prevValue: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
static getDerivedStateFromProps(props, state) {
|
static getDerivedStateFromProps(props: Props, state: State) {
|
||||||
if (props.value === state.prevValue) {
|
if (props.value === state.prevValue) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import classnames from 'classnames';
|
|||||||
|
|
||||||
import { LogRowModel, LogLabelStatsModel, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model';
|
import { LogRowModel, LogLabelStatsModel, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model';
|
||||||
import { LogLabels } from './LogLabels';
|
import { LogLabels } from './LogLabels';
|
||||||
import { findHighlightChunksInText, hasAnsiCodes } from 'app/core/utils/text';
|
import { findHighlightChunksInText } from 'app/core/utils/text';
|
||||||
import { LogLabelStats } from './LogLabelStats';
|
import { LogLabelStats } from './LogLabelStats';
|
||||||
import { LogMessageAnsi } from './LogMessageAnsi';
|
import { LogMessageAnsi } from './LogMessageAnsi';
|
||||||
|
|
||||||
@@ -130,13 +130,13 @@ export class LogRow extends PureComponent<Props, State> {
|
|||||||
parsedFieldHighlights,
|
parsedFieldHighlights,
|
||||||
showFieldStats,
|
showFieldStats,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
const { entry, hasAnsi, raw } = row;
|
||||||
const previewHighlights = highlighterExpressions && !_.isEqual(highlighterExpressions, row.searchWords);
|
const previewHighlights = highlighterExpressions && !_.isEqual(highlighterExpressions, row.searchWords);
|
||||||
const highlights = previewHighlights ? highlighterExpressions : row.searchWords;
|
const highlights = previewHighlights ? highlighterExpressions : row.searchWords;
|
||||||
const needsHighlighter = highlights && highlights.length > 0;
|
const needsHighlighter = highlights && highlights.length > 0 && highlights[0].length > 0;
|
||||||
const highlightClassName = classnames('logs-row__match-highlight', {
|
const highlightClassName = classnames('logs-row__match-highlight', {
|
||||||
'logs-row__match-highlight--preview': previewHighlights,
|
'logs-row__match-highlight--preview': previewHighlights,
|
||||||
});
|
});
|
||||||
const containsAnsiCodes = hasAnsiCodes(row.entry);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="logs-row">
|
<div className="logs-row">
|
||||||
@@ -160,25 +160,25 @@ export class LogRow extends PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="logs-row__message" onMouseEnter={this.onMouseOverMessage} onMouseLeave={this.onMouseOutMessage}>
|
<div className="logs-row__message" onMouseEnter={this.onMouseOverMessage} onMouseLeave={this.onMouseOutMessage}>
|
||||||
{containsAnsiCodes && <LogMessageAnsi value={row.entry} />}
|
{parsed && (
|
||||||
{!containsAnsiCodes && parsed && (
|
|
||||||
<Highlighter
|
<Highlighter
|
||||||
autoEscape
|
autoEscape
|
||||||
highlightTag={FieldHighlight(this.onClickHighlight)}
|
highlightTag={FieldHighlight(this.onClickHighlight)}
|
||||||
textToHighlight={row.entry}
|
textToHighlight={entry}
|
||||||
searchWords={parsedFieldHighlights}
|
searchWords={parsedFieldHighlights}
|
||||||
highlightClassName="logs-row__field-highlight"
|
highlightClassName="logs-row__field-highlight"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!containsAnsiCodes && !parsed && needsHighlighter && (
|
{!parsed && needsHighlighter && (
|
||||||
<Highlighter
|
<Highlighter
|
||||||
textToHighlight={row.entry}
|
textToHighlight={entry}
|
||||||
searchWords={highlights}
|
searchWords={highlights}
|
||||||
findChunks={findHighlightChunksInText}
|
findChunks={findHighlightChunksInText}
|
||||||
highlightClassName={highlightClassName}
|
highlightClassName={highlightClassName}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!containsAnsiCodes && !parsed && !needsHighlighter && row.entry}
|
{hasAnsi && !parsed && !needsHighlighter && <LogMessageAnsi value={raw} />}
|
||||||
|
{!hasAnsi && !parsed && !needsHighlighter && entry}
|
||||||
{showFieldStats && (
|
{showFieldStats && (
|
||||||
<div className="logs-row__stats">
|
<div className="logs-row__stats">
|
||||||
<LogLabelStats
|
<LogLabelStats
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ 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.unkown);
|
expect(getLogLevel('')).toBe(LogLevel.unknown);
|
||||||
});
|
});
|
||||||
|
|
||||||
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 information')).toBe(LogLevel.unkown);
|
expect(getLogLevel('this is information')).toBe(LogLevel.unknown);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns same log level for long and short version', () => {
|
it('returns same log level for long and short version', () => {
|
||||||
@@ -158,4 +158,46 @@ describe('mergeStreamsToLogs()', () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('detects ANSI codes', () => {
|
||||||
|
expect(
|
||||||
|
mergeStreamsToLogs([
|
||||||
|
{
|
||||||
|
labels: '{foo="bar"}',
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
line: "foo: [32m'bar'[39m",
|
||||||
|
ts: '1970-01-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
labels: '{bar="foo"}',
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
line: "bar: 'foo'",
|
||||||
|
ts: '1970-01-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]).rows
|
||||||
|
).toMatchObject([
|
||||||
|
{
|
||||||
|
entry: "bar: 'foo'",
|
||||||
|
hasAnsi: false,
|
||||||
|
key: 'EK1970-01-01T00:00:00Z{bar="foo"}',
|
||||||
|
labels: { bar: 'foo' },
|
||||||
|
logLevel: 'unknown',
|
||||||
|
raw: "bar: 'foo'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: "foo: 'bar'",
|
||||||
|
hasAnsi: true,
|
||||||
|
key: 'EK1970-01-01T00:00:00Z{foo="bar"}',
|
||||||
|
labels: { foo: 'bar' },
|
||||||
|
logLevel: 'unknown',
|
||||||
|
raw: "foo: [32m'bar'[39m",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ansicolor from 'ansicolor';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
LogsStreamLabels,
|
LogsStreamLabels,
|
||||||
LogsMetaKind,
|
LogsMetaKind,
|
||||||
} from 'app/core/logs_model';
|
} from 'app/core/logs_model';
|
||||||
|
import { hasAnsiCodes } from 'app/core/utils/text';
|
||||||
import { DEFAULT_MAX_LINES } from './datasource';
|
import { DEFAULT_MAX_LINES } from './datasource';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,7 +23,7 @@ import { DEFAULT_MAX_LINES } from './datasource';
|
|||||||
*/
|
*/
|
||||||
export function getLogLevel(line: string): LogLevel {
|
export function getLogLevel(line: string): LogLevel {
|
||||||
if (!line) {
|
if (!line) {
|
||||||
return LogLevel.unkown;
|
return LogLevel.unknown;
|
||||||
}
|
}
|
||||||
let level: LogLevel;
|
let level: LogLevel;
|
||||||
Object.keys(LogLevel).forEach(key => {
|
Object.keys(LogLevel).forEach(key => {
|
||||||
@@ -33,7 +35,7 @@ export function getLogLevel(line: string): LogLevel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!level) {
|
if (!level) {
|
||||||
level = LogLevel.unkown;
|
level = LogLevel.unknown;
|
||||||
}
|
}
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
@@ -125,6 +127,7 @@ export function processEntry(
|
|||||||
const timeFromNow = time.fromNow();
|
const timeFromNow = time.fromNow();
|
||||||
const timeLocal = time.format('YYYY-MM-DD HH:mm:ss');
|
const timeLocal = time.format('YYYY-MM-DD HH:mm:ss');
|
||||||
const logLevel = getLogLevel(line);
|
const logLevel = getLogLevel(line);
|
||||||
|
const hasAnsi = hasAnsiCodes(line);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
@@ -133,7 +136,9 @@ export function processEntry(
|
|||||||
timeEpochMs,
|
timeEpochMs,
|
||||||
timeLocal,
|
timeLocal,
|
||||||
uniqueLabels,
|
uniqueLabels,
|
||||||
entry: line,
|
hasAnsi,
|
||||||
|
entry: hasAnsi ? ansicolor.strip(line) : line,
|
||||||
|
raw: line,
|
||||||
labels: parsedLabels,
|
labels: parsedLabels,
|
||||||
searchWords: search ? [search] : [],
|
searchWords: search ? [search] : [],
|
||||||
timestamp: ts,
|
timestamp: ts,
|
||||||
|
|||||||
Reference in New Issue
Block a user