github.com/pinpoint-apm/pinpoint-go-agent@v1.4.1-0.20240110120318-a50c2eb18c8c/config.go (about)

     1  package pinpoint
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math"
     7  	"math/rand"
     8  	"os"
     9  	"regexp"
    10  	"sort"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/fsnotify/fsnotify"
    15  	"github.com/spf13/cast"
    16  	"github.com/spf13/pflag"
    17  	"github.com/spf13/viper"
    18  )
    19  
    20  // Config option keys
    21  const (
    22  	CfgAppName                    = "ApplicationName"
    23  	CfgAppType                    = "ApplicationType"
    24  	CfgAgentID                    = "AgentID"
    25  	CfgAgentName                  = "AgentName"
    26  	CfgCollectorHost              = "Collector.Host"
    27  	CfgCollectorAgentPort         = "Collector.AgentPort"
    28  	CfgCollectorSpanPort          = "Collector.SpanPort"
    29  	CfgCollectorStatPort          = "Collector.StatPort"
    30  	CfgLogLevelOld                = "LogLevel"
    31  	CfgLogLevel                   = "Log.Level"
    32  	CfgLogOutput                  = "Log.Output"
    33  	CfgLogMaxSize                 = "Log.MaxSize"
    34  	CfgSamplingType               = "Sampling.Type"
    35  	CfgSamplingCounterRate        = "Sampling.CounterRate"
    36  	CfgSamplingPercentRate        = "Sampling.PercentRate"
    37  	CfgSamplingNewThroughput      = "Sampling.NewThroughput"
    38  	CfgSamplingContinueThroughput = "Sampling.ContinueThroughput"
    39  	CfgSpanQueueSize              = "Span.QueueSize"
    40  	CfgSpanMaxCallStackDepth      = "Span.MaxCallStackDepth"
    41  	CfgSpanMaxCallStackSequence   = "Span.MaxCallStackSequence"
    42  	CfgStatCollectInterval        = "Stat.CollectInterval"
    43  	CfgStatBatchCount             = "Stat.BatchCount"
    44  	CfgIsContainerEnv             = "IsContainerEnv"
    45  	CfgConfigFile                 = "ConfigFile"
    46  	CfgActiveProfile              = "ActiveProfile"
    47  	CfgSQLTraceBindValue          = "SQL.TraceBindValue"
    48  	CfgSQLMaxBindValueSize        = "SQL.MaxBindValueSize"
    49  	CfgSQLTraceCommit             = "SQL.TraceCommit"
    50  	CfgSQLTraceRollback           = "SQL.TraceRollback"
    51  	CfgSQLTraceQueryStat          = "SQL.TraceQueryStat"
    52  	CfgEnable                     = "Enable"
    53  	CfgHttpUrlStatEnable          = "Http.UrlStat.Enable"
    54  	CfgHttpUrlStatLimitSize       = "Http.UrlStat.LimitSize"
    55  	CfgHttpUrlStatWithMethod      = "Http.UrlStat.WithMethod"
    56  	CfgErrorTraceCallStack        = "Error.TraceCallStack"
    57  	CfgErrorCallStackDepth        = "Error.CallStackDepth"
    58  )
    59  
    60  const (
    61  	cfgIdPattern             = "[a-zA-Z0-9\\._\\-]+"
    62  	maxApplicationNameLength = 24
    63  	maxAgentIdLength         = 24
    64  	maxAgentNameLength       = 255
    65  	samplingTypeCounter      = "COUNTER"
    66  	samplingTypePercent      = "PERCENT"
    67  )
    68  
    69  // Config value type
    70  const (
    71  	CfgInt int = iota
    72  	CfgFloat
    73  	CfgBool
    74  	CfgString
    75  	CfgStringSlice
    76  )
    77  
    78  type cfgMapItem struct {
    79  	value        interface{}
    80  	defaultValue interface{}
    81  	valueType    int
    82  	cmdKey       string
    83  	envKey       string
    84  	dynamic      bool
    85  	oldValue     interface{}
    86  }
    87  
    88  var (
    89  	cfgBaseMap map[string]*cfgMapItem
    90  )
    91  
    92  func initConfig() {
    93  	cfgBaseMap = make(map[string]*cfgMapItem, 0)
    94  
    95  	AddConfig(CfgAppName, CfgString, "", false)
    96  	AddConfig(CfgAppType, CfgInt, ServiceTypeGoApp, false)
    97  	AddConfig(CfgAgentID, CfgString, "", false)
    98  	AddConfig(CfgAgentName, CfgString, "", false)
    99  	AddConfig(CfgCollectorHost, CfgString, "localhost", false)
   100  	AddConfig(CfgCollectorAgentPort, CfgInt, 9991, false)
   101  	AddConfig(CfgCollectorSpanPort, CfgInt, 9993, false)
   102  	AddConfig(CfgCollectorStatPort, CfgInt, 9992, false)
   103  	AddConfig(CfgLogLevelOld, CfgString, "info", true)
   104  	AddConfig(CfgLogLevel, CfgString, "info", true)
   105  	AddConfig(CfgLogOutput, CfgString, "stderr", true)
   106  	AddConfig(CfgLogMaxSize, CfgInt, 10, true)
   107  	AddConfig(CfgSamplingType, CfgString, samplingTypeCounter, true)
   108  	AddConfig(CfgSamplingCounterRate, CfgInt, 1, true)
   109  	AddConfig(CfgSamplingPercentRate, CfgFloat, 100, true)
   110  	AddConfig(CfgSamplingNewThroughput, CfgInt, 0, true)
   111  	AddConfig(CfgSamplingContinueThroughput, CfgInt, 0, true)
   112  	AddConfig(CfgSpanQueueSize, CfgInt, defaultQueueSize, false)
   113  	AddConfig(CfgSpanMaxCallStackDepth, CfgInt, defaultEventDepth, true)
   114  	AddConfig(CfgSpanMaxCallStackSequence, CfgInt, defaultEventSequence, true)
   115  	AddConfig(CfgStatCollectInterval, CfgInt, 5000, false)
   116  	AddConfig(CfgStatBatchCount, CfgInt, 6, false)
   117  	AddConfig(CfgIsContainerEnv, CfgBool, false, false)
   118  	AddConfig(CfgConfigFile, CfgString, "", false)
   119  	AddConfig(CfgActiveProfile, CfgString, "", false)
   120  	AddConfig(CfgSQLTraceBindValue, CfgBool, true, true)
   121  	AddConfig(CfgSQLMaxBindValueSize, CfgInt, 1024, true)
   122  	AddConfig(CfgSQLTraceCommit, CfgBool, true, true)
   123  	AddConfig(CfgSQLTraceRollback, CfgBool, true, true)
   124  	AddConfig(CfgSQLTraceQueryStat, CfgBool, false, true)
   125  	AddConfig(CfgEnable, CfgBool, true, false)
   126  	AddConfig(CfgHttpUrlStatEnable, CfgBool, false, true)
   127  	AddConfig(CfgHttpUrlStatLimitSize, CfgInt, 1024, true)
   128  	AddConfig(CfgHttpUrlStatWithMethod, CfgBool, false, true)
   129  	AddConfig(CfgErrorTraceCallStack, CfgBool, false, true)
   130  	AddConfig(CfgErrorCallStackDepth, CfgInt, 32, true)
   131  }
   132  
   133  // AddConfig adds a configuration item.
   134  func AddConfig(cfgName string, valueType int, defaultValue interface{}, dynamic bool) {
   135  	cfgBaseMap[cfgName] = &cfgMapItem{
   136  		defaultValue: defaultValue,
   137  		valueType:    valueType,
   138  		cmdKey:       cmdName(cfgName),
   139  		envKey:       envName(cfgName),
   140  		dynamic:      dynamic,
   141  	}
   142  }
   143  
   144  func cmdName(cfgName string) string {
   145  	return "pinpoint-" + strings.ReplaceAll(strings.ToLower(cfgName), ".", "-")
   146  }
   147  
   148  func envName(cfgName string) string {
   149  	return strings.ReplaceAll(strings.ToLower(cfgName), ".", "_")
   150  }
   151  
   152  // Config holds agent configuration, for passing to NewAgent.
   153  type Config struct {
   154  	cfgMap         map[string]*cfgMapItem
   155  	containerCheck bool
   156  	useNewLogOpt   bool
   157  	offGrpc        bool //for test
   158  
   159  	//dynamic config
   160  	callback             []reloadCallback
   161  	collectUrlStat       bool  // CfgHttpUrlStatEnable
   162  	urlStatLimitSize     int   // CfgHttpUrlStatLimitSize
   163  	urlStatWithMethod    bool  // CfgHttpUrlStatWithMethod
   164  	sqlTraceBindValue    bool  // CfgSQLTraceBindValue
   165  	sqlMaxBindValueSize  int   // CfgSQLMaxBindValueSize
   166  	sqlTraceCommit       bool  // CfgSQLTraceCommit
   167  	sqlTraceRollback     bool  // CfgSQLTraceRollback
   168  	sqlTraceQueryStat    bool  // CfgSQLTraceQueryStat
   169  	spanMaxEventDepth    int32 // CfgSpanMaxCallStackDepth
   170  	spanMaxEventSequence int32 // CfgSpanMaxCallStackSequence
   171  	errorTraceCallStack  bool  // CfgErrorTraceCallStack
   172  	errorCallStackDepth  int   // CfgErrorCallStackDepth
   173  }
   174  
   175  // ConfigOption represents an option that can be passed to NewConfig.
   176  type ConfigOption func(*Config)
   177  
   178  // GetConfig returns a global Config created by NewConfig.
   179  func GetConfig() *Config {
   180  	return GetAgent().Config()
   181  }
   182  
   183  // Set stores the specified configuration item value.
   184  func (config *Config) Set(cfgName string, value interface{}) {
   185  	if v, ok := config.cfgMap[cfgName]; ok {
   186  		v.value = value
   187  	}
   188  }
   189  
   190  // Int returns an integer value for the specified configuration item.
   191  func (config *Config) Int(cfgName string) int {
   192  	if v, ok := config.cfgMap[cfgName]; ok {
   193  		return cast.ToInt(v.value)
   194  	}
   195  	return 0
   196  }
   197  
   198  // Float returns a float value for the specified configuration item.
   199  func (config *Config) Float(cfgName string) float64 {
   200  	if v, ok := config.cfgMap[cfgName]; ok {
   201  		return cast.ToFloat64(v.value)
   202  	}
   203  	return 0
   204  }
   205  
   206  // String returns a string value for the specified configuration item.
   207  func (config *Config) String(cfgName string) string {
   208  	if v, ok := config.cfgMap[cfgName]; ok {
   209  		return cast.ToString(v.value)
   210  	}
   211  	return ""
   212  }
   213  
   214  // StringSlice returns a string slice value for the specified configuration item.
   215  func (config *Config) StringSlice(cfgName string) []string {
   216  	if v, ok := config.cfgMap[cfgName]; ok {
   217  		return cast.ToStringSlice(v.value)
   218  	}
   219  	return []string{}
   220  }
   221  
   222  // Bool returns a boolean value for the specified configuration item.
   223  func (config *Config) Bool(cfgName string) bool {
   224  	if v, ok := config.cfgMap[cfgName]; ok {
   225  		return cast.ToBool(v.value)
   226  	}
   227  	return false
   228  }
   229  
   230  // NewConfig creates a Config populated with default settings, command line arguments,
   231  // environment variables and the given config options.
   232  // Config uses the following precedence order. Each item takes precedence over the item below it:
   233  //  1. command line flag
   234  //  2. environment variable
   235  //  3. configuration file
   236  //  4. ConfigOption
   237  //  5. default
   238  //
   239  // configuration keys used in config files are case-insensitive.
   240  // The generated Config is maintained globally.
   241  //
   242  // example:
   243  //
   244  //	opts := []pinpoint.ConfigOption{
   245  //	  pinpoint.WithAppName("GoTestApp"),
   246  //	  pinpoint.WithConfigFile(os.Getenv("HOME") + "/tmp/pinpoint-config.yaml"),
   247  //	}
   248  //	cfg, err := pinpoint.NewConfig(opts...)
   249  func NewConfig(opts ...ConfigOption) (*Config, error) {
   250  	config := defaultConfig()
   251  	if opts != nil {
   252  		for _, fn := range opts {
   253  			fn(config)
   254  		}
   255  	}
   256  
   257  	cmdEnvViper := viper.New()
   258  	flagSet := config.newFlagSet()
   259  	if err := flagSet.Parse(filterCmdArgs()); err != nil {
   260  		Log("config").Errorf("commad line config loading error: %v", err)
   261  	}
   262  	cmdEnvViper.BindPFlags(flagSet)
   263  	cmdEnvViper.SetEnvPrefix("pinpoint_go")
   264  	cmdEnvViper.AutomaticEnv()
   265  
   266  	cfgFileViper := config.loadConfigFile(cmdEnvViper)
   267  	profileViper := config.loadProfile(cmdEnvViper, cfgFileViper)
   268  	config.loadConfig(cmdEnvViper, cfgFileViper, profileViper)
   269  
   270  	config.callback = make([]reloadCallback, 0)
   271  	config.applyDynamicConfig()
   272  
   273  	if config.containerCheck {
   274  		config.cfgMap[CfgIsContainerEnv].value = isContainerEnv()
   275  	}
   276  	if config.Int(CfgSpanQueueSize) < 1 {
   277  		config.cfgMap[CfgSpanQueueSize].value = defaultQueueSize
   278  	}
   279  	return config, nil
   280  }
   281  
   282  func defaultConfig() *Config {
   283  	config := new(Config)
   284  	config.cfgMap = make(map[string]*cfgMapItem, 0)
   285  	for k, v := range cfgBaseMap {
   286  		config.cfgMap[k] = &cfgMapItem{
   287  			defaultValue: v.defaultValue,
   288  			valueType:    v.valueType,
   289  			cmdKey:       v.cmdKey,
   290  			envKey:       v.envKey,
   291  			dynamic:      v.dynamic,
   292  		}
   293  	}
   294  	for _, v := range config.cfgMap {
   295  		v.value = v.defaultValue
   296  	}
   297  
   298  	config.containerCheck = true
   299  	config.collectUrlStat = false
   300  	config.urlStatLimitSize = 1024
   301  	config.urlStatWithMethod = false
   302  	config.sqlTraceBindValue = true
   303  	config.sqlMaxBindValueSize = 1024
   304  	config.sqlTraceCommit = true
   305  	config.sqlTraceRollback = true
   306  	config.sqlTraceQueryStat = false
   307  	config.spanMaxEventDepth = defaultEventDepth
   308  	config.spanMaxEventSequence = defaultEventSequence
   309  	config.errorTraceCallStack = false
   310  	config.errorCallStackDepth = 32
   311  
   312  	return config
   313  }
   314  
   315  func (config *Config) newFlagSet() *pflag.FlagSet {
   316  	flagSet := pflag.NewFlagSet("pinpoint_go_agent", pflag.ContinueOnError)
   317  
   318  	for _, v := range config.cfgMap {
   319  		switch v.valueType {
   320  		case CfgInt:
   321  			flagSet.Int(v.cmdKey, 0, "")
   322  		case CfgFloat:
   323  			flagSet.Float64(v.cmdKey, 0, "")
   324  		case CfgBool:
   325  			flagSet.Bool(v.cmdKey, false, "")
   326  		case CfgString:
   327  			flagSet.String(v.cmdKey, "", "")
   328  		case CfgStringSlice:
   329  			flagSet.StringSlice(v.cmdKey, nil, "")
   330  		}
   331  	}
   332  
   333  	return flagSet
   334  }
   335  
   336  func filterCmdArgs() []string {
   337  	cmdArgs := make([]string, 0)
   338  
   339  	for _, arg := range os.Args[1:] {
   340  		if strings.HasPrefix(arg, "--pinpoint-") {
   341  			cmdArgs = append(cmdArgs, arg)
   342  		}
   343  	}
   344  	return cmdArgs
   345  }
   346  
   347  func (config *Config) loadConfigFile(cmdEnvViper *viper.Viper) *viper.Viper {
   348  	var cfgFile string
   349  
   350  	item := config.cfgMap[CfgConfigFile]
   351  	if cmdEnvViper.IsSet(item.cmdKey) {
   352  		cfgFile = cmdEnvViper.GetString(item.cmdKey)
   353  	} else if cmdEnvViper.IsSet(item.envKey) {
   354  		cfgFile = cmdEnvViper.GetString(item.envKey)
   355  	} else {
   356  		cfgFile = item.value.(string)
   357  	}
   358  
   359  	cfgFileViper := viper.New()
   360  	if cfgFile != "" {
   361  		cfgFileViper.SetConfigFile(cfgFile)
   362  		if err := cfgFileViper.ReadInConfig(); err != nil {
   363  			Log("config").Errorf("config file loading error: %v", err)
   364  		}
   365  
   366  		cfgFileViper.OnConfigChange(func(e fsnotify.Event) {
   367  			config.reloadConfig(cfgFileViper)
   368  		})
   369  		cfgFileViper.WatchConfig()
   370  	}
   371  
   372  	return cfgFileViper
   373  }
   374  
   375  func (config *Config) loadProfile(cmdEnvViper *viper.Viper, cfgFileViper *viper.Viper) *viper.Viper {
   376  	var profile string
   377  
   378  	item := config.cfgMap[CfgActiveProfile]
   379  	if cmdEnvViper.IsSet(item.cmdKey) {
   380  		profile = cmdEnvViper.GetString(item.cmdKey)
   381  	} else if cmdEnvViper.IsSet(item.envKey) {
   382  		profile = cmdEnvViper.GetString(item.envKey)
   383  	} else if cfgFileViper.IsSet(CfgActiveProfile) {
   384  		profile = cfgFileViper.GetString(CfgActiveProfile)
   385  	} else {
   386  		profile = item.value.(string)
   387  	}
   388  
   389  	if profile != "" {
   390  		profileViper := cfgFileViper.Sub("profile." + profile)
   391  		if profileViper != nil {
   392  			return profileViper
   393  		} else {
   394  			Log("config").Warnf("config file doesn't have the profile: %s", profile)
   395  		}
   396  	}
   397  
   398  	return viper.New()
   399  }
   400  
   401  func (config *Config) loadConfig(cmdEnvViper *viper.Viper, cfgFileViper *viper.Viper, profileViper *viper.Viper) {
   402  	sortKeys := make([]string, 0)
   403  	for k := range config.cfgMap {
   404  		sortKeys = append(sortKeys, k)
   405  	}
   406  	sort.Strings(sortKeys)
   407  	for _, k := range sortKeys {
   408  		v := config.cfgMap[k]
   409  		if cmdEnvViper.IsSet(v.cmdKey) {
   410  			config.setFinalValue(k, v, cmdEnvViper.Get(v.cmdKey))
   411  		} else if cmdEnvViper.IsSet(v.envKey) {
   412  			config.setFinalValue(k, v, cmdEnvViper.Get(v.envKey))
   413  		} else if profileViper.IsSet(k) {
   414  			config.setFinalValue(k, v, profileViper.Get(k))
   415  		} else if cfgFileViper.IsSet(k) {
   416  			config.setFinalValue(k, v, cfgFileViper.Get(k))
   417  		}
   418  	}
   419  }
   420  
   421  func (config *Config) setFinalValue(cfgName string, item *cfgMapItem, value interface{}) {
   422  	if item.valueType == CfgStringSlice {
   423  		if s, ok := value.(string); ok {
   424  			value = strings.Split(s, ",")
   425  		}
   426  	}
   427  
   428  	item.value = value
   429  	if cfgName == CfgIsContainerEnv {
   430  		config.containerCheck = false
   431  	} else if cfgName == CfgLogLevel {
   432  		config.useNewLogOpt = true
   433  	} else if cfgName == CfgLogLevelOld && !config.useNewLogOpt {
   434  		config.cfgMap[CfgLogLevel].value = value
   435  	}
   436  }
   437  
   438  func (config *Config) checkNameAndID() error {
   439  	r, _ := regexp.Compile(cfgIdPattern)
   440  	appName := config.String(CfgAppName)
   441  	if appName == "" {
   442  		return errors.New("application name is required")
   443  	} else if len(appName) > maxApplicationNameLength {
   444  		return errors.New("application name is too long (max length: " + fmt.Sprint(maxApplicationNameLength) + ")")
   445  	} else if !r.MatchString(appName) {
   446  		return errors.New("application name has invalid pattern (" + cfgIdPattern + ")")
   447  	}
   448  
   449  	agentId := config.String(CfgAgentID)
   450  	if agentId == "" || len(agentId) > maxAgentIdLength || !r.MatchString(agentId) {
   451  		config.cfgMap[CfgAgentID].value = randomString(maxAgentIdLength - 1)
   452  		Log("config").Infof("auto-generated AgentID: %v", config.cfgMap[CfgAgentID].value)
   453  	}
   454  
   455  	agentName := config.String(CfgAgentName)
   456  	if agentName != "" {
   457  		if len(agentName) > maxAgentNameLength {
   458  			return errors.New("agent name is too long (max length: " + fmt.Sprint(maxAgentNameLength) + ")")
   459  		} else if !r.MatchString(agentName) {
   460  			return errors.New("agent name has invalid pattern (" + cfgIdPattern + ")")
   461  		}
   462  	}
   463  	return nil
   464  }
   465  
   466  func (config *Config) applyDynamicConfig() {
   467  	sampleType := strings.ToUpper(strings.TrimSpace(config.String(CfgSamplingType)))
   468  	if sampleType != samplingTypeCounter && sampleType != samplingTypePercent {
   469  		config.cfgMap[CfgSamplingType].value = samplingTypeCounter
   470  		config.cfgMap[CfgSamplingCounterRate].value = 0
   471  	}
   472  
   473  	maxBind := config.Int(CfgSQLMaxBindValueSize)
   474  	if maxBind > 1024 {
   475  		config.cfgMap[CfgSQLMaxBindValueSize].value = 1024
   476  	} else if maxBind < 0 {
   477  		config.cfgMap[CfgSQLTraceBindValue].value = false
   478  		config.cfgMap[CfgSQLMaxBindValueSize].value = 0
   479  	}
   480  	config.sqlTraceBindValue = config.Bool(CfgSQLTraceBindValue)
   481  	config.sqlMaxBindValueSize = config.Int(CfgSQLMaxBindValueSize)
   482  	config.sqlTraceCommit = config.Bool(CfgSQLTraceCommit)
   483  	config.sqlTraceRollback = config.Bool(CfgSQLTraceRollback)
   484  	config.sqlTraceQueryStat = config.Bool(CfgSQLTraceQueryStat)
   485  
   486  	maxDepth := config.Int(CfgSpanMaxCallStackDepth)
   487  	if maxDepth == -1 {
   488  		maxDepth = math.MaxInt32
   489  	} else if maxDepth < minEventDepth {
   490  		maxDepth = minEventDepth
   491  	}
   492  	config.cfgMap[CfgSpanMaxCallStackDepth].value = maxDepth
   493  	config.spanMaxEventDepth = int32(config.Int(CfgSpanMaxCallStackDepth))
   494  
   495  	maxSeq := config.Int(CfgSpanMaxCallStackSequence)
   496  	if maxSeq == -1 {
   497  		maxSeq = math.MaxInt32
   498  	} else if maxSeq < minEventSequence {
   499  		maxSeq = minEventSequence
   500  	}
   501  	config.cfgMap[CfgSpanMaxCallStackSequence].value = maxSeq
   502  	config.spanMaxEventSequence = int32(config.Int(CfgSpanMaxCallStackSequence))
   503  
   504  	if config.Int(CfgLogMaxSize) < 1 {
   505  		config.cfgMap[CfgLogMaxSize].value = 10
   506  	}
   507  	config.collectUrlStat = config.Bool(CfgHttpUrlStatEnable)
   508  	config.urlStatLimitSize = config.Int(CfgHttpUrlStatLimitSize)
   509  	config.urlStatWithMethod = config.Bool(CfgHttpUrlStatWithMethod)
   510  	config.errorTraceCallStack = config.Bool(CfgErrorTraceCallStack)
   511  	config.errorCallStackDepth = config.Int(CfgErrorCallStackDepth)
   512  }
   513  
   514  type reloadCallback struct {
   515  	cfgNames []string
   516  	callback func()
   517  }
   518  
   519  // AddReloadCallback adds a callback function will be called after reloading config file.
   520  func (config *Config) AddReloadCallback(optNames []string, callback func()) {
   521  	config.callback = append(config.callback, reloadCallback{optNames, callback})
   522  }
   523  
   524  func (config *Config) reloadConfig(cfgFileViper *viper.Viper) {
   525  	if err := cfgFileViper.ReadInConfig(); err != nil {
   526  		Log("config").Errorf("config file reloading error: %v", err)
   527  		return
   528  	}
   529  
   530  	profileViper := config.loadProfile(viper.New(), cfgFileViper)
   531  	config.loadDynamicConfig(cfgFileViper, profileViper)
   532  	config.applyDynamicConfig()
   533  
   534  	for _, cb := range config.callback {
   535  		cb.do(config)
   536  	}
   537  }
   538  
   539  func (config *Config) loadDynamicConfig(cfgFileViper *viper.Viper, profileViper *viper.Viper) {
   540  	sortKeys := make([]string, 0)
   541  	for k := range config.cfgMap {
   542  		sortKeys = append(sortKeys, k)
   543  	}
   544  	sort.Strings(sortKeys)
   545  	for _, k := range sortKeys {
   546  		if v := config.cfgMap[k]; v.dynamic {
   547  			v.oldValue = nil
   548  			if profileViper.IsSet(k) {
   549  				config.reloadFinalValue(k, v, profileViper)
   550  			} else if cfgFileViper.IsSet(k) {
   551  				config.reloadFinalValue(k, v, cfgFileViper)
   552  			}
   553  		}
   554  	}
   555  }
   556  
   557  func (config *Config) reloadFinalValue(cfgName string, item *cfgMapItem, viper *viper.Viper) {
   558  	item.oldValue = item.value
   559  	config.setFinalValue(cfgName, item, viper.Get(cfgName))
   560  }
   561  
   562  func (config *Config) isReloaded(cfgName string) bool {
   563  	if item, ok := config.cfgMap[cfgName]; ok {
   564  		return item.oldValue != nil && item.oldValue != item.value
   565  	}
   566  	return false
   567  }
   568  
   569  func (cb reloadCallback) do(config *Config) {
   570  	for _, k := range cb.cfgNames {
   571  		if config.isReloaded(k) {
   572  			cb.callback()
   573  			break
   574  		}
   575  	}
   576  }
   577  
   578  func isContainerEnv() bool {
   579  	_, err := os.Stat("/.dockerenv")
   580  	if err == nil || !os.IsNotExist(err) {
   581  		return true
   582  	}
   583  
   584  	if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
   585  		return true
   586  	}
   587  
   588  	return false
   589  }
   590  
   591  // WithAppName sets the application name.
   592  func WithAppName(name string) ConfigOption {
   593  	return func(c *Config) {
   594  		c.cfgMap[CfgAppName].value = name
   595  	}
   596  }
   597  
   598  // WithAppType sets the application type.
   599  func WithAppType(typ int32) ConfigOption {
   600  	return func(c *Config) {
   601  		c.cfgMap[CfgAppType].value = typ
   602  	}
   603  }
   604  
   605  // WithAgentId sets the agent ID.
   606  func WithAgentId(id string) ConfigOption {
   607  	return func(c *Config) {
   608  		c.cfgMap[CfgAgentID].value = id
   609  	}
   610  }
   611  
   612  // WithAgentName sets the agent name.
   613  func WithAgentName(name string) ConfigOption {
   614  	return func(c *Config) {
   615  		c.cfgMap[CfgAgentName].value = name
   616  	}
   617  }
   618  
   619  // WithConfigFile sets the configuration file.
   620  func WithConfigFile(filePath string) ConfigOption {
   621  	return func(c *Config) {
   622  		c.cfgMap[CfgConfigFile].value = filePath
   623  	}
   624  }
   625  
   626  // WithCollectorHost sets the host address of pinpoint collector.
   627  func WithCollectorHost(host string) ConfigOption {
   628  	return func(c *Config) {
   629  		c.cfgMap[CfgCollectorHost].value = host
   630  	}
   631  }
   632  
   633  // WithCollectorAgentPort sets the agent port of pinpoint collector.
   634  func WithCollectorAgentPort(port int) ConfigOption {
   635  	return func(c *Config) {
   636  		c.cfgMap[CfgCollectorAgentPort].value = port
   637  	}
   638  }
   639  
   640  // WithCollectorSpanPort sets the span port of pinpoint collector.
   641  func WithCollectorSpanPort(port int) ConfigOption {
   642  	return func(c *Config) {
   643  		c.cfgMap[CfgCollectorSpanPort].value = port
   644  	}
   645  }
   646  
   647  // WithCollectorStatPort sets the agent stat of pinpoint collector.
   648  func WithCollectorStatPort(port int) ConfigOption {
   649  	return func(c *Config) {
   650  		c.cfgMap[CfgCollectorStatPort].value = port
   651  	}
   652  }
   653  
   654  // WithLogLevel sets the logging level for agent logger.
   655  func WithLogLevel(level string) ConfigOption {
   656  	return func(c *Config) {
   657  		c.cfgMap[CfgLogLevel].value = level
   658  	}
   659  }
   660  
   661  // WithLogOutput sets the output for agent logger.
   662  func WithLogOutput(output string) ConfigOption {
   663  	return func(c *Config) {
   664  		c.cfgMap[CfgLogOutput].value = output
   665  	}
   666  }
   667  
   668  // WithLogMaxSize sets the max size of output file for agent logger.
   669  func WithLogMaxSize(size int) ConfigOption {
   670  	return func(c *Config) {
   671  		c.cfgMap[CfgLogMaxSize].value = size
   672  	}
   673  }
   674  
   675  // WithSamplingType sets the type of agent sampler.
   676  // Either "COUNTER" or "PERCENT" must be specified.
   677  func WithSamplingType(samplingType string) ConfigOption {
   678  	return func(c *Config) {
   679  		c.cfgMap[CfgSamplingType].value = samplingType
   680  	}
   681  }
   682  
   683  // WithSamplingRate DEPRECATED: Use WithSamplingCounterRate()
   684  func WithSamplingRate(rate int) ConfigOption {
   685  	return func(c *Config) {
   686  		c.cfgMap[CfgSamplingCounterRate].value = rate
   687  	}
   688  }
   689  
   690  // WithSamplingCounterRate sets the sampling rate for a 'counter sampler'.
   691  func WithSamplingCounterRate(rate int) ConfigOption {
   692  	return func(c *Config) {
   693  		c.cfgMap[CfgSamplingCounterRate].value = rate
   694  	}
   695  }
   696  
   697  // WithSamplingPercentRate sets the sampling rate for a 'percent sampler'.
   698  func WithSamplingPercentRate(rate float32) ConfigOption {
   699  	return func(c *Config) {
   700  		c.cfgMap[CfgSamplingPercentRate].value = rate
   701  	}
   702  }
   703  
   704  // WithSamplingNewThroughput sets the new tps for a 'throughput sampler'.
   705  func WithSamplingNewThroughput(tps int) ConfigOption {
   706  	return func(c *Config) {
   707  		c.cfgMap[CfgSamplingNewThroughput].value = tps
   708  	}
   709  }
   710  
   711  // WithSamplingContinueThroughput sets the cont tps for a 'throughput sampler'.
   712  func WithSamplingContinueThroughput(tps int) ConfigOption {
   713  	return func(c *Config) {
   714  		c.cfgMap[CfgSamplingContinueThroughput].value = tps
   715  	}
   716  }
   717  
   718  // WithStatCollectInterval sets the statistics collection cycle for the agent.
   719  func WithStatCollectInterval(interval int) ConfigOption {
   720  	return func(c *Config) {
   721  		c.cfgMap[CfgStatCollectInterval].value = interval
   722  	}
   723  }
   724  
   725  // WithStatBatchCount sets batch delivery units for collected statistics.
   726  func WithStatBatchCount(count int) ConfigOption {
   727  	return func(c *Config) {
   728  		c.cfgMap[CfgStatBatchCount].value = count
   729  	}
   730  }
   731  
   732  // WithIsContainerEnv sets whether the application is running in a container environment or not.
   733  // If this is not set, the agent automatically checks it.
   734  func WithIsContainerEnv(isContainer bool) ConfigOption {
   735  	return func(c *Config) {
   736  		c.cfgMap[CfgIsContainerEnv].value = isContainer
   737  		c.containerCheck = false
   738  	}
   739  }
   740  
   741  // WithActiveProfile sets the configuration profile.
   742  func WithActiveProfile(profile string) ConfigOption {
   743  	return func(c *Config) {
   744  		c.cfgMap[CfgActiveProfile].value = profile
   745  	}
   746  }
   747  
   748  // WithSQLTraceBindValue enables bind value tracing for SQL Driver.
   749  func WithSQLTraceBindValue(trace bool) ConfigOption {
   750  	return func(c *Config) {
   751  		c.cfgMap[CfgSQLTraceBindValue].value = trace
   752  	}
   753  }
   754  
   755  // WithSQLMaxBindValueSize sets the max length of traced bind value for SQL Driver.
   756  func WithSQLMaxBindValueSize(size int) ConfigOption {
   757  	return func(c *Config) {
   758  		c.cfgMap[CfgSQLMaxBindValueSize].value = size
   759  	}
   760  }
   761  
   762  // WithSQLTraceCommit enables commit tracing for SQL Driver.
   763  func WithSQLTraceCommit(trace bool) ConfigOption {
   764  	return func(c *Config) {
   765  		c.cfgMap[CfgSQLTraceCommit].value = trace
   766  	}
   767  }
   768  
   769  // WithSQLTraceRollback enables rollback tracing for SQL Driver.
   770  func WithSQLTraceRollback(trace bool) ConfigOption {
   771  	return func(c *Config) {
   772  		c.cfgMap[CfgSQLTraceRollback].value = trace
   773  	}
   774  }
   775  
   776  // WithSQLTraceQueryStat enables to trace SQL query statistics for SQL Driver.
   777  func WithSQLTraceQueryStat(collect bool) ConfigOption {
   778  	return func(c *Config) {
   779  		c.cfgMap[CfgSQLTraceQueryStat].value = collect
   780  	}
   781  }
   782  
   783  // WithEnable enables the agent is operational state.
   784  func WithEnable(enable bool) ConfigOption {
   785  	return func(c *Config) {
   786  		c.cfgMap[CfgEnable].value = enable
   787  	}
   788  }
   789  
   790  // WithSpanQueueSize sets the size of the span queue for gRPC.
   791  func WithSpanQueueSize(size int) ConfigOption {
   792  	return func(c *Config) {
   793  		c.cfgMap[CfgSpanQueueSize].value = size
   794  	}
   795  }
   796  
   797  // WithSpanMaxCallStackDepth sets the max callstack depth of a span.
   798  func WithSpanMaxCallStackDepth(depth int) ConfigOption {
   799  	return func(c *Config) {
   800  		c.cfgMap[CfgSpanMaxCallStackDepth].value = depth
   801  	}
   802  }
   803  
   804  // WithSpanMaxCallStackSequence sets the max callstack sequence of a span.
   805  func WithSpanMaxCallStackSequence(seq int) ConfigOption {
   806  	return func(c *Config) {
   807  		c.cfgMap[CfgSpanMaxCallStackSequence].value = seq
   808  	}
   809  }
   810  
   811  // WithHttpUrlStatEnable enables the agent collects the HTTP URL statistics.
   812  func WithHttpUrlStatEnable(enable bool) ConfigOption {
   813  	return func(c *Config) {
   814  		c.cfgMap[CfgHttpUrlStatEnable].value = enable
   815  	}
   816  }
   817  
   818  // WithHttpUrlStatLimitSize sets the maximum number of URLs that can be stored in one snapshot.
   819  func WithHttpUrlStatLimitSize(size int) ConfigOption {
   820  	return func(c *Config) {
   821  		c.cfgMap[CfgHttpUrlStatLimitSize].value = size
   822  	}
   823  }
   824  
   825  // WithHttpUrlStatWithMethod adds http method as prefix to uri string key.
   826  func WithHttpUrlStatWithMethod(withMethod bool) ConfigOption {
   827  	return func(c *Config) {
   828  		c.cfgMap[CfgHttpUrlStatWithMethod].value = withMethod
   829  	}
   830  }
   831  
   832  // WithErrorTraceCallStack enables the agent collects a call stack when error occurs.
   833  func WithErrorTraceCallStack(trace bool) ConfigOption {
   834  	return func(c *Config) {
   835  		c.cfgMap[CfgErrorTraceCallStack].value = trace
   836  	}
   837  }
   838  
   839  // WithErrorCallStackDepth sets the maximum depth of call stack that can be dumped.
   840  func WithErrorCallStackDepth(depth int) ConfigOption {
   841  	return func(c *Config) {
   842  		c.cfgMap[CfgErrorCallStackDepth].value = depth
   843  	}
   844  }
   845  
   846  func (config *Config) printConfigString() {
   847  	sortKeys := make([]string, 0)
   848  	for k := range config.cfgMap {
   849  		sortKeys = append(sortKeys, k)
   850  	}
   851  	sort.Strings(sortKeys)
   852  
   853  	for _, k := range sortKeys {
   854  		Log("config").Infof("%s = %v", k, config.cfgMap[k].value)
   855  	}
   856  }
   857  
   858  const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
   859  
   860  func randomString(n int) string {
   861  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   862  	l := len(charset)
   863  
   864  	b := make([]byte, n)
   865  	for i := range b {
   866  		b[i] = charset[r.Intn(l)]
   867  	}
   868  	return string(b)
   869  }