github.com/go-graphite/carbonapi@v0.17.0/cmd/carbonapi/config/init.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "expvar" 6 "fmt" 7 "os" 8 "runtime" 9 "sort" 10 "strconv" 11 "strings" 12 "time" 13 "unicode" 14 15 "github.com/go-graphite/carbonapi/util/pidfile" 16 zipperConfig "github.com/go-graphite/carbonapi/zipper/config" 17 18 "github.com/ansel1/merry" 19 "github.com/lomik/zapwriter" 20 "github.com/spf13/viper" 21 "go.uber.org/zap" 22 23 "github.com/go-graphite/carbonapi/cache" 24 "github.com/go-graphite/carbonapi/expr/functions" 25 "github.com/go-graphite/carbonapi/expr/functions/cairo/png" 26 fconfig "github.com/go-graphite/carbonapi/expr/functions/config" 27 "github.com/go-graphite/carbonapi/expr/helper" 28 "github.com/go-graphite/carbonapi/expr/rewrite" 29 tconfig "github.com/go-graphite/carbonapi/expr/types/config" 30 "github.com/go-graphite/carbonapi/limiter" 31 "github.com/go-graphite/carbonapi/pkg/parser" 32 zipperTypes "github.com/go-graphite/carbonapi/zipper/types" 33 ) 34 35 var graphTemplates map[string]png.PictureParams 36 37 func truncateTimeSlice(m map[time.Duration]time.Duration) ([]DurationTruncate, error) { 38 s := make([]DurationTruncate, len(m)) 39 n := 0 40 for k, v := range m { 41 if v <= 0 || k < 0 { 42 return nil, fmt.Errorf("invalid duration truncate: %v:%v", k, v) 43 } 44 s[n] = DurationTruncate{Duration: k, Truncate: v} 45 n++ 46 } 47 48 s = s[0:n] 49 // sort in reverse order 50 sort.Slice(s, func(i, j int) bool { 51 return s[i].Duration > s[j].Duration 52 }) 53 54 return s, nil 55 } 56 57 func SetUpConfig(logger *zap.Logger, BuildVersion string) { 58 Config.ResponseCacheConfig.MemcachedServers = viper.GetStringSlice("cache.memcachedServers") 59 Config.BackendCacheConfig.MemcachedServers = viper.GetStringSlice("backendCache.memcachedServers") 60 if n := viper.GetString("logger.logger"); n != "" { 61 Config.Logger[0].Logger = n 62 } 63 if n := viper.GetString("logger.file"); n != "" { 64 Config.Logger[0].File = n 65 } 66 if n := viper.GetString("logger.level"); n != "" { 67 Config.Logger[0].Level = n 68 } 69 if n := viper.GetString("logger.encoding"); n != "" { 70 Config.Logger[0].Encoding = n 71 } 72 if n := viper.GetString("logger.encodingtime"); n != "" { 73 Config.Logger[0].EncodingTime = n 74 } 75 if n := viper.GetString("logger.encodingduration"); n != "" { 76 Config.Logger[0].EncodingDuration = n 77 } 78 err := zapwriter.ApplyConfig(Config.Logger) 79 if err != nil { 80 logger.Fatal("failed to initialize logger with requested configuration", 81 zap.Any("configuration", Config.Logger), 82 zap.Error(err), 83 ) 84 } 85 86 needStackTrace := false 87 for _, l := range Config.Logger { 88 if strings.ToLower(l.Level) == "debug" { 89 needStackTrace = true 90 break 91 } 92 } 93 merry.SetStackCaptureEnabled(needStackTrace) 94 95 if Config.GraphTemplates != "" { 96 graphTemplates = make(map[string]png.PictureParams) 97 graphTemplatesViper := viper.New() 98 b, err := os.ReadFile(Config.GraphTemplates) 99 if err != nil { 100 logger.Fatal("error reading graphTemplates file", 101 zap.String("graphTemplate_path", Config.GraphTemplates), 102 zap.Error(err), 103 ) 104 } 105 106 if strings.HasSuffix(Config.GraphTemplates, ".toml") { 107 logger.Info("will parse config as toml", 108 zap.String("graphTemplate_path", Config.GraphTemplates), 109 ) 110 graphTemplatesViper.SetConfigType("TOML") 111 } else { 112 logger.Info("will parse config as yaml", 113 zap.String("graphTemplate_path", Config.GraphTemplates), 114 ) 115 graphTemplatesViper.SetConfigType("YAML") 116 } 117 118 err = graphTemplatesViper.ReadConfig(bytes.NewBuffer(b)) 119 if err != nil { 120 logger.Fatal("failed to parse config", 121 zap.String("graphTemplate_path", Config.GraphTemplates), 122 zap.Error(err), 123 ) 124 } 125 126 for k := range graphTemplatesViper.AllSettings() { 127 // we need to explicitly copy YDivisors and ColorList 128 newStruct := png.DefaultParams 129 newStruct.ColorList = nil 130 newStruct.YDivisors = nil 131 sub := graphTemplatesViper.Sub(k) 132 err = sub.Unmarshal(&newStruct) 133 if err != nil { 134 logger.Error("failed to parse graphTemplates config, settings will be ignored", 135 zap.String("graphTemplate_path", Config.GraphTemplates), 136 zap.Error(err), 137 ) 138 } 139 if newStruct.ColorList == nil || len(newStruct.ColorList) == 0 { 140 newStruct.ColorList = make([]string, len(png.DefaultParams.ColorList)) 141 copy(newStruct.ColorList, png.DefaultParams.ColorList) 142 } 143 if newStruct.YDivisors == nil || len(newStruct.YDivisors) == 0 { 144 newStruct.YDivisors = make([]float64, len(png.DefaultParams.YDivisors)) 145 copy(newStruct.YDivisors, png.DefaultParams.YDivisors) 146 } 147 graphTemplates[k] = newStruct 148 } 149 150 // skipcq: CRT-P0006 151 for name, params := range graphTemplates { 152 png.SetTemplate(name, ¶ms) 153 } 154 } 155 156 if Config.DefaultColors != nil { 157 for name, color := range Config.DefaultColors { 158 err = png.SetColor(name, color) 159 if err != nil { 160 logger.Warn("invalid color specified and will be ignored", 161 zap.String("reason", "color must be valid hex rgb or rbga value, e.x. '#c80032', 'c80032', 'c80032ff', etc."), 162 zap.Error(err), 163 ) 164 } 165 } 166 } 167 168 if Config.FunctionsConfigs != nil { 169 logger.Info("extra configuration for functions found", 170 zap.Any("extra_config", Config.FunctionsConfigs), 171 ) 172 } else { 173 Config.FunctionsConfigs = make(map[string]string) 174 } 175 176 rewrite.New(Config.FunctionsConfigs) 177 functions.New(Config.FunctionsConfigs) 178 179 expvar.NewString("GoVersion").Set(runtime.Version()) 180 expvar.NewString("BuildVersion").Set(BuildVersion) 181 expvar.Publish("config", Config) 182 183 Config.Limiter = limiter.NewSimpleLimiter(Config.Concurency) 184 185 Config.ResponseCache = createCache(logger, "cache", &Config.ResponseCacheConfig) 186 Config.BackendCache = createCache(logger, "backendCache", &Config.BackendCacheConfig) 187 188 if Config.TimezoneString != "" { 189 fields := strings.Split(Config.TimezoneString, ",") 190 191 if len(fields) != 2 { 192 logger.Fatal("unexpected amount of fields in tz", 193 zap.String("timezone_string", Config.TimezoneString), 194 zap.Int("fields_got", len(fields)), 195 zap.Int("fields_expected", 2), 196 ) 197 } 198 199 offs, err := strconv.Atoi(fields[1]) 200 if err != nil { 201 logger.Fatal("unable to parse seconds", 202 zap.String("field[1]", fields[1]), 203 zap.Error(err), 204 ) 205 } 206 207 Config.DefaultTimeZone = time.FixedZone(fields[0], offs) 208 logger.Info("using fixed timezone", 209 zap.String("timezone", Config.DefaultTimeZone.String()), 210 zap.Int("offset", offs), 211 ) 212 } 213 214 if len(Config.UnicodeRangeTables) != 0 { 215 if strings.ToLower(Config.UnicodeRangeTables[0]) == "all" { 216 for _, t := range unicode.Scripts { 217 parser.RangeTables = append(parser.RangeTables, t) 218 } 219 } else { 220 for _, stringRange := range Config.UnicodeRangeTables { 221 t, ok := unicode.Scripts[stringRange] 222 if !ok { 223 supportedTables := make([]string, 0) 224 for tt := range unicode.Scripts { 225 supportedTables = append(supportedTables, tt) 226 } 227 logger.Fatal("unknown unicode table", 228 zap.String("specified_table", stringRange), 229 zap.Strings("supported_tables", supportedTables), 230 zap.String("more_info", "you need to specify the table, by it's alias in unicode"+ 231 " 10.0.0, see https://golang.org/src/unicode/tables.go?#L3437"), 232 ) 233 } 234 parser.RangeTables = append(parser.RangeTables, t) 235 } 236 } 237 } else { 238 parser.RangeTables = append(parser.RangeTables, unicode.Latin) 239 } 240 241 if Config.Cpus != 0 { 242 runtime.GOMAXPROCS(Config.Cpus) 243 } 244 245 if Config.PidFile != "" { 246 err := pidfile.WritePidFile(Config.PidFile) 247 if err != nil { 248 logger.Fatal("error when writing pidfile", 249 zap.Error(err), 250 ) 251 } 252 } 253 254 helper.ExtrapolatePoints = Config.ExtrapolateExperiment 255 if Config.ExtrapolateExperiment { 256 logger.Warn("extraploation experiment is enabled", 257 zap.String("reason", "this feature is highly experimental and untested"), 258 ) 259 } 260 261 tconfig.Config.NudgeStartTimeOnAggregation = Config.NudgeStartTimeOnAggregation 262 tconfig.Config.UseBucketsHighestTimestampOnAggregation = Config.UseBucketsHighestTimestampOnAggregation 263 264 if Config.Listen != "" { 265 listeners := make(map[string]struct{}) 266 for _, l := range Config.Listeners { 267 listeners[l.Address] = struct{}{} 268 } 269 if _, ok := listeners[Config.Listen]; !ok { 270 Config.Listeners = append(Config.Listeners, Listener{ 271 Address: Config.Listen, 272 }) 273 } 274 } 275 276 if len(Config.Listeners) == 0 { 277 Config.Listeners = append(Config.Listeners, Listener{Address: "127.0.0.1:8081"}) 278 } 279 280 for _, define := range Config.Define { 281 if define.Name == "" { 282 logger.Fatal("empty define name") 283 } 284 err := parser.Define(define.Name, define.Template) 285 if err != nil { 286 logger.Fatal("unable to compile define template", 287 zap.Error(err), 288 zap.String("template", define.Template), 289 ) 290 } 291 } 292 } 293 294 func createCache(logger *zap.Logger, cacheName string, cacheConfig *CacheConfig) cache.BytesCache { 295 if cacheConfig.DefaultTimeoutSec <= 0 && cacheConfig.ShortTimeoutSec <= 0 { 296 return cache.NullCache{} 297 } 298 if cacheConfig.ShortTimeoutSec < 0 || cacheConfig.DefaultTimeoutSec == cacheConfig.ShortTimeoutSec { 299 // broken value or short timeout not need due to equal 300 cacheConfig.ShortTimeoutSec = 0 301 } 302 if cacheConfig.DefaultTimeoutSec < cacheConfig.ShortTimeoutSec { 303 cacheConfig.DefaultTimeoutSec = cacheConfig.ShortTimeoutSec 304 } 305 if cacheConfig.ShortDuration == 0 { 306 cacheConfig.ShortDuration = 3 * time.Hour 307 } 308 if cacheConfig.ShortUntilOffsetSec == 0 { 309 cacheConfig.ShortUntilOffsetSec = 120 310 } 311 312 switch cacheConfig.Type { 313 case "memcache": 314 if len(cacheConfig.MemcachedServers) == 0 { 315 logger.Fatal(cacheName + ": memcache cache requested but no memcache servers provided") 316 } 317 318 logger.Info(cacheName+": memcached configured", 319 zap.Strings("servers", cacheConfig.MemcachedServers), 320 ) 321 return cache.NewMemcached("capi-"+cacheName, cacheConfig.MemcachedServers...) 322 case "mem": 323 logger.Info(cacheName + ": in-memory cache configured") 324 return cache.NewExpireCache(uint64(cacheConfig.Size * 1024 * 1024)) 325 case "null": 326 // defaults 327 return cache.NullCache{} 328 default: 329 logger.Error(cacheName+": unknown cache type", 330 zap.String("cache_type", cacheConfig.Type), 331 zap.Strings("known_cache_types", []string{"null", "mem", "memcache"}), 332 ) 333 return nil 334 } 335 } 336 337 func SetUpViper(logger *zap.Logger, configPath *string, exactConfig bool, viperPrefix string) { 338 if *configPath != "" { 339 b, err := os.ReadFile(*configPath) 340 if err != nil { 341 logger.Fatal("error reading config file", 342 zap.String("config_path", *configPath), 343 zap.Error(err), 344 ) 345 } 346 347 if strings.HasSuffix(*configPath, ".toml") { 348 logger.Info("will parse config as toml", 349 zap.String("config_file", *configPath), 350 ) 351 viper.SetConfigType("TOML") 352 } else { 353 logger.Info("will parse config as yaml", 354 zap.String("config_file", *configPath), 355 ) 356 viper.SetConfigType("YAML") 357 } 358 err = viper.ReadConfig(bytes.NewBuffer(b)) 359 if err != nil { 360 logger.Fatal("failed to parse config", 361 zap.String("config_path", *configPath), 362 zap.Error(err), 363 ) 364 } 365 } 366 367 if viperPrefix != "" { 368 viper.SetEnvPrefix(viperPrefix) 369 } 370 viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 371 _ = viper.BindEnv("tz", "carbonapi_tz") 372 viper.SetDefault("listeners", []Listener{}) 373 viper.SetDefault("concurency", 20) 374 viper.SetDefault("cache.type", "mem") 375 viper.SetDefault("cache.size_mb", 0) 376 viper.SetDefault("cache.defaultTimeoutSec", 60) 377 viper.SetDefault("cache.memcachedServers", []string{}) 378 viper.SetDefault("cpus", 0) 379 viper.SetDefault("tz", "") 380 viper.SetDefault("sendGlobsAsIs", nil) 381 viper.SetDefault("alwaysSendGlobsAsIs", nil) 382 viper.SetDefault("extractTagsFromArgs", false) 383 viper.SetDefault("maxBatchSize", 100) 384 viper.SetDefault("graphite.host", "") 385 viper.SetDefault("graphite.interval", "60s") 386 viper.SetDefault("graphite.prefix", "carbon.api") 387 viper.SetDefault("graphite.pattern", "{prefix}.{fqdn}") 388 viper.SetDefault("idleConnections", 10) 389 viper.SetDefault("pidFile", "") 390 viper.SetDefault("upstreams.internalRoutingCache", "600s") 391 viper.SetDefault("upstreams.buckets", 10) 392 viper.SetDefault("upstreams.sumBuckets", false) 393 viper.SetDefault("upstreams.bucketsWidth", []int64{}) 394 viper.SetDefault("upstreams.bucketsLabels", []string{}) 395 viper.SetDefault("upstreams.slowLogThreshold", "1s") 396 viper.SetDefault("upstreams.timeouts.find", "2s") 397 viper.SetDefault("upstreams.timeouts.render", "10s") 398 viper.SetDefault("upstreams.timeouts.connect", "200ms") 399 viper.SetDefault("upstreams.concurrencyLimitPerServer", 0) 400 viper.SetDefault("upstreams.keepAliveInterval", "30s") 401 viper.SetDefault("upstreams.maxIdleConnsPerHost", 100) 402 viper.SetDefault("upstreams.scaleToCommonStep", true) 403 viper.SetDefault("graphite09compat", false) 404 viper.SetDefault("expireDelaySec", 600) 405 viper.SetDefault("useCachingDNSResolver", false) 406 viper.SetDefault("logger", map[string]string{}) 407 viper.SetDefault("combineMultipleTargetsInOne", false) 408 viper.SetDefault("nudgeStartTimeOnAggregation", false) 409 viper.SetDefault("useBucketsHighestTimestampOnAggregation", false) 410 411 viper.AutomaticEnv() 412 413 var err error 414 if exactConfig { 415 err = viper.UnmarshalExact(&Config) 416 } else { 417 err = viper.Unmarshal(&Config) 418 } 419 420 if err != nil { 421 logger.Fatal("failed to parse config", 422 zap.Error(err), 423 ) 424 } 425 426 fconfig.Config.ExtractTagsFromArgs = Config.ExtractTagsFromArgs 427 } 428 429 func SetUpConfigUpstreams(logger *zap.Logger) { 430 if Config.Zipper != "" { 431 logger.Warn("found legacy 'zipper' option, will use it instead of any 'upstreams' specified. This will be removed in future versions!") 432 433 Config.Upstreams.Backends = []string{Config.Zipper} 434 Config.Upstreams.ConcurrencyLimitPerServer = Config.Concurency 435 Config.Upstreams.MaxIdleConnsPerHost = Config.IdleConnections 436 Config.Upstreams.MaxBatchSize = &Config.MaxBatchSize 437 Config.Upstreams.KeepAliveInterval = 10 * time.Second 438 Config.Upstreams.SlowLogThreshold = 1 * time.Second 439 // To emulate previous behavior 440 Config.Upstreams.Timeouts = zipperTypes.Timeouts{ 441 Connect: 1 * time.Second, 442 Render: 600 * time.Second, 443 Find: 600 * time.Second, 444 } 445 Config.Upstreams.ScaleToCommonStep = true 446 } 447 if len(Config.Upstreams.Backends) == 0 && len(Config.Upstreams.BackendsV2.Backends) == 0 { 448 logger.Fatal("no backends specified for upstreams!") 449 } 450 451 oldStyleGlobsUsed := false 452 alwaysSendGlobs := false 453 sendGlobs := false 454 if Config.AlwaysSendGlobsAsIs != nil { 455 alwaysSendGlobs = *Config.AlwaysSendGlobsAsIs 456 oldStyleGlobsUsed = true 457 } 458 459 if Config.SendGlobsAsIs != nil { 460 alwaysSendGlobs = *Config.SendGlobsAsIs 461 oldStyleGlobsUsed = true 462 } 463 464 if oldStyleGlobsUsed { 465 if alwaysSendGlobs { 466 Config.Upstreams.FallbackMaxBatchSize = 0 467 } else if sendGlobs { 468 Config.Upstreams.FallbackMaxBatchSize = Config.MaxBatchSize 469 } else { 470 Config.Upstreams.FallbackMaxBatchSize = 1 471 } 472 } else { 473 Config.Upstreams.FallbackMaxBatchSize = Config.MaxBatchSize 474 } 475 476 Config.Upstreams = *zipperConfig.SanitizeConfig(logger, Config.Upstreams) 477 478 if Config.Buckets != 10 { 479 logger.Warn("`buckets` config option was moved to `upstreams` section, this will be removed in future releases, please migrate your configuration") 480 Config.Upstreams.Buckets = Config.Buckets 481 } 482 483 var err error 484 Config.TruncateTime, err = truncateTimeSlice(Config.TruncateTimeMap) 485 if err != nil { 486 logger.Warn("`truncateTime` config option is invalid", zap.Error(err)) 487 } 488 }