github.com/outbrain/consul@v1.4.5/lib/telemetry.go (about) 1 package lib 2 3 import ( 4 "reflect" 5 "time" 6 7 metrics "github.com/armon/go-metrics" 8 "github.com/armon/go-metrics/circonus" 9 "github.com/armon/go-metrics/datadog" 10 "github.com/armon/go-metrics/prometheus" 11 ) 12 13 // TelemetryConfig is embedded in config.RuntimeConfig and holds the 14 // configuration variables for go-metrics. It is a separate struct to allow it 15 // to be exported as JSON and passed to other process like managed connect 16 // proxies so they can inherit the agent's telemetry config. 17 // 18 // It is in lib package rather than agent/config because we need to use it in 19 // the shared InitTelemetry functions below, but we can't import agent/config 20 // due to a dependency cycle. 21 type TelemetryConfig struct { 22 // Circonus*: see https://github.com/circonus-labs/circonus-gometrics 23 // for more details on the various configuration options. 24 // Valid configuration combinations: 25 // - CirconusAPIToken 26 // metric management enabled (search for existing check or create a new one) 27 // - CirconusSubmissionUrl 28 // metric management disabled (use check with specified submission_url, 29 // broker must be using a public SSL certificate) 30 // - CirconusAPIToken + CirconusCheckSubmissionURL 31 // metric management enabled (use check with specified submission_url) 32 // - CirconusAPIToken + CirconusCheckID 33 // metric management enabled (use check with specified id) 34 35 // CirconusAPIApp is an app name associated with API token. 36 // Default: "consul" 37 // 38 // hcl: telemetry { circonus_api_app = string } 39 CirconusAPIApp string `json:"circonus_api_app,omitempty" mapstructure:"circonus_api_app"` 40 41 // CirconusAPIToken is a valid API Token used to create/manage check. If provided, 42 // metric management is enabled. 43 // Default: none 44 // 45 // hcl: telemetry { circonus_api_token = string } 46 CirconusAPIToken string `json:"circonus_api_token,omitempty" mapstructure:"circonus_api_token"` 47 48 // CirconusAPIURL is the base URL to use for contacting the Circonus API. 49 // Default: "https://api.circonus.com/v2" 50 // 51 // hcl: telemetry { circonus_api_url = string } 52 CirconusAPIURL string `json:"circonus_apiurl,omitempty" mapstructure:"circonus_apiurl"` 53 54 // CirconusBrokerID is an explicit broker to use when creating a new check. The numeric portion 55 // of broker._cid. If metric management is enabled and neither a Submission URL nor Check ID 56 // is provided, an attempt will be made to search for an existing check using Instance ID and 57 // Search Tag. If one is not found, a new HTTPTRAP check will be created. 58 // Default: use Select Tag if provided, otherwise, a random Enterprise Broker associated 59 // with the specified API token or the default Circonus Broker. 60 // Default: none 61 // 62 // hcl: telemetry { circonus_broker_id = string } 63 CirconusBrokerID string `json:"circonus_broker_id,omitempty" mapstructure:"circonus_broker_id"` 64 65 // CirconusBrokerSelectTag is a special tag which will be used to select a broker when 66 // a Broker ID is not provided. The best use of this is to as a hint for which broker 67 // should be used based on *where* this particular instance is running. 68 // (e.g. a specific geo location or datacenter, dc:sfo) 69 // Default: none 70 // 71 // hcl: telemetry { circonus_broker_select_tag = string } 72 CirconusBrokerSelectTag string `json:"circonus_broker_select_tag,omitempty" mapstructure:"circonus_broker_select_tag"` 73 74 // CirconusCheckDisplayName is the name for the check which will be displayed in the Circonus UI. 75 // Default: value of CirconusCheckInstanceID 76 // 77 // hcl: telemetry { circonus_check_display_name = string } 78 CirconusCheckDisplayName string `json:"circonus_check_display_name,omitempty" mapstructure:"circonus_check_display_name"` 79 80 // CirconusCheckForceMetricActivation will force enabling metrics, as they are encountered, 81 // if the metric already exists and is NOT active. If check management is enabled, the default 82 // behavior is to add new metrics as they are encountered. If the metric already exists in the 83 // check, it will *NOT* be activated. This setting overrides that behavior. 84 // Default: "false" 85 // 86 // hcl: telemetry { circonus_check_metrics_activation = (true|false) 87 CirconusCheckForceMetricActivation string `json:"circonus_check_force_metric_activation,omitempty" mapstructure:"circonus_check_force_metric_activation"` 88 89 // CirconusCheckID is the check id (not check bundle id) from a previously created 90 // HTTPTRAP check. The numeric portion of the check._cid field. 91 // Default: none 92 // 93 // hcl: telemetry { circonus_check_id = string } 94 CirconusCheckID string `json:"circonus_check_id,omitempty" mapstructure:"circonus_check_id"` 95 96 // CirconusCheckInstanceID serves to uniquely identify the metrics coming from this "instance". 97 // It can be used to maintain metric continuity with transient or ephemeral instances as 98 // they move around within an infrastructure. 99 // Default: hostname:app 100 // 101 // hcl: telemetry { circonus_check_instance_id = string } 102 CirconusCheckInstanceID string `json:"circonus_check_instance_id,omitempty" mapstructure:"circonus_check_instance_id"` 103 104 // CirconusCheckSearchTag is a special tag which, when coupled with the instance id, helps to 105 // narrow down the search results when neither a Submission URL or Check ID is provided. 106 // Default: service:app (e.g. service:consul) 107 // 108 // hcl: telemetry { circonus_check_search_tag = string } 109 CirconusCheckSearchTag string `json:"circonus_check_search_tag,omitempty" mapstructure:"circonus_check_search_tag"` 110 111 // CirconusCheckSearchTag is a special tag which, when coupled with the instance id, helps to 112 // narrow down the search results when neither a Submission URL or Check ID is provided. 113 // Default: service:app (e.g. service:consul) 114 // 115 // hcl: telemetry { circonus_check_tags = string } 116 CirconusCheckTags string `json:"circonus_check_tags,omitempty" mapstructure:"circonus_check_tags"` 117 118 // CirconusSubmissionInterval is the interval at which metrics are submitted to Circonus. 119 // Default: 10s 120 // 121 // hcl: telemetry { circonus_submission_interval = "duration" } 122 CirconusSubmissionInterval string `json:"circonus_submission_interval,omitempty" mapstructure:"circonus_submission_interval"` 123 124 // CirconusCheckSubmissionURL is the check.config.submission_url field from a 125 // previously created HTTPTRAP check. 126 // Default: none 127 // 128 // hcl: telemetry { circonus_submission_url = string } 129 CirconusSubmissionURL string `json:"circonus_submission_url,omitempty" mapstructure:"circonus_submission_url"` 130 131 // DisableHostname will disable hostname prefixing for all metrics. 132 // 133 // hcl: telemetry { disable_hostname = (true|false) 134 DisableHostname bool `json:"disable_hostname,omitempty" mapstructure:"disable_hostname"` 135 136 // DogStatsdAddr is the address of a dogstatsd instance. If provided, 137 // metrics will be sent to that instance 138 // 139 // hcl: telemetry { dogstatsd_addr = string } 140 DogstatsdAddr string `json:"dogstatsd_addr,omitempty" mapstructure:"dogstatsd_addr"` 141 142 // DogStatsdTags are the global tags that should be sent with each packet to dogstatsd 143 // It is a list of strings, where each string looks like "my_tag_name:my_tag_value" 144 // 145 // hcl: telemetry { dogstatsd_tags = []string } 146 DogstatsdTags []string `json:"dogstatsd_tags,omitempty" mapstructure:"dogstatsd_tags"` 147 148 // PrometheusRetentionTime is the retention time for prometheus metrics if greater than 0. 149 // A value of 0 disable Prometheus support. Regarding Prometheus, it is considered a good 150 // practice to put large values here (such as a few days), and at least the interval between 151 // prometheus requests. 152 // 153 // hcl: telemetry { prometheus_retention_time = "duration" } 154 PrometheusRetentionTime time.Duration `json:"prometheus_retention_time,omitempty" mapstructure:"prometheus_retention_time"` 155 156 // FilterDefault is the default for whether to allow a metric that's not 157 // covered by the filter. 158 // 159 // hcl: telemetry { filter_default = (true|false) } 160 FilterDefault bool `json:"filter_default,omitempty" mapstructure:"filter_default"` 161 162 // AllowedPrefixes is a list of filter rules to apply for allowing metrics 163 // by prefix. Use the 'prefix_filter' option and prefix rules with '+' to be 164 // included. 165 // 166 // hcl: telemetry { prefix_filter = []string{"+<expr>", "+<expr>", ...} } 167 AllowedPrefixes []string `json:"allowed_prefixes,omitempty" mapstructure:"allowed_prefixes"` 168 169 // BlockedPrefixes is a list of filter rules to apply for blocking metrics 170 // by prefix. Use the 'prefix_filter' option and prefix rules with '-' to be 171 // excluded. 172 // 173 // hcl: telemetry { prefix_filter = []string{"-<expr>", "-<expr>", ...} } 174 BlockedPrefixes []string `json:"blocked_prefixes,omitempty" mapstructure:"blocked_prefixes"` 175 176 // MetricsPrefix is the prefix used to write stats values to. 177 // Default: "consul." 178 // 179 // hcl: telemetry { metrics_prefix = string } 180 MetricsPrefix string `json:"metrics_prefix,omitempty" mapstructure:"metrics_prefix"` 181 182 // StatsdAddr is the address of a statsd instance. If provided, 183 // metrics will be sent to that instance. 184 // 185 // hcl: telemetry { statsd_address = string } 186 StatsdAddr string `json:"statsd_address,omitempty" mapstructure:"statsd_address"` 187 188 // StatsiteAddr is the address of a statsite instance. If provided, 189 // metrics will be streamed to that instance. 190 // 191 // hcl: telemetry { statsite_address = string } 192 StatsiteAddr string `json:"statsite_address,omitempty" mapstructure:"statsite_address"` 193 } 194 195 // MergeDefaults copies any non-zero field from defaults into the current 196 // config. 197 func (c *TelemetryConfig) MergeDefaults(defaults *TelemetryConfig) { 198 if defaults == nil { 199 return 200 } 201 cfgPtrVal := reflect.ValueOf(c) 202 cfgVal := cfgPtrVal.Elem() 203 otherVal := reflect.ValueOf(*defaults) 204 for i := 0; i < cfgVal.NumField(); i++ { 205 f := cfgVal.Field(i) 206 if !f.IsValid() || !f.CanSet() { 207 continue 208 } 209 // See if the current value is a zero-value, if _not_ skip it 210 // 211 // No built in way to check for zero-values for all types so only 212 // implementing this for the types we actually have for now. Test failure 213 // should catch the case where we add new types later. 214 switch f.Kind() { 215 case reflect.Slice: 216 if !f.IsNil() { 217 continue 218 } 219 case reflect.Int, reflect.Int64: // time.Duration == int64 220 if f.Int() != 0 { 221 continue 222 } 223 case reflect.String: 224 if f.String() != "" { 225 continue 226 } 227 case reflect.Bool: 228 if f.Bool() != false { 229 continue 230 } 231 default: 232 // Needs implementing, should be caught by tests. 233 continue 234 } 235 236 // It's zero, copy it from defaults 237 f.Set(otherVal.Field(i)) 238 } 239 } 240 241 func statsiteSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 242 addr := cfg.StatsiteAddr 243 if addr == "" { 244 return nil, nil 245 } 246 return metrics.NewStatsiteSink(addr) 247 } 248 249 func statsdSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 250 addr := cfg.StatsdAddr 251 if addr == "" { 252 return nil, nil 253 } 254 return metrics.NewStatsdSink(addr) 255 } 256 257 func dogstatdSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 258 addr := cfg.DogstatsdAddr 259 if addr == "" { 260 return nil, nil 261 } 262 sink, err := datadog.NewDogStatsdSink(addr, hostname) 263 if err != nil { 264 return nil, err 265 } 266 sink.SetTags(cfg.DogstatsdTags) 267 return sink, nil 268 } 269 270 func prometheusSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 271 if cfg.PrometheusRetentionTime.Nanoseconds() < 1 { 272 return nil, nil 273 } 274 prometheusOpts := prometheus.PrometheusOpts{ 275 Expiration: cfg.PrometheusRetentionTime, 276 } 277 sink, err := prometheus.NewPrometheusSinkFrom(prometheusOpts) 278 if err != nil { 279 return nil, err 280 } 281 return sink, nil 282 } 283 284 func circonusSink(cfg TelemetryConfig, hostname string) (metrics.MetricSink, error) { 285 token := cfg.CirconusAPIToken 286 url := cfg.CirconusSubmissionURL 287 if token == "" && url == "" { 288 return nil, nil 289 } 290 291 conf := &circonus.Config{} 292 conf.Interval = cfg.CirconusSubmissionInterval 293 conf.CheckManager.API.TokenKey = token 294 conf.CheckManager.API.TokenApp = cfg.CirconusAPIApp 295 conf.CheckManager.API.URL = cfg.CirconusAPIURL 296 conf.CheckManager.Check.SubmissionURL = url 297 conf.CheckManager.Check.ID = cfg.CirconusCheckID 298 conf.CheckManager.Check.ForceMetricActivation = cfg.CirconusCheckForceMetricActivation 299 conf.CheckManager.Check.InstanceID = cfg.CirconusCheckInstanceID 300 conf.CheckManager.Check.SearchTag = cfg.CirconusCheckSearchTag 301 conf.CheckManager.Check.DisplayName = cfg.CirconusCheckDisplayName 302 conf.CheckManager.Check.Tags = cfg.CirconusCheckTags 303 conf.CheckManager.Broker.ID = cfg.CirconusBrokerID 304 conf.CheckManager.Broker.SelectTag = cfg.CirconusBrokerSelectTag 305 306 if conf.CheckManager.Check.DisplayName == "" { 307 conf.CheckManager.Check.DisplayName = "Consul" 308 } 309 310 if conf.CheckManager.API.TokenApp == "" { 311 conf.CheckManager.API.TokenApp = "consul" 312 } 313 314 if conf.CheckManager.Check.SearchTag == "" { 315 conf.CheckManager.Check.SearchTag = "service:consul" 316 } 317 318 sink, err := circonus.NewCirconusSink(conf) 319 if err != nil { 320 return nil, err 321 } 322 sink.Start() 323 return sink, nil 324 } 325 326 // InitTelemetry configures go-metrics based on map of telemetry config 327 // values as returned by Runtimecfg.Config(). 328 func InitTelemetry(cfg TelemetryConfig) (*metrics.InmemSink, error) { 329 // Setup telemetry 330 // Aggregate on 10 second intervals for 1 minute. Expose the 331 // metrics over stderr when there is a SIGUSR1 received. 332 memSink := metrics.NewInmemSink(10*time.Second, time.Minute) 333 metrics.DefaultInmemSignal(memSink) 334 metricsConf := metrics.DefaultConfig(cfg.MetricsPrefix) 335 metricsConf.EnableHostname = !cfg.DisableHostname 336 metricsConf.FilterDefault = cfg.FilterDefault 337 metricsConf.AllowedPrefixes = cfg.AllowedPrefixes 338 metricsConf.BlockedPrefixes = cfg.BlockedPrefixes 339 340 var sinks metrics.FanoutSink 341 addSink := func(name string, fn func(TelemetryConfig, string) (metrics.MetricSink, error)) error { 342 s, err := fn(cfg, metricsConf.HostName) 343 if err != nil { 344 return err 345 } 346 if s != nil { 347 sinks = append(sinks, s) 348 } 349 return nil 350 } 351 352 if err := addSink("statsite", statsiteSink); err != nil { 353 return nil, err 354 } 355 if err := addSink("statsd", statsdSink); err != nil { 356 return nil, err 357 } 358 if err := addSink("dogstatd", dogstatdSink); err != nil { 359 return nil, err 360 } 361 if err := addSink("circonus", circonusSink); err != nil { 362 return nil, err 363 } 364 if err := addSink("prometheus", prometheusSink); err != nil { 365 return nil, err 366 } 367 368 if len(sinks) > 0 { 369 sinks = append(sinks, memSink) 370 metrics.NewGlobal(metricsConf, sinks) 371 } else { 372 metricsConf.EnableHostname = false 373 metrics.NewGlobal(metricsConf, memSink) 374 } 375 return memSink, nil 376 }