Repository navigation
Expand file tree
/
Copy pathconifg.go
More file actions
205 lines (169 loc) · 5.56 KB
/
Copy pathconifg.go
File metadata and controls
205 lines (169 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package main
import (
"fmt"
"strings"
"unsafe"
"github.com/fluent/fluent-bit-go/output"
"github.com/go-viper/mapstructure/v2"
)
type PluginConfig struct {
LogLevel string `mapstructure:"LogLevel"`
ClientType string `mapstructure:"ClientType"`
}
type OTLPConfig struct {
Endpoint string `mapstructure:"Endpoint"`
}
type Config struct {
PluginConfig PluginConfig `mapstructure:",squash"`
OTLPConfig OTLPConfig `mapstructure:",squash"`
}
func (conf *Config) Dump() {
logger.V(1).Info("[flb-go] ===== Plugin Config =====")
logger.V(1).Info("[flb-go]", "ClientType", conf.PluginConfig.ClientType)
logger.V(1).Info("[flb-go]", "LogLevel", conf.PluginConfig.LogLevel)
logger.V(1).Info("")
logger.V(1).Info("[flb-go] ===== OTLP Config =====")
// OTLP general configuration
logger.V(1).Info("[flb-go]", "Endpoint", fmt.Sprintf("%+v", conf.OTLPConfig.Endpoint))
}
func defaultConfig() *Config {
return &Config{
PluginConfig: PluginConfig{
ClientType: NOOP.String(),
LogLevel: "info",
},
OTLPConfig: OTLPConfig{
Endpoint: "localhost:4317",
},
}
}
// This is necessary because there is no direct C interface to retrieve the complete plugin configuration at once.
//
// When adding new configuration options to the plugin, the corresponding keys must be
// added to the configKeys slice below to ensure they are properly extracted.
func NewConfig(ctx unsafe.Pointer) (*Config, error) {
rawCfg := rawConfig(ctx)
// We intentionally call strings.ToLower twice.
// Once https://github.com/fluent/fluent-bit/issues/11776 is resolved,
// we will retrieve the configuration directly from the Fluent Bit C API at
// once without calling the need to lower the characters.
normCfg := normalizeConfigMapKeys(rawCfg)
sanitizeConfigMap(normCfg)
cfg, err := decodeConfig(normCfg)
if err != nil {
return nil, err
}
return cfg, err
}
func rawConfig(ctx unsafe.Pointer) map[string]any {
raw := make(map[string]string)
// Define all possible configuration keys based on the structs and documentation
configKeys := []string{
// General config
"ClientType", "clienttype", "client_type",
"LogLevel", "logLevel", "log_level",
// Common OTLP configs
"Endpoint", "endpoint",
}
for _, key := range configKeys {
if value := output.FLBPluginConfigKey(ctx, key); value != "" {
raw[strings.ToLower(strings.ReplaceAll(key, "_", ""))] = value
}
}
interfaceMap := make(map[string]any)
for k, v := range raw {
interfaceMap[k] = v
}
return interfaceMap
}
// normalizeConfigMapKeys converts all keys in the configuration map to lowercase
// This ensures case-insensitive configuration key matching throughout the codebase
func normalizeConfigMapKeys(configMap map[string]any) map[string]any {
normalized := make(map[string]any, len(configMap))
for key, value := range configMap {
lowerKey := strings.ToLower(key)
// Recursively normalize nested maps
switch v := value.(type) {
case map[string]any:
normalized[lowerKey] = normalizeConfigMapKeys(v)
default:
normalized[lowerKey] = value
}
}
return normalized
}
// sanitizeConfigMap recursively sanitizes all string values in the configuration map
func sanitizeConfigMap(configMap map[string]any) {
for key, value := range configMap {
//nolint:revive // enforce-switch-style: default-case is omitted on purpose
switch v := value.(type) {
case string:
// Remove leading and trailing whitespace first
v = strings.TrimSpace(v)
configMap[key] = v
// Remove surrounding double quotes
if len(v) >= 2 && v[0] == '"' && v[len(v)-1] == '"' {
configMap[key] = v[1 : len(v)-1]
}
// Remove surrounding single quotes
if len(v) >= 2 && v[0] == '\'' && v[len(v)-1] == '\'' {
configMap[key] = v[1 : len(v)-1]
}
case map[string]any:
sanitizeConfigMap(v)
}
}
}
func decodeConfig(configMap map[string]any) (*Config, error) {
config := defaultConfig()
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.StringToSliceHookFunc(","),
mapstructure.StringToBoolHookFunc(),
mapstructure.StringToIntHookFunc(),
),
WeaklyTypedInput: true,
Result: config,
TagName: "mapstructure",
// Ignore fields that need custom processing
IgnoreUntaggedFields: false,
})
if err != nil {
return nil, fmt.Errorf("failed to create mapstructure decoder: %w", err)
}
// Decode the configuration
if err = decoder.Decode(configMap); err != nil {
return nil, fmt.Errorf("failed to decode configuration: %w", err)
}
// Apply custom processing for complex fields that can't be handled by mapstructure
if err = postProcessConfig(config, configMap); err != nil {
return nil, fmt.Errorf("failed to post-process config: %w", err)
}
return config, nil
}
// postProcessConfig handles complex field processing that can't be done with simple mapping
func postProcessConfig(config *Config, configMap map[string]any) error {
processors := []func(*Config, map[string]any) error{
processOTLPConfig,
processLogLevel,
}
for _, processor := range processors {
if err := processor(config, configMap); err != nil {
return err
}
}
return nil
}
func processOTLPConfig(config *Config, configMap map[string]any) error {
if endpoint, ok := configMap["endpoint"].(string); ok && endpoint != "" {
config.OTLPConfig.Endpoint = endpoint
}
return nil
}
func processLogLevel(config *Config, configMap map[string]any) error {
if logLevel, ok := configMap["loglevel"].(string); ok && logLevel != "" {
config.PluginConfig.LogLevel = logLevel
}
return nil
}