The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/logs/components/LogMessageAnsi.tsx

103 lines
2.6 KiB

import ansicolor from 'ansicolor';
import React, { PureComponent } from 'react';
import Highlighter from 'react-highlight-words';
import { findHighlightChunksInText, GrafanaTheme2 } from '@grafana/data';
import { withTheme2, Themeable2 } from '@grafana/ui';
interface Style {
[key: string]: string;
}
interface ParsedChunk {
style: Style;
text: string;
}
function convertCSSToStyle(theme: GrafanaTheme2, css: string): Style {
return css.split(/;\s*/).reduce<Style>((accumulated, line) => {
// The ansicolor package returns this color if the chunk has the ANSI dim
// style (`\e[2m`), but it is nearly unreadable in the dark theme, so we use
// GrafanaTheme2 instead to style it in a way that works across all themes.
if (line === 'color:rgba(0,0,0,0.5)') {
return { color: theme.colors.text.secondary };
}
const match = line.match(/([^:\s]+)\s*:\s*(.+)/);
if (match && match[1] && match[2]) {
const key = match[1].replace(/-([a-z])/g, (_, character) => character.toUpperCase());
accumulated[key] = match[2];
}
return accumulated;
}, {});
}
interface Props extends Themeable2 {
value: string;
highlight?: {
searchWords: string[];
highlightClassName: string;
};
}
interface State {
chunks: ParsedChunk[];
prevValue: string;
}
export class UnThemedLogMessageAnsi extends PureComponent<Props, State> {
state: State = {
chunks: [],
prevValue: '',
};
static getDerivedStateFromProps(props: Props, state: State) {
if (props.value === state.prevValue) {
return null;
}
const parsed = ansicolor.parse(props.value);
return {
chunks: parsed.spans.map((span) => {
return span.css
? {
style: convertCSSToStyle(props.theme, span.css),
text: span.text,
}
: { text: span.text };
}),
prevValue: props.value,
};
}
render() {
const { chunks } = this.state;
return chunks.map((chunk, index) => {
const chunkText = this.props.highlight?.searchWords ? (
<Highlighter
key={index}
textToHighlight={chunk.text}
searchWords={this.props.highlight.searchWords}
findChunks={findHighlightChunksInText}
highlightClassName={this.props.highlight.highlightClassName}
/>
) : (
chunk.text
);
return chunk.style ? (
<span key={index} style={chunk.style} data-testid="ansiLogLine">
{chunkText}
</span>
) : (
chunkText
);
});
}
}
export const LogMessageAnsi = withTheme2(UnThemedLogMessageAnsi);
LogMessageAnsi.displayName = 'LogMessageAnsi';