123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- import {
- createAggregationOperation,
- createAggregationOperationWithParam,
- getPromAndLokiOperationDisplayName,
- } from '../../prometheus/querybuilder/shared/operationUtils';
- import {
- QueryBuilderOperation,
- QueryBuilderOperationDef,
- QueryBuilderOperationParamDef,
- VisualQueryModeller,
- } from '../../prometheus/querybuilder/shared/types';
- import { FUNCTIONS } from '../syntax';
- import { binaryScalarOperations } from './binaryScalarOperations';
- import { LokiOperationId, LokiOperationOrder, LokiVisualQuery, LokiVisualQueryOperationCategory } from './types';
- export function getOperationDefinitions(): QueryBuilderOperationDef[] {
- const aggregations = [
- LokiOperationId.Sum,
- LokiOperationId.Min,
- LokiOperationId.Max,
- LokiOperationId.Avg,
- LokiOperationId.Stddev,
- LokiOperationId.Stdvar,
- LokiOperationId.Count,
- ].flatMap((opId) =>
- createAggregationOperation(opId, {
- addOperationHandler: addLokiOperation,
- orderRank: LokiOperationOrder.Last,
- })
- );
- const aggregationsWithParam = [LokiOperationId.TopK, LokiOperationId.BottomK].flatMap((opId) => {
- return createAggregationOperationWithParam(
- opId,
- {
- params: [{ name: 'K-value', type: 'number' }],
- defaultParams: [5],
- },
- {
- addOperationHandler: addLokiOperation,
- orderRank: LokiOperationOrder.Last,
- }
- );
- });
- const list: QueryBuilderOperationDef[] = [
- createRangeOperation(LokiOperationId.Rate),
- createRangeOperation(LokiOperationId.CountOverTime),
- createRangeOperation(LokiOperationId.SumOverTime),
- createRangeOperation(LokiOperationId.BytesRate),
- createRangeOperation(LokiOperationId.BytesOverTime),
- createRangeOperation(LokiOperationId.AbsentOverTime),
- createRangeOperation(LokiOperationId.AvgOverTime),
- createRangeOperation(LokiOperationId.MaxOverTime),
- createRangeOperation(LokiOperationId.MinOverTime),
- createRangeOperation(LokiOperationId.FirstOverTime),
- createRangeOperation(LokiOperationId.LastOverTime),
- createRangeOperation(LokiOperationId.StdvarOverTime),
- createRangeOperation(LokiOperationId.StddevOverTime),
- createRangeOperation(LokiOperationId.QuantileOverTime),
- ...aggregations,
- ...aggregationsWithParam,
- {
- id: LokiOperationId.Json,
- name: 'Json',
- params: [],
- defaultParams: [],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.LineFormats,
- renderer: pipelineRenderer,
- addOperationHandler: addLokiOperation,
- },
- {
- id: LokiOperationId.Logfmt,
- name: 'Logfmt',
- params: [],
- defaultParams: [],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.LineFormats,
- renderer: pipelineRenderer,
- addOperationHandler: addLokiOperation,
- explainHandler: () =>
- `This will extract all keys and values from a [logfmt](https://grafana.com/docs/loki/latest/logql/log_queries/#logfmt) formatted log line as labels. The extracted labels can be used in label filter expressions and used as values for a range aggregation via the unwrap operation.`,
- },
- {
- id: LokiOperationId.Regexp,
- name: 'Regexp',
- params: [
- {
- name: 'String',
- type: 'string',
- hideName: true,
- placeholder: '<re>',
- description: 'The regexp expression that matches the structure of a log line.',
- minWidth: 20,
- },
- ],
- defaultParams: [''],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.LineFormats,
- renderer: (model, def, innerExpr) => `${innerExpr} | regexp \`${model.params[0]}\``,
- addOperationHandler: addLokiOperation,
- explainHandler: () =>
- `The [regexp parser](https://grafana.com/docs/loki/latest/logql/log_queries/#regular-expression) takes a single parameter | regexp "<re>" which is the regular expression using the Golang RE2 syntax. The regular expression must contain a least one named sub-match (e.g (?P<name>re)), each sub-match will extract a different label. The expression matches the structure of a log line. The extracted labels can be used in label filter expressions and used as values for a range aggregation via the unwrap operation.`,
- },
- {
- id: LokiOperationId.Pattern,
- name: 'Pattern',
- params: [
- {
- name: 'String',
- type: 'string',
- hideName: true,
- placeholder: '<pattern-expression>',
- description: 'The expression that matches the structure of a log line.',
- minWidth: 20,
- },
- ],
- defaultParams: [''],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.LineFormats,
- renderer: (model, def, innerExpr) => `${innerExpr} | pattern \`${model.params[0]}\``,
- addOperationHandler: addLokiOperation,
- explainHandler: () =>
- `The [pattern parser](https://grafana.com/docs/loki/latest/logql/log_queries/#pattern) allows the explicit extraction of fields from log lines by defining a pattern expression (| pattern \`<pattern-expression>\`). The expression matches the structure of a log line. The extracted labels can be used in label filter expressions and used as values for a range aggregation via the unwrap operation.`,
- },
- {
- id: LokiOperationId.Unpack,
- name: 'Unpack',
- params: [],
- defaultParams: [],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.LineFormats,
- renderer: pipelineRenderer,
- addOperationHandler: addLokiOperation,
- explainHandler: () =>
- `This will extract all keys and values from a JSON log line, [unpacking](https://grafana.com/docs/loki/latest/logql/log_queries/#unpack) all embedded labels in the pack stage. The extracted labels can be used in label filter expressions and used as values for a range aggregation via the unwrap operation.`,
- },
- {
- id: LokiOperationId.LineFormat,
- name: 'Line format',
- params: [
- {
- name: 'String',
- type: 'string',
- hideName: true,
- placeholder: '{{.status_code}}',
- description: 'A line template that can refer to stream labels and extracted labels.',
- minWidth: 20,
- },
- ],
- defaultParams: [''],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.LineFormats,
- renderer: (model, def, innerExpr) => `${innerExpr} | line_format \`${model.params[0]}\``,
- addOperationHandler: addLokiOperation,
- explainHandler: () =>
- `This will replace log line using a specified template. The template can refer to stream labels and extracted labels.
- Example: \`{{.status_code}} - {{.message}}\`
- [Read the docs](https://grafana.com/docs/loki/latest/logql/log_queries/#line-format-expression) for more.
- `,
- },
- {
- id: LokiOperationId.LabelFormat,
- name: 'Label format',
- params: [
- { name: 'Label', type: 'string' },
- { name: 'Rename', type: 'string' },
- ],
- defaultParams: ['', ''],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.LineFormats,
- renderer: (model, def, innerExpr) => `${innerExpr} | label_format ${model.params[1]}=\`${model.params[0]}\``,
- addOperationHandler: addLokiOperation,
- explainHandler: () =>
- `This will change name of label to desired new label. In the example below, label "error_level" will be renamed to "level".
- Example: error_level=\`level\`
- [Read the docs](https://grafana.com/docs/loki/latest/logql/log_queries/#labels-format-expression) for more.
- `,
- },
- {
- id: LokiOperationId.LineContains,
- name: 'Line contains',
- params: [
- {
- name: 'String',
- type: 'string',
- hideName: true,
- placeholder: 'Text to find',
- description: 'Find log lines that contains this text',
- minWidth: 20,
- runQueryOnEnter: true,
- },
- ],
- defaultParams: [''],
- alternativesKey: 'line filter',
- category: LokiVisualQueryOperationCategory.LineFilters,
- orderRank: LokiOperationOrder.LineFilters,
- renderer: getLineFilterRenderer('|='),
- addOperationHandler: addLokiOperation,
- explainHandler: (op) => `Return log lines that contain string \`${op.params[0]}\`.`,
- },
- {
- id: LokiOperationId.LineContainsNot,
- name: 'Line does not contain',
- params: [
- {
- name: 'String',
- type: 'string',
- hideName: true,
- placeholder: 'Text to exclude',
- description: 'Find log lines that does not contain this text',
- minWidth: 26,
- runQueryOnEnter: true,
- },
- ],
- defaultParams: [''],
- alternativesKey: 'line filter',
- category: LokiVisualQueryOperationCategory.LineFilters,
- orderRank: LokiOperationOrder.LineFilters,
- renderer: getLineFilterRenderer('!='),
- addOperationHandler: addLokiOperation,
- explainHandler: (op) => `Return log lines that does not contain string \`${op.params[0]}\`.`,
- },
- {
- id: LokiOperationId.LineMatchesRegex,
- name: 'Line contains regex match',
- params: [
- {
- name: 'Regex',
- type: 'string',
- hideName: true,
- placeholder: 'Pattern to match',
- description: 'Find log lines that match this regex pattern',
- minWidth: 30,
- runQueryOnEnter: true,
- },
- ],
- defaultParams: [''],
- alternativesKey: 'line filter',
- category: LokiVisualQueryOperationCategory.LineFilters,
- orderRank: LokiOperationOrder.LineFilters,
- renderer: getLineFilterRenderer('|~'),
- addOperationHandler: addLokiOperation,
- explainHandler: (op) => `Return log lines that match regex \`${op.params[0]}\`.`,
- },
- {
- id: LokiOperationId.LineMatchesRegexNot,
- name: 'Line does not match regex',
- params: [
- {
- name: 'Regex',
- type: 'string',
- hideName: true,
- placeholder: 'Pattern to exclude',
- description: 'Find log lines that does not match this regex pattern',
- minWidth: 30,
- runQueryOnEnter: true,
- },
- ],
- defaultParams: [''],
- alternativesKey: 'line filter',
- category: LokiVisualQueryOperationCategory.LineFilters,
- orderRank: LokiOperationOrder.LineFilters,
- renderer: getLineFilterRenderer('!~'),
- addOperationHandler: addLokiOperation,
- explainHandler: (op) => `Return log lines that does not match regex \`${op.params[0]}\`.`,
- },
- {
- id: LokiOperationId.LabelFilter,
- name: '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: labelFilterRenderer,
- addOperationHandler: addLokiOperation,
- explainHandler: () => `Label expression filter allows filtering using original and extracted labels.`,
- },
- {
- id: LokiOperationId.LabelFilterNoErrors,
- name: 'No pipeline errors',
- params: [],
- defaultParams: [],
- alternativesKey: 'label filter',
- category: LokiVisualQueryOperationCategory.LabelFilters,
- orderRank: LokiOperationOrder.NoErrors,
- renderer: (model, def, innerExpr) => `${innerExpr} | __error__=\`\``,
- addOperationHandler: addLokiOperation,
- explainHandler: () => `Filter out all formatting and parsing errors.`,
- },
- {
- id: LokiOperationId.Unwrap,
- name: 'Unwrap',
- params: [{ name: 'Identifier', type: 'string', hideName: true, minWidth: 16, placeholder: 'Label key' }],
- defaultParams: [''],
- alternativesKey: 'format',
- category: LokiVisualQueryOperationCategory.Formats,
- orderRank: LokiOperationOrder.Unwrap,
- renderer: (op, def, innerExpr) => `${innerExpr} | unwrap ${op.params[0]}`,
- addOperationHandler: addLokiOperation,
- explainHandler: (op) => {
- let label = String(op.params[0]).length > 0 ? op.params[0] : '<label>';
- return `Use the extracted label \`${label}\` as sample values instead of log lines for the subsequent range aggregation.`;
- },
- },
- ...binaryScalarOperations,
- {
- id: LokiOperationId.NestedQuery,
- name: 'Binary operation with query',
- params: [],
- defaultParams: [],
- category: LokiVisualQueryOperationCategory.BinaryOps,
- renderer: (model, def, innerExpr) => innerExpr,
- addOperationHandler: addNestedQueryHandler,
- },
- ];
- return list;
- }
- function createRangeOperation(name: string): QueryBuilderOperationDef {
- const params = [getRangeVectorParamDef()];
- const defaultParams = ['$__interval'];
- let renderer = operationWithRangeVectorRenderer;
- if (name === LokiOperationId.QuantileOverTime) {
- defaultParams.push('0.95');
- params.push({
- name: 'Quantile',
- type: 'number',
- });
- renderer = operationWithRangeVectorRendererAndParam;
- }
- return {
- id: name,
- name: getPromAndLokiOperationDisplayName(name),
- params,
- defaultParams,
- alternativesKey: 'range function',
- category: LokiVisualQueryOperationCategory.RangeFunctions,
- orderRank: LokiOperationOrder.RangeVectorFunction,
- renderer,
- addOperationHandler: addLokiOperation,
- explainHandler: (op, def) => {
- let opDocs = FUNCTIONS.find((x) => x.insertText === op.id)?.documentation ?? '';
- if (op.params[0] === '$__interval') {
- return `${opDocs} \`$__interval\` is variable that will be replaced with a calculated interval based on **Max data points**, **Min interval** and query time range. You find these options you find under **Query options** at the right of the data source select dropdown.`;
- } else {
- return `${opDocs} The [range vector](https://grafana.com/docs/loki/latest/logql/metric_queries/#range-vector-aggregation) is set to \`${op.params[0]}\`.`;
- }
- },
- };
- }
- function getRangeVectorParamDef(): QueryBuilderOperationParamDef {
- return {
- name: 'Range',
- type: 'string',
- options: ['$__interval', '$__range', '1m', '5m', '10m', '1h', '24h'],
- };
- }
- function operationWithRangeVectorRenderer(
- model: QueryBuilderOperation,
- def: QueryBuilderOperationDef,
- innerExpr: string
- ) {
- let rangeVector = (model.params ?? [])[0] ?? '$__interval';
- return `${def.id}(${innerExpr} [${rangeVector}])`;
- }
- function operationWithRangeVectorRendererAndParam(
- model: QueryBuilderOperation,
- def: QueryBuilderOperationDef,
- innerExpr: string
- ) {
- const params = model.params ?? [];
- const rangeVector = params[0] ?? '$__interval';
- const param = params[1];
- return `${def.id}(${param}, ${innerExpr} [${rangeVector}])`;
- }
- function getLineFilterRenderer(operation: string) {
- return function lineFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
- return `${innerExpr} ${operation} \`${model.params[0]}\``;
- };
- }
- function labelFilterRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) {
- if (model.params[0] === '') {
- return innerExpr;
- }
- if (model.params[1] === '<' || model.params[1] === '>') {
- 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) {
- return `${innerExpr} | ${model.id}`;
- }
- function isRangeVectorFunction(def: QueryBuilderOperationDef) {
- return def.category === LokiVisualQueryOperationCategory.RangeFunctions;
- }
- function getIndexOfOrLast(
- operations: QueryBuilderOperation[],
- queryModeller: VisualQueryModeller,
- condition: (def: QueryBuilderOperationDef) => boolean
- ) {
- const index = operations.findIndex((x) => {
- const opDef = queryModeller.getOperationDef(x.id);
- if (!opDef) {
- return false;
- }
- return condition(opDef);
- });
- return index === -1 ? operations.length : index;
- }
- export function addLokiOperation(
- def: QueryBuilderOperationDef,
- query: LokiVisualQuery,
- modeller: VisualQueryModeller
- ): LokiVisualQuery {
- const newOperation: QueryBuilderOperation = {
- id: def.id,
- params: def.defaultParams,
- };
- const operations = [...query.operations];
- const existingRangeVectorFunction = operations.find((x) => {
- const opDef = modeller.getOperationDef(x.id);
- if (!opDef) {
- return false;
- }
- return isRangeVectorFunction(opDef);
- });
- switch (def.category) {
- case LokiVisualQueryOperationCategory.Aggregations:
- case LokiVisualQueryOperationCategory.Functions:
- // If we are adding a function but we have not range vector function yet add one
- if (!existingRangeVectorFunction) {
- const placeToInsert = getIndexOfOrLast(
- operations,
- modeller,
- (def) => def.category === LokiVisualQueryOperationCategory.Functions
- );
- operations.splice(placeToInsert, 0, { id: LokiOperationId.Rate, params: ['$__interval'] });
- }
- operations.push(newOperation);
- break;
- case LokiVisualQueryOperationCategory.RangeFunctions:
- // If adding a range function and range function is already added replace it
- if (existingRangeVectorFunction) {
- const index = operations.indexOf(existingRangeVectorFunction);
- operations[index] = newOperation;
- break;
- }
- // Add range functions after any formats, line filters and label filters
- default:
- const placeToInsert = getIndexOfOrLast(
- operations,
- modeller,
- (x) => (def.orderRank ?? 100) < (x.orderRank ?? 100)
- );
- operations.splice(placeToInsert, 0, newOperation);
- break;
- }
- return {
- ...query,
- operations,
- };
- }
- function addNestedQueryHandler(def: QueryBuilderOperationDef, query: LokiVisualQuery): LokiVisualQuery {
- return {
- ...query,
- binaryQueries: [
- ...(query.binaryQueries ?? []),
- {
- operator: '/',
- query,
- },
- ],
- };
- }
|