|  | // Licensed to Apache Software Foundation (ASF) under one or more contributor | 
|  | // license agreements. See the NOTICE file distributed with | 
|  | // this work for additional information regarding copyright | 
|  | // ownership. Apache Software Foundation (ASF) licenses this file to you under | 
|  | // the Apache License, Version 2.0 (the "License"); you may | 
|  | // not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, | 
|  | // software distributed under the License is distributed on an | 
|  | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | // KIND, either express or implied.  See the License for the | 
|  | // specific language governing permissions and limitations | 
|  | // under the License. | 
|  |  | 
|  | package config | 
|  |  | 
|  | import ( | 
|  | "embed" | 
|  | "fmt" | 
|  | "os" | 
|  | "reflect" | 
|  | "regexp" | 
|  | "strings" | 
|  |  | 
|  | "gopkg.in/yaml.v3" | 
|  | ) | 
|  |  | 
|  | //go:embed agent.default.yaml | 
|  | var defaultAgentFS embed.FS | 
|  |  | 
|  | var config *Config | 
|  |  | 
|  | var EnvRegularRegex = regexp.MustCompile(`\${(?P<ENV>[_A-Z0-9]+):(?P<DEF>.*)}`) | 
|  |  | 
|  | var ConfigTypeAutomatic = "auto" | 
|  |  | 
|  | type Config struct { | 
|  | Agent    Agent    `yaml:"agent"` | 
|  | Reporter Reporter `yaml:"reporter"` | 
|  | Log      Log      `yaml:"log"` | 
|  | Plugin   Plugin   `yaml:"plugin"` | 
|  | } | 
|  |  | 
|  | type Agent struct { | 
|  | ServiceName     StringValue `yaml:"service_name"` | 
|  | InstanceEnvName StringValue `yaml:"instance_env_name"` | 
|  | Sampler         StringValue `yaml:"sampler"` | 
|  | Meter           Meter       `yaml:"meter"` | 
|  | Correlation     Correlation `yaml:"correlation"` | 
|  | IgnoreSuffix    StringValue `yaml:"ignore_suffix"` | 
|  | TraceIgnorePath StringValue `yaml:"trace_ignore_path"` | 
|  | } | 
|  |  | 
|  | type Reporter struct { | 
|  | Discard       StringValue   `yaml:"discard"` | 
|  | Type          StringValue   `yaml:"type"` | 
|  | CheckInterval StringValue   `yaml:"check_interval"` | 
|  | GRPC          GRPCReporter  `yaml:"grpc"` | 
|  | Kafka         KafkaReporter `yaml:"kafka"` | 
|  | } | 
|  |  | 
|  | type Log struct { | 
|  | Type     StringValue `yaml:"type"` | 
|  | Tracing  LogTracing  `yaml:"tracing"` | 
|  | Reporter LogReporter `yaml:"reporter"` | 
|  | } | 
|  |  | 
|  | type LogTracing struct { | 
|  | Enabled StringValue `yaml:"enable"` | 
|  | Key     StringValue `yaml:"key"` | 
|  | } | 
|  |  | 
|  | type LogReporter struct { | 
|  | Enabled   StringValue `yaml:"enable"` | 
|  | LabelKeys StringValue `yaml:"label_keys"` | 
|  | } | 
|  |  | 
|  | type Meter struct { | 
|  | CollectInterval StringValue `yaml:"collect_interval"` | 
|  | } | 
|  |  | 
|  | type GRPCReporter struct { | 
|  | BackendService       StringValue       `yaml:"backend_service"` | 
|  | MaxSendQueue         StringValue       `yaml:"max_send_queue"` | 
|  | Authentication       StringValue       `yaml:"authentication"` | 
|  | CDSFetchInterval     StringValue       `yaml:"cds_fetch_interval"` | 
|  | ProfileFetchInterval StringValue       `yaml:"profile_fetch_interval"` | 
|  | TLS                  GRPCReporterTLS   `yaml:"tls"` | 
|  | Pprof                GRPCReporterPprof `yaml:"pprof"` | 
|  | } | 
|  |  | 
|  | type GRPCReporterPprof struct { | 
|  | PprofFetchInterval StringValue `yaml:"pprof_fetch_interval"` | 
|  | PprofFilePath      StringValue `yaml:"pprof_file_path"` | 
|  | } | 
|  |  | 
|  | type GRPCReporterTLS struct { | 
|  | Enable              StringValue `yaml:"enable"` | 
|  | CAPath              StringValue `yaml:"ca_path"` | 
|  | ClientKeyPath       StringValue `yaml:"client_key_path"` | 
|  | ClientCertChainPath StringValue `yaml:"client_cert_chain_path"` | 
|  | InsecureSkipVerify  StringValue `yaml:"insecure_skip_verify"` | 
|  | } | 
|  |  | 
|  | type KafkaReporter struct { | 
|  | Brokers            StringValue `yaml:"brokers"` | 
|  | TopicSegment       StringValue `yaml:"topic_segment"` | 
|  | TopicMeter         StringValue `yaml:"topic_meter"` | 
|  | TopicLogging       StringValue `yaml:"topic_logging"` | 
|  | TopicManagement    StringValue `yaml:"topic_management"` | 
|  | MaxSendQueue       StringValue `yaml:"max_send_queue"` | 
|  | BatchSize          StringValue `yaml:"batch_size"` | 
|  | BatchBytes         StringValue `yaml:"batch_bytes"` | 
|  | BatchTimeoutMillis StringValue `yaml:"batch_timeout_millis"` | 
|  | Acks               StringValue `yaml:"acks"` | 
|  | } | 
|  |  | 
|  | type Plugin struct { | 
|  | Config   PluginConfig `yaml:"config"` | 
|  | Excluded StringValue  `yaml:"excluded"` | 
|  | } | 
|  |  | 
|  | type Correlation struct { | 
|  | MaxKeyCount  StringValue `yaml:"max_key_count"` | 
|  | MaxValueSize StringValue `yaml:"max_value_size"` | 
|  | } | 
|  |  | 
|  | func LoadConfig(path string) error { | 
|  | // load the default config | 
|  | defaultConfig, err := defaultAgentFS.ReadFile("agent.default.yaml") | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | if err1 := yaml.Unmarshal(defaultConfig, &config); err1 != nil { | 
|  | return err1 | 
|  | } | 
|  |  | 
|  | // if the path defined, then merge this two files | 
|  | if path == "" { | 
|  | return nil | 
|  | } | 
|  | definedContent, err := os.ReadFile(path) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | userConfig := &Config{} | 
|  | if err := yaml.Unmarshal(definedContent, userConfig); err != nil { | 
|  | return err | 
|  | } | 
|  | config.overwriteFrom(userConfig) | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func GetConfig() *Config { | 
|  | return config | 
|  | } | 
|  |  | 
|  | type PluginConfig struct { | 
|  | data map[string]interface{} | 
|  | } | 
|  |  | 
|  | func (c *PluginConfig) UnmarshalYAML(value *yaml.Node) error { | 
|  | result := make(map[string]interface{}) | 
|  | if err := value.Decode(&result); err != nil { | 
|  | return err | 
|  | } | 
|  | c.data = result | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (c *PluginConfig) ParseToStringValue(paths ...string) *StringValue { | 
|  | if len(paths) == 0 { | 
|  | return nil | 
|  | } | 
|  | res := c.data[paths[0]] | 
|  | for i := 1; i < len(paths); i++ { | 
|  | if res == nil { | 
|  | return nil | 
|  | } | 
|  | current, ok := res.(map[string]interface{}) | 
|  | if !ok { | 
|  | panic("cannot identity the path: %s" + strings.Join(paths, ".")) | 
|  | } | 
|  | res = current[paths[i]] | 
|  | } | 
|  | if res == nil { | 
|  | panic("the value of path is not found: " + strings.Join(paths, ".")) | 
|  | } | 
|  |  | 
|  | v := &StringValue{} | 
|  | v.UnmarshalString(fmt.Sprintf("%v", res)) | 
|  | return v | 
|  | } | 
|  |  | 
|  | type StringValue struct { | 
|  | EnvKey  string | 
|  | Default string | 
|  | } | 
|  |  | 
|  | func (s *StringValue) UnmarshalYAML(value *yaml.Node) error { | 
|  | var val string | 
|  | if e := value.Decode(&val); e != nil { | 
|  | return e | 
|  | } | 
|  | s.UnmarshalString(val) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (s *StringValue) UnmarshalString(val string) { | 
|  | groups := EnvRegularRegex.FindStringSubmatch(val) | 
|  | if len(groups) == 0 { | 
|  | s.Default = val | 
|  | return | 
|  | } | 
|  |  | 
|  | s.EnvKey = groups[1] | 
|  | s.Default = groups[2] | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoStringValue() string { | 
|  | return strings.ReplaceAll(fmt.Sprintf(`func() string { | 
|  | if "%s" == "" { return "%s"} | 
|  | tmpValue := os.Getenv("%s") | 
|  | if tmpValue == "" { return "%s"} | 
|  | return tmpValue | 
|  | }()`, s.EnvKey, s.Default, s.EnvKey, s.Default), "\n", ";") | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoStringListValue() string { | 
|  | return strings.ReplaceAll(fmt.Sprintf(`func() []string { | 
|  | splitResult := func(s string) []string { | 
|  | t := strings.Split(s, ",") | 
|  | if len(t) == 1 && t[0] == "" { return nil } | 
|  | res := make([]string, 0, 0) | 
|  | for _, v := range t { | 
|  | if v != "" { | 
|  | res = append(res, v) | 
|  | } | 
|  | } | 
|  | return res | 
|  | } | 
|  | if "%s" == "" { return splitResult("%s") } | 
|  | tmpValue := os.Getenv("%s") | 
|  | if tmpValue == "" { return splitResult("%s") } | 
|  | return splitResult(tmpValue) | 
|  | }()`, s.EnvKey, s.Default, s.EnvKey, s.Default), "\n", ";") | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoStringFunction() string { | 
|  | return fmt.Sprintf("func() string { return %s }", s.ToGoStringValue()) | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoIntValue(errorMessage string) string { | 
|  | return strings.ReplaceAll(fmt.Sprintf(`func() int { | 
|  | if "%s" == "" {return %s} | 
|  | tmpValue := os.Getenv("%s") | 
|  | if tmpValue == "" {return %s} | 
|  | res, err := strconv.Atoi(tmpValue) | 
|  | if err != nil { panic(fmt.Errorf("%s", err))} | 
|  | return res | 
|  | }()`, | 
|  | s.EnvKey, s.Default, s.EnvKey, s.Default, errorMessage), "\n", ";") | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoIntFunction(errorMessage string) string { | 
|  | return fmt.Sprintf("func() int { return %s }", s.ToGoIntValue(errorMessage)) | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoFloatValue(errorMessage string) string { | 
|  | return strings.ReplaceAll(fmt.Sprintf(`func() float64 { | 
|  | if "%s" == "" {return %s} | 
|  | tmpValue := os.Getenv("%s") | 
|  | if tmpValue == "" {return %s} | 
|  | res, err := strconv.ParseFloat(tmpValue, 64) | 
|  | if err != nil { panic(fmt.Errorf("%s", err))} | 
|  | return res | 
|  | }()`, | 
|  | s.EnvKey, s.Default, s.EnvKey, s.Default, errorMessage), "\n", ";") | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoFloatFunction(errorMessage string) string { | 
|  | return fmt.Sprintf("func() float64 { return %s }", s.ToGoFloatValue(errorMessage)) | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoBoolValue() string { | 
|  | return strings.ReplaceAll(fmt.Sprintf(`func() bool { | 
|  | if "%s" == "" {return %s} | 
|  | tmpValue := os.Getenv("%s") | 
|  | if tmpValue == "" {return %s} | 
|  | return strings.EqualFold(tmpValue, "true") | 
|  | }()`, | 
|  | s.EnvKey, s.Default, s.EnvKey, s.Default), "\n", ";") | 
|  | } | 
|  |  | 
|  | func (s *StringValue) ToGoBoolFunction() string { | 
|  | return fmt.Sprintf("func() bool { return %s }", s.ToGoBoolValue()) | 
|  | } | 
|  |  | 
|  | func (s *StringValue) GetStringResult() string { | 
|  | val := s.Default | 
|  | if s.EnvKey != "" { | 
|  | if envValue := os.Getenv(s.EnvKey); envValue != "" { | 
|  | val = envValue | 
|  | } | 
|  | } | 
|  | return val | 
|  | } | 
|  |  | 
|  | func (s *StringValue) GetListStringResult() []string { | 
|  | val := s.GetStringResult() | 
|  | if val == "" { | 
|  | return nil | 
|  | } | 
|  | return strings.Split(val, ",") | 
|  | } | 
|  |  | 
|  | func (s *StringValue) overwriteFrom(other *StringValue) { | 
|  | if other.EnvKey != "" { | 
|  | s.EnvKey = other.EnvKey | 
|  | } | 
|  | if other.Default != "" { | 
|  | s.Default = other.Default | 
|  | } | 
|  | } | 
|  |  | 
|  | func (c *Config) overwriteFrom(other *Config) { | 
|  | c1Value := reflect.ValueOf(c).Elem() | 
|  | c2Value := reflect.ValueOf(other).Elem() | 
|  | combineConfigFields(c1Value, c2Value) | 
|  | } | 
|  |  | 
|  | func combineConfigFields(field1, field2 reflect.Value) { | 
|  | if field1.Type() != field2.Type() { | 
|  | panic("config are not the same") | 
|  | } | 
|  |  | 
|  | if field1.Kind() == reflect.Struct { | 
|  | if s, ok := field1.Addr().Interface().(*StringValue); ok { | 
|  | s2 := field2.Addr().Interface().(*StringValue) | 
|  | s.overwriteFrom(s2) | 
|  | } else { | 
|  | for i := 0; i < field1.NumField(); i++ { | 
|  | combineConfigFields(field1.Field(i), field2.Field(i)) | 
|  | } | 
|  | } | 
|  | } | 
|  | } |