Skip to content

Commit

Permalink
feat(logql): Support drop labels in logql pipeline (#7975)
Browse files Browse the repository at this point in the history
This PR introduces `drop` stage in logql pipeline. 

Fixes #7870, Fixes #7368
  • Loading branch information
adityacs authored Jan 12, 2023
1 parent 7a1fcab commit 8df5803
Show file tree
Hide file tree
Showing 11 changed files with 1,048 additions and 527 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* [7964](https://github.com/grafana/loki/pull/7964) **slim-bean**: Add a `since` query parameter to allow querying based on relative time.
* [7989](https://github.com/grafana/loki/pull/7989) **liguozhong**: logql support `sort` and `sort_desc`.
* [7997](https://github.com/grafana/loki/pull/7997) **kavirajk**: fix(promtail): Fix cri tags extra new lines when joining partial lines
* [7975](https://github.com/grafana/loki/pull/7975) **adityacs**: Support drop labels in logql
* [7946](https://github.com/grafana/loki/pull/7946) **ashwanthgoli** config: Add support for named stores
* [8027](https://github.com/grafana/loki/pull/8027) **kavirajk**: chore(promtail): Make `batchwait` and `batchsize` config explicit with yaml tags
* [7978](https://github.com/grafana/loki/pull/7978) **chaudum**: Shut down query frontend gracefully to allow inflight requests to complete.
Expand Down
52 changes: 52 additions & 0 deletions docs/sources/logql/log_queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,3 +556,55 @@ In both cases, if the destination label doesn't exist, then a new one is created
The renaming form `dst=src` will _drop_ the `src` label after remapping it to the `dst` label. However, the _template_ form will preserve the referenced labels, such that `dst="{{.src}}"` results in both `dst` and `src` having the same value.

> A single label name can only appear once per expression. This means `| label_format foo=bar,foo="new"` is not allowed but you can use two expressions for the desired effect: `| label_format foo=bar | label_format foo="new"`
### Drop Labels expression

**Syntax**: `|drop name, other_name, some_name="some_value"`

The `=` operator after the label name is a **label matching operator**.
The following label matching operators are supported:

- `=`: exactly equal
- `!=`: not equal
- `=~`: regex matches
- `!~`: regex does not match

The `| drop` expression will drop the given labels in the pipeline. For example, for the query `{job="varlogs"}|json|drop level, method="GET"`, with below log line

```
{"level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
```

the result will be

```
{host="grafana.net", path="status="200"} {"level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
```

Similary, this expression can be used to drop `__error__` labels as well. For example, for the query `{job="varlogs"}|json|drop __error__`, with below log line

```
INFO GET / loki.net 200
```

the result will be

```
{} INFO GET / loki.net 200
```

Example with regex and multiple names

For the query `{job="varlogs"}|json|drop level, path, app=~"some-api.*"`, with below log lines

```
{"app": "some-api-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200}
{"app: "other-service", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200}
```

the result will be

```
{host="grafana.net", job="varlogs", method="GET", status="200"} {""app": "some-api-service",", "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
{app="other-service", host="grafana.net", job="varlogs", method="GET", status="200"} {"app": "other-service",, "level": "info", "method": "GET", "path": "/", "host": "grafana.net", "status": "200"}
```
85 changes: 85 additions & 0 deletions pkg/logql/log/drop_labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package log

import (
"github.com/grafana/loki/pkg/logqlmodel"
"github.com/prometheus/prometheus/model/labels"
)

type DropLabels struct {
dropLabels []DropLabel
}

type DropLabel struct {
Matcher *labels.Matcher
Name string
}

func NewDropLabel(matcher *labels.Matcher, name string) DropLabel {
return DropLabel{
Matcher: matcher,
Name: name,
}
}

func NewDropLabels(dl []DropLabel) *DropLabels {
return &DropLabels{dropLabels: dl}
}

func (dl *DropLabels) Process(ts int64, line []byte, lbls *LabelsBuilder) ([]byte, bool) {
for _, dropLabel := range dl.dropLabels {
if dropLabel.Matcher != nil {
dropLabelMatches(dropLabel.Matcher, lbls)
continue
}
name := dropLabel.Name
dropLabelNames(name, lbls)
}
return line, true
}

func (dl *DropLabels) RequiredLabelNames() []string { return []string{} }

func isErrorLabel(name string) bool {
return name == logqlmodel.ErrorLabel
}

func isErrorDetailsLabel(name string) bool {
return name == logqlmodel.ErrorDetailsLabel
}

func dropLabelNames(name string, lbls *LabelsBuilder) {
if isErrorLabel(name) {
lbls.ResetError()
return
}
if isErrorDetailsLabel(name) {
lbls.ResetErrorDetails()
return
}
if _, ok := lbls.Get(name); ok {
lbls.Del(name)
}
}

func dropLabelMatches(matcher *labels.Matcher, lbls *LabelsBuilder) {
var value string
name := matcher.Name
if isErrorLabel(name) {
value = lbls.GetErr()
if matcher.Matches(value) {
lbls.ResetError()
}
return
}
if isErrorDetailsLabel(name) {
value = lbls.GetErrorDetails()
if matcher.Matches(value) {
lbls.ResetErrorDetails()
}
return
}
value, _ = lbls.Get(name)
if matcher.Matches(value) {
lbls.Del(name)
}
}
160 changes: 160 additions & 0 deletions pkg/logql/log/drop_labels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package log

import (
"sort"
"testing"

"github.com/prometheus/prometheus/model/labels"
"github.com/stretchr/testify/require"

"github.com/grafana/loki/pkg/logqlmodel"
)

func Test_DropLabels(t *testing.T) {
tests := []struct {
Name string
dropLabels []DropLabel
err string
errDetails string
lbs labels.Labels
want labels.Labels
}{
{
"drop by name",
[]DropLabel{
{
nil,
"app",
},
{
nil,
"namespace",
},
},
"",
"",
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
},
labels.Labels{
{Name: "pod_uuid", Value: "foo"},
},
},
{
"drop by __error__",
[]DropLabel{
{
labels.MustNewMatcher(labels.MatchEqual, logqlmodel.ErrorLabel, errJSON),
"",
},
{
nil,
"__error_details__",
},
},
errJSON,
"json error",
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
},
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
},
},
{
"drop with wrong __error__ value",
[]DropLabel{
{
labels.MustNewMatcher(labels.MatchEqual, logqlmodel.ErrorLabel, errLogfmt),
"",
},
},
errJSON,
"json error",
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
},
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
{Name: logqlmodel.ErrorLabel, Value: errJSON},
{Name: logqlmodel.ErrorDetailsLabel, Value: "json error"},
},
},
{
"drop by __error_details__",
[]DropLabel{
{
labels.MustNewMatcher(labels.MatchRegexp, logqlmodel.ErrorDetailsLabel, "expecting json.*"),
"",
},
{
nil,
"__error__",
},
},
errJSON,
"expecting json object but it is not",
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
},
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
},
},
{
"drop labels with names and matcher",
[]DropLabel{
{
labels.MustNewMatcher(labels.MatchEqual, logqlmodel.ErrorLabel, errJSON),
"",
},
{
nil,
"__error_details__",
},
{
nil,
"app",
},
{
nil,
"namespace",
},
},
errJSON,
"json error",
labels.Labels{
{Name: "app", Value: "foo"},
{Name: "namespace", Value: "prod"},
{Name: "pod_uuid", Value: "foo"},
},
labels.Labels{
{Name: "pod_uuid", Value: "foo"},
},
},
}
for _, tt := range tests {
dropLabels := NewDropLabels(tt.dropLabels)
lbls := NewBaseLabelsBuilder().ForLabels(tt.lbs, tt.lbs.Hash())
lbls.Reset()
lbls.SetErr(tt.err)
lbls.SetErrorDetails(tt.errDetails)
dropLabels.Process(0, []byte(""), lbls)
sort.Sort(tt.want)
require.Equal(t, tt.want, lbls.LabelsResult().Labels())
}
}
10 changes: 10 additions & 0 deletions pkg/logql/log/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ func (b *LabelsBuilder) SetErrorDetails(desc string) *LabelsBuilder {
return b
}

func (b *LabelsBuilder) ResetError() *LabelsBuilder {
b.err = ""
return b
}

func (b *LabelsBuilder) ResetErrorDetails() *LabelsBuilder {
b.errDetails = ""
return b
}

func (b *LabelsBuilder) GetErrorDetails() string {
return b.errDetails
}
Expand Down
Loading

0 comments on commit 8df5803

Please sign in to comment.