mirror of https://github.com/grafana/loki
Support categorized labels in Tailing (#11079)
**What this PR does / why we need it**:
This is a follow-up PR for https://github.com/grafana/loki/pull/10419
adding support for tailing.
I tested it on a dev cell and works fine.
<img width="1296" alt="image"
src="https://github.com/grafana/loki/assets/8354290/6177e0ca-02ce-48cd-a17f-0739dc3caa0a">
**Note**: With these changes, the JSON marshal unmarshal functions for
the tail are no longer used ([example][1]) so I think we can remove
them. Also, the new Tail response is no longer used, so we can also make
it an alias to the _legacy_ one. Let's do it on a follow-up PR to avoid
making this one bigger.
[1]:
52a3f16039/pkg/loghttp/entry.go (L210-L238)
pull/10096/head^2
parent
8742599be1
commit
9be3c0863e
@ -0,0 +1,71 @@ |
||||
package iter |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/prometheus/prometheus/model/labels" |
||||
|
||||
"github.com/grafana/loki/pkg/logproto" |
||||
"github.com/grafana/loki/pkg/logql/syntax" |
||||
) |
||||
|
||||
type categorizeLabelsIterator struct { |
||||
EntryIterator |
||||
currEntry logproto.Entry |
||||
currStreamLabels string |
||||
currHash uint64 |
||||
currErr error |
||||
} |
||||
|
||||
func NewCategorizeLabelsIterator(wrap EntryIterator) EntryIterator { |
||||
return &categorizeLabelsIterator{ |
||||
EntryIterator: wrap, |
||||
} |
||||
} |
||||
|
||||
func (c *categorizeLabelsIterator) Next() bool { |
||||
if !c.EntryIterator.Next() { |
||||
return false |
||||
} |
||||
|
||||
c.currEntry = c.Entry() |
||||
if len(c.currEntry.StructuredMetadata) == 0 && len(c.currEntry.Parsed) == 0 { |
||||
c.currStreamLabels = c.EntryIterator.Labels() |
||||
c.currHash = c.EntryIterator.StreamHash() |
||||
return true |
||||
} |
||||
|
||||
// We need to remove the structured metadata labels and parsed labels from the stream labels.
|
||||
streamLabels := c.EntryIterator.Labels() |
||||
lbls, err := syntax.ParseLabels(streamLabels) |
||||
if err != nil { |
||||
c.currErr = fmt.Errorf("failed to parse series labels to categorize labels: %w", err) |
||||
return false |
||||
} |
||||
|
||||
builder := labels.NewBuilder(lbls) |
||||
for _, label := range c.currEntry.StructuredMetadata { |
||||
builder.Del(label.Name) |
||||
} |
||||
for _, label := range c.currEntry.Parsed { |
||||
builder.Del(label.Name) |
||||
} |
||||
|
||||
newLabels := builder.Labels() |
||||
c.currStreamLabels = newLabels.String() |
||||
c.currHash = newLabels.Hash() |
||||
|
||||
return true |
||||
} |
||||
|
||||
func (c *categorizeLabelsIterator) Error() error { |
||||
return c.currErr |
||||
} |
||||
|
||||
func (c *categorizeLabelsIterator) Labels() string { |
||||
return c.currStreamLabels |
||||
} |
||||
|
||||
func (c *categorizeLabelsIterator) StreamHash() uint64 { |
||||
return c.currHash |
||||
} |
@ -0,0 +1,145 @@ |
||||
package iter |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/prometheus/prometheus/model/labels" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/loki/pkg/logproto" |
||||
) |
||||
|
||||
func TestNewCategorizeLabelsIterator(t *testing.T) { |
||||
for _, tc := range []struct { |
||||
name string |
||||
inner EntryIterator |
||||
expectedStreams []logproto.Stream |
||||
}{ |
||||
{ |
||||
name: "no structured metadata nor parsed labels", |
||||
inner: NewSortEntryIterator([]EntryIterator{ |
||||
NewStreamIterator(logproto.Stream{ |
||||
Labels: labels.FromStrings("namespace", "default").String(), |
||||
Entries: []logproto.Entry{ |
||||
{ |
||||
Timestamp: time.Unix(0, 1), |
||||
Line: "foo=1", |
||||
}, |
||||
{ |
||||
Timestamp: time.Unix(0, 2), |
||||
Line: "foo=2", |
||||
}, |
||||
}, |
||||
}), |
||||
}, logproto.FORWARD), |
||||
expectedStreams: []logproto.Stream{ |
||||
{ |
||||
Labels: labels.FromStrings("namespace", "default").String(), |
||||
Entries: []logproto.Entry{ |
||||
{ |
||||
Timestamp: time.Unix(0, 1), |
||||
Line: "foo=1", |
||||
}, |
||||
{ |
||||
Timestamp: time.Unix(0, 2), |
||||
Line: "foo=2", |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
name: "structured metadata and parsed labels", |
||||
inner: NewSortEntryIterator([]EntryIterator{ |
||||
NewStreamIterator(logproto.Stream{ |
||||
Labels: labels.FromStrings("namespace", "default").String(), |
||||
Entries: []logproto.Entry{ |
||||
{ |
||||
Timestamp: time.Unix(0, 1), |
||||
Line: "foo=1", |
||||
}, |
||||
}, |
||||
}), |
||||
NewStreamIterator(logproto.Stream{ |
||||
Labels: labels.FromStrings("namespace", "default", "traceID", "123").String(), |
||||
Entries: []logproto.Entry{ |
||||
{ |
||||
Timestamp: time.Unix(0, 2), |
||||
Line: "foo=2", |
||||
StructuredMetadata: logproto.FromLabelsToLabelAdapters(labels.FromStrings("traceID", "123")), |
||||
}, |
||||
}, |
||||
}), |
||||
NewStreamIterator(logproto.Stream{ |
||||
Labels: labels.FromStrings("namespace", "default", "foo", "3").String(), |
||||
Entries: []logproto.Entry{ |
||||
{ |
||||
Timestamp: time.Unix(0, 3), |
||||
Line: "foo=3", |
||||
Parsed: logproto.FromLabelsToLabelAdapters(labels.FromStrings("foo", "3")), |
||||
}, |
||||
}, |
||||
}), |
||||
NewStreamIterator(logproto.Stream{ |
||||
Labels: labels.FromStrings("namespace", "default", "traceID", "123", "foo", "4").String(), |
||||
Entries: []logproto.Entry{ |
||||
{ |
||||
Timestamp: time.Unix(0, 4), |
||||
Line: "foo=4", |
||||
StructuredMetadata: logproto.FromLabelsToLabelAdapters(labels.FromStrings("traceID", "123")), |
||||
Parsed: logproto.FromLabelsToLabelAdapters(labels.FromStrings("foo", "4")), |
||||
}, |
||||
}, |
||||
}), |
||||
}, logproto.FORWARD), |
||||
expectedStreams: []logproto.Stream{ |
||||
{ |
||||
Labels: labels.FromStrings("namespace", "default").String(), |
||||
Entries: []logproto.Entry{ |
||||
{ |
||||
Timestamp: time.Unix(0, 1), |
||||
Line: "foo=1", |
||||
}, |
||||
{ |
||||
Timestamp: time.Unix(0, 2), |
||||
Line: "foo=2", |
||||
StructuredMetadata: logproto.FromLabelsToLabelAdapters(labels.FromStrings("traceID", "123")), |
||||
}, |
||||
{ |
||||
Timestamp: time.Unix(0, 3), |
||||
Line: "foo=3", |
||||
Parsed: logproto.FromLabelsToLabelAdapters(labels.FromStrings("foo", "3")), |
||||
}, |
||||
{ |
||||
Timestamp: time.Unix(0, 4), |
||||
Line: "foo=4", |
||||
StructuredMetadata: logproto.FromLabelsToLabelAdapters(labels.FromStrings("traceID", "123")), |
||||
Parsed: logproto.FromLabelsToLabelAdapters(labels.FromStrings("foo", "4")), |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
} { |
||||
t.Run(tc.name, func(t *testing.T) { |
||||
itr := NewCategorizeLabelsIterator(tc.inner) |
||||
|
||||
streamsEntries := make(map[string][]logproto.Entry) |
||||
for itr.Next() { |
||||
streamsEntries[itr.Labels()] = append(streamsEntries[itr.Labels()], itr.Entry()) |
||||
require.NoError(t, itr.Error()) |
||||
} |
||||
|
||||
var streams []logproto.Stream |
||||
for lbls, entries := range streamsEntries { |
||||
streams = append(streams, logproto.Stream{ |
||||
Labels: lbls, |
||||
Entries: entries, |
||||
}) |
||||
} |
||||
|
||||
require.ElementsMatch(t, tc.expectedStreams, streams) |
||||
}) |
||||
} |
||||
} |
Loading…
Reference in new issue