2022-08-09 08:23:14 -05:00
|
|
|
import { SyntaxNode } from '@lezer/common';
|
2021-04-21 02:38:00 -05:00
|
|
|
import { escapeRegExp } from 'lodash';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2022-09-16 08:35:20 -05:00
|
|
|
import {
|
|
|
|
parser,
|
|
|
|
LineFilter,
|
|
|
|
PipeExact,
|
|
|
|
PipeMatch,
|
|
|
|
Filter,
|
|
|
|
String,
|
|
|
|
LabelFormatExpr,
|
|
|
|
Selector,
|
|
|
|
PipelineExpr,
|
|
|
|
LabelParser,
|
|
|
|
JsonExpressionParser,
|
|
|
|
LabelFilter,
|
|
|
|
MetricExpr,
|
|
|
|
Matcher,
|
|
|
|
Identifier,
|
|
|
|
} from '@grafana/lezer-logql';
|
|
|
|
|
|
|
|
import { ErrorId } from '../prometheus/querybuilder/shared/parsingUtils';
|
2022-07-07 08:49:31 -05:00
|
|
|
|
2022-02-03 03:25:37 -06:00
|
|
|
import { LokiQuery, LokiQueryType } from './types';
|
2019-05-13 02:58:26 -05:00
|
|
|
|
2020-07-06 14:16:27 -05:00
|
|
|
export function formatQuery(selector: string | undefined): string {
|
2020-05-06 04:21:25 -05:00
|
|
|
return `${selector || ''}`.trim();
|
2018-11-28 03:46:35 -06:00
|
|
|
}
|
2019-05-13 02:58:26 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns search terms from a LogQL query.
|
|
|
|
* E.g., `{} |= foo |=bar != baz` returns `['foo', 'bar']`.
|
|
|
|
*/
|
|
|
|
export function getHighlighterExpressionsFromQuery(input: string): string[] {
|
|
|
|
const results = [];
|
2020-07-06 14:16:27 -05:00
|
|
|
|
2022-08-09 08:23:14 -05:00
|
|
|
const tree = parser.parse(input);
|
|
|
|
const filters: SyntaxNode[] = [];
|
|
|
|
tree.iterate({
|
2022-08-22 08:45:29 -05:00
|
|
|
enter: ({ type, node }): void => {
|
2022-08-09 08:23:14 -05:00
|
|
|
if (type.id === LineFilter) {
|
2022-08-22 08:45:29 -05:00
|
|
|
filters.push(node);
|
2022-08-09 08:23:14 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
for (let filter of filters) {
|
|
|
|
const pipeExact = filter.getChild(Filter)?.getChild(PipeExact);
|
|
|
|
const pipeMatch = filter.getChild(Filter)?.getChild(PipeMatch);
|
|
|
|
const string = filter.getChild(String);
|
|
|
|
|
|
|
|
if ((!pipeExact && !pipeMatch) || !string) {
|
2019-05-13 02:58:26 -05:00
|
|
|
continue;
|
|
|
|
}
|
2019-07-02 03:06:04 -05:00
|
|
|
|
2022-08-09 08:23:14 -05:00
|
|
|
const filterTerm = input.substring(string.from, string.to).trim();
|
|
|
|
const backtickedTerm = filterTerm[0] === '`';
|
|
|
|
const unwrappedFilterTerm = filterTerm.substring(1, filterTerm.length - 1);
|
2021-06-24 05:55:25 -05:00
|
|
|
|
2022-08-09 08:23:14 -05:00
|
|
|
if (!unwrappedFilterTerm) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-07-15 07:26:15 -05:00
|
|
|
|
2022-08-09 08:23:14 -05:00
|
|
|
let resultTerm = '';
|
2022-07-15 07:26:15 -05:00
|
|
|
|
2022-08-09 08:23:14 -05:00
|
|
|
// Only filter expressions with |~ operator are treated as regular expressions
|
|
|
|
if (pipeMatch) {
|
|
|
|
// When using backticks, Loki doesn't require to escape special characters and we can just push regular expression to highlights array
|
|
|
|
// When using quotes, we have extra backslash escaping and we need to replace \\ with \
|
|
|
|
resultTerm = backtickedTerm ? unwrappedFilterTerm : unwrappedFilterTerm.replace(/\\\\/g, '\\');
|
2019-07-02 03:06:04 -05:00
|
|
|
} else {
|
2022-08-09 08:23:14 -05:00
|
|
|
// We need to escape this string so it is not matched as regular expression
|
|
|
|
resultTerm = escapeRegExp(unwrappedFilterTerm);
|
2019-07-02 03:06:04 -05:00
|
|
|
}
|
2020-07-06 14:16:27 -05:00
|
|
|
|
2022-08-09 08:23:14 -05:00
|
|
|
if (resultTerm) {
|
|
|
|
results.push(resultTerm);
|
|
|
|
}
|
|
|
|
}
|
2019-05-13 02:58:26 -05:00
|
|
|
return results;
|
|
|
|
}
|
2021-06-21 11:50:42 -05:00
|
|
|
|
2022-02-03 03:25:37 -06:00
|
|
|
// we are migrating from `.instant` and `.range` to `.queryType`
|
|
|
|
// this function returns a new query object that:
|
|
|
|
// - has `.queryType`
|
|
|
|
// - does not have `.instant`
|
|
|
|
// - does not have `.range`
|
|
|
|
export function getNormalizedLokiQuery(query: LokiQuery): LokiQuery {
|
2022-06-14 04:48:49 -05:00
|
|
|
// if queryType field contains invalid data we behave as if the queryType is empty
|
|
|
|
const { queryType } = query;
|
|
|
|
const hasValidQueryType =
|
|
|
|
queryType === LokiQueryType.Range || queryType === LokiQueryType.Instant || queryType === LokiQueryType.Stream;
|
|
|
|
|
2022-02-03 03:25:37 -06:00
|
|
|
// if queryType exists, it is respected
|
2022-06-14 04:48:49 -05:00
|
|
|
if (hasValidQueryType) {
|
2022-02-03 03:25:37 -06:00
|
|
|
const { instant, range, ...rest } = query;
|
|
|
|
return rest;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if no queryType, and instant===true, it's instant
|
|
|
|
if (query.instant === true) {
|
|
|
|
const { instant, range, ...rest } = query;
|
|
|
|
return { ...rest, queryType: LokiQueryType.Instant };
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise it is range
|
|
|
|
const { instant, range, ...rest } = query;
|
|
|
|
return { ...rest, queryType: LokiQueryType.Range };
|
|
|
|
}
|
2022-07-07 08:49:31 -05:00
|
|
|
|
2022-12-06 14:54:20 -06:00
|
|
|
export function parseToNodeNamesArray(query: string): string[] {
|
|
|
|
const queryParts: string[] = [];
|
|
|
|
const tree = parser.parse(query);
|
|
|
|
tree.iterate({
|
|
|
|
enter: ({ name }): false | void => {
|
|
|
|
queryParts.push(name);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return queryParts;
|
|
|
|
}
|
|
|
|
|
2022-07-07 08:49:31 -05:00
|
|
|
export function isValidQuery(query: string): boolean {
|
|
|
|
let isValid = true;
|
|
|
|
const tree = parser.parse(query);
|
|
|
|
tree.iterate({
|
2022-09-16 08:35:20 -05:00
|
|
|
enter: ({ type }): false | void => {
|
|
|
|
if (type.id === ErrorId) {
|
2022-07-07 08:49:31 -05:00
|
|
|
isValid = false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return isValid;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isLogsQuery(query: string): boolean {
|
|
|
|
let isLogsQuery = true;
|
|
|
|
const tree = parser.parse(query);
|
|
|
|
tree.iterate({
|
2022-09-16 08:35:20 -05:00
|
|
|
enter: ({ type }): false | void => {
|
|
|
|
if (type.id === MetricExpr) {
|
2022-07-07 08:49:31 -05:00
|
|
|
isLogsQuery = false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return isLogsQuery;
|
|
|
|
}
|
|
|
|
|
2022-07-14 03:48:27 -05:00
|
|
|
export function isQueryWithParser(query: string): { queryWithParser: boolean; parserCount: number } {
|
|
|
|
let parserCount = 0;
|
2022-07-07 08:49:31 -05:00
|
|
|
const tree = parser.parse(query);
|
|
|
|
tree.iterate({
|
2022-09-16 08:35:20 -05:00
|
|
|
enter: ({ type }): false | void => {
|
|
|
|
if (type.id === LabelParser || type.id === JsonExpressionParser) {
|
2022-07-14 03:48:27 -05:00
|
|
|
parserCount++;
|
2022-07-07 08:49:31 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
2022-07-14 03:48:27 -05:00
|
|
|
return { queryWithParser: parserCount > 0, parserCount };
|
|
|
|
}
|
|
|
|
|
2022-12-14 10:37:08 -06:00
|
|
|
export function getParserFromQuery(query: string) {
|
|
|
|
const tree = parser.parse(query);
|
|
|
|
let logParser;
|
|
|
|
tree.iterate({
|
|
|
|
enter: (node: SyntaxNode): false | void => {
|
|
|
|
if (node.type.id === LabelParser || node.type.id === JsonExpressionParser) {
|
|
|
|
logParser = query.substring(node.from, node.to).trim();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return logParser;
|
|
|
|
}
|
|
|
|
|
2022-07-14 03:48:27 -05:00
|
|
|
export function isQueryPipelineErrorFiltering(query: string): boolean {
|
|
|
|
let isQueryPipelineErrorFiltering = false;
|
|
|
|
const tree = parser.parse(query);
|
|
|
|
tree.iterate({
|
2022-09-16 08:35:20 -05:00
|
|
|
enter: ({ type, node }): false | void => {
|
|
|
|
if (type.id === LabelFilter) {
|
|
|
|
const label = node.getChild(Matcher)?.getChild(Identifier);
|
2022-07-14 03:48:27 -05:00
|
|
|
if (label) {
|
|
|
|
const labelName = query.substring(label.from, label.to);
|
|
|
|
if (labelName === '__error__') {
|
|
|
|
isQueryPipelineErrorFiltering = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return isQueryPipelineErrorFiltering;
|
2022-07-07 08:49:31 -05:00
|
|
|
}
|
2022-07-22 09:59:25 -05:00
|
|
|
|
|
|
|
export function isQueryWithLabelFormat(query: string): boolean {
|
|
|
|
let queryWithLabelFormat = false;
|
|
|
|
const tree = parser.parse(query);
|
|
|
|
tree.iterate({
|
2022-09-16 08:35:20 -05:00
|
|
|
enter: ({ type }): false | void => {
|
|
|
|
if (type.id === LabelFormatExpr) {
|
2022-07-22 09:59:25 -05:00
|
|
|
queryWithLabelFormat = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return queryWithLabelFormat;
|
|
|
|
}
|
2022-07-27 03:45:30 -05:00
|
|
|
|
|
|
|
export function getLogQueryFromMetricsQuery(query: string): string {
|
|
|
|
if (isLogsQuery(query)) {
|
|
|
|
return query;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tree = parser.parse(query);
|
|
|
|
|
|
|
|
// Log query in metrics query composes of Selector & PipelineExpr
|
|
|
|
let selector = '';
|
|
|
|
tree.iterate({
|
2022-09-16 08:35:20 -05:00
|
|
|
enter: ({ type, from, to }): false | void => {
|
|
|
|
if (type.id === Selector) {
|
2022-07-27 03:45:30 -05:00
|
|
|
selector = query.substring(from, to);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
let pipelineExpr = '';
|
|
|
|
tree.iterate({
|
2022-09-16 08:35:20 -05:00
|
|
|
enter: ({ type, from, to }): false | void => {
|
|
|
|
if (type.id === PipelineExpr) {
|
2022-07-27 03:45:30 -05:00
|
|
|
pipelineExpr = query.substring(from, to);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return selector + pipelineExpr;
|
|
|
|
}
|