feat(statsreporter): build a statsreporter service (#8177)

- build a new statsreporter service
This commit is contained in:
Vibhu Pandey 2025-06-09 16:43:29 +05:30 committed by GitHub
parent decb660992
commit a1fa2769e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1163 additions and 98 deletions

View File

@ -74,7 +74,8 @@ jobs:
-X github.com/SigNoz/signoz/pkg/version.variant=community -X github.com/SigNoz/signoz/pkg/version.variant=community
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }} -X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }} -X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}' -X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
GO_CGO_ENABLED: 1 GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}' DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./pkg/query-service/Dockerfile.multi-arch DOCKER_DOCKERFILE_PATH: ./pkg/query-service/Dockerfile.multi-arch

View File

@ -108,7 +108,8 @@ jobs:
-X github.com/SigNoz/signoz/ee/zeus.url=https://api.signoz.cloud -X github.com/SigNoz/signoz/ee/zeus.url=https://api.signoz.cloud
-X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.signoz.io -X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.signoz.io
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud -X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1' -X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
GO_CGO_ENABLED: 1 GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}' DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch

View File

@ -107,7 +107,8 @@ jobs:
-X github.com/SigNoz/signoz/ee/zeus.url=https://api.staging.signoz.cloud -X github.com/SigNoz/signoz/ee/zeus.url=https://api.staging.signoz.cloud
-X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.staging.signoz.cloud -X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.staging.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.staging.signoz.cloud -X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.staging.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.staging.signoz.cloud/api/v1' -X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.staging.signoz.cloud/api/v1
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
GO_CGO_ENABLED: 1 GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}' DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch

View File

@ -165,12 +165,6 @@ alertmanager:
# Retention of the notification logs. # Retention of the notification logs.
retention: 120h retention: 120h
##################### Analytics #####################
analytics:
# Whether to enable analytics.
enabled: false
##################### Emailing ##################### ##################### Emailing #####################
emailing: emailing:
# Whether to enable emailing. # Whether to enable emailing.
@ -215,3 +209,18 @@ sharder:
single: single:
# The org id to which this instance belongs to. # The org id to which this instance belongs to.
org_id: org_id org_id: org_id
##################### Analytics #####################
analytics:
# Whether to enable analytics.
enabled: false
segment:
# The key to use for segment.
key: ""
##################### StatsReporter #####################
statsreporter:
# Whether to enable stats reporter. This is used to provide valuable insights to the SigNoz team. It does not collect any sensitive/PII data.
enabled: true
# The interval at which the stats are collected.
interval: 6h

View File

@ -211,3 +211,16 @@ func (provider *provider) GetFeatureFlags(ctx context.Context, organizationID va
return license.Features, nil return license.Features, nil
} }
func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
activeLicense, err := provider.GetActive(ctx, orgID)
if err != nil {
if errors.Ast(err, errors.TypeNotFound) {
return map[string]any{}, nil
}
return nil, err
}
return licensetypes.NewStatsFromLicense(activeLicense), nil
}

View File

@ -39,6 +39,7 @@ builds:
- -X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.signoz.io - -X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.signoz.io
- -X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud - -X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
- -X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1 - -X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1
- -X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr
- >- - >-
{{- if eq .Os "linux" }}-linkmode external -extldflags '-static'{{- end }} {{- if eq .Os "linux" }}-linkmode external -extldflags '-static'{{- end }}
mod_timestamp: "{{ .CommitTimestamp }}" mod_timestamp: "{{ .CommitTimestamp }}"

View File

@ -5,6 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes" "github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
@ -53,4 +54,7 @@ type Alertmanager interface {
// SetDefaultConfig sets the default config for the organization. // SetDefaultConfig sets the default config for the organization.
SetDefaultConfig(context.Context, string) error SetDefaultConfig(context.Context, string) error
// Collects stats for the organization.
statsreporter.StatsCollector
} }

View File

@ -471,3 +471,12 @@ func (provider *provider) SetDefaultConfig(ctx context.Context, orgID string) er
return provider.configStore.Set(ctx, config) return provider.configStore.Set(ctx, config)
} }
func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
channels, err := provider.configStore.ListChannels(ctx, orgID.String())
if err != nil {
return nil, err
}
return alertmanagertypes.NewStatsFromChannels(channels), nil
}

View File

@ -182,3 +182,12 @@ func (provider *provider) SetDefaultConfig(ctx context.Context, orgID string) er
return provider.configStore.Set(ctx, config) return provider.configStore.Set(ctx, config)
} }
func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
channels, err := provider.configStore.ListChannels(ctx, orgID.String())
if err != nil {
return nil, err
}
return alertmanagertypes.NewStatsFromChannels(channels), nil
}

View File

@ -0,0 +1,30 @@
package analyticstest
import (
"context"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/types/analyticstypes"
)
var _ analytics.Analytics = (*Provider)(nil)
type Provider struct {
stopC chan struct{}
}
func New() *Provider {
return &Provider{stopC: make(chan struct{})}
}
func (provider *Provider) Start(_ context.Context) error {
<-provider.stopC
return nil
}
func (provider *Provider) Send(ctx context.Context, messages ...analyticstypes.Message) {}
func (provider *Provider) Stop(_ context.Context) error {
close(provider.stopC)
return nil
}

View File

@ -1,8 +1,6 @@
package analytics package analytics
import ( import (
"fmt"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
) )
@ -13,6 +11,10 @@ var (
type Config struct { type Config struct {
Enabled bool `mapstructure:"enabled"` Enabled bool `mapstructure:"enabled"`
Segment Segment `mapstructure:"segment"`
}
type Segment struct {
Key string `mapstructure:"key"` Key string `mapstructure:"key"`
} }
@ -23,14 +25,20 @@ func NewConfigFactory() factory.ConfigFactory {
func newConfig() factory.Config { func newConfig() factory.Config {
return Config{ return Config{
Enabled: false, Enabled: false,
Segment: Segment{
Key: key, Key: key,
},
} }
} }
func (c Config) Validate() error { func (c Config) Validate() error {
if c.Key != key {
return fmt.Errorf("cannot override key set at build time with key: %s", c.Key)
}
return nil return nil
} }
func (c Config) Provider() string {
if c.Enabled {
return "segment"
}
return "noop"
}

View File

@ -9,31 +9,27 @@ import (
) )
type provider struct { type provider struct {
settings factory.ScopedProviderSettings stopC chan struct{}
startC chan struct{}
} }
func NewProviderFactory() factory.ProviderFactory[analytics.Analytics, analytics.Config] { func NewFactory() factory.ProviderFactory[analytics.Analytics, analytics.Config] {
return factory.NewProviderFactory(factory.MustNewName("noop"), New) return factory.NewProviderFactory(factory.MustNewName("noop"), New)
} }
func New(ctx context.Context, providerSettings factory.ProviderSettings, config analytics.Config) (analytics.Analytics, error) { func New(ctx context.Context, providerSettings factory.ProviderSettings, config analytics.Config) (analytics.Analytics, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/analytics/noopanalytics")
return &provider{ return &provider{
settings: settings, stopC: make(chan struct{}),
startC: make(chan struct{}),
}, nil }, nil
} }
func (provider *provider) Start(_ context.Context) error { func (provider *provider) Start(_ context.Context) error {
<-provider.startC <-provider.stopC
return nil return nil
} }
func (provider *provider) Send(ctx context.Context, messages ...analyticstypes.Message) {} func (provider *provider) Send(ctx context.Context, messages ...analyticstypes.Message) {}
func (provider *provider) Stop(_ context.Context) error { func (provider *provider) Stop(_ context.Context) error {
close(provider.startC) close(provider.stopC)
return nil return nil
} }

View File

@ -0,0 +1,28 @@
package segmentanalytics
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
segment "github.com/segmentio/analytics-go/v3"
)
type logger struct {
settings factory.ScopedProviderSettings
}
func newSegmentLogger(settings factory.ScopedProviderSettings) segment.Logger {
return &logger{
settings: settings,
}
}
func (logger *logger) Logf(format string, args ...interface{}) {
// the no lint directive is needed because the segmentlogger is not a slog.Logger
logger.settings.Logger().InfoContext(context.TODO(), format, args...) //nolint:sloglint
}
func (logger *logger) Errorf(format string, args ...interface{}) {
// the no lint directive is needed because the segment logger is not a slog.Logger
logger.settings.Logger().ErrorContext(context.TODO(), format, args...) //nolint:sloglint
}

View File

@ -12,25 +12,32 @@ import (
type provider struct { type provider struct {
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
client segment.Client client segment.Client
startC chan struct{} stopC chan struct{}
} }
func NewProviderFactory() factory.ProviderFactory[analytics.Analytics, analytics.Config] { func NewFactory() factory.ProviderFactory[analytics.Analytics, analytics.Config] {
return factory.NewProviderFactory(factory.MustNewName("segment"), New) return factory.NewProviderFactory(factory.MustNewName("segment"), New)
} }
func New(ctx context.Context, providerSettings factory.ProviderSettings, config analytics.Config) (analytics.Analytics, error) { func New(ctx context.Context, providerSettings factory.ProviderSettings, config analytics.Config) (analytics.Analytics, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/analytics/segmentanalytics") settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/analytics/segmentanalytics")
client, err := segment.NewWithConfig(config.Segment.Key, segment.Config{
Logger: newSegmentLogger(settings),
})
if err != nil {
return nil, err
}
return &provider{ return &provider{
settings: settings, settings: settings,
client: segment.New(config.Key), client: client,
startC: make(chan struct{}), stopC: make(chan struct{}),
}, nil }, nil
} }
func (provider *provider) Start(_ context.Context) error { func (provider *provider) Start(_ context.Context) error {
<-provider.startC <-provider.stopC
return nil return nil
} }
@ -43,7 +50,11 @@ func (provider *provider) Send(ctx context.Context, messages ...analyticstypes.M
} }
} }
func (provider *provider) Stop(_ context.Context) error { func (provider *provider) Stop(ctx context.Context) error {
close(provider.startC) if err := provider.client.Close(); err != nil {
provider.settings.Logger().WarnContext(ctx, "unable to close segment client", "err", err)
}
close(provider.stopC)
return nil return nil
} }

View File

@ -24,27 +24,12 @@ func (a *Analytics) Wrap(next http.Handler) http.Handler {
route := mux.CurrentRoute(r) route := mux.CurrentRoute(r)
path, _ := route.GetPathTemplate() path, _ := route.GetPathTemplate()
queryRangeData, metadataExists := a.extractQueryRangeData(path, r) _, _ = a.extractQueryRangeData(path, r)
a.getActiveLogs(path, r) a.getActiveLogs(path, r)
badResponseBuffer := new(bytes.Buffer) badResponseBuffer := new(bytes.Buffer)
writer := newBadResponseLoggingWriter(w, badResponseBuffer) writer := newBadResponseLoggingWriter(w, badResponseBuffer)
next.ServeHTTP(writer, r) next.ServeHTTP(writer, r)
data := map[string]interface{}{"path": path, "statusCode": writer.StatusCode()}
if metadataExists {
for key, value := range queryRangeData {
data[key] = value
}
}
if _, ok := telemetry.EnabledPaths()[path]; ok {
claims, err := authtypes.ClaimsFromContext(r.Context())
if err == nil {
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, claims.Email, true, false)
}
}
}) })
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types/licensetypes" "github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
@ -32,6 +33,8 @@ type Licensing interface {
Portal(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error) Portal(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error)
// GetFeatureFlags fetches all the defined feature flags // GetFeatureFlags fetches all the defined feature flags
GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error) GetFeatureFlags(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.Feature, error)
statsreporter.StatsCollector
} }
type API interface { type API interface {

View File

@ -62,3 +62,7 @@ func (provider *noopLicensing) GetActive(ctx context.Context, organizationID val
func (provider *noopLicensing) GetFeatureFlags(_ context.Context, _ valuer.UUID) ([]*licensetypes.Feature, error) { func (provider *noopLicensing) GetFeatureFlags(_ context.Context, _ valuer.UUID) ([]*licensetypes.Feature, error) {
return licensetypes.DefaultFeatureSet, nil return licensetypes.DefaultFeatureSet, nil
} }
func (provider *noopLicensing) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
return map[string]any{}, nil
}

View File

@ -4,12 +4,13 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes" "github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
type Module interface { type Module interface {
Create(ctx context.Context, orgID valuer.UUID, createdBy string, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, data dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error)
Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error)
@ -22,6 +23,8 @@ type Module interface {
Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error
GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error) GetByMetricNames(ctx context.Context, orgID valuer.UUID, metricNames []string) (map[string][]map[string]string, error)
statsreporter.StatsCollector
} }
type Handler interface { type Handler interface {

View File

@ -46,7 +46,7 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
return return
} }
dashboard, err := handler.module.Create(ctx, orgID, claims.Email, req) dashboard, err := handler.module.Create(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.ID), req)
if err != nil { if err != nil {
render.Error(rw, err) render.Error(rw, err)
return return

View File

@ -4,10 +4,12 @@ import (
"context" "context"
"strings" "strings"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/dashboard" "github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/analyticstypes"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes" "github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
@ -15,17 +17,19 @@ import (
type module struct { type module struct {
store dashboardtypes.Store store dashboardtypes.Store
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
analytics analytics.Analytics
} }
func NewModule(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) dashboard.Module { func NewModule(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics) dashboard.Module {
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/impldashboard") scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/impldashboard")
return &module{ return &module{
store: NewStore(sqlstore), store: NewStore(sqlstore),
settings: scopedProviderSettings, settings: scopedProviderSettings,
analytics: analytics,
} }
} }
func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) { func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy string, creator valuer.UUID, postableDashboard dashboardtypes.PostableDashboard) (*dashboardtypes.Dashboard, error) {
dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, postableDashboard) dashboard, err := dashboardtypes.NewDashboard(orgID, createdBy, postableDashboard)
if err != nil { if err != nil {
return nil, err return nil, err
@ -35,11 +39,25 @@ func (module *module) Create(ctx context.Context, orgID valuer.UUID, createdBy s
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = module.store.Create(ctx, storableDashboard) err = module.store.Create(ctx, storableDashboard)
if err != nil { if err != nil {
return nil, err return nil, err
} }
module.analytics.Send(ctx,
analyticstypes.Track{
UserId: creator.String(),
Event: "Dashboard Created",
Properties: analyticstypes.NewPropertiesFromMap(dashboardtypes.NewStatsFromStorableDashboards([]*dashboardtypes.StorableDashboard{storableDashboard})),
Context: &analyticstypes.Context{
Extra: map[string]interface{}{
analyticstypes.KeyGroupID: orgID,
},
},
},
)
return dashboard, nil return dashboard, nil
} }
@ -207,3 +225,12 @@ func (module *module) GetByMetricNames(ctx context.Context, orgID valuer.UUID, m
return result, nil return result, nil
} }
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
dashboards, err := module.store.List(ctx, orgID)
if err != nil {
return nil, err
}
return dashboardtypes.NewStatsFromStorableDashboards(dashboards), nil
}

View File

@ -22,10 +22,6 @@ func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.Organizat
return module.store.Get(ctx, id) return module.store.Get(ctx, id)
} }
func (module *getter) List(ctx context.Context) ([]*types.Organization, error) {
return module.store.GetAll(ctx)
}
func (module *getter) ListByOwnedKeyRange(ctx context.Context) ([]*types.Organization, error) { func (module *getter) ListByOwnedKeyRange(ctx context.Context) ([]*types.Organization, error) {
start, end, err := module.sharder.GetMyOwnedKeyRange(ctx) start, end, err := module.sharder.GetMyOwnedKeyRange(ctx)
if err != nil { if err != nil {

View File

@ -12,9 +12,6 @@ type Getter interface {
// Get gets the organization based on the given id // Get gets the organization based on the given id
Get(context.Context, valuer.UUID) (*types.Organization, error) Get(context.Context, valuer.UUID) (*types.Organization, error)
// Lists all the organizations
List(context.Context) ([]*types.Organization, error)
// ListByOwnedKeyRange gets all the organizations owned by the instance // ListByOwnedKeyRange gets all the organizations owned by the instance
ListByOwnedKeyRange(context.Context) ([]*types.Organization, error) ListByOwnedKeyRange(context.Context) ([]*types.Organization, error)
} }

View File

@ -169,3 +169,20 @@ func (module *module) DeleteView(ctx context.Context, orgID string, uuid valuer.
} }
return nil return nil
} }
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
savedViews := []*types.SavedView{}
err := module.
sqlstore.
BunDB().
NewSelect().
Model(&savedViews).
Where("org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, err
}
return types.NewStatsFromSavedViews(savedViews), nil
}

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3" v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
) )
@ -18,6 +19,8 @@ type Module interface {
UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error
DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error
statsreporter.StatsCollector
} }
type Handler interface { type Handler interface {

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/emailing" "github.com/SigNoz/signoz/pkg/emailing"
"github.com/SigNoz/signoz/pkg/errors" "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
@ -17,6 +18,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/model" "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/query-service/telemetry" "github.com/SigNoz/signoz/pkg/query-service/telemetry"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/analyticstypes"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/emailtypes" "github.com/SigNoz/signoz/pkg/types/emailtypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
@ -29,10 +31,11 @@ type Module struct {
emailing emailing.Emailing emailing emailing.Emailing
settings factory.ScopedProviderSettings settings factory.ScopedProviderSettings
orgSetter organization.Setter orgSetter organization.Setter
analytics analytics.Analytics
} }
// This module is a WIP, don't take inspiration from this. // This module is a WIP, don't take inspiration from this.
func NewModule(store types.UserStore, jwt *authtypes.JWT, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter) user.Module { func NewModule(store types.UserStore, jwt *authtypes.JWT, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, analytics analytics.Analytics) user.Module {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser") settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
return &Module{ return &Module{
store: store, store: store,
@ -40,6 +43,7 @@ func NewModule(store types.UserStore, jwt *authtypes.JWT, emailing emailing.Emai
emailing: emailing, emailing: emailing,
settings: settings, settings: settings,
orgSetter: orgSetter, orgSetter: orgSetter,
analytics: analytics,
} }
} }
@ -126,17 +130,80 @@ func (m *Module) GetInviteByEmailInOrg(ctx context.Context, orgID string, email
} }
func (m *Module) CreateUserWithPassword(ctx context.Context, user *types.User, password *types.FactorPassword) (*types.User, error) { func (m *Module) CreateUserWithPassword(ctx context.Context, user *types.User, password *types.FactorPassword) (*types.User, error) {
user, err := m.store.CreateUserWithPassword(ctx, user, password) user, err := m.store.CreateUserWithPassword(ctx, user, password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.analytics.Send(ctx,
analyticstypes.Identify{
UserId: user.ID.String(),
Traits: analyticstypes.
NewTraits().
SetName(user.DisplayName).
SetEmail(user.Email).
Set("role", user.Role).
SetCreatedAt(user.CreatedAt),
},
analyticstypes.Group{
UserId: user.ID.String(),
GroupId: user.OrgID,
},
analyticstypes.Track{
UserId: user.ID.String(),
Event: "User Created",
Properties: analyticstypes.NewPropertiesFromMap(map[string]any{
"role": user.Role,
"email": user.Email,
"name": user.DisplayName,
}),
Context: &analyticstypes.Context{
Extra: map[string]interface{}{
analyticstypes.KeyGroupID: user.OrgID,
},
},
},
)
return user, nil return user, nil
} }
func (m *Module) CreateUser(ctx context.Context, user *types.User) error { func (m *Module) CreateUser(ctx context.Context, user *types.User) error {
return m.store.CreateUser(ctx, user) if err := m.store.CreateUser(ctx, user); err != nil {
return err
}
m.analytics.Send(ctx,
analyticstypes.Identify{
UserId: user.ID.String(),
Traits: analyticstypes.
NewTraits().
SetName(user.DisplayName).
SetEmail(user.Email).
Set("role", user.Role).
SetCreatedAt(user.CreatedAt),
},
analyticstypes.Group{
UserId: user.ID.String(),
GroupId: user.OrgID,
},
analyticstypes.Track{
UserId: user.ID.String(),
Event: "User Created",
Properties: analyticstypes.NewPropertiesFromMap(map[string]any{
"role": user.Role,
"email": user.Email,
"name": user.DisplayName,
}),
Context: &analyticstypes.Context{
Extra: map[string]interface{}{
analyticstypes.KeyGroupID: user.OrgID,
},
},
},
)
return nil
} }
func (m *Module) GetUserByID(ctx context.Context, orgID string, id string) (*types.GettableUser, error) { func (m *Module) GetUserByID(ctx context.Context, orgID string, id string) (*types.GettableUser, error) {
@ -575,3 +642,12 @@ func (m *Module) Register(ctx context.Context, req *types.PostableRegisterOrgAnd
return user, nil return user, nil
} }
func (m *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
count, err := m.store.CountByOrgID(ctx, orgID)
if err != nil {
return nil, err
}
return map[string]any{"user.count": count}, nil
}

View File

@ -809,3 +809,20 @@ func (store *store) DeleteDomain(ctx context.Context, id uuid.UUID) error {
return nil return nil
} }
func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
user := new(types.User)
count, err := store.
sqlstore.
BunDB().
NewSelect().
Model(user).
Where("org_id = ?", orgID).
Count(ctx)
if err != nil {
return 0, err
}
return int64(count), nil
}

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
@ -65,6 +66,8 @@ type Module interface {
// Register // Register
Register(ctx context.Context, req *types.PostableRegisterOrgAndAdmin) (*types.User, error) Register(ctx context.Context, req *types.PostableRegisterOrgAndAdmin) (*types.User, error)
statsreporter.StatsCollector
} }
type Handler interface { type Handler interface {

View File

@ -35,6 +35,7 @@ builds:
- -X github.com/SigNoz/signoz/pkg/version.hash={{ .ShortCommit }} - -X github.com/SigNoz/signoz/pkg/version.hash={{ .ShortCommit }}
- -X github.com/SigNoz/signoz/pkg/version.time={{ .CommitTimestamp }} - -X github.com/SigNoz/signoz/pkg/version.time={{ .CommitTimestamp }}
- -X github.com/SigNoz/signoz/pkg/version.branch={{ .Branch }} - -X github.com/SigNoz/signoz/pkg/version.branch={{ .Branch }}
- -X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr
- >- - >-
{{- if eq .Os "linux" }}-linkmode external -extldflags '-static'{{- end }} {{- if eq .Os "linux" }}-linkmode external -extldflags '-static'{{- end }}
mod_timestamp: "{{ .CommitTimestamp }}" mod_timestamp: "{{ .CommitTimestamp }}"

View File

@ -8,6 +8,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver" "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics/analyticstest"
"github.com/SigNoz/signoz/pkg/emailing/emailingtest" "github.com/SigNoz/signoz/pkg/emailing/emailingtest"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest" "github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
@ -38,7 +39,8 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
require.NoError(err) require.NoError(err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
user, apiErr := createTestUser(modules.OrgSetter, modules.User) user, apiErr := createTestUser(modules.OrgSetter, modules.User)
require.Nil(apiErr) require.Nil(apiErr)
@ -94,7 +96,8 @@ func TestAgentCheckIns(t *testing.T) {
require.NoError(err) require.NoError(err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
user, apiErr := createTestUser(modules.OrgSetter, modules.User) user, apiErr := createTestUser(modules.OrgSetter, modules.User)
require.Nil(apiErr) require.Nil(apiErr)
@ -189,7 +192,8 @@ func TestCantDisconnectNonExistentAccount(t *testing.T) {
require.NoError(err) require.NoError(err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
user, apiErr := createTestUser(modules.OrgSetter, modules.User) user, apiErr := createTestUser(modules.OrgSetter, modules.User)
require.Nil(apiErr) require.Nil(apiErr)
@ -216,7 +220,8 @@ func TestConfigureService(t *testing.T) {
require.NoError(err) require.NoError(err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
user, apiErr := createTestUser(modules.OrgSetter, modules.User) user, apiErr := createTestUser(modules.OrgSetter, modules.User)
require.Nil(apiErr) require.Nil(apiErr)

View File

@ -8,6 +8,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver" "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics/analyticstest"
"github.com/SigNoz/signoz/pkg/emailing/emailingtest" "github.com/SigNoz/signoz/pkg/emailing/emailingtest"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest" "github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
@ -31,7 +32,8 @@ func TestIntegrationLifecycle(t *testing.T) {
alertmanager, _ := signozalertmanager.New(context.TODO(), providerSettings, alertmanager.Config{Provider: "signoz", Signoz: alertmanager.Signoz{PollInterval: 10 * time.Second, Config: alertmanagerserver.NewConfig()}}, store, orgGetter) alertmanager, _ := signozalertmanager.New(context.TODO(), providerSettings, alertmanager.Config{Provider: "signoz", Signoz: alertmanager.Signoz{PollInterval: 10 * time.Second, Config: alertmanagerserver.NewConfig()}}, store, orgGetter)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(store, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(store, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
user, apiErr := createTestUser(modules.OrgSetter, modules.User) user, apiErr := createTestUser(modules.OrgSetter, modules.User)
if apiErr != nil { if apiErr != nil {
t.Fatalf("could not create test user: %v", apiErr) t.Fatalf("could not create test user: %v", apiErr)

View File

@ -18,8 +18,10 @@ const (
OpAmpWsEndpoint = "0.0.0.0:4320" // address for opamp websocket OpAmpWsEndpoint = "0.0.0.0:4320" // address for opamp websocket
) )
// Deprecated: Use the new analytics service instead
var DEFAULT_TELEMETRY_ANONYMOUS = false var DEFAULT_TELEMETRY_ANONYMOUS = false
// Deprecated: Use the new analytics service instead
func IsOSSTelemetryEnabled() bool { func IsOSSTelemetryEnabled() bool {
ossSegmentKey := GetOrDefaultEnv("OSS_TELEMETRY_ENABLED", "true") ossSegmentKey := GetOrDefaultEnv("OSS_TELEMETRY_ENABLED", "true")
return ossSegmentKey == "true" return ossSegmentKey == "true"
@ -27,6 +29,7 @@ func IsOSSTelemetryEnabled() bool {
const MaxAllowedPointsInTimeSeries = 300 const MaxAllowedPointsInTimeSeries = 300
// Deprecated: Use the new analytics service instead
func IsTelemetryEnabled() bool { func IsTelemetryEnabled() bool {
if testing.Testing() { if testing.Testing() {
return false return false
@ -48,8 +51,10 @@ const SpanSearchScopeRoot = "isroot"
const SpanSearchScopeEntryPoint = "isentrypoint" const SpanSearchScopeEntryPoint = "isentrypoint"
const OrderBySpanCount = "span_count" const OrderBySpanCount = "span_count"
// Deprecated: Use the new statsreporter service instead
var TELEMETRY_HEART_BEAT_DURATION_MINUTES = GetOrDefaultEnvInt("TELEMETRY_HEART_BEAT_DURATION_MINUTES", 720) var TELEMETRY_HEART_BEAT_DURATION_MINUTES = GetOrDefaultEnvInt("TELEMETRY_HEART_BEAT_DURATION_MINUTES", 720)
// Deprecated: Use the new statsreporter service instead
var TELEMETRY_ACTIVE_USER_DURATION_MINUTES = GetOrDefaultEnvInt("TELEMETRY_ACTIVE_USER_DURATION_MINUTES", 360) var TELEMETRY_ACTIVE_USER_DURATION_MINUTES = GetOrDefaultEnvInt("TELEMETRY_ACTIVE_USER_DURATION_MINUTES", 360)
// Deprecated: Use the new emailing service instead // Deprecated: Use the new emailing service instead

View File

@ -195,7 +195,7 @@ func defaultPrepareTaskFunc(opts PrepareTaskOptions) (Task, error) {
// by calling the Run method. // by calling the Run method.
func NewManager(o *ManagerOptions) (*Manager, error) { func NewManager(o *ManagerOptions) (*Manager, error) {
o = defaultOptions(o) o = defaultOptions(o)
ruleStore := sqlrulestore.NewRuleStore(o.DBConn, o.SQLStore) ruleStore := sqlrulestore.NewRuleStore(o.SQLStore)
maintenanceStore := sqlrulestore.NewMaintenanceStore(o.SQLStore) maintenanceStore := sqlrulestore.NewMaintenanceStore(o.SQLStore)
m := &Manager{ m := &Manager{

View File

@ -1,13 +1,5 @@
package telemetry package telemetry
func EnabledPaths() map[string]struct{} {
enabledPaths := map[string]struct{}{
"/api/v1/channels": {},
}
return enabledPaths
}
func ignoreEvents(event string, attributes map[string]interface{}) bool { func ignoreEvents(event string, attributes map[string]interface{}) bool {
if event == TELEMETRY_EVENT_ACTIVE_USER { if event == TELEMETRY_EVENT_ACTIVE_USER {

View File

@ -613,7 +613,6 @@ func (a *Telemetry) SendIdentifyEvent(data map[string]interface{}, userEmail str
} }
func (a *Telemetry) SendGroupEvent(data map[string]interface{}, userEmail string) { func (a *Telemetry) SendGroupEvent(data map[string]interface{}, userEmail string) {
if !a.isTelemetryEnabled() || a.isTelemetryAnonymous() { if !a.isTelemetryEnabled() || a.isTelemetryAnonymous() {
return return
} }

View File

@ -14,6 +14,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver" "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics/analyticstest"
"github.com/SigNoz/signoz/pkg/emailing/emailingtest" "github.com/SigNoz/signoz/pkg/emailing/emailingtest"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder" "github.com/SigNoz/signoz/pkg/sharder/noopsharder"
@ -315,7 +316,8 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
require.NoError(t, err) require.NoError(t, err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
handlers := signoz.NewHandlers(modules) handlers := signoz.NewHandlers(modules)
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{ apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{

View File

@ -14,6 +14,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver" "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics/analyticstest"
"github.com/SigNoz/signoz/pkg/emailing/emailingtest" "github.com/SigNoz/signoz/pkg/emailing/emailingtest"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest" "github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
@ -491,7 +492,8 @@ func NewTestbedWithoutOpamp(t *testing.T, sqlStore sqlstore.SQLStore) *LogPipeli
require.NoError(t, err) require.NoError(t, err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
handlers := signoz.NewHandlers(modules) handlers := signoz.NewHandlers(modules)
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{ apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{

View File

@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver" "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics/analyticstest"
"github.com/SigNoz/signoz/pkg/emailing/emailingtest" "github.com/SigNoz/signoz/pkg/emailing/emailingtest"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder" "github.com/SigNoz/signoz/pkg/sharder/noopsharder"
@ -376,7 +377,8 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
require.NoError(t, err) require.NoError(t, err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
handlers := signoz.NewHandlers(modules) handlers := signoz.NewHandlers(modules)
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{ apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{

View File

@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver" "github.com/SigNoz/signoz/pkg/alertmanager/alertmanagerserver"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics/analyticstest"
"github.com/SigNoz/signoz/pkg/emailing/emailingtest" "github.com/SigNoz/signoz/pkg/emailing/emailingtest"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest" "github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
@ -582,7 +583,8 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
require.NoError(t, err) require.NoError(t, err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings, orgGetter, alertmanager) analytics := analyticstest.New()
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
handlers := signoz.NewHandlers(modules) handlers := signoz.NewHandlers(modules)
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{ apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{

20
pkg/ruler/config.go Normal file
View File

@ -0,0 +1,20 @@
package ruler
import (
"github.com/SigNoz/signoz/pkg/factory"
)
type Config struct {
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("ruler"), newConfig)
}
func newConfig() factory.Config {
return Config{}
}
func (c Config) Validate() error {
return nil
}

7
pkg/ruler/ruler.go Normal file
View File

@ -0,0 +1,7 @@
package ruler
import "github.com/SigNoz/signoz/pkg/statsreporter"
type Ruler interface {
statsreporter.StatsCollector
}

View File

@ -6,16 +6,14 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes" ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
"github.com/jmoiron/sqlx"
) )
type rule struct { type rule struct {
*sqlx.DB
sqlstore sqlstore.SQLStore sqlstore sqlstore.SQLStore
} }
func NewRuleStore(db *sqlx.DB, store sqlstore.SQLStore) ruletypes.RuleStore { func NewRuleStore(store sqlstore.SQLStore) ruletypes.RuleStore {
return &rule{sqlstore: store, DB: db} return &rule{sqlstore: store}
} }
func (r *rule) CreateRule(ctx context.Context, storedRule *ruletypes.Rule, cb func(context.Context, valuer.UUID) error) (valuer.UUID, error) { func (r *rule) CreateRule(ctx context.Context, storedRule *ruletypes.Rule, cb func(context.Context, valuer.UUID) error) (valuer.UUID, error) {

View File

@ -0,0 +1,35 @@
package signozruler
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type provider struct {
ruleStore ruletypes.RuleStore
}
func NewFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[ruler.Ruler, ruler.Config] {
return factory.NewProviderFactory(factory.MustNewName("signoz"), func(ctx context.Context, settings factory.ProviderSettings, config ruler.Config) (ruler.Ruler, error) {
return New(ctx, settings, config, sqlstore)
})
}
func New(ctx context.Context, settings factory.ProviderSettings, config ruler.Config, sqlstore sqlstore.SQLStore) (ruler.Ruler, error) {
return &provider{ruleStore: sqlrulestore.NewRuleStore(sqlstore)}, nil
}
func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
rules, err := provider.ruleStore.GetStoredRules(ctx, orgID.String())
if err != nil {
return nil, err
}
return ruletypes.NewStatsFromRules(rules), nil
}

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/apiserver" "github.com/SigNoz/signoz/pkg/apiserver"
"github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/config" "github.com/SigNoz/signoz/pkg/config"
@ -17,10 +18,12 @@ import (
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/instrumentation" "github.com/SigNoz/signoz/pkg/instrumentation"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sqlmigration" "github.com/SigNoz/signoz/pkg/sqlmigration"
"github.com/SigNoz/signoz/pkg/sqlmigrator" "github.com/SigNoz/signoz/pkg/sqlmigrator"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/telemetrystore" "github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/version" "github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/web" "github.com/SigNoz/signoz/pkg/web"
@ -34,6 +37,9 @@ type Config struct {
// Instrumentation config // Instrumentation config
Instrumentation instrumentation.Config `mapstructure:"instrumentation"` Instrumentation instrumentation.Config `mapstructure:"instrumentation"`
// Analytics config
Analytics analytics.Config `mapstructure:"analytics"`
// Web config // Web config
Web web.Config `mapstructure:"web"` Web web.Config `mapstructure:"web"`
@ -61,11 +67,17 @@ type Config struct {
// Alertmanager config // Alertmanager config
Alertmanager alertmanager.Config `mapstructure:"alertmanager" yaml:"alertmanager"` Alertmanager alertmanager.Config `mapstructure:"alertmanager" yaml:"alertmanager"`
// Ruler config
Ruler ruler.Config `mapstructure:"ruler"`
// Emailing config // Emailing config
Emailing emailing.Config `mapstructure:"emailing" yaml:"emailing"` Emailing emailing.Config `mapstructure:"emailing" yaml:"emailing"`
// Sharder config // Sharder config
Sharder sharder.Config `mapstructure:"sharder" yaml:"sharder"` Sharder sharder.Config `mapstructure:"sharder" yaml:"sharder"`
// StatsReporter config
StatsReporter statsreporter.Config `mapstructure:"statsreporter"`
} }
// DeprecatedFlags are the flags that are deprecated and scheduled for removal. // DeprecatedFlags are the flags that are deprecated and scheduled for removal.
@ -81,6 +93,7 @@ func NewConfig(ctx context.Context, resolverConfig config.ResolverConfig, deprec
configFactories := []factory.ConfigFactory{ configFactories := []factory.ConfigFactory{
version.NewConfigFactory(), version.NewConfigFactory(),
instrumentation.NewConfigFactory(), instrumentation.NewConfigFactory(),
analytics.NewConfigFactory(),
web.NewConfigFactory(), web.NewConfigFactory(),
cache.NewConfigFactory(), cache.NewConfigFactory(),
sqlstore.NewConfigFactory(), sqlstore.NewConfigFactory(),
@ -89,8 +102,10 @@ func NewConfig(ctx context.Context, resolverConfig config.ResolverConfig, deprec
telemetrystore.NewConfigFactory(), telemetrystore.NewConfigFactory(),
prometheus.NewConfigFactory(), prometheus.NewConfigFactory(),
alertmanager.NewConfigFactory(), alertmanager.NewConfigFactory(),
ruler.NewConfigFactory(),
emailing.NewConfigFactory(), emailing.NewConfigFactory(),
sharder.NewConfigFactory(), sharder.NewConfigFactory(),
statsreporter.NewConfigFactory(),
} }
conf, err := config.New(ctx, resolverConfig, configFactories) conf, err := config.New(ctx, resolverConfig, configFactories)
@ -235,4 +250,14 @@ func mergeAndEnsureBackwardCompatibility(config *Config, deprecatedFlags Depreca
fmt.Println("[Deprecated] env SMTP_FROM is deprecated and scheduled for removal. Please use SIGNOZ_EMAILING_FROM instead.") fmt.Println("[Deprecated] env SMTP_FROM is deprecated and scheduled for removal. Please use SIGNOZ_EMAILING_FROM instead.")
config.Emailing.SMTP.From = os.Getenv("SMTP_FROM") config.Emailing.SMTP.From = os.Getenv("SMTP_FROM")
} }
if os.Getenv("SIGNOZ_SAAS_SEGMENT_KEY") != "" {
fmt.Println("[Deprecated] env SIGNOZ_SAAS_SEGMENT_KEY is deprecated and scheduled for removal. Please use SIGNOZ_ANALYTICS_SEGMENT_KEY instead.")
config.Analytics.Segment.Key = os.Getenv("SIGNOZ_SAAS_SEGMENT_KEY")
}
if os.Getenv("TELEMETRY_ENABLED") != "" {
fmt.Println("[Deprecated] env TELEMETRY_ENABLED is deprecated and scheduled for removal. Please use SIGNOZ_ANALYTICS_ENABLED instead.")
config.Analytics.Enabled = os.Getenv("TELEMETRY_ENABLED") == "true"
}
} }

View File

@ -33,7 +33,7 @@ func TestNewHandlers(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := NewModules(sqlstore, jwt, emailing, providerSettings, orgGetter, alertmanager) modules := NewModules(sqlstore, jwt, emailing, providerSettings, orgGetter, alertmanager, nil)
handlers := NewHandlers(modules) handlers := NewHandlers(modules)

View File

@ -2,6 +2,7 @@ package signoz
import ( import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/emailing" "github.com/SigNoz/signoz/pkg/emailing"
"github.com/SigNoz/signoz/pkg/factory" "github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/apdex" "github.com/SigNoz/signoz/pkg/modules/apdex"
@ -44,17 +45,18 @@ func NewModules(
providerSettings factory.ProviderSettings, providerSettings factory.ProviderSettings,
orgGetter organization.Getter, orgGetter organization.Getter,
alertmanager alertmanager.Alertmanager, alertmanager alertmanager.Alertmanager,
analytics analytics.Analytics,
) Modules { ) Modules {
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore)) quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter) orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), jwt, emailing, providerSettings, orgSetter) user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), jwt, emailing, providerSettings, orgSetter, analytics)
return Modules{ return Modules{
OrgGetter: orgGetter, OrgGetter: orgGetter,
OrgSetter: orgSetter, OrgSetter: orgSetter,
Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewAvailablePreference()), Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewAvailablePreference()),
SavedView: implsavedview.NewModule(sqlstore), SavedView: implsavedview.NewModule(sqlstore),
Apdex: implapdex.NewModule(sqlstore), Apdex: implapdex.NewModule(sqlstore),
Dashboard: impldashboard.NewModule(sqlstore, providerSettings), Dashboard: impldashboard.NewModule(sqlstore, providerSettings, analytics),
User: user, User: user,
QuickFilter: quickfilter, QuickFilter: quickfilter,
TraceFunnel: impltracefunnel.NewModule(impltracefunnel.NewStore(sqlstore)), TraceFunnel: impltracefunnel.NewModule(impltracefunnel.NewStore(sqlstore)),

View File

@ -33,7 +33,7 @@ func TestNewModules(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour) jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
emailing := emailingtest.New() emailing := emailingtest.New()
modules := NewModules(sqlstore, jwt, emailing, providerSettings, orgGetter, alertmanager) modules := NewModules(sqlstore, jwt, emailing, providerSettings, orgGetter, alertmanager, nil)
reflectVal := reflect.ValueOf(modules) reflectVal := reflect.ValueOf(modules)
for i := 0; i < reflectVal.NumField(); i++ { for i := 0; i < reflectVal.NumField(); i++ {

View File

@ -4,6 +4,9 @@ import (
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/legacyalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/legacyalertmanager"
"github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager" "github.com/SigNoz/signoz/pkg/alertmanager/signozalertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/analytics/noopanalytics"
"github.com/SigNoz/signoz/pkg/analytics/segmentanalytics"
"github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/cache/memorycache" "github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/cache/rediscache" "github.com/SigNoz/signoz/pkg/cache/rediscache"
@ -14,6 +17,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/prometheus/clickhouseprometheus" "github.com/SigNoz/signoz/pkg/prometheus/clickhouseprometheus"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sharder/noopsharder" "github.com/SigNoz/signoz/pkg/sharder/noopsharder"
"github.com/SigNoz/signoz/pkg/sharder/singlesharder" "github.com/SigNoz/signoz/pkg/sharder/singlesharder"
@ -21,14 +26,25 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlitesqlstore" "github.com/SigNoz/signoz/pkg/sqlstore/sqlitesqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook" "github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/statsreporter/analyticsstatsreporter"
"github.com/SigNoz/signoz/pkg/statsreporter/noopstatsreporter"
"github.com/SigNoz/signoz/pkg/telemetrystore" "github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrystore/clickhousetelemetrystore" "github.com/SigNoz/signoz/pkg/telemetrystore/clickhousetelemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystorehook" "github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystorehook"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/web" "github.com/SigNoz/signoz/pkg/web"
"github.com/SigNoz/signoz/pkg/web/noopweb" "github.com/SigNoz/signoz/pkg/web/noopweb"
"github.com/SigNoz/signoz/pkg/web/routerweb" "github.com/SigNoz/signoz/pkg/web/routerweb"
) )
func NewAnalyticsProviderFactories() factory.NamedMap[factory.ProviderFactory[analytics.Analytics, analytics.Config]] {
return factory.MustNewNamedMap(
noopanalytics.NewFactory(),
segmentanalytics.NewFactory(),
)
}
func NewCacheProviderFactories() factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]] { func NewCacheProviderFactories() factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]] {
return factory.MustNewNamedMap( return factory.MustNewNamedMap(
memorycache.NewFactory(), memorycache.NewFactory(),
@ -114,6 +130,12 @@ func NewAlertmanagerProviderFactories(sqlstore sqlstore.SQLStore, orgGetter orga
) )
} }
func NewRulerProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(
signozruler.NewFactory(sqlstore),
)
}
func NewEmailingProviderFactories() factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]] { func NewEmailingProviderFactories() factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]] {
return factory.MustNewNamedMap( return factory.MustNewNamedMap(
noopemailing.NewFactory(), noopemailing.NewFactory(),
@ -127,3 +149,10 @@ func NewSharderProviderFactories() factory.NamedMap[factory.ProviderFactory[shar
noopsharder.NewFactory(), noopsharder.NewFactory(),
) )
} }
func NewStatsReporterProviderFactories(telemetryStore telemetrystore.TelemetryStore, collectors []statsreporter.StatsCollector, orgGetter organization.Getter, build version.Build, analyticsConfig analytics.Config) factory.NamedMap[factory.ProviderFactory[statsreporter.StatsReporter, statsreporter.Config]] {
return factory.MustNewNamedMap(
analyticsstatsreporter.NewFactory(telemetryStore, collectors, orgGetter, build, analyticsConfig),
noopstatsreporter.NewFactory(),
)
}

View File

@ -4,11 +4,14 @@ import (
"testing" "testing"
"github.com/DATA-DOG/go-sqlmock" "github.com/DATA-DOG/go-sqlmock"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest" "github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/telemetrystore" "github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest" "github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
"github.com/SigNoz/signoz/pkg/version"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -45,6 +48,10 @@ func TestNewProviderFactories(t *testing.T) {
NewAlertmanagerProviderFactories(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual), orgGetter) NewAlertmanagerProviderFactories(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual), orgGetter)
}) })
assert.NotPanics(t, func() {
NewRulerProviderFactories(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual))
})
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
NewEmailingProviderFactories() NewEmailingProviderFactories()
}) })
@ -52,4 +59,10 @@ func TestNewProviderFactories(t *testing.T) {
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
NewSharderProviderFactories() NewSharderProviderFactories()
}) })
assert.NotPanics(t, func() {
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual)), nil)
telemetryStore := telemetrystoretest.New(telemetrystore.Config{Provider: "clickhouse"}, sqlmock.QueryMatcherEqual)
NewStatsReporterProviderFactories(telemetryStore, []statsreporter.StatsCollector{}, orgGetter, version.Build{}, analytics.Config{Enabled: true})
})
} }

View File

@ -12,10 +12,12 @@ import (
"github.com/SigNoz/signoz/pkg/modules/organization" "github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization" "github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/prometheus" "github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/sharder" "github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sqlmigration" "github.com/SigNoz/signoz/pkg/sqlmigration"
"github.com/SigNoz/signoz/pkg/sqlmigrator" "github.com/SigNoz/signoz/pkg/sqlmigrator"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/telemetrystore" "github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/version" "github.com/SigNoz/signoz/pkg/version"
@ -33,10 +35,12 @@ type SigNoz struct {
TelemetryStore telemetrystore.TelemetryStore TelemetryStore telemetrystore.TelemetryStore
Prometheus prometheus.Prometheus Prometheus prometheus.Prometheus
Alertmanager alertmanager.Alertmanager Alertmanager alertmanager.Alertmanager
Rules ruler.Ruler
Zeus zeus.Zeus Zeus zeus.Zeus
Licensing licensing.Licensing Licensing licensing.Licensing
Emailing emailing.Emailing Emailing emailing.Emailing
Sharder sharder.Sharder Sharder sharder.Sharder
StatsReporter statsreporter.StatsReporter
Modules Modules Modules Modules
Handlers Handlers Handlers Handlers
} }
@ -48,7 +52,7 @@ func New(
zeusConfig zeus.Config, zeusConfig zeus.Config,
zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config], zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config],
licenseConfig licensing.Config, licenseConfig licensing.Config,
licenseProviderFactoryCb func(sqlstore.SQLStore, zeus.Zeus, organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config], licenseProviderFactory func(sqlstore.SQLStore, zeus.Zeus, organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config],
emailingProviderFactories factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]], emailingProviderFactories factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]],
cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]], cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]],
webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]], webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]],
@ -67,6 +71,18 @@ func New(
// Get the provider settings from instrumentation // Get the provider settings from instrumentation
providerSettings := instrumentation.ToProviderSettings() providerSettings := instrumentation.ToProviderSettings()
// Initialize analytics just after instrumentation, as providers might require it
analytics, err := factory.NewProviderFromNamedMap(
ctx,
providerSettings,
config.Analytics,
NewAnalyticsProviderFactories(),
config.Analytics.Provider(),
)
if err != nil {
return nil, err
}
// Initialize zeus from the available zeus provider factory. This is not config controlled // Initialize zeus from the available zeus provider factory. This is not config controlled
// and depends on the variant of the build. // and depends on the variant of the build.
zeus, err := zeusProviderFactory.New( zeus, err := zeusProviderFactory.New(
@ -193,7 +209,19 @@ func New(
return nil, err return nil, err
} }
licensingProviderFactory := licenseProviderFactoryCb(sqlstore, zeus, orgGetter) // Initialize ruler from the available ruler provider factories
ruler, err := factory.NewProviderFromNamedMap(
ctx,
providerSettings,
config.Ruler,
NewRulerProviderFactories(sqlstore),
"signoz",
)
if err != nil {
return nil, err
}
licensingProviderFactory := licenseProviderFactory(sqlstore, zeus, orgGetter)
licensing, err := licensingProviderFactory.New( licensing, err := licensingProviderFactory.New(
ctx, ctx,
providerSettings, providerSettings,
@ -204,16 +232,40 @@ func New(
} }
// Initialize all modules // Initialize all modules
modules := NewModules(sqlstore, jwt, emailing, providerSettings, orgGetter, alertmanager) modules := NewModules(sqlstore, jwt, emailing, providerSettings, orgGetter, alertmanager, analytics)
// Initialize all handlers for the modules // Initialize all handlers for the modules
handlers := NewHandlers(modules) handlers := NewHandlers(modules)
// Create a list of all stats collectors
statsCollectors := []statsreporter.StatsCollector{
alertmanager,
ruler,
modules.Dashboard,
modules.SavedView,
modules.User,
licensing,
}
// Initialize stats reporter from the available stats reporter provider factories
statsReporter, err := factory.NewProviderFromNamedMap(
ctx,
providerSettings,
config.StatsReporter,
NewStatsReporterProviderFactories(telemetrystore, statsCollectors, orgGetter, version.Info, config.Analytics),
config.StatsReporter.Provider(),
)
if err != nil {
return nil, err
}
registry, err := factory.NewRegistry( registry, err := factory.NewRegistry(
instrumentation.Logger(), instrumentation.Logger(),
factory.NewNamedService(factory.MustNewName("instrumentation"), instrumentation), factory.NewNamedService(factory.MustNewName("instrumentation"), instrumentation),
factory.NewNamedService(factory.MustNewName("analytics"), analytics),
factory.NewNamedService(factory.MustNewName("alertmanager"), alertmanager), factory.NewNamedService(factory.MustNewName("alertmanager"), alertmanager),
factory.NewNamedService(factory.MustNewName("licensing"), licensing), factory.NewNamedService(factory.MustNewName("licensing"), licensing),
factory.NewNamedService(factory.MustNewName("statsreporter"), statsReporter),
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -0,0 +1,216 @@
package analyticsstatsreporter
import (
"context"
"sync"
"time"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/analytics/segmentanalytics"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/analyticstypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/version"
)
type provider struct {
// settings
settings factory.ScopedProviderSettings
// config
config statsreporter.Config
// used to get telemetry details. srikanthcvv to move this to the querier layer
telemetryStore telemetrystore.TelemetryStore
// a list of collectors, used to collect stats from across the codebase
collectors []statsreporter.StatsCollector
// used to get organizations
orgGetter organization.Getter
// used to send stats to an analytics backend
analytics analytics.Analytics
// used to get build information
build version.Build
// used to get deployment information
deployment version.Deployment
// used to stop the provider
stopC chan struct{}
}
func NewFactory(telemetryStore telemetrystore.TelemetryStore, collectors []statsreporter.StatsCollector, orgGetter organization.Getter, build version.Build, analyticsConfig analytics.Config) factory.ProviderFactory[statsreporter.StatsReporter, statsreporter.Config] {
return factory.NewProviderFactory(factory.MustNewName("analytics"), func(ctx context.Context, settings factory.ProviderSettings, config statsreporter.Config) (statsreporter.StatsReporter, error) {
return New(ctx, settings, config, telemetryStore, collectors, orgGetter, build, analyticsConfig)
})
}
func New(
ctx context.Context,
providerSettings factory.ProviderSettings,
config statsreporter.Config,
telemetryStore telemetrystore.TelemetryStore,
collectors []statsreporter.StatsCollector,
orgGetter organization.Getter,
build version.Build,
analyticsConfig analytics.Config,
) (statsreporter.StatsReporter, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/statsreporter/analyticsstatsreporter")
deployment := version.NewDeployment()
analytics, err := segmentanalytics.New(ctx, providerSettings, analyticsConfig)
if err != nil {
return nil, err
}
return &provider{
settings: settings,
config: config,
telemetryStore: telemetryStore,
collectors: collectors,
orgGetter: orgGetter,
analytics: analytics,
build: build,
deployment: deployment,
stopC: make(chan struct{}),
}, nil
}
func (provider *provider) Start(ctx context.Context) error {
go func() {
if err := provider.analytics.Start(ctx); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to start analytics", "error", err)
}
}()
ticker := time.NewTicker(provider.config.Interval)
defer ticker.Stop()
for {
select {
case <-provider.stopC:
return nil
case <-ticker.C:
err := provider.Report(ctx)
if err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to report stats", "error", err)
}
}
}
}
func (provider *provider) Report(ctx context.Context) error {
orgs, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
if err != nil {
return err
}
for _, org := range orgs {
stats := provider.collectOrg(ctx, org.ID)
if len(stats) == 0 {
provider.settings.Logger().WarnContext(ctx, "no stats collected", "org_id", org.ID)
continue
}
stats["build.version"] = provider.build.Version()
stats["build.branch"] = provider.build.Branch()
stats["build.hash"] = provider.build.Hash()
stats["build.variant"] = provider.build.Variant()
stats["deployment.mode"] = provider.deployment.Mode()
stats["deployment.platform"] = provider.deployment.Platform()
stats["deployment.os"] = provider.deployment.OS()
stats["deployment.arch"] = provider.deployment.Arch()
provider.settings.Logger().DebugContext(ctx, "reporting stats", "stats", stats)
provider.analytics.Send(
ctx,
analyticstypes.Track{
UserId: org.ID.String(),
Event: "Stats Reported",
Properties: analyticstypes.NewPropertiesFromMap(stats),
Context: &analyticstypes.Context{
Extra: map[string]interface{}{
analyticstypes.KeyGroupID: org.ID.String(),
},
},
},
analyticstypes.Group{
UserId: org.ID.String(),
GroupId: org.ID.String(),
Traits: analyticstypes.
NewTraitsFromMap(stats).
SetName(org.DisplayName).
SetUsername(org.Name).
SetCreatedAt(org.CreatedAt),
},
analyticstypes.Identify{
UserId: org.ID.String(),
Traits: analyticstypes.
NewTraits().
SetName(org.DisplayName).
SetUsername(org.Name).
SetCreatedAt(org.CreatedAt),
},
)
}
return nil
}
func (provider *provider) Stop(ctx context.Context) error {
close(provider.stopC)
if err := provider.analytics.Stop(ctx); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to stop analytics", "error", err)
}
return nil
}
func (provider *provider) collectOrg(ctx context.Context, orgID valuer.UUID) map[string]any {
var wg sync.WaitGroup
wg.Add(len(provider.collectors))
stats := make(map[string]any, 0)
mtx := sync.Mutex{}
for _, collector := range provider.collectors {
go func(collector statsreporter.StatsCollector) {
defer wg.Done()
collectorStats, err := collector.Collect(ctx, orgID)
if err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to collect stats", "error", err)
return
}
mtx.Lock()
for k, v := range collectorStats {
stats[k] = v
}
mtx.Unlock()
}(collector)
}
wg.Wait()
var traces uint64
if err := provider.telemetryStore.ClickhouseDB().QueryRow(ctx, "SELECT COUNT(*) FROM signoz_traces.distributed_signoz_index_v3").Scan(&traces); err == nil {
stats["telemetry.traces.count"] = traces
}
var logs uint64
if err := provider.telemetryStore.ClickhouseDB().QueryRow(ctx, "SELECT COUNT(*) FROM signoz_logs.distributed_logs_v2").Scan(&logs); err == nil {
stats["telemetry.logs.count"] = logs
}
var metrics uint64
if err := provider.telemetryStore.ClickhouseDB().QueryRow(ctx, "SELECT COUNT(*) FROM signoz_metrics.distributed_samples_v4").Scan(&metrics); err == nil {
stats["telemetry.metrics.count"] = metrics
}
return stats
}

View File

@ -0,0 +1,38 @@
package statsreporter
import (
"time"
"github.com/SigNoz/signoz/pkg/factory"
)
type Config struct {
// Enabled is a flag to enable or disable the stats reporter.
Enabled bool `mapstructure:"enabled"`
// Interval is the interval at which the stats are collected.
Interval time.Duration `mapstructure:"interval"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("statsreporter"), newConfig)
}
func newConfig() factory.Config {
return Config{
Enabled: true,
Interval: 6 * time.Hour,
}
}
func (c Config) Validate() error {
return nil
}
func (c Config) Provider() string {
if c.Enabled {
return "analytics"
}
return "noop"
}

View File

@ -0,0 +1,36 @@
package noopstatsreporter
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/statsreporter"
)
type provider struct {
stopC chan struct{}
}
func NewFactory() factory.ProviderFactory[statsreporter.StatsReporter, statsreporter.Config] {
return factory.NewProviderFactory(factory.MustNewName("noop"), New)
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, config statsreporter.Config) (statsreporter.StatsReporter, error) {
return &provider{
stopC: make(chan struct{}),
}, nil
}
func (provider *provider) Start(ctx context.Context) error {
<-provider.stopC
return nil
}
func (provider *provider) Report(ctx context.Context) error {
return nil
}
func (provider *provider) Stop(ctx context.Context) error {
close(provider.stopC)
return nil
}

View File

@ -0,0 +1,18 @@
package statsreporter
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/valuer"
)
type StatsReporter interface {
factory.Service
Report(context.Context) error
}
type StatsCollector interface {
Collect(context.Context, valuer.UUID) (map[string]any, error)
}

View File

@ -146,6 +146,22 @@ func GetChannelByName(channels Channels, name string) (int, *Channel, error) {
return 0, nil, errors.Newf(errors.TypeNotFound, ErrCodeAlertmanagerChannelNotFound, "cannot find channel with name %s", name) return 0, nil, errors.Newf(errors.TypeNotFound, ErrCodeAlertmanagerChannelNotFound, "cannot find channel with name %s", name)
} }
func NewStatsFromChannels(channels Channels) map[string]any {
stats := make(map[string]any)
for _, channel := range channels {
key := "alertmanager.channel.type." + channel.Type
if _, ok := stats[key]; !ok {
stats[key] = int64(1)
} else {
stats[key] = stats[key].(int64) + 1
}
}
stats["alertmanager.channel.count"] = int64(len(channels))
return stats
}
func (c *Channel) Update(receiver Receiver) error { func (c *Channel) Update(receiver Receiver) error {
channel := NewChannelFromReceiver(receiver, c.OrgID) channel := NewChannelFromReceiver(receiver, c.OrgID)
if channel == nil { if channel == nil {

View File

@ -1,9 +1,15 @@
package analyticstypes package analyticstypes
import ( import (
"strings"
segment "github.com/segmentio/analytics-go/v3" segment "github.com/segmentio/analytics-go/v3"
) )
const (
KeyGroupID string = "groupId"
)
type Message = segment.Message type Message = segment.Message
type Group = segment.Group type Group = segment.Group
type Identify = segment.Identify type Identify = segment.Identify
@ -19,3 +25,21 @@ func NewTraits() Traits {
func NewProperties() Properties { func NewProperties() Properties {
return segment.NewProperties() return segment.NewProperties()
} }
func NewPropertiesFromMap(m map[string]any) Properties {
properties := NewProperties()
for k, v := range m {
properties.Set(strings.ReplaceAll(k, ".", "_"), v)
}
return properties
}
func NewTraitsFromMap(m map[string]any) Traits {
traits := NewTraits()
for k, v := range m {
traits.Set(strings.ReplaceAll(k, ".", "_"), v)
}
return traits
}

View File

@ -145,6 +145,68 @@ func NewGettableDashboardFromDashboard(dashboard *Dashboard) (*GettableDashboard
}, nil }, nil
} }
func NewStatsFromStorableDashboards(dashboards []*StorableDashboard) map[string]any {
stats := make(map[string]any)
stats["dashboard.panels.count"] = int64(0)
stats["dashboard.panels.traces.count"] = int64(0)
stats["dashboard.panels.metrics.count"] = int64(0)
stats["dashboard.panels.logs.count"] = int64(0)
for _, dashboard := range dashboards {
addStatsFromStorableDashboard(dashboard, stats)
}
stats["dashboard.count"] = int64(len(dashboards))
return stats
}
func addStatsFromStorableDashboard(dashboard *StorableDashboard, stats map[string]any) {
if dashboard.Data == nil {
return
}
if dashboard.Data["widgets"] == nil {
return
}
widgets, ok := dashboard.Data["widgets"]
if !ok {
return
}
data, ok := widgets.([]interface{})
if !ok {
return
}
for _, widget := range data {
sData, ok := widget.(map[string]interface{})
if ok && sData["query"] != nil {
stats["dashboard.panels.count"] = stats["dashboard.panels.count"].(int64) + 1
query, ok := sData["query"].(map[string]interface{})
if ok && query["queryType"] == "builder" && query["builder"] != nil {
builderData, ok := query["builder"].(map[string]interface{})
if ok && builderData["queryData"] != nil {
builderQueryData, ok := builderData["queryData"].([]interface{})
if ok {
for _, queryData := range builderQueryData {
data, ok := queryData.(map[string]interface{})
if ok {
if data["dataSource"] == "traces" {
stats["dashboard.panels.traces.count"] = stats["dashboard.panels.traces.count"].(int64) + 1
} else if data["dataSource"] == "metrics" {
stats["dashboard.panels.metrics.count"] = stats["dashboard.panels.metrics.count"].(int64) + 1
} else if data["dataSource"] == "logs" {
stats["dashboard.panels.logs.count"] = stats["dashboard.panels.logs.count"].(int64) + 1
}
}
}
}
}
}
}
}
}
func (storableDashboardData *StorableDashboardData) GetWidgetIds() []string { func (storableDashboardData *StorableDashboardData) GetWidgetIds() []string {
data := *storableDashboardData data := *storableDashboardData
widgetIds := []string{} widgetIds := []string{}

View File

@ -323,6 +323,13 @@ func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License,
} }
func NewStatsFromLicense(license *License) map[string]any {
return map[string]any{
"license.plan": license.PlanName.StringValue(),
"license.id": license.ID.StringValue(),
}
}
func (license *License) UpdateFeatures(features []*Feature) { func (license *License) UpdateFeatures(features []*Feature) {
license.Features = features license.Features = features
} }

View File

@ -2,6 +2,8 @@ package ruletypes
import ( import (
"context" "context"
"encoding/json"
"strings"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer" "github.com/SigNoz/signoz/pkg/valuer"
@ -18,6 +20,33 @@ type Rule struct {
OrgID string `bun:"org_id,type:text"` OrgID string `bun:"org_id,type:text"`
} }
func NewStatsFromRules(rules []*Rule) map[string]any {
stats := make(map[string]any)
for _, rule := range rules {
gettableRule := &GettableRule{}
if err := json.Unmarshal([]byte(rule.Data), gettableRule); err != nil {
continue
}
key := "rule.type." + strings.TrimSuffix(strings.ToLower(string(gettableRule.RuleType)), "_rule") + ".count"
if _, ok := stats[key]; !ok {
stats[key] = int64(1)
} else {
stats[key] = stats[key].(int64) + 1
}
key = "alert.type." + strings.TrimSuffix(strings.ToLower(string(gettableRule.AlertType)), "_based_alert") + ".count"
if _, ok := stats[key]; !ok {
stats[key] = int64(1)
} else {
stats[key] = stats[key].(int64) + 1
}
}
stats["rule.count"] = int64(len(rules))
return stats
}
type RuleStore interface { type RuleStore interface {
CreateRule(context.Context, *Rule, func(context.Context, valuer.UUID) error) (valuer.UUID, error) CreateRule(context.Context, *Rule, func(context.Context, valuer.UUID) error) (valuer.UUID, error)
EditRule(context.Context, *Rule, func(context.Context) error) error EditRule(context.Context, *Rule, func(context.Context) error) error

View File

@ -1,6 +1,8 @@
package types package types
import ( import (
"strings"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
@ -18,3 +20,18 @@ type SavedView struct {
Data string `json:"data" bun:"data,type:text,notnull"` Data string `json:"data" bun:"data,type:text,notnull"`
ExtraData string `json:"extraData" bun:"extra_data,type:text"` ExtraData string `json:"extraData" bun:"extra_data,type:text"`
} }
func NewStatsFromSavedViews(savedViews []*SavedView) map[string]any {
stats := make(map[string]any)
for _, savedView := range savedViews {
key := "savedview.source." + strings.ToLower(string(savedView.SourcePage)) + ".count"
if _, ok := stats[key]; !ok {
stats[key] = int64(1)
} else {
stats[key] = stats[key].(int64) + 1
}
}
stats["savedview.count"] = int64(len(savedViews))
return stats
}

View File

@ -72,6 +72,8 @@ type UserStore interface {
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*StorableAPIKeyUser, error) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*StorableAPIKeyUser, error)
RevokeAPIKey(ctx context.Context, id valuer.UUID, revokedByUserID valuer.UUID) error RevokeAPIKey(ctx context.Context, id valuer.UUID, revokedByUserID valuer.UUID) error
GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*StorableAPIKeyUser, error) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*StorableAPIKeyUser, error)
CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error)
} }
type GettableUser struct { type GettableUser struct {

155
pkg/version/deployment.go Normal file
View File

@ -0,0 +1,155 @@
package version
import (
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
gotime "time"
)
var (
current = Deployment{mode: "unknown", platform: "unknown", os: "unknown", arch: "unknown"}
once = sync.Once{}
)
type Deployment struct {
// mode of deployment, e.g. kubernetes, binary, etc.
mode string
// platform of deployment, e.g. heroku, render, aws, gcp, azure, digitalocean, etc.
platform string
// os of deployment, e.g. linux, darwin, etc.
os string
// arch of deployment, e.g. amd64, arm64, etc.
arch string
}
func NewDeployment() Deployment {
once.Do(func() {
current.mode = detectMode()
current.platform = detectPlatform()
current.os = runtime.GOOS
current.arch = runtime.GOARCH
})
return current
}
func (d Deployment) Mode() string {
return d.mode
}
func (d Deployment) Platform() string {
return d.platform
}
func (d Deployment) OS() string {
return d.os
}
func (d Deployment) Arch() string {
return d.arch
}
func detectMode() string {
// Check if running in Kubernetes
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
return "kubernetes"
}
// Check if running in a container and identify the runtime
if data, err := os.ReadFile("/proc/self/cgroup"); err == nil {
cgroupData := string(data)
switch {
case strings.Contains(cgroupData, "docker"):
return "docker"
case strings.Contains(cgroupData, "containerd"):
return "containerd"
case strings.Contains(cgroupData, "libpod") || strings.Contains(cgroupData, "podman"):
return "podman"
case strings.Contains(cgroupData, "crio"):
return "cri-o"
}
}
// Check if running as a binary
if exe, err := os.Executable(); err == nil {
// Check if the executable is in a standard binary location
exePath := filepath.Clean(exe)
if strings.HasPrefix(exePath, "/usr/local/bin/") ||
strings.HasPrefix(exePath, "/usr/bin/") ||
strings.HasPrefix(exePath, "/bin/") ||
strings.HasPrefix(exePath, "/opt/") {
return "binary"
}
// Check if the executable is in the current directory
if filepath.Dir(exePath) == "." || filepath.Dir(exePath) == filepath.Clean(os.Getenv("PWD")) {
return "binary"
}
}
return "unknown"
}
func detectPlatform() string {
// Check for PaaS platforms first as they use environment variables
switch {
case os.Getenv("DYNO") != "" || os.Getenv("HEROKU_APP_ID") != "":
return "heroku"
case os.Getenv("RENDER") != "" || os.Getenv("RENDER_SERVICE_ID") != "":
return "render"
}
// Try to detect cloud provider through metadata endpoints
client := &http.Client{Timeout: 1 * gotime.Second}
// AWS metadata
if req, err := http.NewRequest(http.MethodGet, "http://169.254.169.254/latest/meta-data/", nil); err == nil {
if resp, err := client.Do(req); err == nil {
resp.Body.Close()
if resp.StatusCode == 200 {
return "aws"
}
}
}
// GCP metadata
if req, err := http.NewRequest(http.MethodGet, "http://169.254.169.254/computeMetadata/v1/", nil); err == nil {
req.Header.Add("Metadata-Flavor", "Google")
if resp, err := client.Do(req); err == nil {
resp.Body.Close()
if resp.StatusCode == 200 {
return "gcp"
}
}
}
// Azure metadata
if req, err := http.NewRequest(http.MethodGet, "http://169.254.169.254/metadata/instance", nil); err == nil {
req.Header.Add("Metadata", "true")
if resp, err := client.Do(req); err == nil {
resp.Body.Close()
if resp.StatusCode == 200 {
return "azure"
}
}
}
// Digitalocean metadata
if req, err := http.NewRequest(http.MethodGet, "http://169.254.169.254/metadata/v1/", nil); err == nil {
if resp, err := client.Do(req); err == nil {
resp.Body.Close()
if resp.StatusCode == 200 {
return "digitalocean"
}
}
}
return "unknown"
}