mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 04:09:01 +08:00
Feature flagging (#1674)
* feat: introduce feature flagging via env variables * refactor: enable sorting by default for users
This commit is contained in:
parent
36315fcf9c
commit
674883cd18
@ -10,6 +10,8 @@ import (
|
||||
|
||||
"sync"
|
||||
|
||||
baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
|
||||
validate "go.signoz.io/signoz/ee/query-service/integrations/signozio"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
@ -92,6 +94,8 @@ func (lm *Manager) SetActive(l *model.License) {
|
||||
|
||||
lm.activeLicense = l
|
||||
lm.activeFeatures = l.FeatureSet
|
||||
// set default features
|
||||
setDefaultFeatures(lm)
|
||||
if !lm.validatorRunning {
|
||||
// we want to make sure only one validator runs,
|
||||
// we already have lock() so good to go
|
||||
@ -101,7 +105,13 @@ func (lm *Manager) SetActive(l *model.License) {
|
||||
|
||||
}
|
||||
|
||||
// LoadActiveLicense loads the most recent active licenseex
|
||||
func setDefaultFeatures(lm *Manager) {
|
||||
for k, v := range baseconstants.DEFAULT_FEATURE_SET {
|
||||
lm.activeFeatures[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// LoadActiveLicense loads the most recent active license
|
||||
func (lm *Manager) LoadActiveLicense() error {
|
||||
var err error
|
||||
active, err := lm.repo.GetActiveLicense(context.Background())
|
||||
@ -111,7 +121,10 @@ func (lm *Manager) LoadActiveLicense() error {
|
||||
if active != nil {
|
||||
lm.SetActive(active)
|
||||
} else {
|
||||
zap.S().Info("No active license found.")
|
||||
zap.S().Info("No active license found, defaulting to basic plan")
|
||||
// if no active license is found, we default to basic(free) plan with all default features
|
||||
lm.activeFeatures = basemodel.BasicPlan
|
||||
setDefaultFeatures(lm)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -278,8 +291,11 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
|
||||
// CheckFeature will be internally used by backend routines
|
||||
// for feature gating
|
||||
func (lm *Manager) CheckFeature(featureKey string) error {
|
||||
if _, ok := lm.activeFeatures[featureKey]; ok {
|
||||
return nil
|
||||
if value, ok := lm.activeFeatures[featureKey]; ok {
|
||||
if value {
|
||||
return nil
|
||||
}
|
||||
return basemodel.ErrFeatureUnavailable{Key: featureKey}
|
||||
}
|
||||
return basemodel.ErrFeatureUnavailable{Key: featureKey}
|
||||
}
|
||||
|
@ -358,6 +358,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/api/v1/settings/ttl", ViewAccess(aH.getTTL)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/featureFlags", OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/getSpanFilters", ViewAccess(aH.getSpanFilters)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/getTagFilters", ViewAccess(aH.getTagFilters)).Methods(http.MethodPost)
|
||||
@ -1422,7 +1423,7 @@ func (aH *APIHandler) getSpanFilters(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (aH *APIHandler) getFilteredSpans(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
query, err := parseFilteredSpansRequest(r)
|
||||
query, err := parseFilteredSpansRequest(r, aH)
|
||||
if aH.HandleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
@ -1533,6 +1534,20 @@ func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
||||
aH.WriteJSON(w, r, map[string]string{"version": version, "ee": "N"})
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
featureSet := aH.FF().GetFeatureFlags()
|
||||
aH.Respond(w, featureSet)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) FF() interfaces.FeatureLookup {
|
||||
return aH.featureFlags
|
||||
}
|
||||
|
||||
func (aH *APIHandler) CheckFeature(f string) bool {
|
||||
err := aH.FF().CheckFeature(f)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// inviteUser is used to invite a user. It is used by an admin api.
|
||||
func (aH *APIHandler) inviteUser(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseInviteRequest(r)
|
||||
|
@ -255,7 +255,7 @@ func parseSpanFilterRequestBody(r *http.Request) (*model.SpanFilterParams, error
|
||||
return postData, nil
|
||||
}
|
||||
|
||||
func parseFilteredSpansRequest(r *http.Request) (*model.GetFilteredSpansParams, error) {
|
||||
func parseFilteredSpansRequest(r *http.Request, aH *APIHandler) (*model.GetFilteredSpansParams, error) {
|
||||
|
||||
var postData *model.GetFilteredSpansParams
|
||||
err := json.NewDecoder(r.Body).Decode(&postData)
|
||||
@ -277,6 +277,20 @@ func parseFilteredSpansRequest(r *http.Request) (*model.GetFilteredSpansParams,
|
||||
postData.Limit = 10
|
||||
}
|
||||
|
||||
if len(postData.Order) != 0 {
|
||||
if postData.Order != constants.Ascending && postData.Order != constants.Descending {
|
||||
return nil, errors.New("order param is not in correct format")
|
||||
}
|
||||
if postData.OrderParam != constants.Duration && postData.OrderParam != constants.Timestamp {
|
||||
return nil, errors.New("order param is not in correct format")
|
||||
}
|
||||
if postData.OrderParam == constants.Duration && !aH.CheckFeature(constants.DurationSort) {
|
||||
return nil, model.ErrFeatureUnavailable{Key: constants.DurationSort}
|
||||
} else if postData.OrderParam == constants.Timestamp && !aH.CheckFeature(constants.TimestampSort) {
|
||||
return nil, model.ErrFeatureUnavailable{Key: constants.TimestampSort}
|
||||
}
|
||||
}
|
||||
|
||||
return postData, nil
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||
"go.signoz.io/signoz/pkg/query-service/featureManager"
|
||||
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
||||
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
@ -77,6 +78,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
localDB.SetMaxOpenConns(10)
|
||||
|
||||
// initiate feature manager
|
||||
fm := featureManager.StartManager()
|
||||
|
||||
readerReady := make(chan bool)
|
||||
|
||||
var reader interfaces.Reader
|
||||
@ -98,9 +103,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
|
||||
telemetry.GetInstance().SetReader(reader)
|
||||
apiHandler, err := NewAPIHandler(APIHandlerOpts{
|
||||
Reader: reader,
|
||||
AppDao: dao.DB(),
|
||||
RuleManager: rm,
|
||||
Reader: reader,
|
||||
AppDao: dao.DB(),
|
||||
RuleManager: rm,
|
||||
FeatureFlags: fm,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -28,6 +28,9 @@ const TraceTTL = "traces"
|
||||
const MetricsTTL = "metrics"
|
||||
const LogsTTL = "logs"
|
||||
|
||||
const DurationSort = "DurationSort"
|
||||
const TimestampSort = "TimestampSort"
|
||||
|
||||
func GetAlertManagerApiPrefix() string {
|
||||
if os.Getenv("ALERTMANAGER_API_PREFIX") != "" {
|
||||
return os.Getenv("ALERTMANAGER_API_PREFIX")
|
||||
@ -40,6 +43,33 @@ var AmChannelApiPath = GetOrDefaultEnv("ALERTMANAGER_API_CHANNEL_PATH", "v1/rout
|
||||
|
||||
var RELATIONAL_DATASOURCE_PATH = GetOrDefaultEnv("SIGNOZ_LOCAL_DB_PATH", "/var/lib/signoz/signoz.db")
|
||||
|
||||
var DurationSortFeature = GetOrDefaultEnv("DURATION_SORT_FEATURE", "true")
|
||||
|
||||
var TimestampSortFeature = GetOrDefaultEnv("TIMESTAMP_SORT_FEATURE", "true")
|
||||
|
||||
func IsDurationSortFeatureEnabled() bool {
|
||||
isDurationSortFeatureEnabledStr := DurationSortFeature
|
||||
isDurationSortFeatureEnabledBool, err := strconv.ParseBool(isDurationSortFeatureEnabledStr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isDurationSortFeatureEnabledBool
|
||||
}
|
||||
|
||||
func IsTimestampSortFeatureEnabled() bool {
|
||||
isTimestampSortFeatureEnabledStr := TimestampSortFeature
|
||||
isTimestampSortFeatureEnabledBool, err := strconv.ParseBool(isTimestampSortFeatureEnabledStr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isTimestampSortFeatureEnabledBool
|
||||
}
|
||||
|
||||
var DEFAULT_FEATURE_SET = model.FeatureSet{
|
||||
DurationSort: IsDurationSortFeatureEnabled(),
|
||||
TimestampSort: IsTimestampSortFeatureEnabled(),
|
||||
}
|
||||
|
||||
const (
|
||||
TraceID = "traceID"
|
||||
ServiceName = "serviceName"
|
||||
|
34
pkg/query-service/featureManager/manager.go
Normal file
34
pkg/query-service/featureManager/manager.go
Normal file
@ -0,0 +1,34 @@
|
||||
package featureManager
|
||||
|
||||
import (
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
type FeatureManager struct {
|
||||
activeFeatures model.FeatureSet
|
||||
}
|
||||
|
||||
func StartManager() *FeatureManager {
|
||||
fM := &FeatureManager{
|
||||
activeFeatures: constants.DEFAULT_FEATURE_SET,
|
||||
}
|
||||
return fM
|
||||
}
|
||||
|
||||
// CheckFeature will be internally used by backend routines
|
||||
// for feature gating
|
||||
func (fm *FeatureManager) CheckFeature(featureKey string) error {
|
||||
if value, ok := fm.activeFeatures[featureKey]; ok {
|
||||
if value {
|
||||
return nil
|
||||
}
|
||||
return model.ErrFeatureUnavailable{Key: featureKey}
|
||||
}
|
||||
return model.ErrFeatureUnavailable{Key: featureKey}
|
||||
}
|
||||
|
||||
// GetFeatureFlags returns current active features
|
||||
func (fm *FeatureManager) GetFeatureFlags() model.FeatureSet {
|
||||
return fm.activeFeatures
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user