Skip to content

Commit 4e3da08

Browse files
committed
Loki Write: Internal Labels
This change is to add logic so that labels that start with `__` are filtered out, unless they are `externalLabels`. Labels that start with `__` are used internally for things such as metadata. If a user want to preserve the contents of one of these internal labels, then can do so using relabelling to remove the `__`. This change also adds tests which check various different scenarios
1 parent 82052a3 commit 4e3da08

File tree

3 files changed

+201
-3
lines changed

3 files changed

+201
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Main (unreleased)
5353

5454
- Only log EOF errors for syslog port investigations in `loki.source.syslog` as Debug, not Warn. (@dehaansa)
5555

56+
- Remove entry labels in `loki.write` that start with `__` used for metadata. @matt-gp
57+
5658
v1.11.2
5759
-----------------
5860

internal/component/loki/write/write.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"path/filepath"
7+
"strings"
78
"sync"
89
"time"
910

@@ -228,10 +229,19 @@ func (c *Component) Update(args component.Arguments) error {
228229

229230
func newEntryHandler(handler loki.EntryHandler, externalLabels model.LabelSet) loki.EntryHandler {
230231
return loki.NewEntryMutatorHandler(handler, func(e loki.Entry) loki.Entry {
231-
if len(externalLabels) == 0 {
232-
return e
232+
// Merge external labels with entry labels first (entry labels take precedence)
233+
mergedLabels := externalLabels.Merge(e.Labels)
234+
235+
// Filter out labels starting with "__" in a single pass
236+
filteredLabels := make(model.LabelSet)
237+
for k, v := range mergedLabels {
238+
labelName := string(k)
239+
if !strings.HasPrefix(labelName, "__") {
240+
filteredLabels[k] = v
241+
}
233242
}
234-
e.Labels = externalLabels.Merge(e.Labels)
243+
244+
e.Labels = filteredLabels
235245
return e
236246
})
237247
}

internal/component/loki/write/write_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,189 @@ func benchSingleEndpoint(b *testing.B, tc testCase, alterConfig func(arguments *
408408
}, time.Minute, time.Second, "haven't seen expected number of lines")
409409
}
410410
}
411+
412+
func TestNewEntryHandler(t *testing.T) {
413+
testCases := []struct {
414+
name string
415+
externalLabels model.LabelSet
416+
entryLabels model.LabelSet
417+
expectedLabels model.LabelSet
418+
}{
419+
{
420+
name: "filter meta labels without external labels",
421+
externalLabels: nil,
422+
entryLabels: model.LabelSet{
423+
"normal_label": "value1",
424+
"__meta_label": "value2",
425+
"__another_meta": "value3",
426+
"regular_label": "value4",
427+
},
428+
expectedLabels: model.LabelSet{
429+
"normal_label": "value1",
430+
"regular_label": "value4",
431+
},
432+
},
433+
{
434+
name: "filter meta labels from both external and entry labels",
435+
externalLabels: model.LabelSet{
436+
"__external_meta": "external_value", // should be filtered
437+
"external_label": "external_normal",
438+
},
439+
entryLabels: model.LabelSet{
440+
"normal_label": "value1",
441+
"__meta_label": "value2", // should be filtered
442+
"regular_label": "value4",
443+
},
444+
expectedLabels: model.LabelSet{
445+
"external_label": "external_normal", // external normal label preserved
446+
"normal_label": "value1", // entry normal label preserved
447+
"regular_label": "value4", // entry normal label preserved
448+
},
449+
},
450+
{
451+
name: "entry labels override external labels",
452+
externalLabels: model.LabelSet{
453+
"common_label": "external_value",
454+
},
455+
entryLabels: model.LabelSet{
456+
"common_label": "entry_value", // should override external
457+
"entry_only": "value1",
458+
},
459+
expectedLabels: model.LabelSet{
460+
"common_label": "entry_value", // entry takes precedence
461+
"entry_only": "value1",
462+
},
463+
},
464+
{
465+
name: "empty labels",
466+
externalLabels: nil,
467+
entryLabels: model.LabelSet{},
468+
expectedLabels: model.LabelSet{},
469+
},
470+
{
471+
name: "only meta labels",
472+
externalLabels: nil,
473+
entryLabels: model.LabelSet{
474+
"__meta_only": "value1",
475+
"__another_meta": "value2",
476+
},
477+
expectedLabels: model.LabelSet{},
478+
},
479+
{
480+
name: "external meta labels with no entry labels",
481+
externalLabels: model.LabelSet{
482+
"__external_meta": "external_value", // should now be filtered
483+
"external_normal": "normal_value",
484+
},
485+
entryLabels: model.LabelSet{},
486+
expectedLabels: model.LabelSet{
487+
"external_normal": "normal_value", // only normal external label preserved
488+
},
489+
},
490+
{
491+
name: "mixed scenario with overrides and filtering",
492+
externalLabels: model.LabelSet{
493+
"__external_meta": "external_value", // should now be filtered
494+
"shared_label": "external_shared",
495+
},
496+
entryLabels: model.LabelSet{
497+
"normal_label": "normal_value",
498+
"__meta_label": "meta_value", // should be filtered
499+
"shared_label": "entry_shared", // should override external
500+
"another_normal": "another_value",
501+
},
502+
expectedLabels: model.LabelSet{
503+
"shared_label": "entry_shared", // entry takes precedence (original behavior)
504+
"normal_label": "normal_value", // entry normal preserved
505+
"another_normal": "another_value", // entry normal preserved
506+
},
507+
},
508+
{
509+
name: "labels starting with single underscore should be preserved",
510+
externalLabels: nil,
511+
entryLabels: model.LabelSet{
512+
"_single_underscore": "value1",
513+
"__double_underscore": "value2", // should be filtered
514+
"normal_label": "value3",
515+
},
516+
expectedLabels: model.LabelSet{
517+
"_single_underscore": "value1",
518+
"normal_label": "value3",
519+
},
520+
},
521+
{
522+
name: "external labels with empty values",
523+
externalLabels: model.LabelSet{
524+
"empty_external": "",
525+
"normal_external": "external_value",
526+
},
527+
entryLabels: model.LabelSet{
528+
"entry_label": "entry_value",
529+
},
530+
expectedLabels: model.LabelSet{
531+
"empty_external": "",
532+
"normal_external": "external_value",
533+
"entry_label": "entry_value",
534+
},
535+
},
536+
{
537+
name: "only external labels",
538+
externalLabels: model.LabelSet{
539+
"external_only": "external_value",
540+
"__external_meta": "meta_value", // should now be filtered
541+
},
542+
entryLabels: nil,
543+
expectedLabels: model.LabelSet{
544+
"external_only": "external_value", // only normal external label preserved
545+
},
546+
},
547+
{
548+
name: "all meta labels filtered regardless of source",
549+
externalLabels: model.LabelSet{
550+
"__external_meta1": "external_value1", // should be filtered
551+
"__external_meta2": "external_value2", // should be filtered
552+
"external_normal": "external_normal_value",
553+
},
554+
entryLabels: model.LabelSet{
555+
"__entry_meta1": "entry_value1", // should be filtered
556+
"__entry_meta2": "entry_value2", // should be filtered
557+
"entry_normal": "entry_normal_value",
558+
},
559+
expectedLabels: model.LabelSet{
560+
"external_normal": "external_normal_value",
561+
"entry_normal": "entry_normal_value",
562+
},
563+
},
564+
}
565+
566+
for _, tc := range testCases {
567+
t.Run(tc.name, func(t *testing.T) {
568+
// Create a channel to capture the entry
569+
captureChannel := make(chan loki.Entry, 1)
570+
mockHandler := loki.NewEntryHandler(captureChannel, func() {})
571+
572+
handler := newEntryHandler(mockHandler, tc.externalLabels)
573+
574+
testEntry := loki.Entry{
575+
Labels: tc.entryLabels,
576+
Entry: push.Entry{
577+
Timestamp: time.Now(),
578+
Line: "test log line",
579+
},
580+
}
581+
582+
go func() {
583+
handler.Chan() <- testEntry
584+
}()
585+
586+
select {
587+
case capturedEntry := <-captureChannel:
588+
require.Equal(t, tc.expectedLabels, capturedEntry.Labels)
589+
case <-time.After(time.Second):
590+
t.Fatalf("timeout waiting for entry in test case: %s", tc.name)
591+
}
592+
593+
handler.Stop()
594+
})
595+
}
596+
}

0 commit comments

Comments
 (0)