@@ -17,6 +17,7 @@ package rule
1717import (
1818 "encoding/json"
1919 "strconv"
20+ "sync"
2021 "time"
2122
2223 "buf.build/gen/go/coscene-io/coscene-openapi/protocolbuffers/go/coscene/openapi/dataplatform/v1alpha1/resources"
@@ -42,9 +43,10 @@ const (
4243
4344// Engine represents the rule engine that processes messages against rules.
4445type Engine struct {
45- reqClient api.RequestClient
46- deviceName string
46+ reqClient api.RequestClient
4747
48+ mu sync.RWMutex
49+ deviceName string
4850 rules []* rule_engine.Rule
4951 activeTopics mapset.Set [string ]
5052
@@ -120,28 +122,62 @@ func (e *Engine) UpdateRules(apiRules []*resources.DiagnosisRule, configTopics [
120122 }
121123 }
122124 }
123- e .rules = rules
124-
125125 configTopicSet := mapset .NewSet (configTopics ... )
126+ var newActiveTopics mapset.Set [string ]
126127 if hasWildcardTopicRule {
127- e . activeTopics = mapset .NewSet [string ]()
128+ newActiveTopics = mapset .NewSet [string ]()
128129 } else {
129- e . activeTopics = activeTopics .Intersect (configTopicSet )
130+ newActiveTopics = activeTopics .Intersect (configTopicSet )
130131 }
131132
133+ e .mu .Lock ()
134+ e .rules = rules
135+ e .activeTopics = newActiveTopics
136+ e .mu .Unlock ()
137+
132138 log .Infof ("Updated %d valid rules" , len (rules ))
133139}
134140
135141// ActiveTopics returns all the active topics in the rule engine.
136- func (e * Engine ) ActiveTopics () mapset.Set [string ] {
137- return e .activeTopics
142+ func (e * Engine ) ActiveTopics () []string {
143+ e .mu .RLock ()
144+ defer e .mu .RUnlock ()
145+ if e .activeTopics == nil {
146+ return []string {}
147+ }
148+ return e .activeTopics .ToSlice ()
149+ }
150+
151+ func (e * Engine ) activeTopicSet () mapset.Set [string ] {
152+ e .mu .RLock ()
153+ defer e .mu .RUnlock ()
154+ if e .activeTopics == nil {
155+ return mapset .NewSet [string ]()
156+ }
157+ return e .activeTopics .Clone ()
158+ }
159+
160+ func (e * Engine ) SetDeviceName (name string ) {
161+ e .mu .Lock ()
162+ defer e .mu .Unlock ()
163+ e .deviceName = name
164+ }
165+
166+ func (e * Engine ) getDeviceName () string {
167+ e .mu .RLock ()
168+ defer e .mu .RUnlock ()
169+ return e .deviceName
138170}
139171
140172// ConsumeNext shows how to process a message through the rule engine.
141173func (e * Engine ) ConsumeNext (item rule_engine.RuleItem ) {
142174 log .Debugf ("consuming message: %+v" , item )
143175
144- for _ , rule := range e .rules {
176+ e .mu .RLock ()
177+ rules := e .rules
178+ e .mu .RUnlock ()
179+
180+ for _ , rule := range rules {
145181 if rule .Topics .Cardinality () > 0 && ! rule .Topics .Contains (item .Topic ) {
146182 log .Debugf ("rule %s topics %v does not match topic %s, skipping" , rule .Metadata ["rule_display_name" ], rule .Topics , item .Topic )
147183 continue
@@ -169,9 +205,17 @@ func (e *Engine) ConsumeNext(item rule_engine.RuleItem) {
169205
170206 log .Debugf ("rule %s: isActive: %v, preActivationTime: %v, msgTs: %v" , rule .Metadata ["rule_display_name" ], isActive , prevActivationTime , msgTs )
171207 if isActive {
172- log .Infof ("rule %s met, save collect info" , rule .Metadata ["rule_display_name" ])
173-
174208 collectInfoId := uuid .New ().String ()
209+ ruleLog := log .WithFields (log.Fields {
210+ "collectID" : collectInfoId ,
211+ "ruleName" : rule .Metadata ["rule_name" ],
212+ "ruleDisplayName" : rule .Metadata ["rule_display_name" ],
213+ "topic" : item .Topic ,
214+ "source" : item .Source ,
215+ "ts" : item .Ts ,
216+ })
217+ ruleLog .Infof ("rule met, save collect info" )
218+
175219 additionalArgs := map [string ]interface {}{}
176220 for k , v := range rule .Metadata {
177221 additionalArgs [k ] = v
@@ -180,11 +224,13 @@ func (e *Engine) ConsumeNext(item rule_engine.RuleItem) {
180224
181225 for _ , action := range rule .Actions {
182226 if err := action .Run (curActivation , additionalArgs ); err != nil {
183- log . Errorf ("failed to run action %s : %v" , action . Name , err )
227+ ruleLog . WithField ( "action" , action . Name ). Errorf ("failed to run action: %v" , err )
184228 }
185229 }
186230 if err := model .PublishCollectInfo (collectInfoId ); err != nil {
187- log .Errorf ("failed to publish collect info %s: %v" , collectInfoId , err )
231+ ruleLog .Errorf ("failed to publish collect info: %v" , err )
232+ } else {
233+ ruleLog .Infof ("published collect info" )
188234 }
189235 }
190236 }
@@ -239,8 +285,6 @@ func (e *Engine) cleanupDebounceTime() {
239285// uploadActionImpl is the implementation of "upload" function,
240286// it stores the collect info in the database.
241287func (e * Engine ) uploadActionImpl (kwargs map [string ]interface {}) error {
242- log .Infof ("triggered saving collect info" )
243-
244288 // Extract required fields with type assertions
245289 beforeStr , ok := kwargs ["before" ].(string )
246290 if ! ok {
@@ -325,9 +369,20 @@ func (e *Engine) uploadActionImpl(kwargs map[string]interface{}) error {
325369 return errors .Errorf ("rule must be a DiagnosisRule object" )
326370 }
327371
328- canUpload := e .canUpload (uploadLimit , rule )
329- if err := e .reqClient .HitDiagnosisRule (rule , e .deviceName , canUpload ); err != nil {
330- return errors .Wrap (err , "failed to hit diagnosis rule" )
372+ deviceName := e .getDeviceName ()
373+ actionLog := log .WithFields (log.Fields {
374+ "collectID" : collectInfoId ,
375+ "ruleName" : ruleName ,
376+ "ruleDisplayName" : ruleDisplayName ,
377+ "project" : projectName ,
378+ "device" : deviceName ,
379+ "triggerTs" : triggerTsFloat ,
380+ })
381+ actionLog .Infof ("triggered saving collect info" )
382+
383+ canUpload := e .canUpload (uploadLimit , rule , deviceName )
384+ if err := e .reqClient .HitDiagnosisRule (rule , deviceName , canUpload ); err != nil {
385+ actionLog .Errorf ("failed to hit diagnosis rule, continuing with save: %v" , err )
331386 }
332387
333388 // Calculate start and end times
@@ -360,43 +415,86 @@ func (e *Engine) uploadActionImpl(kwargs map[string]interface{}) error {
360415 WhiteList : whiteList ,
361416 }
362417 collectInfo .Skip = ! canUpload
418+ actionLog .WithFields (log.Fields {
419+ "canUpload" : canUpload ,
420+ "start" : collectInfo .Cut .Start ,
421+ "end" : collectInfo .Cut .End ,
422+ "labels" : labels ,
423+ }).Infof ("prepared collect info" )
363424
364425 // Save the collect info
365426 if err = collectInfo .SaveDraft (); err != nil {
366427 return errors .Wrap (err , "failed to save collect info" )
367428 }
368- log .Infof ("saved collect info %s" , collectInfoId )
429+ actionLog .Infof ("saved collect info draft" )
369430
370431 return nil
371432}
372433
373- func (e * Engine ) canUpload (uploadLimit * resources.UploadLimit , rule * resources.DiagnosisRule ) bool {
434+ func (e * Engine ) canUpload (uploadLimit * resources.UploadLimit , rule * resources.DiagnosisRule , deviceName string ) bool {
435+ ruleName := rule .GetName ()
436+ ruleDisplayName := rule .GetDisplayName ()
374437 if uploadLimit .HasDevice () {
375- count , err := e .reqClient .CountDiagnosisRuleHits (rule , e . deviceName )
438+ count , err := e .reqClient .CountDiagnosisRuleHits (rule , deviceName )
376439 if err != nil {
377- log .Errorf ("failed to count diagnosis rule hits: %v, skipping" , err )
378- return false
440+ log .WithFields (log.Fields {
441+ "ruleName" : ruleName ,
442+ "ruleDisplayName" : ruleDisplayName ,
443+ "device" : deviceName ,
444+ "scope" : "device" ,
445+ }).Errorf ("failed to count diagnosis rule hits, defaulting to allow upload: %v" , err )
446+ return true
379447 }
380448
381- log .Infof ("device count: %d, limit: %d" , count , uploadLimit .GetDevice ().GetTimes ())
449+ log .WithFields (log.Fields {
450+ "ruleName" : ruleName ,
451+ "ruleDisplayName" : ruleDisplayName ,
452+ "device" : deviceName ,
453+ "scope" : "device" ,
454+ "count" : count ,
455+ "limit" : uploadLimit .GetDevice ().GetTimes (),
456+ }).Infof ("checked diagnosis rule upload limit" )
382457
383458 if count >= uploadLimit .GetDevice ().GetTimes () {
384- log .Infof ("device count %d exceeds limit %d, skipping" , count , uploadLimit .GetDevice ().GetTimes ())
459+ log .WithFields (log.Fields {
460+ "ruleName" : ruleName ,
461+ "ruleDisplayName" : ruleDisplayName ,
462+ "device" : deviceName ,
463+ "scope" : "device" ,
464+ "count" : count ,
465+ "limit" : uploadLimit .GetDevice ().GetTimes (),
466+ }).Infof ("diagnosis rule upload limit reached, skipping upload" )
385467 return false
386468 }
387469 }
388470
389471 if uploadLimit .HasGlobal () {
390472 count , err := e .reqClient .CountDiagnosisRuleHits (rule , "" )
391473 if err != nil {
392- log .Errorf ("failed to count diagnosis rule hits: %v, skipping" , err )
393- return false
474+ log .WithFields (log.Fields {
475+ "ruleName" : ruleName ,
476+ "ruleDisplayName" : ruleDisplayName ,
477+ "scope" : "global" ,
478+ }).Errorf ("failed to count diagnosis rule hits, defaulting to allow upload: %v" , err )
479+ return true
394480 }
395481
396- log .Infof ("global count: %d, limit: %d" , count , uploadLimit .GetGlobal ().GetTimes ())
482+ log .WithFields (log.Fields {
483+ "ruleName" : ruleName ,
484+ "ruleDisplayName" : ruleDisplayName ,
485+ "scope" : "global" ,
486+ "count" : count ,
487+ "limit" : uploadLimit .GetGlobal ().GetTimes (),
488+ }).Infof ("checked diagnosis rule upload limit" )
397489
398490 if count >= uploadLimit .GetGlobal ().GetTimes () {
399- log .Infof ("global count %d exceeds limit %d, skipping" , count , uploadLimit .GetGlobal ().GetTimes ())
491+ log .WithFields (log.Fields {
492+ "ruleName" : ruleName ,
493+ "ruleDisplayName" : ruleDisplayName ,
494+ "scope" : "global" ,
495+ "count" : count ,
496+ "limit" : uploadLimit .GetGlobal ().GetTimes (),
497+ }).Infof ("diagnosis rule upload limit reached, skipping upload" )
400498 return false
401499 }
402500 }
0 commit comments