@@ -16,10 +16,12 @@ package exporter
16
16
import (
17
17
"bytes"
18
18
"encoding/json"
19
+ "fmt"
19
20
"time"
20
21
21
22
"github.com/go-kit/log"
22
23
"github.com/go-kit/log/level"
24
+ "github.com/google/cel-go/cel"
23
25
"github.com/prometheus-community/json_exporter/config"
24
26
"github.com/prometheus/client_golang/prometheus"
25
27
"k8s.io/client-go/util/jsonpath"
@@ -34,6 +36,7 @@ type JSONMetricCollector struct {
34
36
type JSONMetric struct {
35
37
Desc * prometheus.Desc
36
38
Type config.ScrapeType
39
+ EngineType config.EngineType
37
40
KeyJSONPath string
38
41
ValueJSONPath string
39
42
LabelsJSONPaths []string
@@ -51,7 +54,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
51
54
for _ , m := range mc .JSONMetrics {
52
55
switch m .Type {
53
56
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 )
55
59
if err != nil {
56
60
level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
57
61
continue
@@ -62,7 +66,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
62
66
m .Desc ,
63
67
m .ValueType ,
64
68
floatValue ,
65
- extractLabels (mc .Logger , mc .Data , m .LabelsJSONPaths )... ,
69
+ extractLabels (mc .Logger , m . EngineType , mc .Data , m .LabelsJSONPaths )... ,
66
70
)
67
71
ch <- timestampMetric (mc .Logger , m , mc .Data , metric )
68
72
} else {
@@ -71,7 +75,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
71
75
}
72
76
73
77
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 )
75
80
if err != nil {
76
81
level .Error (mc .Logger ).Log ("msg" , "Failed to extract json objects for metric" , "err" , err , "metric" , m .Desc )
77
82
continue
@@ -85,7 +90,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
85
90
level .Error (mc .Logger ).Log ("msg" , "Failed to marshal data to json" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc , "data" , data )
86
91
continue
87
92
}
88
- value , err := extractValue (mc .Logger , jdata , m .ValueJSONPath , false )
93
+ value , err := extractValue (mc .Logger , m . EngineType , jdata , m .ValueJSONPath , false )
89
94
if err != nil {
90
95
level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc )
91
96
continue
@@ -96,7 +101,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
96
101
m .Desc ,
97
102
m .ValueType ,
98
103
floatValue ,
99
- extractLabels (mc .Logger , jdata , m .LabelsJSONPaths )... ,
104
+ extractLabels (mc .Logger , m . EngineType , jdata , m .LabelsJSONPaths )... ,
100
105
)
101
106
ch <- timestampMetric (mc .Logger , m , jdata , metric )
102
107
} else {
@@ -115,8 +120,19 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
115
120
}
116
121
}
117
122
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
+
118
134
// 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 ) {
120
136
var jsonData interface {}
121
137
buf := new (bytes.Buffer )
122
138
@@ -148,11 +164,61 @@ func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput
148
164
return buf .String (), nil
149
165
}
150
166
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
+
151
217
// 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 {
153
219
labels := make ([]string , len (paths ))
154
220
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 {
156
222
labels [i ] = result
157
223
} else {
158
224
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
165
231
if m .EpochTimestampJSONPath == "" {
166
232
return pm
167
233
}
168
- ts , err := extractValue (logger , data , m .EpochTimestampJSONPath , false )
234
+ ts , err := extractValue (logger , m . EngineType , data , m .EpochTimestampJSONPath , false )
169
235
if err != nil {
170
236
level .Error (logger ).Log ("msg" , "Failed to extract timestamp for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
171
237
return pm
0 commit comments