@@ -16,12 +16,19 @@ package exporter
1616import (
1717 "bytes"
1818 "encoding/json"
19+ "fmt"
20+ "reflect"
1921 "time"
2022
2123 "github.com/go-kit/log"
2224 "github.com/go-kit/log/level"
25+ "github.com/google/cel-go/cel"
26+ "github.com/google/cel-go/common/types/ref"
2327 "github.com/prometheus-community/json_exporter/config"
2428 "github.com/prometheus/client_golang/prometheus"
29+ "google.golang.org/protobuf/encoding/protojson"
30+ "google.golang.org/protobuf/proto"
31+ structpb "google.golang.org/protobuf/types/known/structpb"
2532 "k8s.io/client-go/util/jsonpath"
2633)
2734
@@ -34,6 +41,7 @@ type JSONMetricCollector struct {
3441type JSONMetric struct {
3542 Desc * prometheus.Desc
3643 Type config.ScrapeType
44+ EngineType config.EngineType
3745 KeyJSONPath string
3846 ValueJSONPath string
3947 LabelsJSONPaths []string
@@ -51,7 +59,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
5159 for _ , m := range mc .JSONMetrics {
5260 switch m .Type {
5361 case config .ValueScrape :
54- value , err := extractValue (mc .Logger , mc .Data , m .KeyJSONPath , false )
62+ level .Debug (mc .Logger ).Log ("msg" , "Extracting value for metric" , "path" , m .KeyJSONPath , "metric" , m .Desc )
63+ value , err := extractValue (mc .Logger , m .EngineType , mc .Data , m .KeyJSONPath , false )
5564 if err != nil {
5665 level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
5766 continue
@@ -62,7 +71,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
6271 m .Desc ,
6372 m .ValueType ,
6473 floatValue ,
65- extractLabels (mc .Logger , mc .Data , m .LabelsJSONPaths )... ,
74+ extractLabels (mc .Logger , m . EngineType , mc .Data , m .LabelsJSONPaths )... ,
6675 )
6776 ch <- timestampMetric (mc .Logger , m , mc .Data , metric )
6877 } else {
@@ -71,7 +80,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
7180 }
7281
7382 case config .ObjectScrape :
74- values , err := extractValue (mc .Logger , mc .Data , m .KeyJSONPath , true )
83+ level .Debug (mc .Logger ).Log ("msg" , "Extracting object for metric" , "path" , m .KeyJSONPath , "metric" , m .Desc )
84+ values , err := extractValue (mc .Logger , m .EngineType , mc .Data , m .KeyJSONPath , true )
7585 if err != nil {
7686 level .Error (mc .Logger ).Log ("msg" , "Failed to extract json objects for metric" , "err" , err , "metric" , m .Desc )
7787 continue
@@ -85,7 +95,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
8595 level .Error (mc .Logger ).Log ("msg" , "Failed to marshal data to json" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc , "data" , data )
8696 continue
8797 }
88- value , err := extractValue (mc .Logger , jdata , m .ValueJSONPath , false )
98+ value , err := extractValue (mc .Logger , m . EngineType , jdata , m .ValueJSONPath , false )
8999 if err != nil {
90100 level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc )
91101 continue
@@ -96,7 +106,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
96106 m .Desc ,
97107 m .ValueType ,
98108 floatValue ,
99- extractLabels (mc .Logger , jdata , m .LabelsJSONPaths )... ,
109+ extractLabels (mc .Logger , m . EngineType , jdata , m .LabelsJSONPaths )... ,
100110 )
101111 ch <- timestampMetric (mc .Logger , m , jdata , metric )
102112 } else {
@@ -105,7 +115,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
105115 }
106116 }
107117 } else {
108- level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted objects to json" , "err" , err , "metric" , m .Desc )
118+ level .Error (mc .Logger ).Log ("msg" , "Failed to convert extracted objects to json" , "value" , values , " err" , err , "metric" , m .Desc )
109119 continue
110120 }
111121 default :
@@ -115,8 +125,19 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
115125 }
116126}
117127
128+ func extractValue (logger log.Logger , engine config.EngineType , data []byte , path string , enableJSONOutput bool ) (string , error ) {
129+ switch engine {
130+ case config .EngineTypeJSONPath :
131+ return extractValueJSONPath (logger , data , path , enableJSONOutput )
132+ case config .EngineTypeCEL :
133+ return extractValueCEL (logger , data , path , enableJSONOutput )
134+ default :
135+ return "" , fmt .Errorf ("Unknown engine type: %s" , engine )
136+ }
137+ }
138+
118139// Returns the last matching value at the given json path
119- func extractValue (logger log.Logger , data []byte , path string , enableJSONOutput bool ) (string , error ) {
140+ func extractValueJSONPath (logger log.Logger , data []byte , path string , enableJSONOutput bool ) (string , error ) {
120141 var jsonData interface {}
121142 buf := new (bytes.Buffer )
122143
@@ -148,11 +169,71 @@ func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput
148169 return buf .String (), nil
149170}
150171
172+ // Returns the last matching value at the given json path
173+ func extractValueCEL (logger log.Logger , data []byte , expression string , enableJSONOutput bool ) (string , error ) {
174+
175+ var jsonData map [string ]any
176+
177+ err := json .Unmarshal (data , & jsonData )
178+ if err != nil {
179+ level .Error (logger ).Log ("msg" , "Failed to unmarshal data to json" , "err" , err , "data" , data )
180+ return "" , err
181+ }
182+
183+ inputVars := make ([]cel.EnvOption , 0 , len (jsonData ))
184+ for k := range jsonData {
185+ inputVars = append (inputVars , cel .Variable (k , cel .DynType ))
186+ }
187+
188+ env , err := cel .NewEnv (inputVars ... )
189+
190+ if err != nil {
191+ level .Error (logger ).Log ("msg" , "Failed to set up CEL environment" , "err" , err , "data" , data )
192+ return "" , err
193+ }
194+
195+ ast , issues := env .Compile (expression )
196+ if issues != nil && issues .Err () != nil {
197+ level .Error (logger ).Log ("CEL type-check error" , issues .Err (), "expression" , expression )
198+ return "" , err
199+ }
200+ prg , err := env .Program (ast )
201+ if err != nil {
202+ level .Error (logger ).Log ("CEL program construction error" , err )
203+ return "" , err
204+ }
205+
206+ out , _ , err := prg .Eval (jsonData )
207+ if err != nil {
208+ level .Error (logger ).Log ("msg" , "Failed to evaluate cel query" , "err" , err , "expression" , expression , "data" , jsonData )
209+ return "" , err
210+ }
211+
212+ // Since we are finally going to extract only float64, unquote if necessary
213+
214+ //res, err := jsonpath.UnquoteExtend(fmt.Sprintf("%g", out))
215+ //if err == nil {
216+ // level.Error(logger).Log("msg","Triggered")
217+ // return res, nil
218+ //}
219+ level .Error (logger ).Log ("msg" , "Triggered later" , "val" , out )
220+ if enableJSONOutput {
221+ res , err := valueToJSON (out )
222+ if err != nil {
223+ return "" , err
224+ } else {
225+ return res , nil
226+ }
227+ }
228+
229+ return fmt .Sprintf ("%v" , out ), nil
230+ }
231+
151232// Returns the list of labels created from the list of provided json paths
152- func extractLabels (logger log.Logger , data []byte , paths []string ) []string {
233+ func extractLabels (logger log.Logger , engine config. EngineType , data []byte , paths []string ) []string {
153234 labels := make ([]string , len (paths ))
154235 for i , path := range paths {
155- if result , err := extractValue (logger , data , path , false ); err == nil {
236+ if result , err := extractValue (logger , engine , data , path , false ); err == nil {
156237 labels [i ] = result
157238 } else {
158239 level .Error (logger ).Log ("msg" , "Failed to extract label value" , "err" , err , "path" , path , "data" , data )
@@ -165,7 +246,7 @@ func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus
165246 if m .EpochTimestampJSONPath == "" {
166247 return pm
167248 }
168- ts , err := extractValue (logger , data , m .EpochTimestampJSONPath , false )
249+ ts , err := extractValue (logger , m . EngineType , data , m .EpochTimestampJSONPath , false )
169250 if err != nil {
170251 level .Error (logger ).Log ("msg" , "Failed to extract timestamp for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
171252 return pm
@@ -178,3 +259,18 @@ func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus
178259 timestamp := time .UnixMilli (epochTime )
179260 return prometheus .NewMetricWithTimestamp (timestamp , pm )
180261}
262+
263+ // valueToJSON converts the CEL type to a protobuf JSON representation and
264+ // marshals the result to a string.
265+ func valueToJSON (val ref.Val ) (string , error ) {
266+ v , err := val .ConvertToNative (reflect .TypeOf (& structpb.Value {}))
267+ if err != nil {
268+ return "" , err
269+ }
270+ marshaller := protojson.MarshalOptions {Indent : " " }
271+ bytes , err := marshaller .Marshal (v .(proto.Message ))
272+ if err != nil {
273+ return "" , err
274+ }
275+ return string (bytes ), err
276+ }
0 commit comments