@@ -16,10 +16,12 @@ package exporter
1616import (
1717 "bytes"
1818 "encoding/json"
19+ "fmt"
1920 "time"
2021
2122 "github.com/go-kit/log"
2223 "github.com/go-kit/log/level"
24+ "github.com/google/cel-go/cel"
2325 "github.com/prometheus-community/json_exporter/config"
2426 "github.com/prometheus/client_golang/prometheus"
2527 "k8s.io/client-go/util/jsonpath"
@@ -34,6 +36,7 @@ type JSONMetricCollector struct {
3436type JSONMetric struct {
3537 Desc * prometheus.Desc
3638 Type config.ScrapeType
39+ EngineType config.EngineType
3740 KeyJSONPath string
3841 ValueJSONPath string
3942 LabelsJSONPaths []string
@@ -51,7 +54,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
5154 for _ , m := range mc .JSONMetrics {
5255 switch m .Type {
5356 case config .ValueScrape :
54- value , err := extractValue (mc .Logger , mc .Data , m .KeyJSONPath , false )
57+ level .Debug (mc .Logger ).Log ("msg" , "Extracting value for metric" , "path" , m .KeyJSONPath , "metric" , m .Desc )
58+ value , err := extractValue (mc .Logger , m .EngineType , mc .Data , m .KeyJSONPath , false )
5559 if err != nil {
5660 level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
5761 continue
@@ -62,7 +66,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
6266 m .Desc ,
6367 m .ValueType ,
6468 floatValue ,
65- extractLabels (mc .Logger , mc .Data , m .LabelsJSONPaths )... ,
69+ extractLabels (mc .Logger , m . EngineType , mc .Data , m .LabelsJSONPaths )... ,
6670 )
6771 ch <- timestampMetric (mc .Logger , m , mc .Data , metric )
6872 } else {
@@ -71,7 +75,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
7175 }
7276
7377 case config .ObjectScrape :
74- values , err := extractValue (mc .Logger , mc .Data , m .KeyJSONPath , true )
78+ level .Debug (mc .Logger ).Log ("msg" , "Extracting object for metric" , "path" , m .KeyJSONPath , "metric" , m .Desc )
79+ values , err := extractValue (mc .Logger , m .EngineType , mc .Data , m .KeyJSONPath , true )
7580 if err != nil {
7681 level .Error (mc .Logger ).Log ("msg" , "Failed to extract json objects for metric" , "err" , err , "metric" , m .Desc )
7782 continue
@@ -85,7 +90,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
8590 level .Error (mc .Logger ).Log ("msg" , "Failed to marshal data to json" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc , "data" , data )
8691 continue
8792 }
88- value , err := extractValue (mc .Logger , jdata , m .ValueJSONPath , false )
93+ value , err := extractValue (mc .Logger , m . EngineType , jdata , m .ValueJSONPath , false )
8994 if err != nil {
9095 level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc )
9196 continue
@@ -96,7 +101,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
96101 m .Desc ,
97102 m .ValueType ,
98103 floatValue ,
99- extractLabels (mc .Logger , jdata , m .LabelsJSONPaths )... ,
104+ extractLabels (mc .Logger , m . EngineType , jdata , m .LabelsJSONPaths )... ,
100105 )
101106 ch <- timestampMetric (mc .Logger , m , jdata , metric )
102107 } else {
@@ -115,8 +120,19 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
115120 }
116121}
117122
123+ func extractValue (logger log.Logger , engine config.EngineType , data []byte , path string , enableJSONOutput bool ) (string , error ) {
124+ switch engine {
125+ case config .EngineTypeJSONPath :
126+ return extractValueJSONPath (logger , data , path , enableJSONOutput )
127+ case config .EngineTypeCEL :
128+ return extractValueCEL (logger , data , path )
129+ default :
130+ return "" , fmt .Errorf ("Unknown engine type: %s" , engine )
131+ }
132+ }
133+
118134// Returns the last matching value at the given json path
119- func extractValue (logger log.Logger , data []byte , path string , enableJSONOutput bool ) (string , error ) {
135+ func extractValueJSONPath (logger log.Logger , data []byte , path string , enableJSONOutput bool ) (string , error ) {
120136 var jsonData interface {}
121137 buf := new (bytes.Buffer )
122138
@@ -148,11 +164,61 @@ func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput
148164 return buf .String (), nil
149165}
150166
167+ // Returns the last matching value at the given json path
168+ func extractValueCEL (logger log.Logger , data []byte , expression string ) (string , error ) {
169+
170+ var jsonData map [string ]any
171+
172+ err := json .Unmarshal (data , & jsonData )
173+ if err != nil {
174+ level .Error (logger ).Log ("msg" , "Failed to unmarshal data to json" , "err" , err , "data" , data )
175+ return "" , err
176+ }
177+
178+ inputVars := make ([]cel.EnvOption , 0 , len (jsonData ))
179+ for k := range jsonData {
180+ inputVars = append (inputVars , cel .Variable (k , cel .DynType ))
181+ }
182+
183+ env , err := cel .NewEnv (inputVars ... )
184+
185+ if err != nil {
186+ level .Error (logger ).Log ("msg" , "Failed to set up CEL environment" , "err" , err , "data" , data )
187+ return "" , err
188+ }
189+
190+ ast , issues := env .Compile (expression )
191+ if issues != nil && issues .Err () != nil {
192+ level .Error (logger ).Log ("CEL type-check error" , issues .Err (), "expression" , expression )
193+ return "" , err
194+ }
195+ prg , err := env .Program (ast )
196+ if err != nil {
197+ level .Error (logger ).Log ("CEL program construction error" , err )
198+ return "" , err
199+ }
200+
201+ out , _ , err := prg .Eval (jsonData )
202+ if err != nil {
203+ level .Error (logger ).Log ("msg" , "Failed to evaluate cel query" , "err" , err , "expression" , expression , "data" , jsonData )
204+ return "" , err
205+ }
206+
207+ result := out .ConvertToType (cel .StringType )
208+
209+ // Since we are finally going to extract only float64, unquote if necessary
210+ if res , err := jsonpath .UnquoteExtend (result .Value ().(string )); err == nil {
211+ return res , nil
212+ }
213+
214+ return result .Value ().(string ), nil
215+ }
216+
151217// Returns the list of labels created from the list of provided json paths
152- func extractLabels (logger log.Logger , data []byte , paths []string ) []string {
218+ func extractLabels (logger log.Logger , engine config. EngineType , data []byte , paths []string ) []string {
153219 labels := make ([]string , len (paths ))
154220 for i , path := range paths {
155- if result , err := extractValue (logger , data , path , false ); err == nil {
221+ if result , err := extractValue (logger , engine , data , path , false ); err == nil {
156222 labels [i ] = result
157223 } else {
158224 level .Error (logger ).Log ("msg" , "Failed to extract label value" , "err" , err , "path" , path , "data" , data )
@@ -165,7 +231,7 @@ func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus
165231 if m .EpochTimestampJSONPath == "" {
166232 return pm
167233 }
168- ts , err := extractValue (logger , data , m .EpochTimestampJSONPath , false )
234+ ts , err := extractValue (logger , m . EngineType , data , m .EpochTimestampJSONPath , false )
169235 if err != nil {
170236 level .Error (logger ).Log ("msg" , "Failed to extract timestamp for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
171237 return pm
0 commit comments