Loki: Add support for IP label and line filter in query builder (#52658)

* Add support for IP line filter

* Add support for IP label filter

* Updates for Explain mode

* Update test

* Remove invalid options in LabelFilterIpMatches
This commit is contained in:
Ivana Huckova 2022-07-25 15:16:04 +02:00 committed by GitHub
parent bdcef92e35
commit 77421907b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 36 deletions

View File

@ -127,7 +127,7 @@ describe('LokiQueryModeller', () => {
labels: [{ label: 'app', op: '=', value: 'grafana' }],
operations: [{ id: LokiOperationId.LabelFilter, params: ['__error__', '=', 'value'] }],
})
).toBe('{app="grafana"} | __error__=`value`');
).toBe('{app="grafana"} | __error__ = `value`');
});
it('Can query with label filter expression using greater than operator', () => {

View File

@ -292,6 +292,27 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
addOperationHandler: addLokiOperation,
explainHandler: (op) => `Return log lines that does not match regex \`${op.params[0]}\`.`,
},
{
id: LokiOperationId.LineFilterIpMatches,
name: 'IP line filter expression',
params: [
{ name: 'Operator', type: 'string', options: ['|=', '!='] },
{
name: 'Pattern',
type: 'string',
placeholder: '<pattern>',
minWidth: 16,
runQueryOnEnter: true,
},
],
defaultParams: ['|=', ''],
alternativesKey: 'line filter',
category: LokiVisualQueryOperationCategory.LineFilters,
orderRank: LokiOperationOrder.LineFilters,
renderer: (op, def, innerExpr) => `${innerExpr} ${op.params[0]} ip(\`${op.params[1]}\`)`,
addOperationHandler: addLokiOperation,
explainHandler: (op) => `Return log lines using IP matching of \`${op.params[1]}\``,
},
{
id: LokiOperationId.LabelFilter,
name: 'Label filter expression',
@ -308,6 +329,23 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] {
addOperationHandler: addLokiOperation,
explainHandler: () => `Label expression filter allows filtering using original and extracted labels.`,
},
{
id: LokiOperationId.LabelFilterIpMatches,
name: 'IP label filter expression',
params: [
{ name: 'Label', type: 'string' },
{ name: 'Operator', type: 'string', options: ['=', '!='] },
{ name: 'Value', type: 'string' },
],
defaultParams: ['', '=', ''],
alternativesKey: 'label filter',
category: LokiVisualQueryOperationCategory.LabelFilters,
orderRank: LokiOperationOrder.LabelFilters,
renderer: (model, def, innerExpr) =>
`${innerExpr} | ${model.params[0]} ${model.params[1]} ip(\`${model.params[2]}\`)`,
addOperationHandler: addLokiOperation,
explainHandler: (op) => `Return log lines using IP matching of \`${op.params[2]}\` for \`${op.params[0]}\` label`,
},
{
id: LokiOperationId.LabelFilterNoErrors,
name: 'No pipeline errors',
@ -443,7 +481,7 @@ function labelFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOper
return `${innerExpr} | ${model.params[0]} ${model.params[1]} ${model.params[2]}`;
}
return `${innerExpr} | ${model.params[0]}${model.params[1]}\`${model.params[2]}\``;
return `${innerExpr} | ${model.params[0]} ${model.params[1]} \`${model.params[2]}\``;
}
function pipelineRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {

View File

@ -1,5 +1,5 @@
import { buildVisualQueryFromString } from './parsing';
import { LokiVisualQuery } from './types';
import { LokiOperationId, LokiVisualQuery } from './types';
describe('buildVisualQueryFromString', () => {
it('creates no errors for empty query', () => {
@ -77,15 +77,22 @@ describe('buildVisualQueryFromString', () => {
});
it('returns error for query with ip matching line filter', () => {
const context = buildVisualQueryFromString('{app="frontend"} |= ip("192.168.4.5/16")');
expect(context.errors).toEqual([
{
text: 'Matching ip addresses not supported in query builder: |= ip("192.168.4.5/16")',
from: 17,
to: 40,
parentType: 'LineFilters',
},
]);
const context = buildVisualQueryFromString('{app="frontend"} |= ip("192.168.4.5/16") | logfmt');
expect(context).toEqual(
noErrors({
labels: [
{
op: '=',
value: 'frontend',
label: 'app',
},
],
operations: [
{ id: '__line_filter_ip_matches', params: ['|=', '192.168.4.5/16'] },
{ id: 'logfmt', params: [] },
],
})
);
});
it('parses query with matcher label filter', () => {
@ -162,14 +169,21 @@ describe('buildVisualQueryFromString', () => {
it('returns error for query with ip label filter', () => {
const context = buildVisualQueryFromString('{app="frontend"} | logfmt | address=ip("192.168.4.5/16")');
expect(context.errors).toEqual([
{
text: 'IpLabelFilter not supported in query builder: address=ip("192.168.4.5/16")',
from: 28,
to: 56,
parentType: 'PipelineStage',
},
]);
expect(context).toEqual(
noErrors({
labels: [
{
op: '=',
value: 'frontend',
label: 'app',
},
],
operations: [
{ id: 'logfmt', params: [] },
{ id: LokiOperationId.LabelFilterIpMatches, params: ['address', '=', '192.168.4.5/16'] },
],
})
);
});
it('parses query with with parser', () => {

View File

@ -14,7 +14,7 @@ import {
import { QueryBuilderLabelFilter, QueryBuilderOperation } from '../../prometheus/querybuilder/shared/types';
import { binaryScalarDefs } from './binaryScalarOperations';
import { LokiVisualQuery, LokiVisualQueryBinary } from './types';
import { LokiOperationId, LokiVisualQuery, LokiVisualQueryBinary } from './types';
interface Context {
query: LokiVisualQuery;
@ -182,22 +182,24 @@ function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter {
}
function getLineFilter(expr: string, node: SyntaxNode): { operation?: QueryBuilderOperation; error?: string } {
// Check for nodes not supported in visual builder and return error
const ipLineFilter = getAllByType(expr, node, 'Ip');
if (ipLineFilter.length > 0) {
return {
error: 'Matching ip addresses not supported in query builder',
};
}
const mapFilter: any = {
'|=': '__line_contains',
'!=': '__line_contains_not',
'|~': '__line_matches_regex',
'!~': '"__line_matches_regex"_not',
};
const filter = getString(expr, node.getChild('Filter'));
const filterExpr = handleQuotes(getString(expr, node.getChild('String')));
const ipLineFilter = node.getChild('FilterOp')?.getChild('Ip');
if (ipLineFilter) {
return {
operation: {
id: LokiOperationId.LineFilterIpMatches,
params: [filter, filterExpr],
},
};
}
const mapFilter: any = {
'|=': LokiOperationId.LineContains,
'!=': LokiOperationId.LineContainsNot,
'|~': LokiOperationId.LineMatchesRegex,
'!~': LokiOperationId.LineMatchesRegexNot,
};
return {
operation: {
@ -238,8 +240,17 @@ function getLabelFilter(expr: string, node: SyntaxNode): { operation?: QueryBuil
};
}
if (node.firstChild!.name === 'IpLabelFilter') {
const ipLabelFilter = node.firstChild;
const label = ipLabelFilter?.getChild('Identifier');
const op = label?.nextSibling;
const value = ipLabelFilter?.getChild('String');
const valueString = handleQuotes(getString(expr, value));
return {
error: 'IpLabelFilter not supported in query builder',
operation: {
id: LokiOperationId.LabelFilterIpMatches,
params: [getString(expr, label), getString(expr, op), valueString],
},
};
}

View File

@ -62,8 +62,10 @@ export enum LokiOperationId {
LineContainsNot = '__line_contains_not',
LineMatchesRegex = '__line_matches_regex',
LineMatchesRegexNot = '__line_matches_regex_not',
LineFilterIpMatches = '__line_filter_ip_matches',
LabelFilter = '__label_filter',
LabelFilterNoErrors = '__label_filter_no_errors',
LabelFilterIpMatches = '__label_filter_ip_marches',
Unwrap = 'unwrap',
// Binary ops
Addition = '__addition',