@@ -20,6 +20,7 @@ import (
2020
2121 "github.com/go-kit/log"
2222 "github.com/go-kit/log/level"
23+ "github.com/google/cel-go/cel"
2324 "github.com/prometheus-community/json_exporter/config"
2425 "github.com/prometheus/client_golang/prometheus"
2526 "k8s.io/client-go/util/jsonpath"
@@ -35,6 +36,7 @@ type JSONMetric struct {
3536 Desc * prometheus.Desc
3637 Type config.ScrapeType
3738 KeyJSONPath string
39+ KeyCelExpression string
3840 ValueJSONPath string
3941 LabelsJSONPaths []string
4042 ValueType prometheus.ValueType
@@ -51,10 +53,25 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
5153 for _ , m := range mc .JSONMetrics {
5254 switch m .Type {
5355 case config .ValueScrape :
54- value , err := extractValue (mc .Logger , mc .Data , m .KeyJSONPath , false )
55- if err != nil {
56- level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
57- continue
56+ var value string
57+ var err error
58+ if m .KeyJSONPath != "" {
59+ level .Debug (mc .Logger ).Log ("msg" , "Extracting value for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
60+
61+ value , err = extractValue (mc .Logger , mc .Data , m .KeyJSONPath , false )
62+ if err != nil {
63+ level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
64+ continue
65+ }
66+ } else {
67+ level .Debug (mc .Logger ).Log ("msg" , "Extracting value for metric" , "expression" , m .KeyCelExpression , "err" , err , "metric" , m .Desc )
68+
69+ value , err = extractValueCEL (mc .Logger , mc .Data , m .KeyCelExpression )
70+ if err != nil {
71+ level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "expression" , m .KeyCelExpression , "err" , err , "metric" , m .Desc )
72+ continue
73+ }
74+
5875 }
5976
6077 if floatValue , err := SanitizeValue (value ); err == nil {
@@ -66,28 +83,52 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
6683 )
6784 ch <- timestampMetric (mc .Logger , m , mc .Data , metric )
6885 } else {
69- level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted value to float64" , "path" , m .KeyJSONPath , "value" , value , "err" , err , "metric" , m .Desc )
86+ if m .KeyJSONPath != "" {
87+ level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted value to float64" , "path" , m .KeyJSONPath , "value" , value , "err" , err , "metric" , m .Desc )
88+ } else {
89+ level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted value to float64" , "expression" , m .KeyCelExpression , "value" , value , "err" , err , "metric" , m .Desc )
90+ }
7091 continue
7192 }
7293
7394 case config .ObjectScrape :
74- values , err := extractValue (mc .Logger , mc .Data , m .KeyJSONPath , true )
75- if err != nil {
76- level .Error (mc .Logger ).Log ("msg" , "Failed to extract json objects for metric" , "err" , err , "metric" , m .Desc )
77- continue
95+ var values string
96+ var err error
97+ if m .KeyJSONPath != "" {
98+ values , err = extractValue (mc .Logger , mc .Data , m .KeyJSONPath , true )
99+ if err != nil {
100+ level .Error (mc .Logger ).Log ("msg" , "Failed to extract json objects for metric" , "err" , err , "metric" , m .Desc )
101+ continue
102+ }
103+ } else {
104+ level .Debug (mc .Logger ).Log ("msg" , "Extracting json objects for metric" , "err" , err , "metric" , m .Desc )
105+ values , err = extractValueCEL (mc .Logger , mc .Data , m .KeyCelExpression )
106+ if err != nil {
107+ level .Error (mc .Logger ).Log ("msg" , "Failed to extract json objects for metric" , "err" , err , "metric" , m .Desc )
108+ continue
109+ }
78110 }
79111
80112 var jsonData []interface {}
81113 if err := json .Unmarshal ([]byte (values ), & jsonData ); err == nil {
82114 for _ , data := range jsonData {
83115 jdata , err := json .Marshal (data )
84116 if err != nil {
85- level .Error (mc .Logger ).Log ("msg" , "Failed to marshal data to json" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc , "data" , data )
117+ if m .KeyJSONPath != "" {
118+ level .Error (mc .Logger ).Log ("msg" , "Failed to marshal data to json" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc , "data" , data )
119+ } else {
120+ level .Error (mc .Logger ).Log ("msg" , "Failed to marshal data to json" , "expression" , m .KeyCelExpression , "err" , err , "metric" , m .Desc , "data" , data )
121+ }
86122 continue
87123 }
88124 value , err := extractValue (mc .Logger , jdata , m .ValueJSONPath , false )
89125 if err != nil {
90- level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc )
126+ if m .KeyJSONPath != "" {
127+ level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc )
128+ } else {
129+ level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "expression" , m .KeyCelExpression , "err" , err , "metric" , m .Desc )
130+
131+ }
91132 continue
92133 }
93134
@@ -100,7 +141,11 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
100141 )
101142 ch <- timestampMetric (mc .Logger , m , jdata , metric )
102143 } else {
103- level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted value to float64" , "path" , m .ValueJSONPath , "value" , value , "err" , err , "metric" , m .Desc )
144+ if m .KeyJSONPath != "" {
145+ level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted value to float64" , "path" , m .KeyJSONPath , "value" , value , "err" , err , "metric" , m .Desc )
146+ } else {
147+ level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted value to float64" , "expression" , m .KeyCelExpression , "value" , value , "err" , err , "metric" , m .Desc )
148+ }
104149 continue
105150 }
106151 }
@@ -148,6 +193,56 @@ func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput
148193 return buf .String (), nil
149194}
150195
196+ // Returns the last matching value at the given json path
197+ func extractValueCEL (logger log.Logger , data []byte , expression string ) (string , error ) {
198+
199+ var jsonData map [string ]any
200+
201+ err := json .Unmarshal (data , & jsonData )
202+ if err != nil {
203+ level .Error (logger ).Log ("msg" , "Failed to unmarshal data to json" , "err" , err , "data" , data )
204+ return "" , err
205+ }
206+
207+ inputVars := make ([]cel.EnvOption , 0 , len (jsonData ))
208+ for k := range jsonData {
209+ inputVars = append (inputVars , cel .Variable (k , cel .DynType ))
210+ }
211+
212+ env , err := cel .NewEnv (inputVars ... )
213+
214+ if err != nil {
215+ level .Error (logger ).Log ("msg" , "Failed to set up CEL environment" , "err" , err , "data" , data )
216+ return "" , err
217+ }
218+
219+ ast , issues := env .Compile (expression )
220+ if issues != nil && issues .Err () != nil {
221+ level .Error (logger ).Log ("CEL type-check error" , issues .Err (), "expression" , expression )
222+ return "" , err
223+ }
224+ prg , err := env .Program (ast )
225+ if err != nil {
226+ level .Error (logger ).Log ("CEL program construction error" , err )
227+ return "" , err
228+ }
229+
230+ out , _ , err := prg .Eval (jsonData )
231+ if err != nil {
232+ level .Error (logger ).Log ("msg" , "Failed to evaluate cel query" , "err" , err , "expression" , expression , "data" , jsonData )
233+ return "" , err
234+ }
235+
236+ result := out .ConvertToType (cel .StringType )
237+
238+ // Since we are finally going to extract only float64, unquote if necessary
239+ if res , err := jsonpath .UnquoteExtend (result .Value ().(string )); err == nil {
240+ return res , nil
241+ }
242+
243+ return result .Value ().(string ), nil
244+ }
245+
151246// Returns the list of labels created from the list of provided json paths
152247func extractLabels (logger log.Logger , data []byte , paths []string ) []string {
153248 labels := make ([]string , len (paths ))
0 commit comments