diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index f4ad11e1b0e..a9b96190d9b 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -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; diff --git a/public/app/features/explore/LogMessageAnsi.tsx b/public/app/features/explore/LogMessageAnsi.tsx index bed28bc674d..53147656d6a 100644 --- a/public/app/features/explore/LogMessageAnsi.tsx +++ b/public/app/features/explore/LogMessageAnsi.tsx @@ -38,7 +38,7 @@ export class LogMessageAnsi extends PureComponent { prevValue: '', }; - static getDerivedStateFromProps(props, state) { + static getDerivedStateFromProps(props: Props, state: State) { if (props.value === state.prevValue) { return null; } diff --git a/public/app/features/explore/LogRow.tsx b/public/app/features/explore/LogRow.tsx index e5c3ec0558c..d7615446b21 100644 --- a/public/app/features/explore/LogRow.tsx +++ b/public/app/features/explore/LogRow.tsx @@ -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 { 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 (
@@ -160,25 +160,25 @@ export class LogRow extends PureComponent {
)}
- {containsAnsiCodes && } - {!containsAnsiCodes && parsed && ( + {parsed && ( )} - {!containsAnsiCodes && !parsed && needsHighlighter && ( + {!parsed && needsHighlighter && ( )} - {!containsAnsiCodes && !parsed && !needsHighlighter && row.entry} + {hasAnsi && !parsed && !needsHighlighter && } + {!hasAnsi && !parsed && !needsHighlighter && entry} {showFieldStats && (
{ 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'", + }, + ]); + }); }); diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 9cd4ee0779b..1fbabba1789 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -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,