@@ -16,6 +16,7 @@ use spacegate_kernel::plugins::{
16
16
filters:: { BoxSgPluginFilter , SgPluginFilter , SgPluginFilterAccept , SgPluginFilterDef , SgPluginFilterInitDto } ,
17
17
} ;
18
18
use tardis:: basic:: dto:: TardisContext ;
19
+ use tardis:: basic:: error:: TardisError ;
19
20
use tardis:: serde_json:: { json, Value } ;
20
21
21
22
use tardis:: {
@@ -27,6 +28,8 @@ use tardis::{
27
28
TardisFuns , TardisFunsInst ,
28
29
} ;
29
30
31
+ use super :: plugin_constants;
32
+
30
33
pub const CODE : & str = "audit_log" ;
31
34
pub struct SgFilterAuditLogDef ;
32
35
@@ -46,9 +49,88 @@ pub struct SgFilterAuditLog {
46
49
header_token_name : String ,
47
50
success_json_path : String ,
48
51
success_json_path_values : Vec < String > ,
52
+ /// Exclude log path exact match.
53
+ exclude_log_path : Vec < String > ,
49
54
enabled : bool ,
50
55
}
51
56
57
+ impl SgFilterAuditLog {
58
+ async fn get_log_content ( & self , end_time : i64 , ctx : & mut SgRoutePluginContext ) -> TardisResult < LogParamContent > {
59
+ let start_time = ctx. get_ext ( & get_start_time_ext_code ( ) ) . and_then ( |time| time. parse :: < i64 > ( ) . ok ( ) ) ;
60
+ let body_string = if let Some ( raw_body) = ctx. get_ext ( plugin_constants:: BEFORE_ENCRYPT_BODY ) {
61
+ Some ( raw_body)
62
+ } else {
63
+ ctx. response
64
+ . pop_resp_body ( )
65
+ . await ?
66
+ . map ( |body| {
67
+ let body_string = String :: from_utf8_lossy ( & body) . to_string ( ) ;
68
+ ctx. response . set_resp_body ( body) ?;
69
+ Ok :: < _ , TardisError > ( body_string)
70
+ } )
71
+ . transpose ( ) ?
72
+ } ;
73
+ let success = match serde_json:: from_str :: < Value > ( & body_string. unwrap_or_default ( ) ) {
74
+ Ok ( json) => {
75
+ if let Ok ( matching_value) = json. path ( & self . success_json_path ) {
76
+ if let Some ( matching_value) = matching_value. as_array ( ) {
77
+ let matching_value = & matching_value[ 0 ] ;
78
+ if matching_value. is_string ( ) {
79
+ let mut is_match = false ;
80
+ for value in self . success_json_path_values . clone ( ) {
81
+ if Some ( value. as_str ( ) ) == matching_value. as_str ( ) {
82
+ is_match = true ;
83
+ break ;
84
+ }
85
+ }
86
+ is_match
87
+ } else if matching_value. is_number ( ) {
88
+ let mut is_match = false ;
89
+ for value in self . success_json_path_values . clone ( ) {
90
+ let value = value. parse :: < i64 > ( ) ;
91
+ if value. is_ok ( ) && value. ok ( ) == matching_value. as_i64 ( ) {
92
+ is_match = true ;
93
+ break ;
94
+ }
95
+ }
96
+ is_match
97
+ } else {
98
+ false
99
+ }
100
+ } else {
101
+ false
102
+ }
103
+ } else {
104
+ false
105
+ }
106
+ }
107
+ Err ( _) => false ,
108
+ } ;
109
+ Ok ( LogParamContent {
110
+ op : ctx. request . get_req_method ( ) . to_string ( ) ,
111
+ key : None ,
112
+ name : ctx. get_cert_info ( ) . and_then ( |info| info. account_name . clone ( ) ) . unwrap_or_default ( ) ,
113
+ user_id : ctx. get_cert_info ( ) . map ( |info| info. account_id . clone ( ) ) ,
114
+ role : ctx. get_cert_info ( ) . map ( |info| info. roles . clone ( ) ) . unwrap_or_default ( ) ,
115
+ ip : if let Some ( real_ips) = ctx. request . get_req_headers ( ) . get ( "X-Forwarded-For" ) {
116
+ real_ips
117
+ . to_str ( )
118
+ . ok ( )
119
+ . and_then ( |ips| ips. split ( ',' ) . collect :: < Vec < _ > > ( ) . first ( ) . map ( |ip| ip. to_string ( ) ) )
120
+ . unwrap_or ( ctx. request . get_req_remote_addr ( ) . ip ( ) . to_string ( ) )
121
+ } else {
122
+ ctx. request . get_req_remote_addr ( ) . ip ( ) . to_string ( )
123
+ } ,
124
+ path : ctx. request . get_req_uri_raw ( ) . path ( ) . to_string ( ) ,
125
+ scheme : ctx. request . get_req_uri_raw ( ) . scheme_str ( ) . unwrap_or ( "http" ) . to_string ( ) ,
126
+ token : ctx. request . get_req_headers ( ) . get ( & self . header_token_name ) . and_then ( |v| v. to_str ( ) . ok ( ) . map ( |v| v. to_string ( ) ) ) ,
127
+ server_timing : start_time. map ( |st| end_time - st) ,
128
+ resp_status : ctx. response . get_resp_status_code ( ) . as_u16 ( ) . to_string ( ) ,
129
+ success,
130
+ } )
131
+ }
132
+ }
133
+
52
134
impl Default for SgFilterAuditLog {
53
135
fn default ( ) -> Self {
54
136
Self {
@@ -59,6 +141,7 @@ impl Default for SgFilterAuditLog {
59
141
success_json_path : "$.code" . to_string ( ) ,
60
142
enabled : false ,
61
143
success_json_path_values : vec ! [ "200" . to_string( ) , "201" . to_string( ) ] ,
144
+ exclude_log_path : vec ! [ "/starsysApi/apis" . to_string( ) ] ,
62
145
}
63
146
}
64
147
}
@@ -74,7 +157,7 @@ impl SgPluginFilter for SgFilterAuditLog {
74
157
75
158
async fn init ( & mut self , _: & SgPluginFilterInitDto ) -> TardisResult < ( ) > {
76
159
if !self . log_url . is_empty ( ) && !self . spi_app_id . is_empty ( ) {
77
- if JsonPathInst :: from_str ( & self . success_json_path ) . map_err ( |e| log:: error!( "[[ Plugin.AuditLog] ] invalid json path:{e}" ) ) . is_err ( ) {
160
+ if JsonPathInst :: from_str ( & self . success_json_path ) . map_err ( |e| log:: error!( "[Plugin.AuditLog] invalid json path:{e}" ) ) . is_err ( ) {
78
161
self . enabled = false ;
79
162
return Ok ( ( ) ) ;
80
163
} ;
@@ -87,6 +170,7 @@ impl SgPluginFilter for SgFilterAuditLog {
87
170
} ,
88
171
) ?;
89
172
} else {
173
+ log:: warn!( "[Plugin.AuditLog] plugin is not active, miss log_url or spi_app_id." ) ;
90
174
self . enabled = false ;
91
175
}
92
176
Ok ( ( ) )
@@ -103,66 +187,36 @@ impl SgPluginFilter for SgFilterAuditLog {
103
187
104
188
async fn resp_filter ( & self , _: & str , mut ctx : SgRoutePluginContext ) -> TardisResult < ( bool , SgRoutePluginContext ) > {
105
189
if self . enabled {
190
+ let path = ctx. request . get_req_uri_raw ( ) . path ( ) . to_string ( ) ;
191
+ for exclude_path in self . exclude_log_path . clone ( ) {
192
+ if exclude_path == path {
193
+ return Ok ( ( true , ctx) ) ;
194
+ }
195
+ }
106
196
let funs = get_tardis_inst ( ) ;
107
- let start_time = ctx. get_ext ( & get_start_time_ext_code ( ) ) . and_then ( |time| time. parse :: < i64 > ( ) . ok ( ) ) ;
108
197
let end_time = tardis:: chrono:: Utc :: now ( ) . timestamp_millis ( ) ;
109
198
let spi_ctx = TardisContext {
110
199
owner : ctx. get_cert_info ( ) . map ( |info| info. account_id . clone ( ) ) . unwrap_or_default ( ) ,
111
200
roles : ctx. get_cert_info ( ) . map ( |info| info. roles . clone ( ) . into_iter ( ) . map ( |r| r. id ) . collect ( ) ) . unwrap_or_default ( ) ,
112
201
..Default :: default ( )
113
202
} ;
114
203
let op = ctx. request . get_req_method ( ) . to_string ( ) ;
115
- let resp_body = ctx. response . pop_resp_body ( ) . await ?;
116
- let success = match resp_body {
117
- Some ( body) => {
118
- let body_string = String :: from_utf8_lossy ( & body) . to_string ( ) ;
119
- let result = match serde_json:: from_str :: < Value > ( & body_string) {
120
- Ok ( json) => {
121
- if let Ok ( matching_value) = json. path ( & self . success_json_path ) {
122
- if matching_value. is_number ( ) && matching_value. is_string ( ) {
123
- let mut is_match = false ;
124
- for value in self . success_json_path_values . clone ( ) {
125
- if value == matching_value {
126
- is_match = true ;
127
- break ;
128
- }
129
- }
130
- is_match
131
- } else {
132
- false
133
- }
134
- } else {
135
- false
136
- }
137
- }
138
- Err ( _) => false ,
139
- } ;
140
- ctx. response . set_resp_body ( body) ?;
141
- result
142
- }
143
- None => false ,
144
- } ;
145
- let content = LogParamContent {
146
- op : op. clone ( ) ,
147
- key : None ,
148
- name : ctx. get_cert_info ( ) . and_then ( |info| info. account_name . clone ( ) ) . unwrap_or_default ( ) ,
149
- user_id : ctx. get_cert_info ( ) . map ( |info| info. account_id . clone ( ) ) ,
150
- role : ctx. get_cert_info ( ) . map ( |info| info. roles . clone ( ) ) . unwrap_or_default ( ) ,
151
- ip : ctx. request . get_req_remote_addr ( ) . ip ( ) . to_string ( ) ,
152
- token : ctx. request . get_req_headers ( ) . get ( & self . header_token_name ) . and_then ( |v| v. to_str ( ) . ok ( ) . map ( |v| v. to_string ( ) ) ) ,
153
- server_timing : start_time. map ( |st| end_time - st) ,
154
- success,
155
- } ;
204
+
205
+ let content = self . get_log_content ( end_time, & mut ctx) . await ?;
206
+
156
207
let log_ext = json ! ( {
157
208
"name" : content. name,
158
209
"id" : content. user_id,
159
210
"ip" : content. ip,
160
211
"op" : op. clone( ) ,
212
+ "path" : content. path,
213
+ "resp_status" : content. resp_status,
161
214
"success" : content. success,
162
215
} ) ;
216
+ let tag = self . tag . clone ( ) ;
163
217
tokio:: spawn ( async move {
164
218
match spi_log_client:: SpiLogClient :: add (
165
- " tag" ,
219
+ & tag,
166
220
& TardisFuns :: json. obj_to_string ( & content) . unwrap_or_default ( ) ,
167
221
Some ( log_ext) ,
168
222
None ,
@@ -181,7 +235,7 @@ impl SgPluginFilter for SgFilterAuditLog {
181
235
log:: trace!( "[Plugin.AuditLog] add log success" )
182
236
}
183
237
Err ( e) => {
184
- log:: trace !( "[Plugin.AuditLog] failed to add log:{e}" )
238
+ log:: warn !( "[Plugin.AuditLog] failed to add log:{e}" )
185
239
}
186
240
} ;
187
241
} ) ;
@@ -209,8 +263,97 @@ pub struct LogParamContent {
209
263
pub user_id : Option < String > ,
210
264
pub role : Vec < SGRoleInfo > ,
211
265
pub ip : String ,
266
+ pub path : String ,
267
+ pub scheme : String ,
212
268
pub token : Option < String > ,
213
269
pub server_timing : Option < i64 > ,
270
+ pub resp_status : String ,
214
271
//Indicates whether the business operation was successful.
215
272
pub success : bool ,
216
273
}
274
+
275
+ #[ cfg( test) ]
276
+ mod test {
277
+ use spacegate_kernel:: {
278
+ http:: { HeaderName , Uri } ,
279
+ hyper:: { Body , HeaderMap , Method , StatusCode , Version } ,
280
+ plugins:: context:: SgRoutePluginContext ,
281
+ } ;
282
+ use tardis:: tokio;
283
+
284
+ use crate :: plugin:: audit_log:: get_start_time_ext_code;
285
+
286
+ use super :: SgFilterAuditLog ;
287
+
288
+ #[ tokio:: test]
289
+ async fn test_log_content ( ) {
290
+ let sg_filter_audit_log = SgFilterAuditLog { ..Default :: default ( ) } ;
291
+ let end_time = 20100 ;
292
+ let mut header = HeaderMap :: new ( ) ;
293
+ header. insert ( sg_filter_audit_log. header_token_name . parse :: < HeaderName > ( ) . unwrap ( ) , "aaa" . parse ( ) . unwrap ( ) ) ;
294
+ let mut ctx = SgRoutePluginContext :: new_http (
295
+ Method :: POST ,
296
+ Uri :: from_static ( "http://sg.idealworld.group/test1" ) ,
297
+ Version :: HTTP_11 ,
298
+ header,
299
+ Body :: from ( "" ) ,
300
+ "127.0.0.1:8080" . parse ( ) . unwrap ( ) ,
301
+ "" . to_string ( ) ,
302
+ None ,
303
+ ) ;
304
+ ctx. set_ext ( & get_start_time_ext_code ( ) , & 20000 . to_string ( ) ) ;
305
+ let mut ctx = ctx. resp ( StatusCode :: OK , HeaderMap :: new ( ) , Body :: from ( r##"{"code":"200","msg":"success"}"## ) ) ;
306
+ let log_content = sg_filter_audit_log. get_log_content ( end_time, & mut ctx) . await . unwrap ( ) ;
307
+ assert_eq ! ( log_content. token, Some ( "aaa" . to_string( ) ) ) ;
308
+ assert_eq ! ( log_content. server_timing, Some ( 100 ) ) ;
309
+ assert ! ( log_content. success) ;
310
+
311
+ let mut header = HeaderMap :: new ( ) ;
312
+ header. insert ( sg_filter_audit_log. header_token_name . parse :: < HeaderName > ( ) . unwrap ( ) , "aaa" . parse ( ) . unwrap ( ) ) ;
313
+ let ctx = SgRoutePluginContext :: new_http (
314
+ Method :: POST ,
315
+ Uri :: from_static ( "http://sg.idealworld.group/test1" ) ,
316
+ Version :: HTTP_11 ,
317
+ header,
318
+ Body :: from ( "" ) ,
319
+ "127.0.0.1:8080" . parse ( ) . unwrap ( ) ,
320
+ "" . to_string ( ) ,
321
+ None ,
322
+ ) ;
323
+ let mut ctx = ctx. resp ( StatusCode :: OK , HeaderMap :: new ( ) , Body :: from ( r##"{"code":200,"msg":"success"}"## ) ) ;
324
+ let log_content = sg_filter_audit_log. get_log_content ( end_time, & mut ctx) . await . unwrap ( ) ;
325
+ assert ! ( log_content. success) ;
326
+
327
+ let mut header = HeaderMap :: new ( ) ;
328
+ header. insert ( sg_filter_audit_log. header_token_name . parse :: < HeaderName > ( ) . unwrap ( ) , "aaa" . parse ( ) . unwrap ( ) ) ;
329
+ let ctx = SgRoutePluginContext :: new_http (
330
+ Method :: POST ,
331
+ Uri :: from_static ( "http://sg.idealworld.group/test1" ) ,
332
+ Version :: HTTP_11 ,
333
+ header,
334
+ Body :: from ( "" ) ,
335
+ "127.0.0.1:8080" . parse ( ) . unwrap ( ) ,
336
+ "" . to_string ( ) ,
337
+ None ,
338
+ ) ;
339
+ let mut ctx = ctx. resp ( StatusCode :: OK , HeaderMap :: new ( ) , Body :: from ( r##"{"code":"500","msg":"not success"}"## ) ) ;
340
+ let log_content = sg_filter_audit_log. get_log_content ( end_time, & mut ctx) . await . unwrap ( ) ;
341
+ assert ! ( !log_content. success) ;
342
+
343
+ let mut header = HeaderMap :: new ( ) ;
344
+ header. insert ( sg_filter_audit_log. header_token_name . parse :: < HeaderName > ( ) . unwrap ( ) , "aaa" . parse ( ) . unwrap ( ) ) ;
345
+ let ctx = SgRoutePluginContext :: new_http (
346
+ Method :: POST ,
347
+ Uri :: from_static ( "http://sg.idealworld.group/test1" ) ,
348
+ Version :: HTTP_11 ,
349
+ header,
350
+ Body :: from ( "" ) ,
351
+ "127.0.0.1:8080" . parse ( ) . unwrap ( ) ,
352
+ "" . to_string ( ) ,
353
+ None ,
354
+ ) ;
355
+ let mut ctx = ctx. resp ( StatusCode :: OK , HeaderMap :: new ( ) , Body :: from ( r##"{"code":500,"msg":"not success"}"## ) ) ;
356
+ let log_content = sg_filter_audit_log. get_log_content ( end_time, & mut ctx) . await . unwrap ( ) ;
357
+ assert ! ( !log_content. success) ;
358
+ }
359
+ }
0 commit comments