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',
|
||||
debug = 'debug',
|
||||
trace = 'trace',
|
||||
unkown = 'unkown',
|
||||
unknown = 'unknown',
|
||||
}
|
||||
|
||||
export const LogLevelColor = {
|
||||
@@ -32,7 +32,7 @@ export const LogLevelColor = {
|
||||
[LogLevel.info]: colors[0],
|
||||
[LogLevel.debug]: colors[5],
|
||||
[LogLevel.trace]: colors[2],
|
||||
[LogLevel.unkown]: getThemeColor('#8e8e8e', '#dde4ed'),
|
||||
[LogLevel.unknown]: getThemeColor('#8e8e8e', '#dde4ed'),
|
||||
};
|
||||
|
||||
export interface LogSearchMatch {
|
||||
@@ -44,9 +44,11 @@ export interface LogSearchMatch {
|
||||
export interface LogRowModel {
|
||||
duplicates?: number;
|
||||
entry: string;
|
||||
hasAnsi: boolean;
|
||||
key: string; // timestamp + labels
|
||||
labels: LogsStreamLabels;
|
||||
logLevel: LogLevel;
|
||||
raw: string;
|
||||
searchWords?: string[];
|
||||
timestamp: string; // ISO with nanosec precision
|
||||
timeFromNow: string;
|
||||
|
@@ -38,7 +38,7 @@ export class LogMessageAnsi extends PureComponent<Props, State> {
|
||||
prevValue: '',
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
if (props.value === state.prevValue) {
|
||||
return null;
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import classnames from 'classnames';
|
||||
|
||||
import { LogRowModel, LogLabelStatsModel, LogsParser, calculateFieldStats, getParser } from 'app/core/logs_model';
|
||||
import { LogLabels } from './LogLabels';
|
||||
import { findHighlightChunksInText, hasAnsiCodes } from 'app/core/utils/text';
|
||||
import { findHighlightChunksInText } from 'app/core/utils/text';
|
||||
import { LogLabelStats } from './LogLabelStats';
|
||||
import { LogMessageAnsi } from './LogMessageAnsi';
|
||||
|
||||
@@ -130,13 +130,13 @@ export class LogRow extends PureComponent<Props, State> {
|
||||
parsedFieldHighlights,
|
||||
showFieldStats,
|
||||
} = this.state;
|
||||
const { entry, hasAnsi, raw } = row;
|
||||
const previewHighlights = highlighterExpressions && !_.isEqual(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', {
|
||||
'logs-row__match-highlight--preview': previewHighlights,
|
||||
});
|
||||
const containsAnsiCodes = hasAnsiCodes(row.entry);
|
||||
|
||||
return (
|
||||
<div className="logs-row">
|
||||
@@ -160,25 +160,25 @@ export class LogRow extends PureComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
<div className="logs-row__message" onMouseEnter={this.onMouseOverMessage} onMouseLeave={this.onMouseOutMessage}>
|
||||
{containsAnsiCodes && <LogMessageAnsi value={row.entry} />}
|
||||
{!containsAnsiCodes && parsed && (
|
||||
{parsed && (
|
||||
<Highlighter
|
||||
autoEscape
|
||||
highlightTag={FieldHighlight(this.onClickHighlight)}
|
||||
textToHighlight={row.entry}
|
||||
textToHighlight={entry}
|
||||
searchWords={parsedFieldHighlights}
|
||||
highlightClassName="logs-row__field-highlight"
|
||||
/>
|
||||
)}
|
||||
{!containsAnsiCodes && !parsed && needsHighlighter && (
|
||||
{!parsed && needsHighlighter && (
|
||||
<Highlighter
|
||||
textToHighlight={row.entry}
|
||||
textToHighlight={entry}
|
||||
searchWords={highlights}
|
||||
findChunks={findHighlightChunksInText}
|
||||
highlightClassName={highlightClassName}
|
||||
/>
|
||||
)}
|
||||
{!containsAnsiCodes && !parsed && !needsHighlighter && row.entry}
|
||||
{hasAnsi && !parsed && !needsHighlighter && <LogMessageAnsi value={raw} />}
|
||||
{!hasAnsi && !parsed && !needsHighlighter && entry}
|
||||
{showFieldStats && (
|
||||
<div className="logs-row__stats">
|
||||
<LogLabelStats
|
||||
|
@@ -11,11 +11,11 @@ import {
|
||||
|
||||
describe('getLoglevel()', () => {
|
||||
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', () => {
|
||||
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', () => {
|
||||
@@ -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 moment from 'moment';
|
||||
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
LogsStreamLabels,
|
||||
LogsMetaKind,
|
||||
} from 'app/core/logs_model';
|
||||
import { hasAnsiCodes } from 'app/core/utils/text';
|
||||
import { DEFAULT_MAX_LINES } from './datasource';
|
||||
|
||||
/**
|
||||
@@ -21,7 +23,7 @@ import { DEFAULT_MAX_LINES } from './datasource';
|
||||
*/
|
||||
export function getLogLevel(line: string): LogLevel {
|
||||
if (!line) {
|
||||
return LogLevel.unkown;
|
||||
return LogLevel.unknown;
|
||||
}
|
||||
let level: LogLevel;
|
||||
Object.keys(LogLevel).forEach(key => {
|
||||
@@ -33,7 +35,7 @@ export function getLogLevel(line: string): LogLevel {
|
||||
}
|
||||
});
|
||||
if (!level) {
|
||||
level = LogLevel.unkown;
|
||||
level = LogLevel.unknown;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
@@ -125,6 +127,7 @@ export function processEntry(
|
||||
const timeFromNow = time.fromNow();
|
||||
const timeLocal = time.format('YYYY-MM-DD HH:mm:ss');
|
||||
const logLevel = getLogLevel(line);
|
||||
const hasAnsi = hasAnsiCodes(line);
|
||||
|
||||
return {
|
||||
key,
|
||||
@@ -133,7 +136,9 @@ export function processEntry(
|
||||
timeEpochMs,
|
||||
timeLocal,
|
||||
uniqueLabels,
|
||||
entry: line,
|
||||
hasAnsi,
|
||||
entry: hasAnsi ? ansicolor.strip(line) : line,
|
||||
raw: line,
|
||||
labels: parsedLabels,
|
||||
searchWords: search ? [search] : [],
|
||||
timestamp: ts,
|
||||
|
Reference in New Issue
Block a user