Compare commits

...

2 Commits

Author SHA1 Message Date
Tushar Vats
c200693b50 feat(querier): expose ValidateRequest and log invalid in-process requests
Handlers validate through the querier so its limit stays private.
In-process callers only warn for now; QueryRange still rejects bad
time windows.
2026-11-03 17:22:23 +05:30
Tushar Vats
0d965d63c6 feat(querier): make the future end-time limit configurable
Handlers have no querier config, so the check moves from Validate into
QueryRange and QueryRangePreview; in-process callers get it too.
2026-11-03 16:44:22 +05:30
32 changed files with 212 additions and 42 deletions

View File

@@ -178,6 +178,9 @@ querier:
# range with padding to include slightly delayed log exports. Logs only; set
# to 0 to disable.
log_trace_id_window_padding: 5m
# How far past now a query's end time may be, tolerating client clock skew.
# Requests ending later are rejected as malformed.
max_end_time_ahead: 24h
schemametadatastore:
# How long found schema metadata stays cached; 0 caches it for the life of the process.
cache_ttl: 0s

View File

@@ -5,6 +5,7 @@ import (
"log/slog"
"math"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -75,36 +76,54 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
})
// TODO(srikanthccv): parallelize this?
p.logger.InfoContext(ctx, "fetching results for current period", slog.Any("anomaly_current_period_query", params.CurrentPeriodQuery))
if err := p.querier.ValidateRequest(&params.CurrentPeriodQuery); err != nil {
p.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
currentPeriodResults, err := p.querier.QueryRange(ctx, orgID, &params.CurrentPeriodQuery)
if err != nil {
return nil, err
}
p.logger.InfoContext(ctx, "fetching results for past period", slog.Any("anomaly_past_period_query", params.PastPeriodQuery))
if err := p.querier.ValidateRequest(&params.PastPeriodQuery); err != nil {
p.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
pastPeriodResults, err := p.querier.QueryRange(ctx, orgID, &params.PastPeriodQuery)
if err != nil {
return nil, err
}
p.logger.InfoContext(ctx, "fetching results for current season", slog.Any("anomaly_current_season_query", params.CurrentSeasonQuery))
if err := p.querier.ValidateRequest(&params.CurrentSeasonQuery); err != nil {
p.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
currentSeasonResults, err := p.querier.QueryRange(ctx, orgID, &params.CurrentSeasonQuery)
if err != nil {
return nil, err
}
p.logger.InfoContext(ctx, "fetching results for past season", slog.Any("anomaly_past_season_query", params.PastSeasonQuery))
if err := p.querier.ValidateRequest(&params.PastSeasonQuery); err != nil {
p.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
pastSeasonResults, err := p.querier.QueryRange(ctx, orgID, &params.PastSeasonQuery)
if err != nil {
return nil, err
}
p.logger.InfoContext(ctx, "fetching results for past 2 season", slog.Any("anomaly_past_2season_query", params.Past2SeasonQuery))
if err := p.querier.ValidateRequest(&params.Past2SeasonQuery); err != nil {
p.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
past2SeasonResults, err := p.querier.QueryRange(ctx, orgID, &params.Past2SeasonQuery)
if err != nil {
return nil, err
}
p.logger.InfoContext(ctx, "fetching results for past 3 season", slog.Any("anomaly_past_3season_query", params.Past3SeasonQuery))
if err := p.querier.ValidateRequest(&params.Past3SeasonQuery); err != nil {
p.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
past3SeasonResults, err := p.querier.QueryRange(ctx, orgID, &params.Past3SeasonQuery)
if err != nil {
return nil, err

View File

@@ -153,6 +153,9 @@ func (module *module) GetPublicWidgetQueryRangeV2(ctx context.Context, id valuer
return nil, err
}
if err := module.querier.ValidateRequest(query); err != nil {
module.settings.Logger().WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
return module.querier.QueryRange(ctx, dashboard.OrgID, query)
}

View File

@@ -53,7 +53,7 @@ func (h *handler) QueryRange(rw http.ResponseWriter, req *http.Request) {
return
}
if err := queryRangeRequest.Validate(); err != nil {
if err := h.querier.ValidateRequest(&queryRangeRequest); err != nil {
render.Error(rw, err)
return
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -191,6 +192,9 @@ func (m *module) getTopClusterGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -7,6 +7,7 @@ import (
"strings"
"github.com/SigNoz/signoz/pkg/clickhousesql"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetryschema/metricstelemetryschema"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
@@ -232,6 +233,9 @@ func (m *module) getTopContainerGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -182,6 +183,9 @@ func (m *module) getTopDaemonSetGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -174,6 +175,9 @@ func (m *module) getTopDeploymentGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -7,6 +7,7 @@ import (
"strings"
"github.com/SigNoz/signoz/pkg/clickhousesql"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetryschema/metricstelemetryschema"
@@ -321,6 +322,9 @@ func (m *module) getTopHostGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -182,6 +183,9 @@ func (m *module) getTopJobGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -5,6 +5,7 @@ import (
"log/slog"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/modules/inframonitoring"
@@ -222,6 +223,9 @@ func (m *module) ListHosts(ctx context.Context, orgID valuer.UUID, req *inframon
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -325,6 +329,9 @@ func (m *module) ListPods(ctx context.Context, orgID valuer.UUID, req *inframoni
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -436,6 +443,9 @@ func (m *module) ListContainers(ctx context.Context, orgID valuer.UUID, req *inf
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -552,6 +562,9 @@ func (m *module) ListNodes(ctx context.Context, orgID valuer.UUID, req *inframon
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -666,6 +679,9 @@ func (m *module) ListNamespaces(ctx context.Context, orgID valuer.UUID, req *inf
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -778,6 +794,9 @@ func (m *module) ListClusters(ctx context.Context, orgID valuer.UUID, req *infra
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -877,6 +896,9 @@ func (m *module) ListVolumes(ctx context.Context, orgID valuer.UUID, req *infram
}
fullQueryReq := buildFullQueryRequest(req.Start, req.End, filterExpr, req.GroupBy, pageGroups, m.newVolumesTableListQuery())
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
queryResp, err := m.querier.QueryRange(ctx, orgID, fullQueryReq)
if err != nil {
return nil, err
@@ -974,6 +996,9 @@ func (m *module) ListDeployments(ctx context.Context, orgID valuer.UUID, req *in
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -1083,6 +1108,9 @@ func (m *module) ListStatefulSets(ctx context.Context, orgID valuer.UUID, req *i
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -1192,6 +1220,9 @@ func (m *module) ListJobs(ctx context.Context, orgID valuer.UUID, req *inframoni
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})
@@ -1301,6 +1332,9 @@ func (m *module) ListDaemonSets(ctx context.Context, orgID valuer.UUID, req *inf
g.Go(func() error {
var err error
if err := m.querier.ValidateRequest(fullQueryReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
queryResp, err = m.querier.QueryRange(gCtx, orgID, fullQueryReq)
return err
})

View File

@@ -4,6 +4,7 @@ import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -157,6 +158,9 @@ func (m *module) getTopNamespaceGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -6,6 +6,7 @@ import (
"slices"
"github.com/SigNoz/signoz/pkg/clickhousesql"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetryschema/metricstelemetryschema"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
@@ -199,6 +200,9 @@ func (m *module) getTopNodeGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/SigNoz/signoz/pkg/clickhousesql"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/querybuilder"
"github.com/SigNoz/signoz/pkg/telemetryschema/metricstelemetryschema"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
@@ -239,6 +240,9 @@ func (m *module) getTopPodGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -174,6 +175,9 @@ func (m *module) getTopStatefulSetGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"context"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/inframonitoringtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -133,6 +134,9 @@ func (m *module) getTopVolumeGroupsAndMetadata(
}
g.Go(func() error {
if err := m.querier.ValidateRequest(topReq); err != nil {
m.logger.WarnContext(gCtx, "invalid query range request", errors.Attr(err))
}
resp, err := m.querier.QueryRange(gCtx, orgID, topReq)
if err != nil {
return err

View File

@@ -4,9 +4,11 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/llmpricingrule"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
@@ -24,10 +26,12 @@ const unmappedModelsLookback = time.Hour
type module struct {
store llmpricingruletypes.Store
querier querier.Querier
logger *slog.Logger
}
func NewModule(store llmpricingruletypes.Store, querier querier.Querier) llmpricingrule.Module {
return &module{store: store, querier: querier}
func NewModule(store llmpricingruletypes.Store, querier querier.Querier, settings factory.ProviderSettings) llmpricingrule.Module {
logger := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/llmpricingrule/impllmpricingrule").Logger()
return &module{store: store, querier: querier, logger: logger}
}
func (module *module) List(ctx context.Context, orgID valuer.UUID, offset, limit int, search string, isOverride *bool) ([]*llmpricingruletypes.LLMPricingRule, int, error) {
@@ -201,6 +205,9 @@ func (module *module) discoverModels(ctx context.Context, orgID valuer.UUID) ([]
},
}
if err := module.querier.ValidateRequest(req); err != nil {
module.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
resp, err := module.querier.QueryRange(ctx, orgID, req)
if err != nil {
return nil, err

View File

@@ -2,8 +2,10 @@ package implrawdataexport
import (
"context"
"log/slog"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/rawdataexport"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
@@ -14,11 +16,13 @@ import (
type Module struct {
querier querier.Querier
logger *slog.Logger
}
func NewModule(querier querier.Querier) rawdataexport.Module {
func NewModule(querier querier.Querier, settings factory.ProviderSettings) rawdataexport.Module {
return &Module{
querier: querier,
logger: factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/rawdataexport/implrawdataexport").Logger(),
}
}
@@ -28,6 +32,10 @@ func (m *Module) ExportRawData(ctx context.Context, orgID valuer.UUID, rangeRequ
instrumentationtypes.CodeFunctionName: "ExportRawData",
})
if err := m.querier.ValidateRequest(rangeRequest, qbtypes.WithSkipLimitOffsetValidation()); err != nil {
m.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
traceOperatorQueryIndex := rangeRequest.TraceOperatorQueryIndex()
queries := rangeRequest.CompositeQuery.Queries

View File

@@ -3,12 +3,14 @@ package implservices
import (
"context"
"fmt"
"log/slog"
"time"
"strconv"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/services"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/telemetryschema/tracestelemetryschema"
@@ -24,13 +26,15 @@ import (
type module struct {
Querier querier.Querier
TelemetryStore telemetrystore.TelemetryStore
logger *slog.Logger
}
// NewModule constructs the services module with the provided querier dependency.
func NewModule(q querier.Querier, ts telemetrystore.TelemetryStore) services.Module {
func NewModule(q querier.Querier, ts telemetrystore.TelemetryStore, settings factory.ProviderSettings) services.Module {
return &module{
Querier: q,
TelemetryStore: ts,
logger: factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/services/implservices").Logger(),
}
}
@@ -220,6 +224,9 @@ func (m *module) buildQueryRangeRequest(req *servicetypesv1.Request) (*qbtypes.Q
// executeQuery calls the underlying Querier with the provided request.
func (m *module) executeQuery(ctx context.Context, orgUUID valuer.UUID, qr *qbtypes.QueryRangeRequest) (*qbtypes.QueryRangeResponse, error) {
if err := m.Querier.ValidateRequest(qr); err != nil {
m.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
return m.Querier.QueryRange(ctx, orgUUID, qr)
}

View File

@@ -38,7 +38,7 @@ func (m *module) GetSpanPercentile(ctx context.Context, orgID valuer.UUID, req *
return nil, err
}
if err := queryRangeRequest.Validate(); err != nil {
if err := m.querier.ValidateRequest(queryRangeRequest); err != nil {
return nil, err
}

View File

@@ -59,7 +59,7 @@ func (handler *handler) QueryRange(rw http.ResponseWriter, req *http.Request) {
}
// Validate the query request
if err := queryRangeRequest.Validate(); err != nil {
if err := handler.querier.ValidateRequest(&queryRangeRequest); err != nil {
render.Error(rw, err)
return
}
@@ -188,7 +188,7 @@ func (handler *handler) QueryRawStream(rw http.ResponseWriter, req *http.Request
}
// Validate the query request
if err := queryRangeRequest.Validate(); err != nil {
if err := handler.querier.ValidateRequest(&queryRangeRequest); err != nil {
render.Error(rw, err)
return
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/statementbuilder"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
)
const DefaultMaxConcurrentQueries = 8
@@ -20,6 +21,8 @@ type Config struct {
MaxConcurrentQueries int `yaml:"max_concurrent_queries" mapstructure:"max_concurrent_queries"`
// LogTraceIDWindowPadding is the padding added to narrowed down timerange from trace summary to logs with trace_id filter.
LogTraceIDWindowPadding time.Duration `yaml:"log_trace_id_window_padding" mapstructure:"log_trace_id_window_padding"`
// MaxEndTimeAhead is how far past now a query's end time may be; it tolerates client clock skew.
MaxEndTimeAhead time.Duration `yaml:"max_end_time_ahead" mapstructure:"max_end_time_ahead"`
// Keys sit under querier.skip_resource_fingerprint.
statementbuilder.Config `mapstructure:",squash" yaml:",squash"`
@@ -37,6 +40,7 @@ func newConfig() factory.Config {
FluxInterval: 5 * time.Minute,
MaxConcurrentQueries: DefaultMaxConcurrentQueries,
LogTraceIDWindowPadding: 5 * time.Minute,
MaxEndTimeAhead: qbtypes.DefaultMaxEndTimeAhead,
Config: statementbuilder.NewConfig(),
}
}
@@ -55,6 +59,9 @@ func (c Config) Validate() error {
if c.LogTraceIDWindowPadding < 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "log_trace_id_window_padding must not be negative, got %v", c.LogTraceIDWindowPadding)
}
if c.MaxEndTimeAhead <= 0 {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "max_end_time_ahead must be positive, got %v", c.MaxEndTimeAhead)
}
// Embedded Validate is shadowed by this one; call it explicitly.
if err := c.Config.Validate(); err != nil {
return err

View File

@@ -23,7 +23,7 @@ func (q *querier) QueryRangePreview(
opts qbtypes.QueryRangePreviewOptions,
) (*qbtypes.QueryRangePreviewResponse, error) {
validationOpts, err := req.ValidateRequestScope()
validationOpts, err := req.ValidateRequestScope(q.validationOptions()...)
if err != nil {
return nil, err
}

View File

@@ -43,6 +43,8 @@ type Querier interface {
statsreporter.StatsCollector
// QueryRangePreview validates and renders the queries without executing them.
QueryRangePreview(ctx context.Context, orgID valuer.UUID, req *qbtypes.QueryRangeRequest, opts qbtypes.QueryRangePreviewOptions) (*qbtypes.QueryRangePreviewResponse, error)
// ValidateRequest checks req against the querier's preconditions; opts add to the querier's own.
ValidateRequest(req *qbtypes.QueryRangeRequest, opts ...qbtypes.ValidationOption) error
}
type querier struct {
@@ -62,6 +64,7 @@ type querier struct {
liveDataRefresh time.Duration
builderConfig builderConfig
maxConcurrentQueries int
config Config
}
var _ Querier = (*querier)(nil)
@@ -80,12 +83,14 @@ func New(
traceOperatorStmtBuilder qbtypes.TraceOperatorStatementBuilder,
bucketCache BucketCache,
flagger flagger.Flagger,
logTraceIDWindowPadding time.Duration,
maxConcurrentQueries int,
config Config,
) *querier {
querierSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/querier")
if maxConcurrentQueries <= 0 {
maxConcurrentQueries = DefaultMaxConcurrentQueries
if config.MaxConcurrentQueries <= 0 {
config.MaxConcurrentQueries = DefaultMaxConcurrentQueries
}
if config.MaxEndTimeAhead <= 0 {
config.MaxEndTimeAhead = qbtypes.DefaultMaxEndTimeAhead
}
return &querier{
logger: querierSettings.Logger(),
@@ -103,12 +108,21 @@ func New(
bucketCache: bucketCache,
liveDataRefresh: 5 * time.Second,
builderConfig: builderConfig{
logTraceIDWindowPaddingMS: uint64(logTraceIDWindowPadding.Milliseconds()),
logTraceIDWindowPaddingMS: uint64(config.LogTraceIDWindowPadding.Milliseconds()),
},
maxConcurrentQueries: maxConcurrentQueries,
maxConcurrentQueries: config.MaxConcurrentQueries,
config: config,
}
}
func (q *querier) ValidateRequest(req *qbtypes.QueryRangeRequest, opts ...qbtypes.ValidationOption) error {
return req.Validate(append(opts, q.validationOptions()...)...)
}
func (q *querier) validationOptions() []qbtypes.ValidationOption {
return []qbtypes.ValidationOption{qbtypes.WithMaxEndTimeAhead(q.config.MaxEndTimeAhead)}
}
func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtypes.QueryRangeRequest) (*qbtypes.QueryRangeResponse, error) {
// Callers that build the request in-process never pass through
@@ -116,6 +130,11 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
req.Start = qbtypes.ToMilliSecs(req.Start)
req.End = qbtypes.ToMilliSecs(req.End)
// In-process callers skip ValidateRequest; the time window still guards the querier.
if err := req.ValidateTimeRange(q.validationOptions()...); err != nil {
return nil, err
}
event := &qbtypes.QBEvent{
Version: "v5",
NumberOfQueries: len(req.CompositeQuery.Queries),

View File

@@ -57,8 +57,7 @@ func TestQueryRange_MetricTypeMissing(t *testing.T) {
nil, // traceOperatorStmtBuilder
nil, // bucketCache
flaggertest.New(t), // flagger
0, // logTraceIDWindowPadding
0, // maxConcurrentQueries
Config{},
)
req := &qbtypes.QueryRangeRequest{
@@ -130,8 +129,7 @@ func TestQueryRange_MetricTypeFromStore(t *testing.T) {
nil, // traceOperatorStmtBuilder
nil, // bucketCache
flaggertest.New(t), // flagger
0, // logTraceIDWindowPadding
0, // maxConcurrentQueries
Config{},
)
req := &qbtypes.QueryRangeRequest{

View File

@@ -37,6 +37,10 @@ func (m *MockQuerier) QueryRawStream(ctx context.Context, orgID valuer.UUID, req
}
}
func (m *MockQuerier) ValidateRequest(req *qbtypes.QueryRangeRequest, opts ...qbtypes.ValidationOption) error {
return req.Validate(opts...)
}
func (m *MockQuerier) QueryRangePreview(ctx context.Context, orgID valuer.UUID, req *qbtypes.QueryRangeRequest, opts qbtypes.QueryRangePreviewOptions) (*qbtypes.QueryRangePreviewResponse, error) {
if m.QueryRangePreviewFunc != nil {
return m.QueryRangePreviewFunc(ctx, orgID, req, opts)

View File

@@ -50,8 +50,7 @@ func NewFactory(
traceOperatorStmtBuilder,
bucketCache,
flagger,
cfg.LogTraceIDWindowPadding,
cfg.MaxConcurrentQueries,
cfg,
), nil
},
)

View File

@@ -3718,6 +3718,9 @@ func (aH *APIHandler) getDomainList(w http.ResponseWriter, r *http.Request) {
instrumentationtypes.CodeFunctionName: "getDomainList",
})
// Execute the query using the v5 querier
if err := aH.Signoz.Querier.ValidateRequest(queryRangeRequest); err != nil {
aH.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
result, err := aH.Signoz.Querier.QueryRange(ctx, orgID, queryRangeRequest)
if err != nil {
aH.logger.ErrorContext(r.Context(), "query execution failed", errors.Attr(err))
@@ -3778,6 +3781,9 @@ func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
instrumentationtypes.CodeFunctionName: "getDomainInfo",
})
// Execute the query using the v5 querier
if err := aH.Signoz.Querier.ValidateRequest(queryRangeRequest); err != nil {
aH.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
result, err := aH.Signoz.Querier.QueryRange(ctx, orgID, queryRangeRequest)
if err != nil {
aH.logger.ErrorContext(r.Context(), "query execution failed", errors.Attr(err))

View File

@@ -50,8 +50,7 @@ func prepareQuerierForMetrics(t *testing.T, telemetryStore telemetrystore.Teleme
nil, // traceOperatorStmtBuilder
nil, // bucketCache
fl,
0,
0, // maxConcurrentQueries (0 means default)
querier.Config{},
), metadataStore
}
@@ -85,8 +84,7 @@ func prepareQuerierForLogs(t *testing.T, telemetryStore telemetrystore.Telemetry
nil, // traceOperatorStmtBuilder
nil, // bucketCache
fl,
5*time.Minute, // logTraceIDWindowPadding
0, // maxConcurrentQueries (0 means default)
querier.Config{LogTraceIDWindowPadding: 5 * time.Minute},
)
}
@@ -121,8 +119,7 @@ func prepareQuerierForTraces(t *testing.T, telemetryStore telemetrystore.Telemet
nil, // traceOperatorStmtBuilder
nil, // bucketCache
fl,
0,
0, // maxConcurrentQueries (0 means default)
querier.Config{},
)
}
@@ -157,7 +154,6 @@ func prepareQuerierForAITraces(t *testing.T, telemetryStore telemetrystore.Telem
nil, // traceOperatorStmtBuilder
nil, // bucketCache
fl,
0,
0, // maxConcurrentQueries (0 means default)
querier.Config{},
)
}

View File

@@ -138,6 +138,9 @@ func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, orgID valuer.UUID,
instrumentationtypes.CodeFunctionName: "buildAndRunQuery",
})
if err := r.querier.ValidateRequest(params); err != nil {
r.logger.WarnContext(ctx, "invalid query range request", errors.Attr(err))
}
v5Result, err := r.querier.QueryRange(ctx, orgID, params)
if err != nil {
return nil, err

View File

@@ -148,11 +148,11 @@ func NewModules(
RetentionGetter: retentionGetter,
QuickFilter: quickfilter,
TraceFunnel: impltracefunnel.NewModule(impltracefunnel.NewStore(sqlstore)),
RawDataExport: implrawdataexport.NewModule(querier),
RawDataExport: implrawdataexport.NewModule(querier, providerSettings),
AuthDomain: authDomainModule,
Session: implsession.NewModule(providerSettings, authNs, userSetter, userGetter, authDomainModule, tokenizer, orgGetter, authz, config.Global),
SpanPercentile: implspanpercentile.NewModule(querier, providerSettings),
Services: implservices.NewModule(querier, telemetryStore),
Services: implservices.NewModule(querier, telemetryStore, providerSettings),
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, fl, providerSettings, config.MetricsExplorer),
MetricReductionRule: metricReductionRule,
InfraMonitoring: implinframonitoring.NewModule(telemetryStore, telemetryMetadataStore, querier, fl, providerSettings, config.InfraMonitoring),
@@ -164,7 +164,7 @@ func NewModules(
CloudIntegration: cloudIntegrationModule,
TraceDetail: impltracedetail.NewModule(impltracedetail.NewTraceStore(telemetryStore, telemetryMetadataStore, schemaMetadataStore, fl), providerSettings, config.TraceDetail),
SpanMapper: spanMapper,
LLMPricingRule: impllmpricingrule.NewModule(impllmpricingrule.NewStore(sqlstore), querier),
LLMPricingRule: impllmpricingrule.NewModule(impllmpricingrule.NewStore(sqlstore), querier, providerSettings),
Tag: tagModule,
}
}

View File

@@ -12,9 +12,6 @@ import (
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
)
// maxEndTimeAhead tolerates client clock skew; anything further ahead is a malformed timestamp.
const maxEndTimeAhead = 24 * time.Hour
// getQueryIdentifier returns a friendly identifier for a query based on its type and name/content.
func getQueryIdentifier(envelope QueryEnvelope, index int) string {
name := envelope.GetQueryName()
@@ -74,6 +71,9 @@ const (
MaxNumBuckets = 512
)
// DefaultMaxEndTimeAhead tolerates client clock skew; an end time further ahead is a malformed timestamp.
const DefaultMaxEndTimeAhead = 24 * time.Hour
// ValidationOption is a functional option for configuring validation behaviour.
type ValidationOption func(*validationConfig)
@@ -86,6 +86,7 @@ type validationConfig struct {
skipGroupByValidation bool
withTimestampGroupByValidation bool
withReduceToValidation bool
maxEndTimeAhead time.Duration
}
func applyValidationOptions(opts []ValidationOption) validationConfig {
@@ -160,6 +161,13 @@ func WithReduceToValidation() ValidationOption {
}
}
// WithMaxEndTimeAhead sets how far past now a request's end time may be.
func WithMaxEndTimeAhead(d time.Duration) ValidationOption {
return func(cfg *validationConfig) {
cfg.maxEndTimeAhead = d
}
}
// Validate performs preliminary validation on QueryBuilderQuery.
func (q *QueryBuilderQuery[T]) Validate(opts ...ValidationOption) error {
cfg := applyValidationOptions(opts)
@@ -578,7 +586,7 @@ func (q *QueryBuilderQuery[T]) validateOrderByForAggregation() error {
// Validate validates the entire query range request.
func (r *QueryRangeRequest) Validate(opts ...ValidationOption) error {
if err := r.validateTimeRange(); err != nil {
if err := r.ValidateTimeRange(opts...); err != nil {
return err
}
@@ -630,15 +638,14 @@ func (r *QueryRangeRequest) Validate(opts ...ValidationOption) error {
// specs) and returns the request type's ValidationOptions. The dry-run path uses
// this so per-query errors can be attributed individually via QueryEnvelope.Validate
// instead of failing fast like Validate does.
func (r *QueryRangeRequest) ValidateRequestScope() ([]ValidationOption, error) {
if err := r.validateTimeRange(); err != nil {
func (r *QueryRangeRequest) ValidateRequestScope(opts ...ValidationOption) ([]ValidationOption, error) {
if err := r.ValidateTimeRange(opts...); err != nil {
return nil, err
}
var opts []ValidationOption
switch r.RequestType {
case RequestTypeRaw, RequestTypeRawStream, RequestTypeTrace, RequestTypeTimeSeries, RequestTypeScalar, RequestTypeHeatmap:
opts = GetValidationOptions(r.RequestType)
opts = append(opts, GetValidationOptions(r.RequestType)...)
default:
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "invalid request type: %s", r.RequestType).
WithAdditional("Valid request types are: raw, timeseries, scalar, heatmap")
@@ -699,7 +706,9 @@ func (e QueryEnvelope) Validate(opts ...ValidationOption) error {
return validateQueryEnvelope(e, opts...)
}
func (r *QueryRangeRequest) validateTimeRange() error {
// ValidateTimeRange checks only the request's time window.
func (r *QueryRangeRequest) ValidateTimeRange(opts ...ValidationOption) error {
cfg := applyValidationOptions(opts)
if r.RequestType == RequestTypeRawStream {
return nil
}
@@ -709,7 +718,11 @@ func (r *QueryRangeRequest) validateTimeRange() error {
if r.Start >= r.End {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "start time must be before end time")
}
if r.End > uint64(time.Now().Add(maxEndTimeAhead).UnixMilli()) {
maxAhead := cfg.maxEndTimeAhead
if maxAhead <= 0 {
maxAhead = DefaultMaxEndTimeAhead
}
if r.End > uint64(time.Now().Add(maxAhead).UnixMilli()) {
return errors.NewInvalidInputf(errors.CodeInvalidInput, "end time %s is too far in the future", time.UnixMilli(int64(r.End)).UTC().Format(time.RFC3339))
}
return nil