// Copyright 2020 The Prometheus Authors // Licensed 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 internal import ( "context" "errors" "fmt" "io/ioutil" "math" "net/http" "strconv" "strings" "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/kawamuray/jsonpath" "github.com/prometheus-community/json_exporter/config" "github.com/prometheus/client_golang/prometheus" pconfig "github.com/prometheus/common/config" ) func MakeMetricName(parts ...string) string { return strings.Join(parts, "_") } func SanitizeValue(v *jsonpath.Result) (float64, error) { var value float64 var boolValue bool var err error switch v.Type { case jsonpath.JsonNumber: value, err = parseValue(v.Value) case jsonpath.JsonString: // If it is a string, lets pull off the quotes and attempt to parse it as a number value, err = parseValue(v.Value[1 : len(v.Value)-1]) case jsonpath.JsonNull: value = math.NaN() case jsonpath.JsonBool: if boolValue, err = strconv.ParseBool(string(v.Value)); boolValue { value = 1.0 } else { value = 0.0 } default: value, err = parseValue(v.Value) } if err != nil { // Should never happen. return -1.0, err } return value, err } func parseValue(bytes []byte) (float64, error) { value, err := strconv.ParseFloat(string(bytes), 64) if err != nil { return -1.0, fmt.Errorf("failed to parse value as float;value:<%s>", bytes) } return value, nil } func CreateMetricsList(r *prometheus.Registry, c config.Config) ([]JsonGaugeCollector, error) { var metrics []JsonGaugeCollector for _, metric := range c.Metrics { switch metric.Type { case config.ValueScrape: jsonCollector := JsonGaugeCollector{ GaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: metric.Name, Help: metric.Help, }, metric.LabelNames()), KeyJsonPath: metric.Path, LabelsJsonPath: metric.Labels, } r.MustRegister(jsonCollector) metrics = append(metrics, jsonCollector) case config.ObjectScrape: for subName, valuePath := range metric.Values { name := MakeMetricName(metric.Name, subName) jsonCollector := JsonGaugeCollector{ GaugeVec: prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: name, Help: name, }, metric.LabelNames()), KeyJsonPath: metric.Path, ValueJsonPath: valuePath, LabelsJsonPath: metric.Labels, } r.MustRegister(jsonCollector) metrics = append(metrics, jsonCollector) } default: return nil, fmt.Errorf("Unknown metric type: '%s', for metric: '%s'", metric.Type, metric.Name) } } return metrics, nil } func FetchJson(ctx context.Context, logger log.Logger, endpoint string, config config.Config) ([]byte, error) { httpClientConfig := config.HTTPClientConfig client, err := pconfig.NewClientFromConfig(httpClientConfig, "fetch_json", true) if err != nil { level.Error(logger).Log("msg", "Error generating HTTP client", "err", err) return nil, err } req, err := http.NewRequest("GET", endpoint, nil) req = req.WithContext(ctx) if err != nil { level.Error(logger).Log("msg", "Failed to create request", "err", err) //nolint:errcheck return nil, err } for key, value := range config.Headers { req.Header.Add(key, value) } if req.Header.Get("Accept") == "" { req.Header.Add("Accept", "application/json") } resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, errors.New(resp.Status) } data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } return data, nil }