@@ -16,12 +16,19 @@ package exporter
16
16
import (
17
17
"bytes"
18
18
"encoding/json"
19
+ "fmt"
20
+ "reflect"
19
21
"time"
20
22
21
23
"github.com/go-kit/log"
22
24
"github.com/go-kit/log/level"
25
+ "github.com/google/cel-go/cel"
26
+ "github.com/google/cel-go/common/types/ref"
23
27
"github.com/prometheus-community/json_exporter/config"
24
28
"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"
25
32
"k8s.io/client-go/util/jsonpath"
26
33
)
27
34
@@ -34,6 +41,7 @@ type JSONMetricCollector struct {
34
41
type JSONMetric struct {
35
42
Desc * prometheus.Desc
36
43
Type config.ScrapeType
44
+ EngineType config.EngineType
37
45
KeyJSONPath string
38
46
ValueJSONPath string
39
47
LabelsJSONPaths []string
@@ -51,7 +59,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
51
59
for _ , m := range mc .JSONMetrics {
52
60
switch m .Type {
53
61
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 )
55
64
if err != nil {
56
65
level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
57
66
continue
@@ -62,7 +71,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
62
71
m .Desc ,
63
72
m .ValueType ,
64
73
floatValue ,
65
- extractLabels (mc .Logger , mc .Data , m .LabelsJSONPaths )... ,
74
+ extractLabels (mc .Logger , m . EngineType , mc .Data , m .LabelsJSONPaths )... ,
66
75
)
67
76
ch <- timestampMetric (mc .Logger , m , mc .Data , metric )
68
77
} else {
@@ -71,7 +80,8 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
71
80
}
72
81
73
82
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 )
75
85
if err != nil {
76
86
level .Error (mc .Logger ).Log ("msg" , "Failed to extract json objects for metric" , "err" , err , "metric" , m .Desc )
77
87
continue
@@ -85,7 +95,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
85
95
level .Error (mc .Logger ).Log ("msg" , "Failed to marshal data to json" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc , "data" , data )
86
96
continue
87
97
}
88
- value , err := extractValue (mc .Logger , jdata , m .ValueJSONPath , false )
98
+ value , err := extractValue (mc .Logger , m . EngineType , jdata , m .ValueJSONPath , false )
89
99
if err != nil {
90
100
level .Error (mc .Logger ).Log ("msg" , "Failed to extract value for metric" , "path" , m .ValueJSONPath , "err" , err , "metric" , m .Desc )
91
101
continue
@@ -96,7 +106,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
96
106
m .Desc ,
97
107
m .ValueType ,
98
108
floatValue ,
99
- extractLabels (mc .Logger , jdata , m .LabelsJSONPaths )... ,
109
+ extractLabels (mc .Logger , m . EngineType , jdata , m .LabelsJSONPaths )... ,
100
110
)
101
111
ch <- timestampMetric (mc .Logger , m , jdata , metric )
102
112
} else {
@@ -105,7 +115,7 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
105
115
}
106
116
}
107
117
} 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 )
109
119
continue
110
120
}
111
121
default :
@@ -115,8 +125,19 @@ func (mc JSONMetricCollector) Collect(ch chan<- prometheus.Metric) {
115
125
}
116
126
}
117
127
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
+
118
139
// 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 ) {
120
141
var jsonData interface {}
121
142
buf := new (bytes.Buffer )
122
143
@@ -148,11 +169,70 @@ func extractValue(logger log.Logger, data []byte, path string, enableJSONOutput
148
169
return buf .String (), nil
149
170
}
150
171
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
+ }
225
+ return res , nil
226
+ }
227
+
228
+ return fmt .Sprintf ("%v" , out ), nil
229
+ }
230
+
151
231
// Returns the list of labels created from the list of provided json paths
152
- func extractLabels (logger log.Logger , data []byte , paths []string ) []string {
232
+ func extractLabels (logger log.Logger , engine config. EngineType , data []byte , paths []string ) []string {
153
233
labels := make ([]string , len (paths ))
154
234
for i , path := range paths {
155
- if result , err := extractValue (logger , data , path , false ); err == nil {
235
+ if result , err := extractValue (logger , engine , data , path , false ); err == nil {
156
236
labels [i ] = result
157
237
} else {
158
238
level .Error (logger ).Log ("msg" , "Failed to extract label value" , "err" , err , "path" , path , "data" , data )
@@ -165,7 +245,7 @@ func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus
165
245
if m .EpochTimestampJSONPath == "" {
166
246
return pm
167
247
}
168
- ts , err := extractValue (logger , data , m .EpochTimestampJSONPath , false )
248
+ ts , err := extractValue (logger , m . EngineType , data , m .EpochTimestampJSONPath , false )
169
249
if err != nil {
170
250
level .Error (logger ).Log ("msg" , "Failed to extract timestamp for metric" , "path" , m .KeyJSONPath , "err" , err , "metric" , m .Desc )
171
251
return pm
@@ -178,3 +258,18 @@ func timestampMetric(logger log.Logger, m JSONMetric, data []byte, pm prometheus
178
258
timestamp := time .UnixMilli (epochTime )
179
259
return prometheus .NewMetricWithTimestamp (timestamp , pm )
180
260
}
261
+
262
+ // valueToJSON converts the CEL type to a protobuf JSON representation and
263
+ // marshals the result to a string.
264
+ func valueToJSON (val ref.Val ) (string , error ) {
265
+ v , err := val .ConvertToNative (reflect .TypeOf (& structpb.Value {}))
266
+ if err != nil {
267
+ return "" , err
268
+ }
269
+ marshaller := protojson.MarshalOptions {Indent : " " }
270
+ bytes , err := marshaller .Marshal (v .(proto.Message ))
271
+ if err != nil {
272
+ return "" , err
273
+ }
274
+ return string (bytes ), err
275
+ }
0 commit comments