Files
signoz/pkg/modules/tracefunnel/query.go
Pranjul Kalsi bdce97a727 fix: replace fmt.Errorf with signoz/pkg/errors and update golangci-li… (#9373)
This PR fulfills the requirements of #9069 by:

- Adding a golangci-lint directive (forbidigo) to disallow all fmt.Errorf usages.
- Replacing existing fmt.Errorf instances with structured errors from github.com/SigNoz/signoz/pkg/errors for consistent error classification and lint compliance.
- Verified lint and build integrity.
2025-10-27 16:30:18 +05:30

329 lines
8.4 KiB
Go

package tracefunnel
import (
"strings"
"github.com/SigNoz/signoz/pkg/errors"
tracev4 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v4"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
)
// sanitizeClause adds AND prefix to non-empty clauses if not already present
func sanitizeClause(clause string) string {
if clause == "" {
return ""
}
// Check if clause already starts with AND
if strings.HasPrefix(strings.TrimSpace(clause), "AND") {
return clause
}
return "AND " + clause
}
func ValidateTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange) (*v3.ClickHouseQuery, error) {
funnelSteps := funnel.Steps
// Build step data for the dynamic query builder
steps := make([]struct {
ServiceName string
SpanName string
ContainsError int
Clause string
}, len(funnelSteps))
for i, step := range funnelSteps {
// Build filter clause
clause, err := tracev4.BuildTracesFilterQuery(step.Filters, false)
if err != nil {
return nil, err
}
steps[i] = struct {
ServiceName string
SpanName string
ContainsError int
Clause string
}{
ServiceName: step.ServiceName,
SpanName: step.SpanName,
ContainsError: 0,
Clause: sanitizeClause(clause),
}
if step.HasErrors {
steps[i].ContainsError = 1
}
}
query := BuildFunnelValidationQuery(steps, timeRange.StartTime, timeRange.EndTime)
return &v3.ClickHouseQuery{
Query: query,
}, nil
}
func GetFunnelAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange) (*v3.ClickHouseQuery, error) {
funnelSteps := funnel.Steps
// Build step data for the dynamic query builder
steps := make([]struct {
ServiceName string
SpanName string
ContainsError int
LatencyPointer string
Clause string
}, len(funnelSteps))
for i, step := range funnelSteps {
// Build filter clause
clause, err := tracev4.BuildTracesFilterQuery(step.Filters, false)
if err != nil {
return nil, err
}
latencyPointer := step.LatencyPointer
if latencyPointer == "" {
latencyPointer = "start"
}
steps[i] = struct {
ServiceName string
SpanName string
ContainsError int
LatencyPointer string
Clause string
}{
ServiceName: step.ServiceName,
SpanName: step.SpanName,
ContainsError: 0,
LatencyPointer: latencyPointer,
Clause: sanitizeClause(clause),
}
if step.HasErrors {
steps[i].ContainsError = 1
}
}
query := BuildFunnelOverviewQuery(steps, timeRange.StartTime, timeRange.EndTime)
return &v3.ClickHouseQuery{Query: query}, nil
}
func GetFunnelStepAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange, stepStart, stepEnd int64) (*v3.ClickHouseQuery, error) {
if stepStart == stepEnd {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "step start and end cannot be the same for /step/overview")
}
funnelSteps := funnel.Steps
// Build step data for the dynamic query builder
steps := make([]struct {
ServiceName string
SpanName string
ContainsError int
LatencyPointer string
LatencyType string
Clause string
}, len(funnelSteps))
for i, step := range funnelSteps {
// Build filter clause
clause, err := tracev4.BuildTracesFilterQuery(step.Filters, false)
if err != nil {
return nil, err
}
latencyPointer := step.LatencyPointer
if latencyPointer == "" {
latencyPointer = "start"
}
steps[i] = struct {
ServiceName string
SpanName string
ContainsError int
LatencyPointer string
LatencyType string
Clause string
}{
ServiceName: step.ServiceName,
SpanName: step.SpanName,
ContainsError: 0,
LatencyPointer: latencyPointer,
LatencyType: step.LatencyType,
Clause: sanitizeClause(clause),
}
if step.HasErrors {
steps[i].ContainsError = 1
}
}
query := BuildFunnelStepOverviewQuery(steps, timeRange.StartTime, timeRange.EndTime, stepStart, stepEnd)
return &v3.ClickHouseQuery{Query: query}, nil
}
func GetStepAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange) (*v3.ClickHouseQuery, error) {
funnelSteps := funnel.Steps
// Build step data for the dynamic query builder
steps := make([]struct {
ServiceName string
SpanName string
ContainsError int
Clause string
}, len(funnelSteps))
for i, step := range funnelSteps {
// Build filter clause
clause, err := tracev4.BuildTracesFilterQuery(step.Filters, false)
if err != nil {
return nil, err
}
steps[i] = struct {
ServiceName string
SpanName string
ContainsError int
Clause string
}{
ServiceName: step.ServiceName,
SpanName: step.SpanName,
ContainsError: 0,
Clause: sanitizeClause(clause),
}
if step.HasErrors {
steps[i].ContainsError = 1
}
}
query := BuildFunnelCountQuery(steps, timeRange.StartTime, timeRange.EndTime)
return &v3.ClickHouseQuery{
Query: query,
}, nil
}
func GetSlowestTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange, stepStart, stepEnd int64) (*v3.ClickHouseQuery, error) {
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
stepStartOrder := 0
stepEndOrder := 1
if stepStart != stepEnd {
stepStartOrder = int(stepStart) - 1
stepEndOrder = int(stepEnd) - 1
if funnelSteps[stepStartOrder].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[stepEndOrder].HasErrors {
containsErrorT2 = 1
}
}
// Build filter clauses for the steps
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepStartOrder].Filters, false)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepEndOrder].Filters, false)
if err != nil {
return nil, err
}
// Sanitize clauses
clauseStep1 = sanitizeClause(clauseStep1)
clauseStep2 = sanitizeClause(clauseStep2)
// Get latency pointers with defaults
latencyPointerT1 := funnelSteps[stepStartOrder].LatencyPointer
if latencyPointerT1 == "" {
latencyPointerT1 = "start"
}
latencyPointerT2 := funnelSteps[stepEndOrder].LatencyPointer
if latencyPointerT2 == "" {
latencyPointerT2 = "start"
}
query := BuildFunnelTopSlowTracesQuery(
containsErrorT1,
containsErrorT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[stepStartOrder].ServiceName,
funnelSteps[stepStartOrder].SpanName,
funnelSteps[stepEndOrder].ServiceName,
funnelSteps[stepEndOrder].SpanName,
clauseStep1,
clauseStep2,
latencyPointerT1,
latencyPointerT2,
)
return &v3.ClickHouseQuery{Query: query}, nil
}
// TODO: Showing traces with error which are slow makes little sense as a product. We should show the error spans directly in the funnel chart. Rather showing traces which has drop between steps will be more relevant
func GetErroredTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange, stepStart, stepEnd int64) (*v3.ClickHouseQuery, error) {
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
stepStartOrder := 0
stepEndOrder := 1
if stepStart != stepEnd {
stepStartOrder = int(stepStart) - 1
stepEndOrder = int(stepEnd) - 1
if funnelSteps[stepStartOrder].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[stepEndOrder].HasErrors {
containsErrorT2 = 1
}
}
// Build filter clauses for the steps
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepStartOrder].Filters, false)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepEndOrder].Filters, false)
if err != nil {
return nil, err
}
// Sanitize clauses
clauseStep1 = sanitizeClause(clauseStep1)
clauseStep2 = sanitizeClause(clauseStep2)
// Get latency pointers with defaults
latencyPointerT1 := funnelSteps[stepStartOrder].LatencyPointer
if latencyPointerT1 == "" {
latencyPointerT1 = "start"
}
latencyPointerT2 := funnelSteps[stepEndOrder].LatencyPointer
if latencyPointerT2 == "" {
latencyPointerT2 = "start"
}
query := BuildFunnelTopSlowErrorTracesQuery(
containsErrorT1,
containsErrorT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[stepStartOrder].ServiceName,
funnelSteps[stepStartOrder].SpanName,
funnelSteps[stepEndOrder].ServiceName,
funnelSteps[stepEndOrder].SpanName,
clauseStep1,
clauseStep2,
latencyPointerT1,
latencyPointerT2,
)
return &v3.ClickHouseQuery{Query: query}, nil
}