11package matcher
22
33import (
4+ "context"
45 "crypto/rand"
56 "fmt"
67 "log/slog"
78 "os"
89 "time"
910)
1011
12+ // logger is a package-level alias to the default slog logger. Use the
13+ // contextual logging helpers (InfoContext, WarnContext, ErrorContext,
14+ // DebugContext) when a context is available from the matcher instance.
15+ var logger = slog .Default ()
16+
17+ func SetLogger (log * slog.Logger ) {
18+ if log != nil {
19+ logger = log
20+ }
21+ }
22+
1123// MatcherEngine provides a simple, high-level API for the rule matching system
1224type MatcherEngine struct {
13- matcher * InMemoryMatcher
25+ matcher * MemoryMatcherEngine
1426 persistence PersistenceInterface
1527 eventBroker Broker // Changed from eventSub to eventBroker
1628 nodeID string // Create forest index
1729 autoSaveStop chan bool
1830}
1931
2032// NewMatcherEngine creates a new matcher engine with the specified persistence and event broker
21- func NewMatcherEngine (persistence PersistenceInterface , eventBroker Broker , nodeID string ) (* MatcherEngine , error ) {
22- matcher , err := NewInMemoryMatcher (persistence , eventBroker , nodeID )
33+ // Note: ctx should not be cancelled in normal status
34+ func NewMatcherEngine (ctx context.Context , persistence PersistenceInterface , broker Broker , nodeID string , dcs * DimensionConfigs , initialTimeout time.Duration ) (* MatcherEngine , error ) {
35+ matcher , err := newInMemoryMatcherEngine (ctx , persistence , broker , nodeID , dcs , initialTimeout )
2336 if err != nil {
2437 return nil , fmt .Errorf ("failed to create matcher: %w" , err )
2538 }
2639
40+ logger .InfoContext (matcher .ctx , "created matcher engine" , "node_id" , nodeID )
41+
2742 return & MatcherEngine {
2843 matcher : matcher ,
2944 persistence : persistence ,
30- eventBroker : eventBroker ,
45+ eventBroker : broker ,
3146 nodeID : nodeID ,
3247 }, nil
3348}
@@ -37,7 +52,8 @@ func NewMatcherEngineWithDefaults(dataDir string) (*MatcherEngine, error) {
3752 persistence := NewJSONPersistence (dataDir )
3853 // Generate a default node ID based on hostname or use a UUID
3954 nodeID := GenerateDefaultNodeID ()
40- return NewMatcherEngine (persistence , nil , nodeID )
55+ logger .InfoContext (context .Background (), "creating matcher engine with defaults" , "data_dir" , dataDir )
56+ return NewMatcherEngine (context .Background (), persistence , nil , nodeID , nil , 0 )
4157}
4258
4359// RuleBuilder provides a fluent API for building rules
@@ -132,11 +148,13 @@ func (rb *RuleBuilder) Build() *Rule {
132148
133149// AddRule adds a rule to the engine
134150func (me * MatcherEngine ) AddRule (rule * Rule ) error {
151+ logger .InfoContext (me .matcher .ctx , "engine.AddRule" , "rule_id" , rule .ID )
135152 return me .matcher .AddRule (rule )
136153}
137154
138155// UpdateRule updates an existing rule
139156func (me * MatcherEngine ) UpdateRule (rule * Rule ) error {
157+ logger .InfoContext (me .matcher .ctx , "engine.UpdateRule" , "rule_id" , rule .ID )
140158 return me .matcher .updateRule (rule )
141159}
142160
@@ -202,9 +220,15 @@ func (me *MatcherEngine) GetRule(ruleID string) (*Rule, error) {
202220
203221// DeleteRule removes a rule by ID
204222func (me * MatcherEngine ) DeleteRule (ruleID string ) error {
223+ logger .InfoContext (me .matcher .ctx , "engine.DeleteRule" , "rule_id" , ruleID )
205224 return me .matcher .DeleteRule (ruleID )
206225}
207226
227+ // GetDimensionConfigs returns deep cloned DimensionConfigs instance
228+ func (me * MatcherEngine ) GetDimensionConfigs () * DimensionConfigs {
229+ return me .matcher .GetDimensionConfigs ()
230+ }
231+
208232// AddDimension adds a new dimension configuration
209233func (me * MatcherEngine ) AddDimension (config * DimensionConfig ) error {
210234 return me .matcher .AddDimension (config )
@@ -218,6 +242,7 @@ func (me *MatcherEngine) SetAllowDuplicateWeights(allow bool) {
218242
219243// FindBestMatch finds the best matching rule for a query
220244func (me * MatcherEngine ) FindBestMatch (query * QueryRule ) (* MatchResult , error ) {
245+ logger .DebugContext (me .matcher .ctx , "engine.FindBestMatch" , "query" , query .Values )
221246 return me .matcher .FindBestMatch (query )
222247}
223248
@@ -226,21 +251,30 @@ func (me *MatcherEngine) FindBestMatch(query *QueryRule) (*MatchResult, error) {
226251// atomic snapshot view for a group of queries with respect to concurrent
227252// updates.
228253func (me * MatcherEngine ) FindBestMatchInBatch (queries ... * QueryRule ) ([]* MatchResult , error ) {
254+ logger .DebugContext (me .matcher .ctx , "engine.FindBestMatchInBatch" , "num_queries" , len (queries ))
229255 return me .matcher .FindBestMatchInBatch (queries )
230256}
231257
232258// FindAllMatches finds all matching rules for a query
233259func (me * MatcherEngine ) FindAllMatches (query * QueryRule ) ([]* MatchResult , error ) {
260+ logger .DebugContext (me .matcher .ctx , "engine.FindAllMatches" , "query" , query .Values )
234261 return me .matcher .FindAllMatches (query )
235262}
236263
237264// FindAllMatches finds all matching rules for a query
238265func (me * MatcherEngine ) FindAllMatchesInBatch (query ... * QueryRule ) ([][]* MatchResult , error ) {
266+ logger .DebugContext (me .matcher .ctx , "engine.FindAllMatchesInBatch" , "num_queries" , len (query ))
239267 return me .matcher .FindAllMatchesInBatch (query )
240268}
241269
270+ // LoadDimensions loads all dimensions in bulk
271+ func (me * MatcherEngine ) LoadDimensions (configs []* DimensionConfig ) {
272+ me .matcher .LoadDimensions (configs )
273+ }
274+
242275// ListRules returns all rules with pagination
243276func (me * MatcherEngine ) ListRules (offset , limit int ) ([]* Rule , error ) {
277+ logger .DebugContext (me .matcher .ctx , "engine.ListRules" , "offset" , offset , "limit" , limit )
244278 return me .matcher .ListRules (offset , limit )
245279}
246280
@@ -256,6 +290,7 @@ func (me *MatcherEngine) GetStats() *MatcherStats {
256290
257291// Save saves the current state to persistence
258292func (me * MatcherEngine ) Save () error {
293+ logger .InfoContext (me .matcher .ctx , "engine.Save requested" )
259294 return me .matcher .SaveToPersistence ()
260295}
261296
@@ -269,6 +304,7 @@ func (me *MatcherEngine) Close() error {
269304 if me .autoSaveStop != nil {
270305 close (me .autoSaveStop )
271306 }
307+ logger .InfoContext (me .matcher .ctx , "engine closing" )
272308 return me .matcher .Close ()
273309}
274310
@@ -286,9 +322,10 @@ func (me *MatcherEngine) AutoSave(interval time.Duration) chan<- bool {
286322 case <- ticker .C :
287323 if err := me .Save (); err != nil {
288324 // Log error but continue
289- slog . Error ( "Auto-save error" , "error" , err )
325+ logger . ErrorContext ( me . matcher . ctx , "Auto-save error" , "error" , err )
290326 }
291327 case <- stopChan :
328+ logger .InfoContext (me .matcher .ctx , "autosave stopped" )
292329 return
293330 }
294331 }
@@ -307,8 +344,6 @@ func (me *MatcherEngine) BatchAddRules(rules []*Rule) error {
307344 return nil
308345}
309346
310- // Convenience methods for quick rule creation
311-
312347// AddSimpleRule creates a rule with all exact matches
313348func (me * MatcherEngine ) AddSimpleRule (id string , dimensions map [string ]string , manualWeight * float64 ) error {
314349 builder := NewRule (id )
@@ -340,6 +375,49 @@ func (me *MatcherEngine) AddAnyRule(id string, dimensionNames []string, manualWe
340375 return me .AddRule (rule )
341376}
342377
378+ // GetForestStats returns detailed forest index statistics
379+ func (me * MatcherEngine ) GetForestStats () map [string ]interface {} {
380+ me .matcher .mu .RLock ()
381+ defer me .matcher .mu .RUnlock ()
382+
383+ stats := make (map [string ]interface {})
384+
385+ for key , forestIndex := range me .matcher .forestIndexes {
386+ stats [key ] = forestIndex .GetStats ()
387+ }
388+
389+ // Add summary stats
390+ stats ["total_forests" ] = len (me .matcher .forestIndexes )
391+ stats ["total_rules" ] = len (me .matcher .rules )
392+
393+ return stats
394+ }
395+
396+ // ClearCache clears the query cache
397+ func (me * MatcherEngine ) ClearCache () {
398+ me .matcher .cache .Clear ()
399+ }
400+
401+ // GetCacheStats returns cache statistics
402+ func (me * MatcherEngine ) GetCacheStats () map [string ]interface {} {
403+ return me .matcher .cache .Stats ()
404+ }
405+
406+ // ValidateRule validates a rule before adding it
407+ func (me * MatcherEngine ) ValidateRule (rule * Rule ) error {
408+ return me .matcher .validateRule (rule )
409+ }
410+
411+ // Rebuild rebuilds the forest index (useful after bulk operations)
412+ func (me * MatcherEngine ) Rebuild () error {
413+ // Use the existing Rebuild method which is already tenant-aware
414+ return me .matcher .Rebuild ()
415+ }
416+
417+ func (me * MatcherEngine ) SaveToPersistence () error {
418+ return me .matcher .SaveToPersistence ()
419+ }
420+
343421// CreateQuery creates a query from a map of dimension values
344422func CreateQuery (values map [string ]string ) * QueryRule {
345423 return & QueryRule {
@@ -473,45 +551,6 @@ func CreateQueryWithAllRulesTenantAndExcluded(tenantID, applicationID string, va
473551 }
474552}
475553
476- // GetForestStats returns detailed forest index statistics
477- func (me * MatcherEngine ) GetForestStats () map [string ]interface {} {
478- me .matcher .mu .RLock ()
479- defer me .matcher .mu .RUnlock ()
480-
481- stats := make (map [string ]interface {})
482-
483- for key , forestIndex := range me .matcher .forestIndexes {
484- stats [key ] = forestIndex .GetStats ()
485- }
486-
487- // Add summary stats
488- stats ["total_forests" ] = len (me .matcher .forestIndexes )
489- stats ["total_rules" ] = len (me .matcher .rules )
490-
491- return stats
492- }
493-
494- // ClearCache clears the query cache
495- func (me * MatcherEngine ) ClearCache () {
496- me .matcher .cache .Clear ()
497- }
498-
499- // GetCacheStats returns cache statistics
500- func (me * MatcherEngine ) GetCacheStats () map [string ]interface {} {
501- return me .matcher .cache .Stats ()
502- }
503-
504- // ValidateRule validates a rule before adding it
505- func (me * MatcherEngine ) ValidateRule (rule * Rule ) error {
506- return me .matcher .validateRule (rule )
507- }
508-
509- // RebuildIndex rebuilds the forest index (useful after bulk operations)
510- func (me * MatcherEngine ) RebuildIndex () error {
511- // Use the existing Rebuild method which is already tenant-aware
512- return me .matcher .Rebuild ()
513- }
514-
515554// GenerateDefaultNodeID generates a default node ID based on hostname and random suffix
516555func GenerateDefaultNodeID () string {
517556 hostname , err := os .Hostname ()
0 commit comments