-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathfeature_flag_errors.go
More file actions
169 lines (140 loc) · 5.09 KB
/
feature_flag_errors.go
File metadata and controls
169 lines (140 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package posthog
import (
"context"
"errors"
"fmt"
"net"
"os"
"strings"
)
// ErrFlagNotFound is returned when a feature flag does not exist or is disabled.
// Use errors.Is(err, ErrFlagNotFound) to check for this error.
var ErrFlagNotFound = errors.New("feature flag not found")
// Feature flag error type constants for the $feature_flag_error property.
// These values are sent in analytics events to track flag evaluation failures.
// They should not be changed without considering impact on existing dashboards
// and queries that filter on these values.
const (
// FeatureFlagErrorErrorsWhileComputing indicates the server returned errorsWhileComputingFlags=true
FeatureFlagErrorErrorsWhileComputing = "errors_while_computing_flags"
// FeatureFlagErrorFlagMissing indicates the requested flag was not in the API response
FeatureFlagErrorFlagMissing = "flag_missing"
// FeatureFlagErrorEvaluationFailed indicates the flag was present but failed to evaluate due to a transient server error
FeatureFlagErrorEvaluationFailed = "evaluation_failed"
// FeatureFlagErrorQuotaLimited indicates rate/quota limit was exceeded
FeatureFlagErrorQuotaLimited = "quota_limited"
// FeatureFlagErrorTimeout indicates request timed out
FeatureFlagErrorTimeout = "timeout"
// FeatureFlagErrorConnectionError indicates network connectivity issue
FeatureFlagErrorConnectionError = "connection_error"
// FeatureFlagErrorUnknownError indicates an unexpected error occurred
FeatureFlagErrorUnknownError = "unknown_error"
// FeatureFlagErrorAPIErrorPrefix is the prefix for API errors with status codes
FeatureFlagErrorAPIErrorPrefix = "api_error_"
)
// APIError represents an HTTP API error with a status code.
// This allows classifyError to generate api_error_{status} strings.
type APIError struct {
StatusCode int
Message string
}
func (e *APIError) Error() string {
return e.Message
}
// NewAPIError creates a new APIError with the given status code.
func NewAPIError(statusCode int, message string) *APIError {
return &APIError{
StatusCode: statusCode,
Message: message,
}
}
// featureFlagEvaluationResult holds internal evaluation context
// along with any error information that occurred during evaluation.
type featureFlagEvaluationResult struct {
Value interface{}
Err error
ErrorsWhileComputingFlags bool
QuotaLimited bool
FlagMissing bool
FlagFailed bool
RequestID *string
EvaluatedAt *int64
FlagDetail *FlagDetail
}
// classifyError determines the error type string for a given error.
// Returns api_error_{status}, timeout, connection_error, or unknown_error.
//
// Classification priority:
// 1. API errors (*APIError) - returns api_error_{status}
// 2. Sentinel errors (context.DeadlineExceeded, context.Canceled)
// 3. Interface checks (net.Error, os.IsTimeout)
// 4. Concrete types (*net.DNSError, *net.OpError)
// 5. Default to unknown_error (acceptable for analytics)
func classifyError(err error) string {
if err == nil {
return ""
}
// Check API errors first (application-level errors with status codes)
var apiErr *APIError
if errors.As(err, &apiErr) {
return fmt.Sprintf("%s%d", FeatureFlagErrorAPIErrorPrefix, apiErr.StatusCode)
}
// Check sentinel errors (works with wrapped errors)
if errors.Is(err, context.DeadlineExceeded) {
return FeatureFlagErrorTimeout
}
if errors.Is(err, context.Canceled) {
return FeatureFlagErrorTimeout
}
// Check os.IsTimeout (handles various timeout wrappers)
if os.IsTimeout(err) {
return FeatureFlagErrorTimeout
}
// Check net.Error interface
var netErr net.Error
if errors.As(err, &netErr) {
if netErr.Timeout() {
return FeatureFlagErrorTimeout
}
return FeatureFlagErrorConnectionError
}
// Check specific network error types
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
return FeatureFlagErrorConnectionError
}
var opErr *net.OpError
if errors.As(err, &opErr) {
return FeatureFlagErrorConnectionError
}
// Default to unknown - this is acceptable for analytics.
// We prefer reliable classification over broad but fragile string matching.
return FeatureFlagErrorUnknownError
}
// GetErrorString returns the error string for the $feature_flag_error property.
// Returns empty string if there are no errors.
// Multiple errors are joined with commas.
func (r *featureFlagEvaluationResult) GetErrorString() string {
var errorStrings []string
// Classify request error
if r.Err != nil {
errorStrings = append(errorStrings, classifyError(r.Err))
}
// Server had errors computing flags
if r.ErrorsWhileComputingFlags {
errorStrings = append(errorStrings, FeatureFlagErrorErrorsWhileComputing)
}
// Quota limited
if r.QuotaLimited {
errorStrings = append(errorStrings, FeatureFlagErrorQuotaLimited)
}
// Flag was not in the response
if r.FlagMissing {
errorStrings = append(errorStrings, FeatureFlagErrorFlagMissing)
}
// Flag was present but failed to evaluate
if r.FlagFailed {
errorStrings = append(errorStrings, FeatureFlagErrorEvaluationFailed)
}
return strings.Join(errorStrings, ",")
}