From d02fc74a606b1373601ee1826817cc10485e15c3 Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Mon, 18 May 2026 16:54:39 +0530 Subject: [PATCH] feat: add search functionality to the filter APIs --- framework/logstore/hybrid.go | 36 ++--- framework/logstore/matviews.go | 59 +++++--- framework/logstore/rdb.go | 144 ++++++++++++++----- framework/logstore/rdb_postgres_perf_test.go | 16 +-- framework/logstore/store.go | 18 +-- plugins/logging/operations.go | 50 +++---- plugins/logging/utils.go | 102 ++++++------- transports/bifrost-http/handlers/logging.go | 93 ++++++------ 8 files changed, 302 insertions(+), 216 deletions(-) diff --git a/framework/logstore/hybrid.go b/framework/logstore/hybrid.go index b7de29133a5..217900fcae1 100644 --- a/framework/logstore/hybrid.go +++ b/framework/logstore/hybrid.go @@ -594,28 +594,28 @@ func (h *HybridLogStore) IsLogEntryPresent(ctx context.Context, id string) (bool return h.inner.IsLogEntryPresent(ctx, id) } -func (h *HybridLogStore) GetDistinctAliases(ctx context.Context) ([]string, error) { - return h.inner.GetDistinctAliases(ctx) +func (h *HybridLogStore) GetDistinctAliases(ctx context.Context, limit int, query string) ([]string, error) { + return h.inner.GetDistinctAliases(ctx, limit, query) } -func (h *HybridLogStore) GetDistinctModels(ctx context.Context) ([]string, error) { - return h.inner.GetDistinctModels(ctx) +func (h *HybridLogStore) GetDistinctModels(ctx context.Context, limit int, query string) ([]string, error) { + return h.inner.GetDistinctModels(ctx, limit, query) } -func (h *HybridLogStore) GetDistinctKeyPairs(ctx context.Context, idCol, nameCol string) ([]KeyPairResult, error) { - return h.inner.GetDistinctKeyPairs(ctx, idCol, nameCol) +func (h *HybridLogStore) GetDistinctKeyPairs(ctx context.Context, idCol, nameCol string, limit int, query string) ([]KeyPairResult, error) { + return h.inner.GetDistinctKeyPairs(ctx, idCol, nameCol, limit, query) } -func (h *HybridLogStore) GetDistinctRoutingEngines(ctx context.Context) ([]string, error) { - return h.inner.GetDistinctRoutingEngines(ctx) +func (h *HybridLogStore) GetDistinctRoutingEngines(ctx context.Context, limit int, query string) ([]string, error) { + return h.inner.GetDistinctRoutingEngines(ctx, limit, query) } -func (h *HybridLogStore) GetDistinctStopReasons(ctx context.Context) ([]string, error) { - return h.inner.GetDistinctStopReasons(ctx) +func (h *HybridLogStore) GetDistinctStopReasons(ctx context.Context, limit int, query string) ([]string, error) { + return h.inner.GetDistinctStopReasons(ctx, limit, query) } -func (h *HybridLogStore) GetDistinctMetadataKeys(ctx context.Context) (map[string][]string, error) { - return h.inner.GetDistinctMetadataKeys(ctx) +func (h *HybridLogStore) GetDistinctMetadataKeys(ctx context.Context, limit int, query string) (map[string][]string, error) { + return h.inner.GetDistinctMetadataKeys(ctx, limit, query) } // MCP Tool Log analytics methods are delegated directly. Detail/write paths @@ -1020,16 +1020,16 @@ func (h *HybridLogStore) FlushMCPToolLogs(ctx context.Context, since time.Time) return h.inner.FlushMCPToolLogs(ctx, since) } -func (h *HybridLogStore) GetAvailableToolNames(ctx context.Context) ([]string, error) { - return h.inner.GetAvailableToolNames(ctx) +func (h *HybridLogStore) GetAvailableToolNames(ctx context.Context, limit int, query string) ([]string, error) { + return h.inner.GetAvailableToolNames(ctx, limit, query) } -func (h *HybridLogStore) GetAvailableServerLabels(ctx context.Context) ([]string, error) { - return h.inner.GetAvailableServerLabels(ctx) +func (h *HybridLogStore) GetAvailableServerLabels(ctx context.Context, limit int, query string) ([]string, error) { + return h.inner.GetAvailableServerLabels(ctx, limit, query) } -func (h *HybridLogStore) GetAvailableMCPVirtualKeys(ctx context.Context) ([]MCPToolLog, error) { - return h.inner.GetAvailableMCPVirtualKeys(ctx) +func (h *HybridLogStore) GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) ([]MCPToolLog, error) { + return h.inner.GetAvailableMCPVirtualKeys(ctx, limit, query) } // Async Job methods — delegated directly. diff --git a/framework/logstore/matviews.go b/framework/logstore/matviews.go index 7e6bf673bf5..a380c7aa0aa 100644 --- a/framework/logstore/matviews.go +++ b/framework/logstore/matviews.go @@ -1623,36 +1623,45 @@ func (s *RDBLogStore) getUserRankingsFromMatView(ctx context.Context, filters Se // getDistinctModelsFromMatView returns unique model names from mv_filter_models. // Limit matches the raw-table fallback so callers see the same row cap regardless // of which path served the request. -func (s *RDBLogStore) getDistinctModelsFromMatView(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) getDistinctModelsFromMatView(ctx context.Context, limit int, query string) ([]string, error) { var models []string - if err := s.db.WithContext(ctx).Table("mv_filter_models"). + q := s.db.WithContext(ctx).Table("mv_filter_models"). Distinct("model"). - Where("model != ''"). - Pluck("model", &models).Error; err != nil { + Where("model != ''") + if query != "" { + q = q.Where("model ILIKE ?", "%"+query+"%") + } + if err := q.Order("model ASC").Limit(limit).Pluck("model", &models).Error; err != nil { return nil, err } return models, nil } // getDistinctAliasesFromMatView returns unique alias values from mv_filter_aliases. -func (s *RDBLogStore) getDistinctAliasesFromMatView(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) getDistinctAliasesFromMatView(ctx context.Context, limit int, query string) ([]string, error) { var aliases []string - if err := s.db.WithContext(ctx).Table("mv_filter_aliases"). + q := s.db.WithContext(ctx).Table("mv_filter_aliases"). Distinct("alias"). - Where("alias != ''"). - Pluck("alias", &aliases).Error; err != nil { + Where("alias != ''") + if query != "" { + q = q.Where("alias ILIKE ?", "%"+query+"%") + } + if err := q.Order("alias ASC").Limit(limit).Pluck("alias", &aliases).Error; err != nil { return nil, err } return aliases, nil } // getDistinctStopReasonsFromMatView returns unique stop reasons from mv_filter_stop_reasons. -func (s *RDBLogStore) getDistinctStopReasonsFromMatView(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) getDistinctStopReasonsFromMatView(ctx context.Context, limit int, query string) ([]string, error) { var stopReasons []string - if err := s.db.WithContext(ctx).Table("mv_filter_stop_reasons"). + q := s.db.WithContext(ctx).Table("mv_filter_stop_reasons"). Distinct("stop_reason"). - Where("stop_reason != ''"). - Pluck("stop_reason", &stopReasons).Error; err != nil { + Where("stop_reason != ''") + if query != "" { + q = q.Where("stop_reason ILIKE ?", "%"+query+"%") + } + if err := q.Order("stop_reason ASC").Limit(limit).Pluck("stop_reason", &stopReasons).Error; err != nil { return nil, err } return stopReasons, nil @@ -1662,22 +1671,23 @@ func (s *RDBLogStore) getDistinctStopReasonsFromMatView(ctx context.Context) ([] // (idCol, nameCol) by selecting from the per-dimension matview pre-aggregated // for that pair. Returns (nil, false) if no matview is registered for the pair — // callers fall back to the raw-table path. -func (s *RDBLogStore) getDistinctKeyPairsFromMatView(ctx context.Context, idCol, nameCol string) ([]KeyPairResult, bool, error) { +func (s *RDBLogStore) getDistinctKeyPairsFromMatView(ctx context.Context, idCol, nameCol string, limit int, query string) ([]KeyPairResult, bool, error) { view, ok := filterMatViewKeyPairColumns[[2]string{idCol, nameCol}] if !ok { return nil, false, nil } var results []KeyPairResult q := s.db.WithContext(ctx).Table(view).Where("id != ''") - // User matview stores name = id and the view-level WHERE already filters - // empty ids; other matviews include name and we additionally guard against - // stragglers with empty names. if !(idCol == "user_id" && nameCol == "user_id") { q = q.Where("name != ''") } + if query != "" { + q = q.Where("name ILIKE ?", "%"+query+"%") + } if err := q. Select("DISTINCT id, name"). Order("name ASC"). + Limit(limit). Find(&results).Error; err != nil { return nil, true, err } @@ -1687,12 +1697,15 @@ func (s *RDBLogStore) getDistinctKeyPairsFromMatView(ctx context.Context, idCol, // getDistinctRoutingEnginesFromMatView returns unique routing engine names by // parsing the comma-separated routing_engines_used values from // mv_filter_routing_engines. -func (s *RDBLogStore) getDistinctRoutingEnginesFromMatView(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) getDistinctRoutingEnginesFromMatView(ctx context.Context, limit int, query string) ([]string, error) { var rawValues []string - if err := s.db.WithContext(ctx).Table("mv_filter_routing_engines"). + q := s.db.WithContext(ctx).Table("mv_filter_routing_engines"). Distinct("routing_engines_used"). - Where("routing_engines_used != ''"). - Pluck("routing_engines_used", &rawValues).Error; err != nil { + Where("routing_engines_used != ''") + if query != "" { + q = q.Where("routing_engines_used ILIKE ?", "%"+query+"%") + } + if err := q.Pluck("routing_engines_used", &rawValues).Error; err != nil { return nil, err } seen := make(map[string]struct{}) @@ -1704,7 +1717,11 @@ func (s *RDBLogStore) getDistinctRoutingEnginesFromMatView(ctx context.Context) } } } - return sortedStringKeys(seen), nil + result := sortedStringKeys(seen) + if len(result) > limit { + result = result[:limit] + } + return result, nil } // --------------------------------------------------------------------------- diff --git a/framework/logstore/rdb.go b/framework/logstore/rdb.go index e3a3b5760e5..ebea180f0a2 100644 --- a/framework/logstore/rdb.go +++ b/framework/logstore/rdb.go @@ -2751,17 +2751,29 @@ func (s *RDBLogStore) Flush(ctx context.Context, since time.Time) error { return nil } +func (s *RDBLogStore) applyLikeFilter(q *gorm.DB, column, search string) *gorm.DB { + pattern := "%" + search + "%" + if s.db.Dialector.Name() == "postgres" { + return q.Where(fmt.Sprintf("%s ILIKE ?", column), pattern) + } + return q.Where(fmt.Sprintf("%s LIKE ?", column), pattern) +} + // GetDistinctModels returns all unique non-empty model values using SELECT DISTINCT. // Scoped to recent data to avoid full table scans. -func (s *RDBLogStore) GetDistinctModels(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) GetDistinctModels(ctx context.Context, limit int, query string) ([]string, error) { if s.db.Dialector.Name() == "postgres" && s.matViewsReady.Load() { - return s.getDistinctModelsFromMatView(ctx) + return s.getDistinctModelsFromMatView(ctx, limit, query) } cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var models []string - err := s.db.WithContext(ctx).Model(&Log{}). + q := s.db.WithContext(ctx).Model(&Log{}). Where("model IS NOT NULL AND model != '' AND timestamp >= ?", cutoff). - Distinct("model").Pluck("model", &models).Error + Distinct("model") + if query != "" { + q = s.applyLikeFilter(q, "model", query) + } + err := q.Order("model ASC").Limit(limit).Pluck("model", &models).Error if err != nil { return nil, fmt.Errorf("failed to get distinct models: %w", err) } @@ -2770,15 +2782,19 @@ func (s *RDBLogStore) GetDistinctModels(ctx context.Context) ([]string, error) { // GetDistinctAliases returns all unique non-empty alias values using SELECT DISTINCT. // Scoped to recent data to avoid full table scans. -func (s *RDBLogStore) GetDistinctAliases(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) GetDistinctAliases(ctx context.Context, limit int, query string) ([]string, error) { if s.db.Dialector.Name() == "postgres" && s.matViewsReady.Load() { - return s.getDistinctAliasesFromMatView(ctx) + return s.getDistinctAliasesFromMatView(ctx, limit, query) } cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var aliases []string - err := s.db.WithContext(ctx).Model(&Log{}). + q := s.db.WithContext(ctx).Model(&Log{}). Where("alias IS NOT NULL AND alias != '' AND timestamp >= ?", cutoff). - Distinct("alias").Pluck("alias", &aliases).Error + Distinct("alias") + if query != "" { + q = s.applyLikeFilter(q, "alias", query) + } + err := q.Order("alias ASC").Limit(limit).Pluck("alias", &aliases).Error if err != nil { return nil, fmt.Errorf("failed to get distinct aliases: %w", err) } @@ -2805,13 +2821,12 @@ var allowedKeyPairColumns = map[string]struct{}{ // GetDistinctKeyPairs returns unique non-empty ID-Name pairs for the given columns using SELECT DISTINCT. // idCol and nameCol must be valid column names (e.g., "selected_key_id", "selected_key_name"). -func (s *RDBLogStore) GetDistinctKeyPairs(ctx context.Context, idCol, nameCol string) ([]KeyPairResult, error) { +func (s *RDBLogStore) GetDistinctKeyPairs(ctx context.Context, idCol, nameCol string, limit int, query string) ([]KeyPairResult, error) { if s.db.Dialector.Name() == "postgres" && s.matViewsReady.Load() { - results, served, err := s.getDistinctKeyPairsFromMatView(ctx, idCol, nameCol) + results, served, err := s.getDistinctKeyPairsFromMatView(ctx, idCol, nameCol, limit, query) if served { return results, err } - // Pair has no per-dimension matview registered — fall through to raw path. } if _, ok := allowedKeyPairColumns[idCol]; !ok { return nil, fmt.Errorf("invalid id column: %s", idCol) @@ -2821,10 +2836,13 @@ func (s *RDBLogStore) GetDistinctKeyPairs(ctx context.Context, idCol, nameCol st } cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var results []KeyPairResult - err := s.db.WithContext(ctx).Model(&Log{}). + q := s.db.WithContext(ctx).Model(&Log{}). Select(fmt.Sprintf("DISTINCT %s as id, %s as name", idCol, nameCol)). - Where(fmt.Sprintf("%s IS NOT NULL AND %s != '' AND %s IS NOT NULL AND %s != '' AND timestamp >= ?", idCol, idCol, nameCol, nameCol), cutoff). - Find(&results).Error + Where(fmt.Sprintf("%s IS NOT NULL AND %s != '' AND %s IS NOT NULL AND %s != '' AND timestamp >= ?", idCol, idCol, nameCol, nameCol), cutoff) + if query != "" { + q = s.applyLikeFilter(q, nameCol, query) + } + err := q.Order("name ASC").Limit(limit).Find(&results).Error if err != nil { return nil, fmt.Errorf("failed to get distinct key pairs (%s, %s): %w", idCol, nameCol, err) } @@ -2833,15 +2851,19 @@ func (s *RDBLogStore) GetDistinctKeyPairs(ctx context.Context, idCol, nameCol st // GetDistinctRoutingEngines returns all unique routing engine values from the comma-separated column. // Scoped to recent data to avoid full table scans. -func (s *RDBLogStore) GetDistinctRoutingEngines(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) GetDistinctRoutingEngines(ctx context.Context, limit int, query string) ([]string, error) { if s.db.Dialector.Name() == "postgres" && s.matViewsReady.Load() { - return s.getDistinctRoutingEnginesFromMatView(ctx) + return s.getDistinctRoutingEnginesFromMatView(ctx, limit, query) } cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var rawValues []string - err := s.db.WithContext(ctx).Model(&Log{}). + q := s.db.WithContext(ctx).Model(&Log{}). Where("routing_engines_used IS NOT NULL AND routing_engines_used != '' AND timestamp >= ?", cutoff). - Distinct("routing_engines_used").Pluck("routing_engines_used", &rawValues).Error + Distinct("routing_engines_used") + if query != "" { + q = s.applyLikeFilter(q, "routing_engines_used", query) + } + err := q.Pluck("routing_engines_used", &rawValues).Error if err != nil { return nil, fmt.Errorf("failed to get distinct routing engines: %w", err) } @@ -2859,20 +2881,28 @@ func (s *RDBLogStore) GetDistinctRoutingEngines(ctx context.Context) ([]string, for engine := range uniqueEngines { engines = append(engines, engine) } + sort.Strings(engines) + if len(engines) > limit { + engines = engines[:limit] + } return engines, nil } // GetDistinctStopReasons returns all unique non-empty stop_reason values using SELECT DISTINCT. // Scoped to recent data to avoid full table scans. -func (s *RDBLogStore) GetDistinctStopReasons(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) GetDistinctStopReasons(ctx context.Context, limit int, query string) ([]string, error) { if s.db.Dialector.Name() == "postgres" && s.matViewsReady.Load() { - return s.getDistinctStopReasonsFromMatView(ctx) + return s.getDistinctStopReasonsFromMatView(ctx, limit, query) } cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var stopReasons []string - err := s.db.WithContext(ctx).Model(&Log{}). + q := s.db.WithContext(ctx).Model(&Log{}). Where("stop_reason IS NOT NULL AND stop_reason != '' AND timestamp >= ?", cutoff). - Distinct("stop_reason").Pluck("stop_reason", &stopReasons).Error + Distinct("stop_reason") + if query != "" { + q = s.applyLikeFilter(q, "stop_reason", query) + } + err := q.Order("stop_reason ASC").Limit(limit).Pluck("stop_reason", &stopReasons).Error if err != nil { return nil, fmt.Errorf("failed to get distinct stop reasons: %w", err) } @@ -2893,7 +2923,7 @@ const ( // GetDistinctMetadataKeys returns unique metadata keys and their distinct values from recent logs. // It scans a bounded number of recent rows to avoid memory bloat on large tables. -func (s *RDBLogStore) GetDistinctMetadataKeys(ctx context.Context) (map[string][]string, error) { +func (s *RDBLogStore) GetDistinctMetadataKeys(ctx context.Context, limit int, query string) (map[string][]string, error) { cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var metadataStrings []string // Guard must match the partial-index predicate so the planner uses the GIN index. @@ -2949,8 +2979,39 @@ func (s *RDBLogStore) GetDistinctMetadataKeys(ctx context.Context) (map[string][ } } + // Apply search filter on both key names and values + if query != "" { + lowerQ := strings.ToLower(query) + filtered := make(map[string]map[string]struct{}) + for key, vals := range keyValues { + if strings.Contains(strings.ToLower(key), lowerQ) { + filtered[key] = vals + } else { + matchedVals := make(map[string]struct{}) + for v := range vals { + if strings.Contains(strings.ToLower(v), lowerQ) { + matchedVals[v] = struct{}{} + } + } + if len(matchedVals) > 0 { + filtered[key] = matchedVals + } + } + } + keyValues = filtered + } + result := make(map[string][]string, len(keyValues)) - for key, vals := range keyValues { + keys := make([]string, 0, len(keyValues)) + for key := range keyValues { + keys = append(keys, key) + } + sort.Strings(keys) + if limit > 0 && len(keys) > limit { + keys = keys[:limit] + } + for _, key := range keys { + vals := keyValues[key] values := make([]string, 0, len(vals)) for v := range vals { values = append(values, v) @@ -3354,42 +3415,49 @@ func (s *RDBLogStore) FlushMCPToolLogs(ctx context.Context, since time.Time) err // GetAvailableToolNames returns all unique tool names from the MCP tool logs. // Scoped to recent data to avoid full table scans. -func (s *RDBLogStore) GetAvailableToolNames(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) GetAvailableToolNames(ctx context.Context, limit int, query string) ([]string, error) { cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var toolNames []string - result := s.db.WithContext(ctx).Model(&MCPToolLog{}). + q := s.db.WithContext(ctx).Model(&MCPToolLog{}). Where("tool_name IS NOT NULL AND tool_name != '' AND timestamp >= ?", cutoff). - Distinct("tool_name").Pluck("tool_name", &toolNames) + Distinct("tool_name") + if query != "" { + q = s.applyLikeFilter(q, "tool_name", query) + } + result := q.Limit(limit).Pluck("tool_name", &toolNames) if result.Error != nil { return nil, fmt.Errorf("failed to get available tool names: %w", result.Error) } return toolNames, nil } -// GetAvailableServerLabels returns all unique server labels from the MCP tool logs. -// Scoped to recent data to avoid full table scans. -func (s *RDBLogStore) GetAvailableServerLabels(ctx context.Context) ([]string, error) { +func (s *RDBLogStore) GetAvailableServerLabels(ctx context.Context, limit int, query string) ([]string, error) { cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var serverLabels []string - result := s.db.WithContext(ctx).Model(&MCPToolLog{}). + q := s.db.WithContext(ctx).Model(&MCPToolLog{}). Where("server_label IS NOT NULL AND server_label != '' AND timestamp >= ?", cutoff). - Distinct("server_label").Pluck("server_label", &serverLabels) + Distinct("server_label") + if query != "" { + q = s.applyLikeFilter(q, "server_label", query) + } + result := q.Limit(limit).Pluck("server_label", &serverLabels) if result.Error != nil { return nil, fmt.Errorf("failed to get available server labels: %w", result.Error) } return serverLabels, nil } -// GetAvailableMCPVirtualKeys returns all unique virtual key ID-Name pairs from MCP tool logs. -// Scoped to recent data to avoid full table scans. -func (s *RDBLogStore) GetAvailableMCPVirtualKeys(ctx context.Context) ([]MCPToolLog, error) { +func (s *RDBLogStore) GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) ([]MCPToolLog, error) { cutoff := time.Now().UTC().AddDate(0, 0, -defaultFilterDataCutoffDays) var logs []MCPToolLog - result := s.db.WithContext(ctx). + q := s.db.WithContext(ctx). Model(&MCPToolLog{}). Select("DISTINCT virtual_key_id, virtual_key_name"). - Where("virtual_key_id IS NOT NULL AND virtual_key_id != '' AND virtual_key_name IS NOT NULL AND virtual_key_name != '' AND timestamp >= ?", cutoff). - Find(&logs) + Where("virtual_key_id IS NOT NULL AND virtual_key_id != '' AND virtual_key_name IS NOT NULL AND virtual_key_name != '' AND timestamp >= ?", cutoff) + if query != "" { + q = s.applyLikeFilter(q, "virtual_key_name", query) + } + result := q.Limit(limit).Find(&logs) if result.Error != nil { return nil, fmt.Errorf("failed to get available virtual keys from MCP logs: %w", result.Error) } diff --git a/framework/logstore/rdb_postgres_perf_test.go b/framework/logstore/rdb_postgres_perf_test.go index a985eb11d48..68674dcd3b9 100644 --- a/framework/logstore/rdb_postgres_perf_test.go +++ b/framework/logstore/rdb_postgres_perf_test.go @@ -298,7 +298,7 @@ func TestGetDistinctModels_TimeCutoff(t *testing.T) { insertPerfLog(t, db, logOpts{Model: "old-model", Timestamp: old}) refreshTestMatViews(t, db) - models, err := store.GetDistinctModels(ctx) + models, err := store.GetDistinctModels(ctx, 1000, "") require.NoError(t, err) assert.Contains(t, models, "recent-model") assert.NotContains(t, models, "old-model") @@ -319,7 +319,7 @@ func TestGetDistinctKeyPairs_TimeCutoff(t *testing.T) { }) refreshTestMatViews(t, db) - pairs, err := store.GetDistinctKeyPairs(ctx, "virtual_key_id", "virtual_key_name") + pairs, err := store.GetDistinctKeyPairs(ctx, "virtual_key_id", "virtual_key_name", 1000, "") require.NoError(t, err) var ids []string @@ -345,7 +345,7 @@ func TestGetDistinctRoutingEngines_TimeCutoff(t *testing.T) { }) refreshTestMatViews(t, db) - engines, err := store.GetDistinctRoutingEngines(ctx) + engines, err := store.GetDistinctRoutingEngines(ctx, 1000, "") require.NoError(t, err) assert.Contains(t, engines, "loadbalancing") assert.Contains(t, engines, "governance") @@ -366,7 +366,7 @@ func TestGetDistinctMetadataKeys_TimeCutoff(t *testing.T) { Timestamp: old, Metadata: `{"old_key": "old_value"}`, }) - keys, err := store.GetDistinctMetadataKeys(ctx) + keys, err := store.GetDistinctMetadataKeys(ctx, 1000, "") require.NoError(t, err) assert.Contains(t, keys, "env") assert.NotContains(t, keys, "old_key") @@ -383,7 +383,7 @@ func TestGetDistinctStopReasons_TimeCutoff(t *testing.T) { insertPerfLog(t, db, logOpts{Timestamp: recent, StopReason: "content_filter"}) insertPerfLog(t, db, logOpts{Timestamp: old, StopReason: "length"}) - stopReasons, err := store.GetDistinctStopReasons(ctx) + stopReasons, err := store.GetDistinctStopReasons(ctx, 1000, "") require.NoError(t, err) assert.Contains(t, stopReasons, "refusal") assert.Contains(t, stopReasons, "content_filter") @@ -406,7 +406,7 @@ func TestGetAvailableToolNames_TimeCutoff(t *testing.T) { VirtualKeyID: "vk-1", VirtualKeyName: "k1", }) - tools, err := store.GetAvailableToolNames(ctx) + tools, err := store.GetAvailableToolNames(ctx, 1000, "") require.NoError(t, err) assert.Contains(t, tools, "recent-tool") assert.NotContains(t, tools, "old-tool") @@ -428,7 +428,7 @@ func TestGetAvailableServerLabels_TimeCutoff(t *testing.T) { VirtualKeyID: "vk-1", VirtualKeyName: "k1", }) - labels, err := store.GetAvailableServerLabels(ctx) + labels, err := store.GetAvailableServerLabels(ctx, 1000, "") require.NoError(t, err) assert.Contains(t, labels, "recent-server") assert.NotContains(t, labels, "old-server") @@ -450,7 +450,7 @@ func TestGetAvailableMCPVirtualKeys_TimeCutoff(t *testing.T) { VirtualKeyID: "vk-old", VirtualKeyName: "Old VK", }) - keys, err := store.GetAvailableMCPVirtualKeys(ctx) + keys, err := store.GetAvailableMCPVirtualKeys(ctx, 1000, "") require.NoError(t, err) var ids []string diff --git a/framework/logstore/store.go b/framework/logstore/store.go index d392f23b2e1..221b8006732 100644 --- a/framework/logstore/store.go +++ b/framework/logstore/store.go @@ -59,12 +59,12 @@ type LogStore interface { DeleteLogsBatch(ctx context.Context, cutoff time.Time, batchSize int) (deletedCount int64, err error) // Distinct value methods for filter data - GetDistinctModels(ctx context.Context) ([]string, error) - GetDistinctAliases(ctx context.Context) ([]string, error) - GetDistinctKeyPairs(ctx context.Context, idCol, nameCol string) ([]KeyPairResult, error) - GetDistinctRoutingEngines(ctx context.Context) ([]string, error) - GetDistinctStopReasons(ctx context.Context) ([]string, error) - GetDistinctMetadataKeys(ctx context.Context) (map[string][]string, error) + GetDistinctModels(ctx context.Context, limit int, query string) ([]string, error) + GetDistinctAliases(ctx context.Context, limit int, query string) ([]string, error) + GetDistinctKeyPairs(ctx context.Context, idCol, nameCol string, limit int, query string) ([]KeyPairResult, error) + GetDistinctRoutingEngines(ctx context.Context, limit int, query string) ([]string, error) + GetDistinctStopReasons(ctx context.Context, limit int, query string) ([]string, error) + GetDistinctMetadataKeys(ctx context.Context, limit int, query string) (map[string][]string, error) // MCP Tool Log histogram methods GetMCPHistogram(ctx context.Context, filters MCPToolLogSearchFilters, bucketSizeSeconds int64) (*MCPHistogramResult, error) @@ -81,9 +81,9 @@ type LogStore interface { HasMCPToolLogs(ctx context.Context) (bool, error) DeleteMCPToolLogs(ctx context.Context, ids []string) error FlushMCPToolLogs(ctx context.Context, since time.Time) error - GetAvailableToolNames(ctx context.Context) ([]string, error) - GetAvailableServerLabels(ctx context.Context) ([]string, error) - GetAvailableMCPVirtualKeys(ctx context.Context) ([]MCPToolLog, error) + GetAvailableToolNames(ctx context.Context, limit int, query string) ([]string, error) + GetAvailableServerLabels(ctx context.Context, limit int, query string) ([]string, error) + GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) ([]MCPToolLog, error) // Async Job methods CreateAsyncJob(ctx context.Context, job *AsyncJob) error diff --git a/plugins/logging/operations.go b/plugins/logging/operations.go index 83f6f4bc147..47d470ffea5 100644 --- a/plugins/logging/operations.go +++ b/plugins/logging/operations.go @@ -1025,8 +1025,8 @@ func (p *LoggerPlugin) GetModelRankings(ctx context.Context, filters logstore.Se // GetAvailableModels returns all unique models from logs. // Uses DISTINCT to avoid loading all rows (28K+) when only unique values are needed. -func (p *LoggerPlugin) GetAvailableModels(ctx context.Context) []string { - models, err := p.store.GetDistinctModels(ctx) +func (p *LoggerPlugin) GetAvailableModels(ctx context.Context, limit int, query string) []string { + models, err := p.store.GetDistinctModels(ctx, limit, query) if err != nil { p.logger.Error("failed to get available models: %v", err) return []string{} @@ -1035,8 +1035,8 @@ func (p *LoggerPlugin) GetAvailableModels(ctx context.Context) []string { } // GetAvailableAliases returns all unique alias values from logs. -func (p *LoggerPlugin) GetAvailableAliases(ctx context.Context) []string { - aliases, err := p.store.GetDistinctAliases(ctx) +func (p *LoggerPlugin) GetAvailableAliases(ctx context.Context, limit int, query string) []string { + aliases, err := p.store.GetDistinctAliases(ctx, limit, query) if err != nil { p.logger.Error("failed to get available aliases: %v", err) return []string{} @@ -1044,8 +1044,8 @@ func (p *LoggerPlugin) GetAvailableAliases(ctx context.Context) []string { return aliases } -func (p *LoggerPlugin) GetAvailableSelectedKeys(ctx context.Context) []KeyPair { - results, err := p.store.GetDistinctKeyPairs(ctx, "selected_key_id", "selected_key_name") +func (p *LoggerPlugin) GetAvailableSelectedKeys(ctx context.Context, limit int, query string) []KeyPair { + results, err := p.store.GetDistinctKeyPairs(ctx, "selected_key_id", "selected_key_name", limit, query) if err != nil { p.logger.Error("failed to get available selected keys: %v", err) return []KeyPair{} @@ -1053,8 +1053,8 @@ func (p *LoggerPlugin) GetAvailableSelectedKeys(ctx context.Context) []KeyPair { return keyPairResultsToKeyPairs(results) } -func (p *LoggerPlugin) GetAvailableVirtualKeys(ctx context.Context) []KeyPair { - results, err := p.store.GetDistinctKeyPairs(ctx, "virtual_key_id", "virtual_key_name") +func (p *LoggerPlugin) GetAvailableVirtualKeys(ctx context.Context, limit int, query string) []KeyPair { + results, err := p.store.GetDistinctKeyPairs(ctx, "virtual_key_id", "virtual_key_name", limit, query) if err != nil { p.logger.Error("failed to get available virtual keys: %v", err) return []KeyPair{} @@ -1062,8 +1062,8 @@ func (p *LoggerPlugin) GetAvailableVirtualKeys(ctx context.Context) []KeyPair { return keyPairResultsToKeyPairs(results) } -func (p *LoggerPlugin) GetAvailableRoutingRules(ctx context.Context) []KeyPair { - results, err := p.store.GetDistinctKeyPairs(ctx, "routing_rule_id", "routing_rule_name") +func (p *LoggerPlugin) GetAvailableRoutingRules(ctx context.Context, limit int, query string) []KeyPair { + results, err := p.store.GetDistinctKeyPairs(ctx, "routing_rule_id", "routing_rule_name", limit, query) if err != nil { p.logger.Error("failed to get available routing rules: %v", err) return []KeyPair{} @@ -1073,8 +1073,8 @@ func (p *LoggerPlugin) GetAvailableRoutingRules(ctx context.Context) []KeyPair { // GetAvailableTeams returns all unique team ID-Name pairs from logs. // Uses DISTINCT to avoid loading all rows when only unique values are needed. -func (p *LoggerPlugin) GetAvailableTeams(ctx context.Context) []KeyPair { - results, err := p.store.GetDistinctKeyPairs(ctx, "team_id", "team_name") +func (p *LoggerPlugin) GetAvailableTeams(ctx context.Context, limit int, query string) []KeyPair { + results, err := p.store.GetDistinctKeyPairs(ctx, "team_id", "team_name", limit, query) if err != nil { p.logger.Error("failed to get available teams: %v", err) return []KeyPair{} @@ -1084,8 +1084,8 @@ func (p *LoggerPlugin) GetAvailableTeams(ctx context.Context) []KeyPair { // GetAvailableCustomers returns all unique customer ID-Name pairs from logs. // Uses DISTINCT to avoid loading all rows when only unique values are needed. -func (p *LoggerPlugin) GetAvailableCustomers(ctx context.Context) []KeyPair { - results, err := p.store.GetDistinctKeyPairs(ctx, "customer_id", "customer_name") +func (p *LoggerPlugin) GetAvailableCustomers(ctx context.Context, limit int, query string) []KeyPair { + results, err := p.store.GetDistinctKeyPairs(ctx, "customer_id", "customer_name", limit, query) if err != nil { p.logger.Error("failed to get available customers: %v", err) return []KeyPair{} @@ -1095,8 +1095,8 @@ func (p *LoggerPlugin) GetAvailableCustomers(ctx context.Context) []KeyPair { // GetAvailableUsers returns all unique user IDs from logs. // Both ID and Name are set to user_id since users don't have a separate name column. -func (p *LoggerPlugin) GetAvailableUsers(ctx context.Context) []KeyPair { - results, err := p.store.GetDistinctKeyPairs(ctx, "user_id", "user_id") +func (p *LoggerPlugin) GetAvailableUsers(ctx context.Context, limit int, query string) []KeyPair { + results, err := p.store.GetDistinctKeyPairs(ctx, "user_id", "user_id", limit, query) if err != nil { p.logger.Error("failed to get available users: %v", err) return []KeyPair{} @@ -1106,8 +1106,8 @@ func (p *LoggerPlugin) GetAvailableUsers(ctx context.Context) []KeyPair { // GetAvailableBusinessUnits returns all unique business unit ID-Name pairs from logs. // Uses DISTINCT to avoid loading all rows when only unique values are needed. -func (p *LoggerPlugin) GetAvailableBusinessUnits(ctx context.Context) []KeyPair { - results, err := p.store.GetDistinctKeyPairs(ctx, "business_unit_id", "business_unit_name") +func (p *LoggerPlugin) GetAvailableBusinessUnits(ctx context.Context, limit int, query string) []KeyPair { + results, err := p.store.GetDistinctKeyPairs(ctx, "business_unit_id", "business_unit_name", limit, query) if err != nil { p.logger.Error("failed to get available business units: %v", err) return []KeyPair{} @@ -1135,8 +1135,8 @@ func (p *LoggerPlugin) GetDimensionLatencyHistogram(ctx context.Context, filters // GetAvailableRoutingEngines returns all unique routing engine types used in logs. // Uses DISTINCT to avoid loading all rows when only unique values are needed. -func (p *LoggerPlugin) GetAvailableRoutingEngines(ctx context.Context) []string { - engines, err := p.store.GetDistinctRoutingEngines(ctx) +func (p *LoggerPlugin) GetAvailableRoutingEngines(ctx context.Context, limit int, query string) []string { + engines, err := p.store.GetDistinctRoutingEngines(ctx, limit, query) if err != nil { p.logger.Error("failed to get available routing engines: %v", err) return []string{} @@ -1146,8 +1146,8 @@ func (p *LoggerPlugin) GetAvailableRoutingEngines(ctx context.Context) []string // GetAvailableStopReasons returns all unique stop reason values from logs. // Uses DISTINCT to avoid loading all rows when only unique values are needed. -func (p *LoggerPlugin) GetAvailableStopReasons(ctx context.Context) []string { - stopReasons, err := p.store.GetDistinctStopReasons(ctx) +func (p *LoggerPlugin) GetAvailableStopReasons(ctx context.Context, limit int, query string) []string { + stopReasons, err := p.store.GetDistinctStopReasons(ctx, limit, query) if err != nil { p.logger.Error("failed to get available stop reasons: %v", err) return []string{} @@ -1165,10 +1165,10 @@ func keyPairResultsToKeyPairs(results []logstore.KeyPairResult) []KeyPair { } // GetAvailableMCPVirtualKeys returns all unique virtual key ID-Name pairs from MCP tool logs -func (p *LoggerPlugin) GetAvailableMCPVirtualKeys(ctx context.Context) []KeyPair { - result, err := p.store.GetAvailableMCPVirtualKeys(ctx) +func (p *LoggerPlugin) GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) []KeyPair { + result, err := p.store.GetAvailableMCPVirtualKeys(ctx, limit, query) if err != nil { - p.logger.Error("failed to get available virtual keys from MCP logs: %w", err) + p.logger.Error("failed to get available virtual keys from MCP logs: %v", err) return []KeyPair{} } return p.extractUniqueMCPKeyPairs(result, func(log *logstore.MCPToolLog) KeyPair { diff --git a/plugins/logging/utils.go b/plugins/logging/utils.go index b4a73bde1e8..02c58d27caa 100644 --- a/plugins/logging/utils.go +++ b/plugins/logging/utils.go @@ -68,40 +68,40 @@ type LogManager interface { GetDroppedRequests(ctx context.Context) int64 // GetAvailableModels returns all unique models from logs - GetAvailableModels(ctx context.Context) []string + GetAvailableModels(ctx context.Context, limit int, query string) []string // GetAvailableAliases returns all unique alias values from logs - GetAvailableAliases(ctx context.Context) []string + GetAvailableAliases(ctx context.Context, limit int, query string) []string // GetAvailableSelectedKeys returns all unique selected key ID-Name pairs from logs - GetAvailableSelectedKeys(ctx context.Context) []KeyPair + GetAvailableSelectedKeys(ctx context.Context, limit int, query string) []KeyPair // GetAvailableVirtualKeys returns all unique virtual key ID-Name pairs from logs - GetAvailableVirtualKeys(ctx context.Context) []KeyPair + GetAvailableVirtualKeys(ctx context.Context, limit int, query string) []KeyPair // GetAvailableRoutingRules returns all unique routing rule ID-Name pairs from logs - GetAvailableRoutingRules(ctx context.Context) []KeyPair + GetAvailableRoutingRules(ctx context.Context, limit int, query string) []KeyPair // GetAvailableRoutingEngines returns all unique routing engine types from logs - GetAvailableRoutingEngines(ctx context.Context) []string + GetAvailableRoutingEngines(ctx context.Context, limit int, query string) []string // GetAvailableStopReasons returns all unique stop reason values from logs - GetAvailableStopReasons(ctx context.Context) []string + GetAvailableStopReasons(ctx context.Context, limit int, query string) []string // GetAvailableTeams returns all unique team ID-Name pairs from logs - GetAvailableTeams(ctx context.Context) []KeyPair + GetAvailableTeams(ctx context.Context, limit int, query string) []KeyPair // GetAvailableCustomers returns all unique customer ID-Name pairs from logs - GetAvailableCustomers(ctx context.Context) []KeyPair + GetAvailableCustomers(ctx context.Context, limit int, query string) []KeyPair // GetAvailableUsers returns all unique user IDs from logs - GetAvailableUsers(ctx context.Context) []KeyPair + GetAvailableUsers(ctx context.Context, limit int, query string) []KeyPair // GetAvailableBusinessUnits returns all unique business unit ID-Name pairs from logs - GetAvailableBusinessUnits(ctx context.Context) []KeyPair + GetAvailableBusinessUnits(ctx context.Context, limit int, query string) []KeyPair // GetAvailableMetadataKeys returns distinct metadata keys and their values from recent logs - GetAvailableMetadataKeys(ctx context.Context) (map[string][]string, error) + GetAvailableMetadataKeys(ctx context.Context, limit int, query string) (map[string][]string, error) // GetDimensionCostHistogram returns time-bucketed cost data grouped by the specified dimension GetDimensionCostHistogram(ctx context.Context, filters *logstore.SearchFilters, bucketSizeSeconds int64, dimension logstore.HistogramDimension) (*logstore.DimensionCostHistogramResult, error) @@ -132,13 +132,13 @@ type LogManager interface { GetMCPToolLogStats(ctx context.Context, filters *logstore.MCPToolLogSearchFilters) (*logstore.MCPToolLogStats, error) // GetAvailableToolNames returns all unique tool names from MCP tool logs - GetAvailableToolNames(ctx context.Context) ([]string, error) + GetAvailableToolNames(ctx context.Context, limit int, query string) ([]string, error) // GetAvailableServerLabels returns all unique server labels from MCP tool logs - GetAvailableServerLabels(ctx context.Context) ([]string, error) + GetAvailableServerLabels(ctx context.Context, limit int, query string) ([]string, error) // GetAvailableMCPVirtualKeys returns all unique virtual key ID-Name pairs from MCP tool logs - GetAvailableMCPVirtualKeys(ctx context.Context) []KeyPair + GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) []KeyPair // GetMCPHistogram returns time-bucketed MCP tool call volume GetMCPHistogram(ctx context.Context, filters logstore.MCPToolLogSearchFilters, bucketSizeSeconds int64) (*logstore.MCPHistogramResult, error) @@ -261,58 +261,48 @@ func (p *PluginLogManager) GetDroppedRequests(ctx context.Context) int64 { } // GetAvailableModels returns all unique models from logs -func (p *PluginLogManager) GetAvailableModels(ctx context.Context) []string { - return p.plugin.GetAvailableModels(ctx) +func (p *PluginLogManager) GetAvailableModels(ctx context.Context, limit int, query string) []string { + return p.plugin.GetAvailableModels(ctx, limit, query) } -// GetAvailableAliases returns all unique alias values from logs -func (p *PluginLogManager) GetAvailableAliases(ctx context.Context) []string { - return p.plugin.GetAvailableAliases(ctx) +func (p *PluginLogManager) GetAvailableAliases(ctx context.Context, limit int, query string) []string { + return p.plugin.GetAvailableAliases(ctx, limit, query) } -// GetAvailableSelectedKeys returns all unique selected key ID-Name pairs from logs -func (p *PluginLogManager) GetAvailableSelectedKeys(ctx context.Context) []KeyPair { - return p.plugin.GetAvailableSelectedKeys(ctx) +func (p *PluginLogManager) GetAvailableSelectedKeys(ctx context.Context, limit int, query string) []KeyPair { + return p.plugin.GetAvailableSelectedKeys(ctx, limit, query) } -// GetAvailableVirtualKeys returns all unique virtual key ID-Name pairs from logs -func (p *PluginLogManager) GetAvailableVirtualKeys(ctx context.Context) []KeyPair { - return p.plugin.GetAvailableVirtualKeys(ctx) +func (p *PluginLogManager) GetAvailableVirtualKeys(ctx context.Context, limit int, query string) []KeyPair { + return p.plugin.GetAvailableVirtualKeys(ctx, limit, query) } -// GetAvailableRoutingRules returns all unique routing rule ID-Name pairs from logs -func (p *PluginLogManager) GetAvailableRoutingRules(ctx context.Context) []KeyPair { - return p.plugin.GetAvailableRoutingRules(ctx) +func (p *PluginLogManager) GetAvailableRoutingRules(ctx context.Context, limit int, query string) []KeyPair { + return p.plugin.GetAvailableRoutingRules(ctx, limit, query) } -// GetAvailableRoutingEngines returns all unique routing engine types from logs -func (p *PluginLogManager) GetAvailableRoutingEngines(ctx context.Context) []string { - return p.plugin.GetAvailableRoutingEngines(ctx) +func (p *PluginLogManager) GetAvailableRoutingEngines(ctx context.Context, limit int, query string) []string { + return p.plugin.GetAvailableRoutingEngines(ctx, limit, query) } -// GetAvailableStopReasons returns all unique stop reason values from logs -func (p *PluginLogManager) GetAvailableStopReasons(ctx context.Context) []string { - return p.plugin.GetAvailableStopReasons(ctx) +func (p *PluginLogManager) GetAvailableStopReasons(ctx context.Context, limit int, query string) []string { + return p.plugin.GetAvailableStopReasons(ctx, limit, query) } -// GetAvailableTeams returns all unique team ID-Name pairs from logs. -func (p *PluginLogManager) GetAvailableTeams(ctx context.Context) []KeyPair { - return p.plugin.GetAvailableTeams(ctx) +func (p *PluginLogManager) GetAvailableTeams(ctx context.Context, limit int, query string) []KeyPair { + return p.plugin.GetAvailableTeams(ctx, limit, query) } -// GetAvailableCustomers returns all unique customer ID-Name pairs from logs. -func (p *PluginLogManager) GetAvailableCustomers(ctx context.Context) []KeyPair { - return p.plugin.GetAvailableCustomers(ctx) +func (p *PluginLogManager) GetAvailableCustomers(ctx context.Context, limit int, query string) []KeyPair { + return p.plugin.GetAvailableCustomers(ctx, limit, query) } -// GetAvailableUsers returns all unique user IDs from logs. -func (p *PluginLogManager) GetAvailableUsers(ctx context.Context) []KeyPair { - return p.plugin.GetAvailableUsers(ctx) +func (p *PluginLogManager) GetAvailableUsers(ctx context.Context, limit int, query string) []KeyPair { + return p.plugin.GetAvailableUsers(ctx, limit, query) } -// GetAvailableBusinessUnits returns all unique business unit ID-Name pairs from logs. -func (p *PluginLogManager) GetAvailableBusinessUnits(ctx context.Context) []KeyPair { - return p.plugin.GetAvailableBusinessUnits(ctx) +func (p *PluginLogManager) GetAvailableBusinessUnits(ctx context.Context, limit int, query string) []KeyPair { + return p.plugin.GetAvailableBusinessUnits(ctx, limit, query) } // GetDimensionCostHistogram returns time-bucketed cost data grouped by the specified dimension. @@ -339,11 +329,11 @@ func (p *PluginLogManager) GetDimensionLatencyHistogram(ctx context.Context, fil return p.plugin.GetDimensionLatencyHistogram(ctx, *filters, bucketSizeSeconds, dimension) } -func (p *PluginLogManager) GetAvailableMetadataKeys(ctx context.Context) (map[string][]string, error) { +func (p *PluginLogManager) GetAvailableMetadataKeys(ctx context.Context, limit int, query string) (map[string][]string, error) { if p.plugin == nil || p.plugin.store == nil { return map[string][]string{}, nil } - return p.plugin.store.GetDistinctMetadataKeys(ctx) + return p.plugin.store.GetDistinctMetadataKeys(ctx, limit, query) } // DeleteLog deletes a log from the log store @@ -397,27 +387,25 @@ func (p *PluginLogManager) GetMCPToolLogStats(ctx context.Context, filters *logs } // GetAvailableToolNames returns all unique tool names from MCP tool logs -func (p *PluginLogManager) GetAvailableToolNames(ctx context.Context) ([]string, error) { +func (p *PluginLogManager) GetAvailableToolNames(ctx context.Context, limit int, query string) ([]string, error) { if p == nil || p.plugin == nil || p.plugin.store == nil { return []string{}, nil } - return p.plugin.store.GetAvailableToolNames(ctx) + return p.plugin.store.GetAvailableToolNames(ctx, limit, query) } -// GetAvailableServerLabels returns all unique server labels from MCP tool logs -func (p *PluginLogManager) GetAvailableServerLabels(ctx context.Context) ([]string, error) { +func (p *PluginLogManager) GetAvailableServerLabels(ctx context.Context, limit int, query string) ([]string, error) { if p == nil || p.plugin == nil || p.plugin.store == nil { return []string{}, nil } - return p.plugin.store.GetAvailableServerLabels(ctx) + return p.plugin.store.GetAvailableServerLabels(ctx, limit, query) } -// GetAvailableMCPVirtualKeys returns all unique virtual key ID-Name pairs from MCP tool logs -func (p *PluginLogManager) GetAvailableMCPVirtualKeys(ctx context.Context) []KeyPair { +func (p *PluginLogManager) GetAvailableMCPVirtualKeys(ctx context.Context, limit int, query string) []KeyPair { if p == nil || p.plugin == nil { return []KeyPair{} } - return p.plugin.GetAvailableMCPVirtualKeys(ctx) + return p.plugin.GetAvailableMCPVirtualKeys(ctx, limit, query) } // GetMCPHistogram returns time-bucketed MCP tool call volume diff --git a/transports/bifrost-http/handlers/logging.go b/transports/bifrost-http/handlers/logging.go index 85211919640..3c54bf442fa 100644 --- a/transports/bifrost-http/handlers/logging.go +++ b/transports/bifrost-http/handlers/logging.go @@ -45,13 +45,10 @@ const sessionLogPageLimit = 500 // longer TTL would just hide stale results. const filterDataCacheTTL = 30 * time.Second -// filterDataFanOutLimit caps how many parallel goroutines hit the DB for one -// filterdata request. The handler issues 12 independent SELECT DISTINCTs; -// firing all of them concurrently spikes both the Go heap (12 result sets held -// simultaneously) and the PG connection pool. 4 keeps it bounded while -// preserving most of the latency win over serial. const filterDataFanOutLimit = 4 +const defaultFilterDataLimit = 1000 + // Filter dimension names accepted by the ?dimensions= query param on // /api/logs/filterdata. Each maps to one DB call and one response field. const ( @@ -1087,21 +1084,25 @@ func (h *LoggingHandler) getModelRankings(ctx *fasthttp.RequestCtx) { func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { hideDeletedVirtualKeys := h.shouldHideDeletedVirtualKeysInFilters() - // Per-dimension subset support: clients pass ?dimensions=models,aliases to - // fetch only the slices they need. Cache key includes the canonical dim list - // so each subset is cached independently. dims := parseFilterDimensions(string(ctx.QueryArgs().Peek("dimensions")), allFilterDimensions) want := dimSet(dims) - cacheKey := fmt.Sprintf("hide_deleted=%v|dims=%s", hideDeletedVirtualKeys, strings.Join(dims, ",")) - entry, cached, ok := h.filterDataCache.load(cacheKey) - if ok { - SendJSON(ctx, cached) - return + query := strings.TrimSpace(string(ctx.QueryArgs().Peek("q"))) + useCache := query == "" + + var entry *filterDataCacheEntry + if useCache { + cacheKey := fmt.Sprintf("hide_deleted=%v|dims=%s", hideDeletedVirtualKeys, strings.Join(dims, ",")) + var cached map[string]interface{} + var ok bool + entry, cached, ok = h.filterDataCache.load(cacheKey) + if ok { + SendJSON(ctx, cached) + return + } } - // We hold entry.mu for single-flight; ensure it's released on every exit. released := false defer func() { - if !released { + if !released && entry != nil { h.filterDataCache.release(entry) } }() @@ -1129,7 +1130,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { if _, ok := want[filterDimModels]; ok { g.Go(func() error { - result := h.logManager.GetAvailableModels(gCtx) + result := h.logManager.GetAvailableModels(gCtx, defaultFilterDataLimit, query) mu.Lock() models = result mu.Unlock() @@ -1138,7 +1139,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimAliases]; ok { g.Go(func() error { - result := h.logManager.GetAvailableAliases(gCtx) + result := h.logManager.GetAvailableAliases(gCtx, defaultFilterDataLimit, query) mu.Lock() aliases = result mu.Unlock() @@ -1147,7 +1148,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimSelectedKeys]; ok { g.Go(func() error { - result := h.logManager.GetAvailableSelectedKeys(gCtx) + result := h.logManager.GetAvailableSelectedKeys(gCtx, defaultFilterDataLimit, query) mu.Lock() selectedKeys = result mu.Unlock() @@ -1156,7 +1157,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimVirtualKeys]; ok { g.Go(func() error { - result := h.logManager.GetAvailableVirtualKeys(gCtx) + result := h.logManager.GetAvailableVirtualKeys(gCtx, defaultFilterDataLimit, query) mu.Lock() virtualKeys = result mu.Unlock() @@ -1165,7 +1166,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimRoutingRules]; ok { g.Go(func() error { - result := h.logManager.GetAvailableRoutingRules(gCtx) + result := h.logManager.GetAvailableRoutingRules(gCtx, defaultFilterDataLimit, query) mu.Lock() routingRules = result mu.Unlock() @@ -1174,7 +1175,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimRoutingEngines]; ok { g.Go(func() error { - result := h.logManager.GetAvailableRoutingEngines(gCtx) + result := h.logManager.GetAvailableRoutingEngines(gCtx, defaultFilterDataLimit, query) mu.Lock() routingEngines = result mu.Unlock() @@ -1183,7 +1184,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimStopReasons]; ok { g.Go(func() error { - result := h.logManager.GetAvailableStopReasons(gCtx) + result := h.logManager.GetAvailableStopReasons(gCtx, defaultFilterDataLimit, query) mu.Lock() stopReasons = result mu.Unlock() @@ -1192,7 +1193,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimTeams]; ok { g.Go(func() error { - result := h.logManager.GetAvailableTeams(gCtx) + result := h.logManager.GetAvailableTeams(gCtx, defaultFilterDataLimit, query) mu.Lock() teams = result mu.Unlock() @@ -1201,7 +1202,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimCustomers]; ok { g.Go(func() error { - result := h.logManager.GetAvailableCustomers(gCtx) + result := h.logManager.GetAvailableCustomers(gCtx, defaultFilterDataLimit, query) mu.Lock() customers = result mu.Unlock() @@ -1210,7 +1211,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimUsers]; ok { g.Go(func() error { - result := h.logManager.GetAvailableUsers(gCtx) + result := h.logManager.GetAvailableUsers(gCtx, defaultFilterDataLimit, query) mu.Lock() users = result mu.Unlock() @@ -1219,7 +1220,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimBusinessUnits]; ok { g.Go(func() error { - result := h.logManager.GetAvailableBusinessUnits(gCtx) + result := h.logManager.GetAvailableBusinessUnits(gCtx, defaultFilterDataLimit, query) mu.Lock() businessUnits = result mu.Unlock() @@ -1228,7 +1229,7 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } if _, ok := want[filterDimMetadataKeys]; ok { g.Go(func() error { - result, err := h.logManager.GetAvailableMetadataKeys(gCtx) + result, err := h.logManager.GetAvailableMetadataKeys(gCtx, defaultFilterDataLimit, query) if err != nil { return err } @@ -1370,8 +1371,10 @@ func (h *LoggingHandler) getAvailableFilterData(ctx *fasthttp.RequestCtx) { } payload[filterDimMetadataKeys] = metadataKeys } - h.filterDataCache.store(entry, payload) - released = true + if useCache && entry != nil { + h.filterDataCache.store(entry, payload) + released = true + } SendJSON(ctx, payload) } @@ -1859,15 +1862,23 @@ func (h *LoggingHandler) getMCPLogsFilterData(ctx *fasthttp.RequestCtx) { dims := parseFilterDimensions(string(ctx.QueryArgs().Peek("dimensions")), allMCPFilterDimensions) want := dimSet(dims) - cacheKey := fmt.Sprintf("hide_deleted=%v|dims=%s", hideDeletedVirtualKeys, strings.Join(dims, ",")) - entry, cached, ok := h.mcpFilterDataCache.load(cacheKey) - if ok { - SendJSON(ctx, cached) - return + query := strings.TrimSpace(string(ctx.QueryArgs().Peek("q"))) + useCache := query == "" + + var entry *filterDataCacheEntry + if useCache { + cacheKey := fmt.Sprintf("hide_deleted=%v|dims=%s", hideDeletedVirtualKeys, strings.Join(dims, ",")) + var cached map[string]interface{} + var ok bool + entry, cached, ok = h.mcpFilterDataCache.load(cacheKey) + if ok { + SendJSON(ctx, cached) + return + } } released := false defer func() { - if !released { + if !released && entry != nil { h.mcpFilterDataCache.release(entry) } }() @@ -1875,7 +1886,7 @@ func (h *LoggingHandler) getMCPLogsFilterData(ctx *fasthttp.RequestCtx) { var toolNames []string if _, ok := want[mcpFilterDimToolNames]; ok { var err error - toolNames, err = h.logManager.GetAvailableToolNames(ctx) + toolNames, err = h.logManager.GetAvailableToolNames(ctx, defaultFilterDataLimit, query) if err != nil { logger.Error("failed to get available tool names: %v", err) SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("Failed to get available tool names: %v", err)) @@ -1886,7 +1897,7 @@ func (h *LoggingHandler) getMCPLogsFilterData(ctx *fasthttp.RequestCtx) { var serverLabels []string if _, ok := want[mcpFilterDimServerLabels]; ok { var err error - serverLabels, err = h.logManager.GetAvailableServerLabels(ctx) + serverLabels, err = h.logManager.GetAvailableServerLabels(ctx, defaultFilterDataLimit, query) if err != nil { logger.Error("failed to get available server labels: %v", err) SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("Failed to get available server labels: %v", err)) @@ -1896,7 +1907,7 @@ func (h *LoggingHandler) getMCPLogsFilterData(ctx *fasthttp.RequestCtx) { var virtualKeysArray []tables.TableVirtualKey if _, ok := want[mcpFilterDimVirtualKeys]; ok { - virtualKeys := h.logManager.GetAvailableMCPVirtualKeys(ctx) + virtualKeys := h.logManager.GetAvailableMCPVirtualKeys(ctx, defaultFilterDataLimit, query) virtualKeyIDs := make([]string, len(virtualKeys)) for i, key := range virtualKeys { @@ -1936,8 +1947,10 @@ func (h *LoggingHandler) getMCPLogsFilterData(ctx *fasthttp.RequestCtx) { if _, ok := want[mcpFilterDimVirtualKeys]; ok { payload[mcpFilterDimVirtualKeys] = virtualKeysArray } - h.mcpFilterDataCache.store(entry, payload) - released = true + if useCache && entry != nil { + h.mcpFilterDataCache.store(entry, payload) + released = true + } SendJSON(ctx, payload) }