├── .gitignore ├── .travis.yml ├── Dockerfile ├── README.md ├── glide.yaml ├── main.go └── main_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .idea 3 | 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | *.out 14 | 15 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 16 | .glide/ 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7.x 5 | - master 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.8 2 | 3 | WORKDIR /go/src/app 4 | COPY . . 5 | RUN go-wrapper download 6 | RUN go-wrapper install 7 | EXPOSE 9116 8 | CMD ["go-wrapper", "run"] # ["app"] 9 | 10 | # Once 17.05 has arrived 11 | #FROM alpine:latest 12 | #RUN apk --no-cache add ca-certificates 13 | #WORKDIR /root/ 14 | #COPY --from= as builder /go/app . 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Json Exporter 2 | ![TravisCI build status](https://travis-ci.org/tolleiv/json-exporter.svg?branch=master) 3 | [![Docker Build Statu](https://img.shields.io/docker/build/tolleiv/json-exporter.svg)](https://hub.docker.com/r/tolleiv/json-exporter/) 4 | 5 | This Prometheus exporter operates similar to the Blackbox exporters. It downloads a JSON file and provides a numerical gauge value from within that file. 6 | Which value to pick is defined through JsonPath. 7 | 8 | ## Parameters 9 | 10 | - `target`: URL / Json-file to download 11 | - `jsonpath`: the field name to read the value from, this follows the syntax provided by [oliveagle/jsonpath](https://github.com/oliveagle/jsonpath) 12 | 13 | ## Docker usage 14 | 15 | docker build -t json_exporter . 16 | docker -d -p 9116:9116 --name json_exporter json_exporter 17 | 18 | The related metrics can then be found under: 19 | 20 | http://localhost:9116/probe?target=http://validate.jsontest.com/?json=%7B%22key%22:%22value%22%7D&jsonpath=$.parse_time_nanoseconds 21 | 22 | ## Prometheus Configuration 23 | 24 | The json exporter needs to be passed the target and the json as a parameter, this can be 25 | done with relabelling. 26 | 27 | Example config: 28 | ```yml 29 | scrape_configs: 30 | - job_name: 'json' 31 | metrics_path: /probe 32 | params: 33 | jsonpath: [$.parse_time_nanoseconds] # Look for the nanoseconds field 34 | static_configs: 35 | - targets: 36 | - http://validate.jsontest.com/?json=%7B%22key%22:%22value%22%7D 37 | relabel_configs: 38 | - source_labels: [__address__] 39 | target_label: __param_target 40 | - source_labels: [__param_target] 41 | target_label: instance 42 | - target_label: __address__ 43 | replacement: 127.0.0.1:9116 # Json exporter. 44 | metric_relabel_configs: 45 | - source_labels: value 46 | target_label: parse_time 47 | 48 | ``` 49 | 50 | ## License 51 | 52 | MIT License 53 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/tolleiv/json-exporter 2 | import: 3 | - package: github.com/prometheus/client_golang 4 | subpackages: 5 | - prometheus/promhttp 6 | - package: github.com/oliveagle/jsonpath 7 | - package: github.com/mohae/utilitybelt 8 | subpackages: 9 | - deepcopy 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/prometheus/client_golang/prometheus/promhttp" 9 | "github.com/prometheus/client_golang/prometheus" 10 | "crypto/tls" 11 | "github.com/oliveagle/jsonpath" 12 | "io/ioutil" 13 | "encoding/json" 14 | ) 15 | 16 | var addr = flag.String("listen-address", ":9116", "The address to listen on for HTTP requests.") 17 | 18 | func main() { 19 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 20 | w.Write([]byte(` 21 | Json Exporter 22 | 23 |

Json Exporter

24 |

Run a probe

25 |

Metrics

26 | 27 | `)) 28 | }) 29 | flag.Parse() 30 | http.HandleFunc("/probe", probeHandler) 31 | http.Handle("/metrics", promhttp.Handler()) 32 | log.Fatal(http.ListenAndServe(*addr, nil)) 33 | } 34 | 35 | func probeHandler(w http.ResponseWriter, r *http.Request) { 36 | 37 | params := r.URL.Query() 38 | target := params.Get("target") 39 | if target == "" { 40 | http.Error(w, "Target parameter is missing", 400) 41 | return 42 | } 43 | lookuppath := params.Get("jsonpath") 44 | if target == "" { 45 | http.Error(w, "The JsonPath to lookup", 400) 46 | return 47 | } 48 | probeSuccessGauge := prometheus.NewGauge(prometheus.GaugeOpts{ 49 | Name: "probe_success", 50 | Help: "Displays whether or not the probe was a success", 51 | }) 52 | probeDurationGauge := prometheus.NewGauge(prometheus.GaugeOpts{ 53 | Name: "probe_duration_seconds", 54 | Help: "Returns how long the probe took to complete in seconds", 55 | }) 56 | valueGauge := prometheus.NewGauge( 57 | prometheus.GaugeOpts{ 58 | Name: "value", 59 | Help: "Retrieved value", 60 | }, 61 | ) 62 | registry := prometheus.NewRegistry() 63 | registry.MustRegister(probeSuccessGauge) 64 | registry.MustRegister(probeDurationGauge) 65 | registry.MustRegister(valueGauge) 66 | 67 | tr := &http.Transport{ 68 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 69 | } 70 | client := &http.Client{Transport: tr} 71 | resp, err := client.Get(target) 72 | if err != nil { 73 | log.Fatal(err) 74 | 75 | } else { 76 | defer resp.Body.Close() 77 | bytes, err := ioutil.ReadAll(resp.Body) 78 | if err != nil { 79 | http.Error(w, err.Error(), http.StatusInternalServerError) 80 | return 81 | } 82 | 83 | var json_data interface{} 84 | json.Unmarshal([]byte(bytes), &json_data) 85 | res, err := jsonpath.JsonPathLookup(json_data, lookuppath) 86 | if err != nil { 87 | http.Error(w, "Jsonpath not found", http.StatusNotFound) 88 | return 89 | } 90 | log.Printf("Found value %v", res) 91 | number, ok := res.(float64) 92 | if !ok { 93 | http.Error(w, "Values could not be parsed to Float64", http.StatusInternalServerError) 94 | return 95 | } 96 | probeSuccessGauge.Set(1) 97 | valueGauge.Set(number) 98 | } 99 | 100 | h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) 101 | h.ServeHTTP(w, r) 102 | } -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "net/http" 6 | "net/http/httptest" 7 | "fmt" 8 | "net/url" 9 | "io/ioutil" 10 | "strings" 11 | ) 12 | 13 | var probeTests = []struct { 14 | in_data string 15 | in_field string 16 | out_http_code int 17 | out_value string 18 | }{ 19 | {"{\"field\": 23}", "$.field", 200, "value 23"}, 20 | {"{\"field\": 19}", "$.field", 200, "value 19"}, 21 | {"{\"field\": 19}", "$.undefined", 404, "Jsonpath not found"}, 22 | } 23 | 24 | func TestProbeHandler(t *testing.T) { 25 | 26 | for _, tt := range probeTests { 27 | 28 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 29 | fmt.Fprintln(w, tt.in_data) 30 | })) 31 | defer ts.Close() 32 | 33 | u := fmt.Sprintf("http://example.com/probe?target=%s&jsonpath=%s", url.QueryEscape(ts.URL), url.QueryEscape(tt.in_field)) 34 | 35 | req := httptest.NewRequest("GET", u, nil) 36 | w := httptest.NewRecorder() 37 | 38 | probeHandler(w, req) 39 | 40 | resp := w.Result() 41 | body, _ := ioutil.ReadAll(resp.Body) 42 | 43 | if tt.out_http_code != resp.StatusCode { 44 | t.Error(fmt.Sprintf("HTTP Code mismatch - %d expected %d", resp.StatusCode, tt.out_http_code)) 45 | } 46 | 47 | if ! strings.Contains(string(body), tt.out_value) { 48 | t.Error(fmt.Sprintf("Expected output: %s got\n%s", tt.out_value, string(body))) 49 | } 50 | } 51 | } 52 | --------------------------------------------------------------------------------