@@ -20,6 +20,7 @@ import (
20
20
21
21
"github.com/go-kit/log"
22
22
"github.com/go-kit/log/level"
23
+ "github.com/google/cel-go/cel"
23
24
"github.com/prometheus-community/json_exporter/config"
24
25
"github.com/prometheus/client_golang/prometheus"
25
26
"k8s.io/client-go/util/jsonpath"
@@ -35,6 +36,7 @@ type JSONMetric struct {
35
36
Desc * prometheus.Desc
36
37
Type config.ScrapeType
37
38
KeyJSONPath string
39
+ KeyCelExpression string
38
40
ValueJSONPath string
39
41
LabelsJSONPaths []string
40
42
ValueType prometheus.ValueType
@@ -51,10 +53,25 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
51
53
for _ , m := range mc .JSONMetrics {
52
54
switch m .Type {
53
55
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
+
58
75
}
59
76
60
77
if floatValue , err := SanitizeValue (value ); err == nil {
@@ -66,28 +83,52 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
66
83
)
67
84
ch <- timestampMetric (mc .Logger , m , mc .Data , metric )
68
85
} 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
+ }
70
91
continue
71
92
}
72
93
73
94
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
+ }
78
110
}
79
111
80
112
var jsonData []interface {}
81
113
if err := json .Unmarshal ([]byte (values ), & jsonData ); err == nil {
82
114
for _ , data := range jsonData {
83
115
jdata , err := json .Marshal (data )
84
116
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
+ }
86
122
continue
87
123
}
88
124
value , err := extractValue (mc .Logger , jdata , m .ValueJSONPath , false )
89
125
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
+ }
91
132
continue
92
133
}
93
134
@@ -100,7 +141,11 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
100
141
)
101
142
ch <- timestampMetric (mc .Logger , m , jdata , metric )
102
143
} 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
+ }
104
149
continue
105
150
}
106
151
}
@@ -148,6 +193,56 @@ func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput
148
193
return buf .String (), nil
149
194
}
150
195
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
+
151
246
// Returns the list of labels created from the list of provided json paths
152
247
func extractLabels (logger log.Logger , data []byte , paths []string ) []string {
153
248
labels := make ([]string , len (paths ))
0 commit comments