Commit 425636ff by Valentin Agachi Committed by David

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
parent c4c150bd
......@@ -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: 'bar'",
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: 'bar'",
},
]);
});
});
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,
......
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