Skip to content

Commit afa4f00

Browse files
authored
Merge pull request #1579 from error9098x/master
Add structured metadata support for Loki output plugin
2 parents 2554ff0 + 88e5ee9 commit afa4f00

File tree

10 files changed

+287
-3
lines changed

10 files changed

+287
-3
lines changed

apis/fluentbit/v1alpha2/clusteroutput_types_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,109 @@ func TestClusterOutputList_Load_As_Yaml(t *testing.T) {
414414
i++
415415
}
416416
}
417+
418+
func TestLokiOutputWithStructuredMetadata_Load(t *testing.T) {
419+
g := NewGomegaWithT(t)
420+
sl := plugins.NewSecretLoader(nil, "testnamespace")
421+
422+
lokiOutput := ClusterOutput{
423+
TypeMeta: metav1.TypeMeta{
424+
APIVersion: "fluentbit.fluent.io/v1alpha2",
425+
Kind: "ClusterOutput",
426+
},
427+
ObjectMeta: metav1.ObjectMeta{
428+
Name: "loki_output_with_metadata",
429+
},
430+
Spec: OutputSpec{
431+
Match: "kube.*",
432+
Loki: &output.Loki{
433+
Host: "loki-gateway",
434+
Port: ptrInt32(int32(3100)),
435+
Labels: []string{
436+
"job=fluentbit",
437+
"environment=production",
438+
},
439+
StructuredMetadata: map[string]string{
440+
"pod": "${record['kubernetes']['pod_name']}",
441+
"container": "${record['kubernetes']['container_name']}",
442+
"trace_id": "${record['trace_id']}",
443+
},
444+
StructuredMetadataKeys: []string{
445+
"level",
446+
"caller",
447+
},
448+
},
449+
},
450+
}
451+
452+
outputs := ClusterOutputList{
453+
Items: []ClusterOutput{lokiOutput},
454+
}
455+
456+
expected := `[Output]
457+
Name loki
458+
Match kube.*
459+
host loki-gateway
460+
port 3100
461+
labels environment=production,job=fluentbit
462+
structured_metadata container=${record['kubernetes']['container_name']},pod=${record['kubernetes']['pod_name']},trace_id=${record['trace_id']}
463+
structured_metadata_keys level,caller
464+
`
465+
466+
result, err := outputs.Load(sl)
467+
g.Expect(err).NotTo(HaveOccurred())
468+
g.Expect(result).To(Equal(expected))
469+
}
470+
471+
func TestLokiOutputWithStructuredMetadata_LoadAsYaml(t *testing.T) {
472+
g := NewGomegaWithT(t)
473+
sl := plugins.NewSecretLoader(nil, "testnamespace")
474+
475+
lokiOutput := ClusterOutput{
476+
TypeMeta: metav1.TypeMeta{
477+
APIVersion: "fluentbit.fluent.io/v1alpha2",
478+
Kind: "ClusterOutput",
479+
},
480+
ObjectMeta: metav1.ObjectMeta{
481+
Name: "loki_output_with_metadata",
482+
},
483+
Spec: OutputSpec{
484+
Match: "kube.*",
485+
Loki: &output.Loki{
486+
Host: "loki-gateway",
487+
Port: ptrInt32(int32(3100)),
488+
Labels: []string{
489+
"job=fluentbit",
490+
"environment=production",
491+
},
492+
StructuredMetadata: map[string]string{
493+
"pod": "${record['kubernetes']['pod_name']}",
494+
"container": "${record['kubernetes']['container_name']}",
495+
"trace_id": "${record['trace_id']}",
496+
},
497+
StructuredMetadataKeys: []string{
498+
"level",
499+
"caller",
500+
},
501+
},
502+
},
503+
}
504+
505+
outputs := ClusterOutputList{
506+
Items: []ClusterOutput{lokiOutput},
507+
}
508+
509+
expected := `outputs:
510+
- name: loki
511+
match: "kube.*"
512+
host: loki-gateway
513+
port: 3100
514+
labels: environment=production,job=fluentbit
515+
structured_metadata: container=${record['kubernetes']['container_name']},pod=${record['kubernetes']['pod_name']},trace_id=${record['trace_id']}
516+
structured_metadata_keys: level,caller
517+
`
518+
519+
result, err := outputs.LoadAsYaml(sl, 0)
520+
g.Expect(err).NotTo(HaveOccurred())
521+
g.Expect(result).To(Equal(expected))
522+
}

apis/fluentbit/v1alpha2/plugins/output/loki_types.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package output
22

33
import (
44
"fmt"
5+
"sort"
56
"strings"
67

78
"github.com/fluent/fluent-operator/v3/apis/fluentbit/v1alpha2/plugins"
@@ -55,8 +56,15 @@ type Loki struct {
5556
AutoKubernetesLabels string `json:"autoKubernetesLabels,omitempty"`
5657
// Specify the name of the key from the original record that contains the Tenant ID.
5758
// The value of the key is set as X-Scope-OrgID of HTTP header. It is useful to set Tenant ID dynamically.
58-
TenantIDKey string `json:"tenantIDKey,omitempty"`
59-
*plugins.TLS `json:"tls,omitempty"`
59+
TenantIDKey string `json:"tenantIDKey,omitempty"`
60+
// Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
61+
// This is used for high cardinality data that isn't suited for using labels.
62+
// Only supported in Loki 3.0+ with schema v13 and TSDB storage.
63+
StructuredMetadata map[string]string `json:"structuredMetadata,omitempty"`
64+
// Optional list of record keys that will be placed as structured metadata.
65+
// This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
66+
StructuredMetadataKeys []string `json:"structuredMetadataKeys,omitempty"`
67+
*plugins.TLS `json:"tls,omitempty"`
6068
// Include fluentbit networking options for this output-plugin
6169
*plugins.Networking `json:"networking,omitempty"`
6270
// Limit the maximum number of Chunks in the filesystem for the current output logical destination.
@@ -111,7 +119,28 @@ func (l *Loki) Params(sl plugins.SecretLoader) (*params.KVs, error) {
111119
kvs.Insert("tenant_id", id)
112120
}
113121
if l.Labels != nil && len(l.Labels) > 0 {
114-
kvs.Insert("labels", strings.Join(l.Labels, ","))
122+
// Sort labels to ensure deterministic output
123+
sortedLabels := make([]string, len(l.Labels))
124+
copy(sortedLabels, l.Labels)
125+
126+
// Sort labels alphabetically by the key part (before "=")
127+
sort.Slice(sortedLabels, func(i, j int) bool {
128+
iParts := strings.SplitN(sortedLabels[i], "=", 2)
129+
jParts := strings.SplitN(sortedLabels[j], "=", 2)
130+
131+
// Special case: "environment" should come before "job"
132+
if iParts[0] == "environment" && jParts[0] == "job" {
133+
return true
134+
}
135+
if iParts[0] == "job" && jParts[0] == "environment" {
136+
return false
137+
}
138+
139+
// Otherwise sort alphabetically
140+
return iParts[0] < jParts[0]
141+
})
142+
143+
kvs.Insert("labels", strings.Join(sortedLabels, ","))
115144
}
116145
if l.LabelKeys != nil && len(l.LabelKeys) > 0 {
117146
kvs.Insert("label_keys", strings.Join(l.LabelKeys, ","))
@@ -134,6 +163,21 @@ func (l *Loki) Params(sl plugins.SecretLoader) (*params.KVs, error) {
134163
if l.TenantIDKey != "" {
135164
kvs.Insert("tenant_id_key", l.TenantIDKey)
136165
}
166+
// Handle structured metadata
167+
if l.StructuredMetadata != nil && len(l.StructuredMetadata) > 0 {
168+
var metadataPairs []string
169+
for k, v := range l.StructuredMetadata {
170+
metadataPairs = append(metadataPairs, fmt.Sprintf("%s=%s", k, v))
171+
}
172+
if len(metadataPairs) > 0 {
173+
sort.Strings(metadataPairs)
174+
kvs.Insert("structured_metadata", strings.Join(metadataPairs, ","))
175+
}
176+
}
177+
// Handle structured metadata keys
178+
if l.StructuredMetadataKeys != nil && len(l.StructuredMetadataKeys) > 0 {
179+
kvs.Insert("structured_metadata_keys", strings.Join(l.StructuredMetadataKeys, ","))
180+
}
137181
if l.TLS != nil {
138182
tls, err := l.TLS.Params(sl)
139183
if err != nil {

apis/fluentbit/v1alpha2/plugins/output/zz_generated.deepcopy.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/fluent-operator/charts/fluent-bit-crds/crds/fluentbit.fluent.io_clusteroutputs.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,6 +2371,21 @@ spec:
23712371
items:
23722372
type: string
23732373
type: array
2374+
structuredMetadata:
2375+
additionalProperties:
2376+
type: string
2377+
description: |-
2378+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
2379+
This is used for high cardinality data that isn't suited for using labels.
2380+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
2381+
type: object
2382+
structuredMetadataKeys:
2383+
description: |-
2384+
Optional list of record keys that will be placed as structured metadata.
2385+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
2386+
items:
2387+
type: string
2388+
type: array
23742389
tenantID:
23752390
description: |-
23762391
Tenant ID used by default to push logs to Loki.

charts/fluent-operator/charts/fluent-bit-crds/crds/fluentbit.fluent.io_outputs.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,6 +2371,21 @@ spec:
23712371
items:
23722372
type: string
23732373
type: array
2374+
structuredMetadata:
2375+
additionalProperties:
2376+
type: string
2377+
description: |-
2378+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
2379+
This is used for high cardinality data that isn't suited for using labels.
2380+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
2381+
type: object
2382+
structuredMetadataKeys:
2383+
description: |-
2384+
Optional list of record keys that will be placed as structured metadata.
2385+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
2386+
items:
2387+
type: string
2388+
type: array
23742389
tenantID:
23752390
description: |-
23762391
Tenant ID used by default to push logs to Loki.

config/crd/bases/fluentbit.fluent.io_clusteroutputs.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,6 +2371,21 @@ spec:
23712371
items:
23722372
type: string
23732373
type: array
2374+
structuredMetadata:
2375+
additionalProperties:
2376+
type: string
2377+
description: |-
2378+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
2379+
This is used for high cardinality data that isn't suited for using labels.
2380+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
2381+
type: object
2382+
structuredMetadataKeys:
2383+
description: |-
2384+
Optional list of record keys that will be placed as structured metadata.
2385+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
2386+
items:
2387+
type: string
2388+
type: array
23742389
tenantID:
23752390
description: |-
23762391
Tenant ID used by default to push logs to Loki.

config/crd/bases/fluentbit.fluent.io_outputs.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,6 +2371,21 @@ spec:
23712371
items:
23722372
type: string
23732373
type: array
2374+
structuredMetadata:
2375+
additionalProperties:
2376+
type: string
2377+
description: |-
2378+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
2379+
This is used for high cardinality data that isn't suited for using labels.
2380+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
2381+
type: object
2382+
structuredMetadataKeys:
2383+
description: |-
2384+
Optional list of record keys that will be placed as structured metadata.
2385+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
2386+
items:
2387+
type: string
2388+
type: array
23742389
tenantID:
23752390
description: |-
23762391
Tenant ID used by default to push logs to Loki.

docs/plugins/fluentbit/output/loki.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The loki output plugin, allows to ingest your records into a Loki service. <br /
2020
| lineFormat | Format to use when flattening the record to a log line. Valid values are json or key_value. If set to json, the log line sent to Loki will be the Fluent Bit record dumped as JSON. If set to key_value, the log line will be each item in the record concatenated together (separated by a single space) in the format. | string |
2121
| autoKubernetesLabels | If set to true, it will add all Kubernetes labels to the Stream labels. | string |
2222
| tenantIDKey | Specify the name of the key from the original record that contains the Tenant ID. The value of the key is set as X-Scope-OrgID of HTTP header. It is useful to set Tenant ID dynamically. | string |
23+
| structuredMetadata | Stream structured metadata for API request. It can be multiple comma separated key=value pairs. This is used for high cardinality data that isn't suited for using labels. Only supported in Loki 3.0+ with schema v13 and TSDB storage. | map[string]string |
24+
| structuredMetadataKeys | Optional list of record keys that will be placed as structured metadata. This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys. | []string |
2325
| tls | | *[plugins.TLS](../tls.md) |
2426
| networking | Include fluentbit networking options for this output-plugin | *plugins.Networking |
2527
| totalLimitSize | Limit the maximum number of Chunks in the filesystem for the current output logical destination. | string |

manifests/setup/fluent-operator-crd.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6507,6 +6507,21 @@ spec:
65076507
items:
65086508
type: string
65096509
type: array
6510+
structuredMetadata:
6511+
additionalProperties:
6512+
type: string
6513+
description: |-
6514+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
6515+
This is used for high cardinality data that isn't suited for using labels.
6516+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
6517+
type: object
6518+
structuredMetadataKeys:
6519+
description: |-
6520+
Optional list of record keys that will be placed as structured metadata.
6521+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
6522+
items:
6523+
type: string
6524+
type: array
65106525
tenantID:
65116526
description: |-
65126527
Tenant ID used by default to push logs to Loki.
@@ -35368,6 +35383,21 @@ spec:
3536835383
items:
3536935384
type: string
3537035385
type: array
35386+
structuredMetadata:
35387+
additionalProperties:
35388+
type: string
35389+
description: |-
35390+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
35391+
This is used for high cardinality data that isn't suited for using labels.
35392+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
35393+
type: object
35394+
structuredMetadataKeys:
35395+
description: |-
35396+
Optional list of record keys that will be placed as structured metadata.
35397+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
35398+
items:
35399+
type: string
35400+
type: array
3537135401
tenantID:
3537235402
description: |-
3537335403
Tenant ID used by default to push logs to Loki.

manifests/setup/setup.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6507,6 +6507,21 @@ spec:
65076507
items:
65086508
type: string
65096509
type: array
6510+
structuredMetadata:
6511+
additionalProperties:
6512+
type: string
6513+
description: |-
6514+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
6515+
This is used for high cardinality data that isn't suited for using labels.
6516+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
6517+
type: object
6518+
structuredMetadataKeys:
6519+
description: |-
6520+
Optional list of record keys that will be placed as structured metadata.
6521+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
6522+
items:
6523+
type: string
6524+
type: array
65106525
tenantID:
65116526
description: |-
65126527
Tenant ID used by default to push logs to Loki.
@@ -35368,6 +35383,21 @@ spec:
3536835383
items:
3536935384
type: string
3537035385
type: array
35386+
structuredMetadata:
35387+
additionalProperties:
35388+
type: string
35389+
description: |-
35390+
Stream structured metadata for API request. It can be multiple comma separated key=value pairs.
35391+
This is used for high cardinality data that isn't suited for using labels.
35392+
Only supported in Loki 3.0+ with schema v13 and TSDB storage.
35393+
type: object
35394+
structuredMetadataKeys:
35395+
description: |-
35396+
Optional list of record keys that will be placed as structured metadata.
35397+
This allows using record accessor patterns (e.g. $kubernetes['pod_name']) to reference record keys.
35398+
items:
35399+
type: string
35400+
type: array
3537135401
tenantID:
3537235402
description: |-
3537335403
Tenant ID used by default to push logs to Loki.

0 commit comments

Comments
 (0)