diff --git a/config/crd/bases/logging.banzaicloud.io_clusteroutputs.yaml b/config/crd/bases/logging.banzaicloud.io_clusteroutputs.yaml
index 6dedda95e..257610268 100644
--- a/config/crd/bases/logging.banzaicloud.io_clusteroutputs.yaml
+++ b/config/crd/bases/logging.banzaicloud.io_clusteroutputs.yaml
@@ -2366,12 +2366,17 @@ spec:
extra_labels:
additionalProperties:
type: string
- description: Set of labels to include with every Loki stream.
+ description: Set of extra labels to include with every Loki stream.
type: object
extract_kubernetes_labels:
description: 'Extract kubernetes labels as loki labels (default:
false)'
type: boolean
+ labels:
+ additionalProperties:
+ type: string
+ description: Set of labels to include with every Loki stream.
+ type: object
line_format:
description: 'Format to use when flattening the record to a log
line: json, key_value (default: key_value)'
diff --git a/config/crd/bases/logging.banzaicloud.io_outputs.yaml b/config/crd/bases/logging.banzaicloud.io_outputs.yaml
index b990e0a75..0eb520ee5 100644
--- a/config/crd/bases/logging.banzaicloud.io_outputs.yaml
+++ b/config/crd/bases/logging.banzaicloud.io_outputs.yaml
@@ -2362,12 +2362,17 @@ spec:
extra_labels:
additionalProperties:
type: string
- description: Set of labels to include with every Loki stream.
+ description: Set of extra labels to include with every Loki stream.
type: object
extract_kubernetes_labels:
description: 'Extract kubernetes labels as loki labels (default:
false)'
type: boolean
+ labels:
+ additionalProperties:
+ type: string
+ description: Set of labels to include with every Loki stream.
+ type: object
line_format:
description: 'Format to use when flattening the record to a log
line: json, key_value (default: key_value)'
diff --git a/docs/plugins/outputs/loki.md b/docs/plugins/outputs/loki.md
index 199b3dbad..15662f536 100644
--- a/docs/plugins/outputs/loki.md
+++ b/docs/plugins/outputs/loki.md
@@ -23,7 +23,8 @@ More info at https://github.com/banzaicloud/fluent-plugin-kubernetes-loki
| username | *secret.Secret | No | - | Specify a username if the Loki server requires authentication.
[Secret](./secret.md)
|
| password | *secret.Secret | No | - | Specify password if the Loki server requires authentication.
[Secret](./secret.md)
|
| tenant | string | No | - | Loki is a multi-tenant log storage platform and all requests sent must include a tenant.
|
-| extra_labels | Label | No | - | Set of labels to include with every Loki stream.
|
+| labels | Label | No | - | Set of labels to include with every Loki stream.
|
+| extra_labels | map[string]string | No | - | Set of extra labels to include with every Loki stream.
|
| line_format | string | No | json | Format to use when flattening the record to a log line: json, key_value (default: key_value)
|
| extract_kubernetes_labels | bool | No | false | Extract kubernetes labels as loki labels
|
| remove_keys | []string | No | [] | Comma separated list of needless record keys to remove
|
diff --git a/pkg/model/output/loki.go b/pkg/model/output/loki.go
index dc8fa3696..5aecd5648 100644
--- a/pkg/model/output/loki.go
+++ b/pkg/model/output/loki.go
@@ -51,7 +51,9 @@ type LokiOutput struct {
// Loki is a multi-tenant log storage platform and all requests sent must include a tenant.
Tenant string `json:"tenant,omitempty"`
// Set of labels to include with every Loki stream.
- ExtraLabels Label `json:"extra_labels,omitempty"`
+ Labels Label `json:"labels,omitempty"`
+ // Set of extra labels to include with every Loki stream.
+ ExtraLabels map[string]string `json:"extra_labels,omitempty"`
// Format to use when flattening the record to a log line: json, key_value (default: key_value)
LineFormat string `json:"line_format,omitempty" plugin:"default:json"`
// Extract kubernetes labels as loki labels (default: false)
@@ -78,6 +80,13 @@ func (r Label) ToDirective(secretLoader secret.SecretLoader, id string) (types.D
}
return directive, nil
}
+
+func (r Label) merge(input Label) {
+ for k, v := range input {
+ r[k] = v
+ }
+}
+
func (l *LokiOutput) ToDirective(secretLoader secret.SecretLoader, id string) (types.Directive, error) {
pluginType := "loki"
pluginID := id + "_" + pluginType
@@ -90,14 +99,18 @@ func (l *LokiOutput) ToDirective(secretLoader secret.SecretLoader, id string) (t
},
}
if l.ConfigureKubernetesLabels {
- l.ExtraLabels = Label{
+ if l.Labels == nil {
+ l.Labels = Label{}
+ }
+ l.Labels.merge(Label{
"namespace": `$.kubernetes.namespace_name`,
"pod": `$.kubernetes.pod_name`,
"container_id": `$.kubernetes.docker_id`,
"container": `$.kubernetes.container_name`,
"pod_id": `$.kubernetes.pod_id`,
"host": `$.kubernetes.host`,
- }
+ })
+
if l.RemoveKeys != nil {
if !util.Contains(l.RemoveKeys, "kubernetes") {
l.RemoveKeys = append(l.RemoveKeys, "kubernetes")
@@ -114,8 +127,8 @@ func (l *LokiOutput) ToDirective(secretLoader secret.SecretLoader, id string) (t
} else {
loki.Params = params
}
- if l.ExtraLabels != nil {
- if meta, err := l.ExtraLabels.ToDirective(secretLoader, ""); err != nil {
+ if l.Labels != nil {
+ if meta, err := l.Labels.ToDirective(secretLoader, ""); err != nil {
return nil, err
} else {
loki.SubDirectives = append(loki.SubDirectives, meta)
diff --git a/pkg/model/output/loki_test.go b/pkg/model/output/loki_test.go
index 1c097320b..8b05140fc 100644
--- a/pkg/model/output/loki_test.go
+++ b/pkg/model/output/loki_test.go
@@ -26,6 +26,10 @@ func TestLoki(t *testing.T) {
CONFIG := []byte(`
url: http://loki:3100
configure_kubernetes_labels: true
+labels:
+ name: $.name
+extra_labels:
+ testing: "testing"
buffer:
timekey: 1m
timekey_wait: 30s
@@ -35,6 +39,7 @@ buffer:
@type loki
@id test_loki
+ extra_labels {"testing":"testing"}
extract_kubernetes_labels true
line_format json
remove_keys ["kubernetes"]
@@ -43,6 +48,7 @@ buffer:
container $.kubernetes.container_name
container_id $.kubernetes.docker_id
host $.kubernetes.host
+ name $.name
namespace $.kubernetes.namespace_name
pod $.kubernetes.pod_name
pod_id $.kubernetes.pod_id
diff --git a/pkg/model/output/zz_generated.deepcopy.go b/pkg/model/output/zz_generated.deepcopy.go
index c2ca14775..9abfe539f 100644
--- a/pkg/model/output/zz_generated.deepcopy.go
+++ b/pkg/model/output/zz_generated.deepcopy.go
@@ -327,9 +327,16 @@ func (in *LokiOutput) DeepCopyInto(out *LokiOutput) {
*out = new(secret.Secret)
(*in).DeepCopyInto(*out)
}
+ if in.Labels != nil {
+ in, out := &in.Labels, &out.Labels
+ *out = make(Label, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
if in.ExtraLabels != nil {
in, out := &in.ExtraLabels, &out.ExtraLabels
- *out = make(Label, len(*in))
+ *out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
diff --git a/pkg/model/types/stringmaps.go b/pkg/model/types/stringmaps.go
index bf9cf2db6..e800f51e8 100644
--- a/pkg/model/types/stringmaps.go
+++ b/pkg/model/types/stringmaps.go
@@ -176,6 +176,25 @@ func (s *StructToStringMapper) fillMap(value reflect.Value, out map[string]strin
}
}
}
+ case reflect.Map:
+ if mapStringString, ok := v.Interface().(map[string]string); ok {
+ if len(mapStringString) > 0 {
+ b, err := json.Marshal(mapStringString)
+ if err != nil {
+ multierror = errors.Combine(multierror, errors.Errorf("can't marshal field: %q value: %q as json", name, mapStringString), err)
+ }
+ out[name] = string(b)
+ } else {
+ if ok, def := pluginTagOpts.ValueForPrefix("default:"); ok {
+ validate := map[string]string{}
+ if err := json.Unmarshal([]byte(def), &validate); err != nil {
+ multierror = errors.Combine(multierror, errors.Errorf("can't marshal field: %q value: %q as json", name, def), err)
+ }
+ out[name] = def
+ }
+ }
+ }
+
}
}
return multierror