Compare commits

...

18 Commits

Author SHA1 Message Date
grandwizard28
b10a9061ec refactor(.): final refactor 2025-01-16 17:34:03 +05:30
grandwizard28
fae0581d83 refactor(.): final refactor 2025-01-16 17:29:37 +05:30
grandwizard28
9d6928b3e6 feat(config): add tests for conf 2025-01-16 15:10:21 +05:30
grandwizard28
baaffd79de test(uri): add tests for uri 2025-01-16 14:11:07 +05:30
grandwizard28
59c416c222 refactor(config): move to top level 2025-01-16 14:05:21 +05:30
grandwizard28
63a7ae586b ci(go): run go mod tidy 2025-01-15 17:17:26 +05:30
grandwizard28
f0cef2d9d5 feat(instrumentation): initialize instrumentation 2025-01-15 17:16:46 +05:30
grandwizard28
616ada91dd feat(instrumentation): add a test instrumentation package 2025-01-15 17:16:46 +05:30
grandwizard28
aab6a1c914 feat(instrumentation): move to instrumentation package 2025-01-15 17:16:45 +05:30
grandwizard28
bc708cd891 feat(factory): embrace the factory pattern 2025-01-15 17:16:45 +05:30
grandwizard28
c2a11960c1 feat(migrations): add cloud integrations 2025-01-15 17:16:45 +05:30
grandwizard28
6d76d56dbd ci(git): rebase with main 2025-01-15 17:16:45 +05:30
grandwizard28
1977756591 feat(signoz): refactor connection config struct 2025-01-15 17:16:45 +05:30
grandwizard28
1d9c10a214 feat(signoz): make mattn the permanent driver 2025-01-15 17:16:45 +05:30
grandwizard28
57a25bf98f feat(signoz): remove references to sqlite 2025-01-15 17:16:45 +05:30
grandwizard28
35c310aa9d fix(tests): fix unit tests 2025-01-15 17:16:44 +05:30
grandwizard28
7c0481de7d refactor(migrations): move migrations into a single package 2025-01-15 17:16:42 +05:30
grandwizard28
147cf28024 refactor(config): refactor config package 2025-01-15 17:16:10 +05:30
99 changed files with 2926 additions and 1533 deletions

View File

@@ -3,8 +3,36 @@
# Do not modify this file # Do not modify this file
# #
##################### Instrumentation #####################
instrumentation:
logs:
level: info
enabled: false
processors:
batch:
exporter:
otlp:
endpoint: localhost:4317
traces:
enabled: false
processors:
batch:
exporter:
otlp:
endpoint: localhost:4317
metrics:
enabled: true
readers:
pull:
exporter:
prometheus:
host: "0.0.0.0"
port: 9090
##################### Web ##################### ##################### Web #####################
web: web:
# Whether to enable the web frontend
enabled: true
# The prefix to serve web on # The prefix to serve web on
prefix: / prefix: /
# The directory containing the static build files. # The directory containing the static build files.
@@ -29,4 +57,14 @@ cache:
# The password for authenticating with the Redis server, if required. # The password for authenticating with the Redis server, if required.
password: password:
# The Redis database number to use # The Redis database number to use
db: 0 db: 0
##################### SQLStore #####################
sqlstore:
# specifies the SQLStore provider to use.
provider: sqlite
# The maximum number of open connections to the database.
max_open_conns: 100
sqlite:
# The path to the SQLite database file.
path: /var/lib/signoz/signoz.db

View File

@@ -64,7 +64,6 @@ import (
const AppDbEngine = "sqlite" const AppDbEngine = "sqlite"
type ServerOptions struct { type ServerOptions struct {
SigNoz *signoz.SigNoz
PromConfigPath string PromConfigPath string
SkipTopLvlOpsPath string SkipTopLvlOpsPath string
HTTPHostPort string HTTPHostPort string
@@ -82,7 +81,6 @@ type ServerOptions struct {
GatewayUrl string GatewayUrl string
UseLogsNewSchema bool UseLogsNewSchema bool
UseTraceNewSchema bool UseTraceNewSchema bool
SkipWebFrontend bool
} }
// Server runs HTTP api service // Server runs HTTP api service
@@ -112,26 +110,15 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
} }
// NewServer creates and initializes Server // NewServer creates and initializes Server
func NewServer(serverOptions *ServerOptions) (*Server, error) { func NewServer(serverOptions *ServerOptions, config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
modelDao, err := dao.InitDao(signoz.SQLStore.SQLxDB())
modelDao, err := dao.InitDao("sqlite", baseconst.RELATIONAL_DATASOURCE_PATH)
if err != nil { if err != nil {
return nil, err return nil, err
} }
baseexplorer.InitWithDSN(baseconst.RELATIONAL_DATASOURCE_PATH) baseexplorer.InitWithDB(signoz.SQLStore.SQLxDB())
preferences.InitDB(signoz.SQLStore.SQLxDB())
if err := preferences.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH); err != nil { dashboards.InitDB(signoz.SQLStore.SQLxDB())
return nil, err
}
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
if err != nil {
return nil, err
}
localDB.SetMaxOpenConns(10)
gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix) gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
if err != nil { if err != nil {
@@ -139,7 +126,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} }
// initiate license manager // initiate license manager
lm, err := licensepkg.StartManager("sqlite", localDB) lm, err := licensepkg.StartManager("sqlite", signoz.SQLStore.SQLxDB())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -153,7 +140,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
if storage == "clickhouse" { if storage == "clickhouse" {
zap.L().Info("Using ClickHouse as datastore ...") zap.L().Info("Using ClickHouse as datastore ...")
qb := db.NewDataConnector( qb := db.NewDataConnector(
localDB, signoz.SQLStore.SQLxDB(),
serverOptions.PromConfigPath, serverOptions.PromConfigPath,
lm, lm,
serverOptions.MaxIdleConns, serverOptions.MaxIdleConns,
@@ -189,7 +176,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
rm, err := makeRulesManager(serverOptions.PromConfigPath, rm, err := makeRulesManager(serverOptions.PromConfigPath,
baseconst.GetAlertManagerApiPrefix(), baseconst.GetAlertManagerApiPrefix(),
serverOptions.RuleRepoURL, serverOptions.RuleRepoURL,
localDB, signoz.SQLStore.SQLxDB(),
reader, reader,
c, c,
serverOptions.DisableRules, serverOptions.DisableRules,
@@ -210,19 +197,16 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}() }()
// initiate opamp // initiate opamp
_, err = opAmpModel.InitDB(localDB) _ = opAmpModel.InitDB(signoz.SQLStore.SQLxDB())
if err != nil {
return nil, err
}
integrationsController, err := integrations.NewController(localDB) integrationsController, err := integrations.NewController(signoz.SQLStore.SQLxDB())
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"couldn't create integrations controller: %w", err, "couldn't create integrations controller: %w", err,
) )
} }
cloudIntegrationsController, err := cloudintegrations.NewController(localDB) cloudIntegrationsController, err := cloudintegrations.NewController(signoz.SQLStore.SQLxDB())
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"couldn't create cloud provider integrations controller: %w", err, "couldn't create cloud provider integrations controller: %w", err,
@@ -231,7 +215,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
// ingestion pipelines manager // ingestion pipelines manager
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController( logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
localDB, "sqlite", integrationsController.GetPipelinesForInstalledIntegrations, signoz.SQLStore.SQLxDB(), integrationsController.GetPipelinesForInstalledIntegrations,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -239,8 +223,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
// initiate agent config handler // initiate agent config handler
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{ agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
DB: localDB, DB: signoz.SQLStore.SQLxDB(),
DBEngine: AppDbEngine,
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController}, AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController},
}) })
if err != nil { if err != nil {
@@ -302,7 +285,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
usageManager: usageManager, usageManager: usageManager,
} }
httpServer, err := s.createPublicServer(apiHandler, serverOptions.SigNoz.Web) httpServer, err := s.createPublicServer(apiHandler, signoz.Web)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -351,7 +334,7 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
}, nil }, nil
} }
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web *web.Web) (*http.Server, error) { func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
r := baseapp.NewRouter() r := baseapp.NewRouter()
@@ -396,11 +379,9 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web *web.Web) (*
handler = handlers.CompressHandler(handler) handler = handlers.CompressHandler(handler)
if !s.serverOptions.SkipWebFrontend { err := web.AddToRouter(r)
err := web.AddToRouter(r) if err != nil {
if err != nil { return nil, err
return nil, err
}
} }
return &http.Server{ return &http.Server{

View File

@@ -1,18 +1,11 @@
package dao package dao
import ( import (
"fmt" "github.com/jmoiron/sqlx"
"go.signoz.io/signoz/ee/query-service/dao/sqlite" "go.signoz.io/signoz/ee/query-service/dao/sqlite"
) )
func InitDao(engine, path string) (ModelDao, error) { func InitDao(inputDB *sqlx.DB) (ModelDao, error) {
return sqlite.InitDB(inputDB)
switch engine {
case "sqlite":
return sqlite.InitDB(path)
default:
return nil, fmt.Errorf("qsdb type: %s is not supported in query service", engine)
}
} }

View File

@@ -65,8 +65,8 @@ func columnExists(db *sqlx.DB, tableName, columnName string) bool {
} }
// InitDB creates and extends base model DB repository // InitDB creates and extends base model DB repository
func InitDB(dataSourceName string) (*modelDao, error) { func InitDB(inputDB *sqlx.DB) (*modelDao, error) {
dao, err := basedsql.InitDB(dataSourceName) dao, err := basedsql.InitDB(inputDB)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -3,86 +3,28 @@ package main
import ( import (
"context" "context"
"flag" "flag"
"log" "fmt"
"os" "os"
"os/signal" "os/signal"
"strconv"
"syscall" "syscall"
"time" "time"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.signoz.io/signoz/ee/query-service/app" "go.signoz.io/signoz/ee/query-service/app"
"go.signoz.io/signoz/pkg/config"
signozconfig "go.signoz.io/signoz/pkg/config" signozconfig "go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider" "go.signoz.io/signoz/pkg/config/envprovider"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
baseconst "go.signoz.io/signoz/pkg/query-service/constants" baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/migrate"
"go.signoz.io/signoz/pkg/query-service/version" "go.signoz.io/signoz/pkg/query-service/version"
"go.signoz.io/signoz/pkg/signoz" "go.signoz.io/signoz/pkg/signoz"
"google.golang.org/grpc" pkgversion "go.signoz.io/signoz/pkg/version"
"google.golang.org/grpc/credentials/insecure"
prommodel "github.com/prometheus/common/model" prommodel "github.com/prometheus/common/model"
zapotlpencoder "github.com/SigNoz/zap_otlp/zap_otlp_encoder"
zapotlpsync "github.com/SigNoz/zap_otlp/zap_otlp_sync"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
func initZapLog(enableQueryServiceLogOTLPExport bool) *zap.Logger {
config := zap.NewProductionConfig()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
config.EncoderConfig.EncodeDuration = zapcore.MillisDurationEncoder
config.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
otlpEncoder := zapotlpencoder.NewOTLPEncoder(config.EncoderConfig)
consoleEncoder := zapcore.NewJSONEncoder(config.EncoderConfig)
defaultLogLevel := zapcore.InfoLevel
res := resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("query-service"),
)
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, os.Stdout, defaultLogLevel),
)
if enableQueryServiceLogOTLPExport {
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
conn, err := grpc.DialContext(ctx, baseconst.OTLPTarget, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to establish connection: %v", err)
} else {
logExportBatchSizeInt, err := strconv.Atoi(baseconst.LogExportBatchSize)
if err != nil {
logExportBatchSizeInt = 512
}
ws := zapcore.AddSync(zapotlpsync.NewOtlpSyncer(conn, zapotlpsync.Options{
BatchSize: logExportBatchSizeInt,
ResourceSchema: semconv.SchemaURL,
Resource: res,
}))
core = zapcore.NewTee(
zapcore.NewCore(consoleEncoder, os.Stdout, defaultLogLevel),
zapcore.NewCore(otlpEncoder, zapcore.NewMultiWriteSyncer(ws), defaultLogLevel),
)
}
}
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
return logger
}
func init() { func init() {
prommodel.NameValidationScheme = prommodel.UTF8Validation prommodel.NameValidationScheme = prommodel.UTF8Validation
} }
@@ -100,7 +42,6 @@ func main() {
var useLogsNewSchema bool var useLogsNewSchema bool
var useTraceNewSchema bool var useTraceNewSchema bool
var cacheConfigPath, fluxInterval string var cacheConfigPath, fluxInterval string
var enableQueryServiceLogOTLPExport bool
var preferSpanMetrics bool var preferSpanMetrics bool
var maxIdleConns int var maxIdleConns int
@@ -108,7 +49,6 @@ func main() {
var dialTimeout time.Duration var dialTimeout time.Duration
var gatewayUrl string var gatewayUrl string
var useLicensesV3 bool var useLicensesV3 bool
var skipWebFrontend bool
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs") flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces") flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces")
@@ -122,39 +62,39 @@ func main() {
flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)") flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)")
flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)") flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)")
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(the interval to exclude data from being cached to avoid incorrect cache for data in motion)") flag.StringVar(&fluxInterval, "flux-interval", "5m", "(the interval to exclude data from being cached to avoid incorrect cache for data in motion)")
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')") flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)") flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses") flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
flag.BoolVar(&skipWebFrontend, "skip-web-frontend", false, "skip web frontend")
flag.Parse() flag.Parse()
loggerMgr := initZapLog(enableQueryServiceLogOTLPExport)
zap.ReplaceGlobals(loggerMgr)
defer loggerMgr.Sync() // flushes buffer, if any
version.PrintVersion() version.PrintVersion()
config, err := signozconfig.New(context.Background(), signozconfig.ProviderSettings{ config, err := signoz.NewConfig(context.Background(), signozconfig.ResolverConfig{
ResolverSettings: confmap.ResolverSettings{ Uris: []string{"env:"},
URIs: []string{"signozenv:"}, ProviderFactories: []config.ProviderFactory{
ProviderFactories: []confmap.ProviderFactory{ envprovider.NewFactory(),
signozenvprovider.NewFactory(),
},
}, },
}) })
if err != nil { if err != nil {
zap.L().Fatal("Failed to create config", zap.Error(err)) zap.L().Fatal("Failed to create config", zap.Error(err))
} }
signoz, err := signoz.New(config, skipWebFrontend) instrumentation, err := instrumentation.New(context.Background(), pkgversion.Build{}, config.Instrumentation)
if err != nil {
fmt.Println(err, err.Error())
zap.L().Fatal("Failed to create instrumentation", zap.Error(err))
}
defer instrumentation.Stop(context.Background())
zap.ReplaceGlobals(instrumentation.Logger())
defer instrumentation.Logger().Sync() // flushes buffer, if any
signoz, err := signoz.New(context.Background(), instrumentation, config, signoz.NewProviderFactories())
if err != nil { if err != nil {
zap.L().Fatal("Failed to create signoz struct", zap.Error(err)) zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
} }
serverOptions := &app.ServerOptions{ serverOptions := &app.ServerOptions{
SigNoz: signoz,
HTTPHostPort: baseconst.HTTPHostPort, HTTPHostPort: baseconst.HTTPHostPort,
PromConfigPath: promConfigPath, PromConfigPath: promConfigPath,
SkipTopLvlOpsPath: skipTopLvlOpsPath, SkipTopLvlOpsPath: skipTopLvlOpsPath,
@@ -171,7 +111,6 @@ func main() {
GatewayUrl: gatewayUrl, GatewayUrl: gatewayUrl,
UseLogsNewSchema: useLogsNewSchema, UseLogsNewSchema: useLogsNewSchema,
UseTraceNewSchema: useTraceNewSchema, UseTraceNewSchema: useTraceNewSchema,
SkipWebFrontend: skipWebFrontend,
} }
// Read the jwt secret key // Read the jwt secret key
@@ -183,13 +122,7 @@ func main() {
zap.L().Info("JWT secret key set successfully.") zap.L().Info("JWT secret key set successfully.")
} }
if err := migrate.Migrate(baseconst.RELATIONAL_DATASOURCE_PATH); err != nil { server, err := app.NewServer(serverOptions, config, signoz)
zap.L().Error("Failed to migrate", zap.Error(err))
} else {
zap.L().Info("Migration successful")
}
server, err := app.NewServer(serverOptions)
if err != nil { if err != nil {
zap.L().Fatal("Failed to create server", zap.Error(err)) zap.L().Fatal("Failed to create server", zap.Error(err))
} }

20
go.mod
View File

@@ -9,8 +9,6 @@ require (
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
github.com/SigNoz/signoz-otel-collector v0.111.16 github.com/SigNoz/signoz-otel-collector v0.111.16
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974
github.com/antonmedv/expr v1.15.3 github.com/antonmedv/expr v1.15.3
github.com/auth0/go-jwt-middleware v1.0.1 github.com/auth0/go-jwt-middleware v1.0.1
github.com/cespare/xxhash/v2 v2.3.0 github.com/cespare/xxhash/v2 v2.3.0
@@ -20,6 +18,7 @@ require (
github.com/go-kit/log v0.2.1 github.com/go-kit/log v0.2.1
github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v8 v8.11.5
github.com/go-redis/redismock/v8 v8.11.5 github.com/go-redis/redismock/v8 v8.11.5
github.com/go-viper/mapstructure/v2 v2.1.0
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.1 github.com/gorilla/handlers v1.5.1
@@ -29,8 +28,9 @@ require (
github.com/jmoiron/sqlx v1.3.4 github.com/jmoiron/sqlx v1.3.4
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/knadh/koanf v1.5.0 github.com/knadh/koanf v1.5.0
github.com/knadh/koanf/v2 v2.1.1
github.com/mailru/easyjson v0.7.7 github.com/mailru/easyjson v0.7.7
github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mattn/go-sqlite3 v1.14.24
github.com/oklog/oklog v0.3.2 github.com/oklog/oklog v0.3.2
github.com/open-telemetry/opamp-go v0.5.0 github.com/open-telemetry/opamp-go v0.5.0
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.111.0 github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.111.0
@@ -48,6 +48,8 @@ require (
github.com/soheilhy/cmux v0.1.5 github.com/soheilhy/cmux v0.1.5
github.com/srikanthccv/ClickHouse-go-mock v0.9.0 github.com/srikanthccv/ClickHouse-go-mock v0.9.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/uptrace/bun v1.2.8
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8
go.opentelemetry.io/collector/confmap v1.17.0 go.opentelemetry.io/collector/confmap v1.17.0
go.opentelemetry.io/collector/pdata v1.17.0 go.opentelemetry.io/collector/pdata v1.17.0
go.opentelemetry.io/collector/processor v0.111.0 go.opentelemetry.io/collector/processor v0.111.0
@@ -65,7 +67,6 @@ require (
golang.org/x/net v0.33.0 golang.org/x/net v0.33.0
golang.org/x/oauth2 v0.23.0 golang.org/x/oauth2 v0.23.0
golang.org/x/text v0.21.0 golang.org/x/text v0.21.0
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.34.2 google.golang.org/protobuf v1.34.2
gopkg.in/segmentio/analytics-go.v3 v3.1.0 gopkg.in/segmentio/analytics-go.v3 v3.1.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@@ -99,6 +100,7 @@ require (
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect github.com/go-faster/errors v0.7.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect
@@ -106,7 +108,6 @@ require (
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
@@ -120,13 +121,13 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/klauspost/compress v1.17.10 // indirect github.com/klauspost/compress v1.17.10 // indirect
github.com/knadh/koanf/v2 v2.1.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-syslog/v4 v4.2.0 // indirect github.com/leodido/go-syslog/v4 v4.2.0 // indirect
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect
@@ -151,6 +152,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.0 // indirect
github.com/segmentio/backo-go v1.0.1 // indirect github.com/segmentio/backo-go v1.0.1 // indirect
@@ -162,8 +164,11 @@ require (
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect github.com/tklauser/numcpus v0.7.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/valyala/fastjson v1.6.4 // indirect github.com/valyala/fastjson v1.6.4 // indirect
github.com/vjeantet/grok v1.0.1 // indirect github.com/vjeantet/grok v1.0.1 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
@@ -212,12 +217,13 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/time v0.6.0 // indirect golang.org/x/time v0.6.0 // indirect
gonum.org/v1/gonum v0.15.1 // indirect gonum.org/v1/gonum v0.15.1 // indirect
google.golang.org/api v0.199.0 // indirect google.golang.org/api v0.199.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.67.1 // indirect
k8s.io/client-go v0.31.1 // indirect k8s.io/client-go v0.31.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect

28
go.sum
View File

@@ -70,12 +70,6 @@ github.com/SigNoz/prometheus v1.12.0 h1:+BXeIHyMOOWWa+xjhJ+x80JFva7r1WzWIfIhQ5PU
github.com/SigNoz/prometheus v1.12.0/go.mod h1:EqNM27OwmPfqMUk+E+XG1L9rfDFcyXnzzDrg0EPOfxA= github.com/SigNoz/prometheus v1.12.0/go.mod h1:EqNM27OwmPfqMUk+E+XG1L9rfDFcyXnzzDrg0EPOfxA=
github.com/SigNoz/signoz-otel-collector v0.111.16 h1:535uKH5Oux+35EsI+L3C6pnAP/Ye0PTCbVizXoL+VqE= github.com/SigNoz/signoz-otel-collector v0.111.16 h1:535uKH5Oux+35EsI+L3C6pnAP/Ye0PTCbVizXoL+VqE=
github.com/SigNoz/signoz-otel-collector v0.111.16/go.mod h1:HJ4m0LY1MPsuZmuRF7Ixb+bY8rxgRzI0VXzOedESsjg= github.com/SigNoz/signoz-otel-collector v0.111.16/go.mod h1:HJ4m0LY1MPsuZmuRF7Ixb+bY8rxgRzI0VXzOedESsjg=
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=
github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo=
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=
github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974/go.mod h1:fpiHtiboLJpIE5TtkQfiWx6xtnlA+uWmv+N9opETqKY=
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974 h1:G2JzCrqdeOTtAn4tDFZEg5gCAEYVRXcddG3ZlrFMumo=
github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974/go.mod h1:YtDal1xBRQfPRNo7iSU3W37RGT0jMW7Rnzk6EON3a4M=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -436,6 +430,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ionos-cloud/sdk-go/v6 v6.2.1 h1:mxxN+frNVmbFrmmFfXnBC3g2USYJrl6mc1LW2iNYbFY= github.com/ionos-cloud/sdk-go/v6 v6.2.1 h1:mxxN+frNVmbFrmmFfXnBC3g2USYJrl6mc1LW2iNYbFY=
github.com/ionos-cloud/sdk-go/v6 v6.2.1/go.mod h1:SXrO9OGyWjd2rZhAhEpdYN6VUAODzzqRdqA9BCviQtI= github.com/ionos-cloud/sdk-go/v6 v6.2.1/go.mod h1:SXrO9OGyWjd2rZhAhEpdYN6VUAODzzqRdqA9BCviQtI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -519,8 +515,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -661,6 +657,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
@@ -740,12 +738,22 @@ github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.2.8 h1:HEiLvy9wc7ehU5S02+O6NdV5BLz48lL4REPhTkMX3Dg=
github.com/uptrace/bun v1.2.8/go.mod h1:JBq0uBKsKqNT0Ccce1IAFZY337Wkf08c6F6qlmfOHE8=
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8 h1:Huqw7YhLFTbocbSv8NETYYXqKtwLa6XsciCWtjzWSWU=
github.com/uptrace/bun/dialect/sqlitedialect v1.2.8/go.mod h1:ni7h2uwIc5zPhxgmCMTEbefONc4XsVr/ATfz1Q7d3CE=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/vjeantet/grok v1.0.1 h1:2rhIR7J4gThTgcZ1m2JY4TrJZNgjn985U28kT2wQrJ4= github.com/vjeantet/grok v1.0.1 h1:2rhIR7J4gThTgcZ1m2JY4TrJZNgjn985U28kT2wQrJ4=
github.com/vjeantet/grok v1.0.1/go.mod h1:ax1aAchzC6/QMXMcyzHQGZWaW1l195+uMYIkCWPCNIo= github.com/vjeantet/grok v1.0.1/go.mod h1:ax1aAchzC6/QMXMcyzHQGZWaW1l195+uMYIkCWPCNIo=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@@ -1086,8 +1094,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=

14
pkg/cache/config.go vendored
View File

@@ -4,12 +4,9 @@ import (
"time" "time"
go_cache "github.com/patrickmn/go-cache" go_cache "github.com/patrickmn/go-cache"
"go.signoz.io/signoz/pkg/confmap" "go.signoz.io/signoz/pkg/factory"
) )
// Config satisfies the confmap.Config interface
var _ confmap.Config = (*Config)(nil)
type Memory struct { type Memory struct {
TTL time.Duration `mapstructure:"ttl"` TTL time.Duration `mapstructure:"ttl"`
CleanupInterval time.Duration `mapstructure:"cleanupInterval"` CleanupInterval time.Duration `mapstructure:"cleanupInterval"`
@@ -28,7 +25,11 @@ type Config struct {
Redis Redis `mapstructure:"redis"` Redis Redis `mapstructure:"redis"`
} }
func (c *Config) NewWithDefaults() confmap.Config { func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("cache"), newConfig)
}
func newConfig() factory.Config {
return &Config{ return &Config{
Provider: "memory", Provider: "memory",
Memory: Memory{ Memory: Memory{
@@ -42,8 +43,9 @@ func (c *Config) NewWithDefaults() confmap.Config {
DB: 0, DB: 0,
}, },
} }
} }
func (c *Config) Validate() error { func (c Config) Validate() error {
return nil return nil
} }

101
pkg/cache/memorycache/memory.go vendored Normal file
View File

@@ -0,0 +1,101 @@
package memorycache
import (
"context"
"fmt"
"reflect"
"time"
gocache "github.com/patrickmn/go-cache"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/factory"
)
type memory struct {
cc *gocache.Cache
}
func NewFactory() factory.ProviderFactory[cache.Cache, cache.Config] {
return factory.NewProviderFactory(factory.MustNewName("memory"), New)
}
func New(ctx context.Context, settings factory.ProviderSettings, config cache.Config) (cache.Cache, error) {
return &memory{cc: gocache.New(config.Memory.TTL, config.Memory.CleanupInterval)}, nil
}
// Connect does nothing
func (c *memory) Connect(_ context.Context) error {
return nil
}
// Store stores the data in the cache
func (c *memory) Store(_ context.Context, cacheKey string, data cache.CacheableEntity, ttl time.Duration) error {
// check if the data being passed is a pointer and is not nil
rv := reflect.ValueOf(data)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory")
}
c.cc.Set(cacheKey, data, ttl)
return nil
}
// Retrieve retrieves the data from the cache
func (c *memory) Retrieve(_ context.Context, cacheKey string, dest cache.CacheableEntity, allowExpired bool) (cache.RetrieveStatus, error) {
// check if the destination being passed is a pointer and is not nil
dstv := reflect.ValueOf(dest)
if dstv.Kind() != reflect.Pointer || dstv.IsNil() {
return cache.RetrieveStatusError, cache.WrapCacheableEntityErrors(reflect.TypeOf(dest), "inmemory")
}
// check if the destination value is settable
if !dstv.Elem().CanSet() {
return cache.RetrieveStatusError, fmt.Errorf("destination value is not settable, %s", dstv.Elem())
}
data, found := c.cc.Get(cacheKey)
if !found {
return cache.RetrieveStatusKeyMiss, nil
}
// check the type compatbility between the src and dest
srcv := reflect.ValueOf(data)
if !srcv.Type().AssignableTo(dstv.Type()) {
return cache.RetrieveStatusError, fmt.Errorf("src type is not assignable to dst type")
}
// set the value to from src to dest
dstv.Elem().Set(srcv.Elem())
return cache.RetrieveStatusHit, nil
}
// SetTTL sets the TTL for the cache entry
func (c *memory) SetTTL(_ context.Context, cacheKey string, ttl time.Duration) {
item, found := c.cc.Get(cacheKey)
if !found {
return
}
c.cc.Replace(cacheKey, item, ttl)
}
// Remove removes the cache entry
func (c *memory) Remove(_ context.Context, cacheKey string) {
c.cc.Delete(cacheKey)
}
// BulkRemove removes the cache entries
func (c *memory) BulkRemove(_ context.Context, cacheKeys []string) {
for _, cacheKey := range cacheKeys {
c.cc.Delete(cacheKey)
}
}
// Close does nothing
func (c *memory) Close(_ context.Context) error {
return nil
}
// Configuration returns the cache configuration
func (c *memory) Configuration() *cache.Memory {
return nil
}

View File

@@ -1,4 +1,4 @@
package memory package memorycache
import ( import (
"context" "context"
@@ -7,18 +7,21 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
_cache "go.signoz.io/signoz/pkg/cache" _cache "go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/factory"
) )
// TestNew tests the New function // TestNew tests the New function
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
assert.NotNil(t, c) assert.NotNil(t, c)
assert.NotNil(t, c.cc) assert.NotNil(t, c.(*memory).cc)
assert.NoError(t, c.Connect(context.Background())) assert.NoError(t, c.Connect(context.Background()))
} }
@@ -53,32 +56,35 @@ func (dce DCacheableEntity) UnmarshalBinary(data []byte) error {
// TestStore tests the Store function // TestStore tests the Store function
// this should fail because of nil pointer error // this should fail because of nil pointer error
func TestStoreWithNilPointer(t *testing.T) { func TestStoreWithNilPointer(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
var storeCacheableEntity *CacheableEntity var storeCacheableEntity *CacheableEntity
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second)) assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
} }
// this should fail because of no pointer error // this should fail because of no pointer error
func TestStoreWithStruct(t *testing.T) { func TestStoreWithStruct(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
var storeCacheableEntity CacheableEntity var storeCacheableEntity CacheableEntity
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second)) assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
} }
func TestStoreWithNonNilPointer(t *testing.T) { func TestStoreWithNonNilPointer(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -89,11 +95,12 @@ func TestStoreWithNonNilPointer(t *testing.T) {
// TestRetrieve tests the Retrieve function // TestRetrieve tests the Retrieve function
func TestRetrieveWithNilPointer(t *testing.T) { func TestRetrieveWithNilPointer(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -109,11 +116,12 @@ func TestRetrieveWithNilPointer(t *testing.T) {
} }
func TestRetrieveWitNonPointer(t *testing.T) { func TestRetrieveWitNonPointer(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -129,11 +137,12 @@ func TestRetrieveWitNonPointer(t *testing.T) {
} }
func TestRetrieveWithDifferentTypes(t *testing.T) { func TestRetrieveWithDifferentTypes(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -148,11 +157,8 @@ func TestRetrieveWithDifferentTypes(t *testing.T) {
} }
func TestRetrieveWithSameTypes(t *testing.T) { func TestRetrieveWithSameTypes(t *testing.T) {
opts := &_cache.Memory{ c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: _cache.Memory{TTL: 10 * time.Second, CleanupInterval: 10 * time.Second}})
TTL: 10 * time.Second, require.NoError(t, err)
CleanupInterval: 10 * time.Second,
}
c := New(opts)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -169,7 +175,8 @@ func TestRetrieveWithSameTypes(t *testing.T) {
// TestSetTTL tests the SetTTL function // TestSetTTL tests the SetTTL function
func TestSetTTL(t *testing.T) { func TestSetTTL(t *testing.T) {
c := New(&_cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second}) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: _cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second}})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -194,11 +201,11 @@ func TestSetTTL(t *testing.T) {
// TestRemove tests the Remove function // TestRemove tests the Remove function
func TestRemove(t *testing.T) { func TestRemove(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -216,11 +223,12 @@ func TestRemove(t *testing.T) {
// TestBulkRemove tests the BulkRemove function // TestBulkRemove tests the BulkRemove function
func TestBulkRemove(t *testing.T) { func TestBulkRemove(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,
@@ -244,11 +252,12 @@ func TestBulkRemove(t *testing.T) {
// TestCache tests the cache // TestCache tests the cache
func TestCache(t *testing.T) { func TestCache(t *testing.T) {
opts := &_cache.Memory{ opts := _cache.Memory{
TTL: 10 * time.Second, TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second, CleanupInterval: 10 * time.Second,
} }
c := New(opts) c, err := New(context.Background(), factory.ProviderSettings{}, _cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{ storeCacheableEntity := &CacheableEntity{
Key: "some-random-key", Key: "some-random-key",
Value: 1, Value: 1,

View File

@@ -1,4 +1,4 @@
package redis package rediscache
import ( import (
"context" "context"
@@ -8,16 +8,21 @@ import (
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
_cache "go.signoz.io/signoz/pkg/cache" _cache "go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/factory"
"go.uber.org/zap" "go.uber.org/zap"
) )
type cache struct { type cache struct {
client *redis.Client client *redis.Client
opts *_cache.Redis opts _cache.Redis
} }
func New(opts *_cache.Redis) *cache { func NewFactory() factory.ProviderFactory[_cache.Cache, _cache.Config] {
return &cache{opts: opts} return factory.NewProviderFactory(factory.MustNewName("redis"), New)
}
func New(ctx context.Context, settings factory.ProviderSettings, config _cache.Config) (_cache.Cache, error) {
return &cache{opts: config.Redis}, nil
} }
// WithClient creates a new cache with the given client // WithClient creates a new cache with the given client
@@ -87,11 +92,6 @@ func (c *cache) GetClient() *redis.Client {
return c.client return c.client
} }
// GetOptions returns the options
func (c *cache) GetOptions() *_cache.Redis {
return c.opts
}
// GetTTL returns the TTL for the cache entry // GetTTL returns the TTL for the cache entry
func (c *cache) GetTTL(ctx context.Context, cacheKey string) time.Duration { func (c *cache) GetTTL(ctx context.Context, cacheKey string) time.Duration {
ttl, err := c.client.TTL(ctx, cacheKey).Result() ttl, err := c.client.TTL(ctx, cacheKey).Result()

View File

@@ -1,4 +1,4 @@
package redis package rediscache
import ( import (
"context" "context"

View File

@@ -1,96 +0,0 @@
package memory
import (
"context"
"fmt"
"reflect"
"time"
go_cache "github.com/patrickmn/go-cache"
_cache "go.signoz.io/signoz/pkg/cache"
)
type cache struct {
cc *go_cache.Cache
}
func New(opts *_cache.Memory) *cache {
return &cache{cc: go_cache.New(opts.TTL, opts.CleanupInterval)}
}
// Connect does nothing
func (c *cache) Connect(_ context.Context) error {
return nil
}
// Store stores the data in the cache
func (c *cache) Store(_ context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error {
// check if the data being passed is a pointer and is not nil
rv := reflect.ValueOf(data)
if rv.Kind() != reflect.Pointer || rv.IsNil() {
return _cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory")
}
c.cc.Set(cacheKey, data, ttl)
return nil
}
// Retrieve retrieves the data from the cache
func (c *cache) Retrieve(_ context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) {
// check if the destination being passed is a pointer and is not nil
dstv := reflect.ValueOf(dest)
if dstv.Kind() != reflect.Pointer || dstv.IsNil() {
return _cache.RetrieveStatusError, _cache.WrapCacheableEntityErrors(reflect.TypeOf(dest), "inmemory")
}
// check if the destination value is settable
if !dstv.Elem().CanSet() {
return _cache.RetrieveStatusError, fmt.Errorf("destination value is not settable, %s", dstv.Elem())
}
data, found := c.cc.Get(cacheKey)
if !found {
return _cache.RetrieveStatusKeyMiss, nil
}
// check the type compatbility between the src and dest
srcv := reflect.ValueOf(data)
if !srcv.Type().AssignableTo(dstv.Type()) {
return _cache.RetrieveStatusError, fmt.Errorf("src type is not assignable to dst type")
}
// set the value to from src to dest
dstv.Elem().Set(srcv.Elem())
return _cache.RetrieveStatusHit, nil
}
// SetTTL sets the TTL for the cache entry
func (c *cache) SetTTL(_ context.Context, cacheKey string, ttl time.Duration) {
item, found := c.cc.Get(cacheKey)
if !found {
return
}
c.cc.Replace(cacheKey, item, ttl)
}
// Remove removes the cache entry
func (c *cache) Remove(_ context.Context, cacheKey string) {
c.cc.Delete(cacheKey)
}
// BulkRemove removes the cache entries
func (c *cache) BulkRemove(_ context.Context, cacheKeys []string) {
for _, cacheKey := range cacheKeys {
c.cc.Delete(cacheKey)
}
}
// Close does nothing
func (c *cache) Close(_ context.Context) error {
return nil
}
// Configuration returns the cache configuration
func (c *cache) Configuration() *_cache.Memory {
return nil
}

90
pkg/config/conf.go Normal file
View File

@@ -0,0 +1,90 @@
package config
import (
"github.com/go-viper/mapstructure/v2"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/v2"
)
const (
KoanfDelimiter string = "::"
)
// Conf is a wrapper around the koanf library.
type Conf struct {
*koanf.Koanf
}
// NewConf creates a new Conf instance.
func NewConf() *Conf {
return &Conf{koanf.New(KoanfDelimiter)}
}
// NewConfFromMap creates a new Conf instance from a map.
func NewConfFromMap(m map[string]any) (*Conf, error) {
conf := NewConf()
if err := conf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil {
return nil, err
}
return conf, nil
}
// MustNewConfFromMap creates a new Conf instance from a map.
// It panics if the conf cannot be created.
func MustNewConfFromMap(m map[string]any) *Conf {
conf, err := NewConfFromMap(m)
if err != nil {
panic(err)
}
return conf
}
// Merge merges the current configuration with the input configuration.
func (conf *Conf) Merge(input *Conf) error {
return conf.Koanf.Merge(input.Koanf)
}
// Merge merges the current configuration with the input configuration.
func (conf *Conf) MergeAt(input *Conf, path string) error {
return conf.Koanf.MergeAt(input.Koanf, path)
}
// Unmarshal unmarshals the configuration at the given path into the input.
// It uses a WeaklyTypedInput to allow for more flexible unmarshalling.
func (conf *Conf) Unmarshal(path string, input any) error {
dc := &mapstructure.DecoderConfig{
TagName: "mapstructure",
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToSliceHookFunc(","),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.TextUnmarshallerHookFunc(),
),
Result: input,
}
return conf.Koanf.UnmarshalWithConf(path, input, koanf.UnmarshalConf{Tag: "mapstructure", DecoderConfig: dc})
}
// Set sets the configuration at the given key.
// It decodes the input into a map as per mapstructure.Decode and then merges it into the configuration.
func (conf *Conf) Set(key string, input any) error {
m := map[string]any{}
err := mapstructure.Decode(input, &m)
if err != nil {
return err
}
newConf := NewConf()
if err := newConf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil {
return err
}
if err := conf.Koanf.MergeAt(newConf.Koanf, key); err != nil {
return err
}
return nil
}

38
pkg/config/conf_test.go Normal file
View File

@@ -0,0 +1,38 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfMerge(t *testing.T) {
testCases := []struct {
name string
conf *Conf
input *Conf
expected *Conf
pass bool
}{
{name: "Empty", conf: NewConf(), input: NewConf(), expected: NewConf(), pass: true},
{name: "Merge", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"c": "d"}), expected: MustNewConfFromMap(map[string]any{"a": "b", "c": "d"}), pass: true},
{name: "NestedMerge", conf: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2"}}), input: MustNewConfFromMap(map[string]any{"a": map[string]any{"d": "v1", "e": "v2"}}), expected: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2", "d": "v1", "e": "v2"}}), pass: true},
{name: "Override", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"a": "c"}), expected: MustNewConfFromMap(map[string]any{"a": "c"}), pass: true},
}
t.Parallel()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.conf.Merge(tc.input)
if !tc.pass {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tc.expected.Raw(), tc.conf.Raw())
assert.Equal(t, tc.expected.Raw(), tc.conf.Raw())
})
}
}

View File

@@ -3,33 +3,34 @@ package config
import ( import (
"context" "context"
"go.signoz.io/signoz/pkg/cache" "go.signoz.io/signoz/pkg/factory"
signozconfmap "go.signoz.io/signoz/pkg/confmap"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/web"
) )
// This map contains the default values of all config structs func New(ctx context.Context, resolverConfig ResolverConfig, configFactories []factory.ConfigFactory) (*Conf, error) {
var ( // Get the config from the resolver
defaults = map[string]signozconfmap.Config{ resolver, err := NewResolver(resolverConfig)
"instrumentation": &instrumentation.Config{},
"web": &web.Config{},
"cache": &cache.Config{},
}
)
// Config defines the entire configuration of signoz.
type Config struct {
Instrumentation instrumentation.Config `mapstructure:"instrumentation"`
Web web.Config `mapstructure:"web"`
Cache cache.Config `mapstructure:"cache"`
}
func New(ctx context.Context, settings ProviderSettings) (*Config, error) {
provider, err := NewProvider(settings)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return provider.Get(ctx) resolvedConf, err := resolver.Do(ctx)
if err != nil {
return nil, err
}
conf := NewConf()
// Set the default configs
for _, factory := range configFactories {
c := factory.New()
if err := conf.Set(factory.Name().String(), c); err != nil {
return nil, err
}
}
err = conf.Merge(resolvedConf)
if err != nil {
return nil, err
}
return conf, nil
} }

View File

@@ -1,54 +0,0 @@
package config
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider"
"go.signoz.io/signoz/pkg/web"
)
func TestNewWithSignozEnvProvider(t *testing.T) {
t.Setenv("SIGNOZ__WEB__PREFIX", "/web")
t.Setenv("SIGNOZ__WEB__DIRECTORY", "/build")
t.Setenv("SIGNOZ__CACHE__PROVIDER", "redis")
t.Setenv("SIGNOZ__CACHE__REDIS__HOST", "127.0.0.1")
config, err := New(context.Background(), ProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{"signozenv:"},
ProviderFactories: []confmap.ProviderFactory{
signozenvprovider.NewFactory(),
},
},
})
require.NoError(t, err)
expected := &Config{
Web: web.Config{
Prefix: "/web",
Directory: "/build",
},
Cache: cache.Config{
Provider: "redis",
Memory: cache.Memory{
TTL: time.Duration(-1),
CleanupInterval: 1 * time.Minute,
},
Redis: cache.Redis{
Host: "127.0.0.1",
Port: 6379,
Password: "",
DB: 0,
},
},
}
assert.Equal(t, expected, config)
}

View File

@@ -0,0 +1,71 @@
package envprovider
import (
"context"
"strings"
koanfenv "github.com/knadh/koanf/providers/env"
"go.signoz.io/signoz/pkg/config"
)
const (
prefix string = "SIGNOZ_"
scheme string = "env"
)
type provider struct{}
func NewFactory() config.ProviderFactory {
return config.NewProviderFactory(New)
}
func New(config config.ProviderConfig) config.Provider {
return &provider{}
}
func (provider *provider) Scheme() string {
return scheme
}
func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) {
conf := config.NewConf()
err := conf.Load(
koanfenv.Provider(
prefix,
// Do not set this to `_`. The correct delimiter is being set by the custom callback provided below.
// Since this had to be passed, using `config.KoanfDelimiter` eliminates any possible side effect.
config.KoanfDelimiter,
func(s string) string {
s = strings.ToLower(strings.TrimPrefix(s, prefix))
return provider.cb(s, config.KoanfDelimiter)
},
),
nil,
)
return conf, err
}
func (provider *provider) cb(s string, delim string) string {
delims := []rune(delim)
runes := []rune(s)
result := make([]rune, 0, len(runes))
for i := 0; i < len(runes); i++ {
// Check for double underscore pattern
if i < len(runes)-1 && runes[i] == '_' && runes[i+1] == '_' {
result = append(result, '_')
i++ // Skip next underscore
continue
}
if runes[i] == '_' {
result = append(result, delims...)
continue
}
result = append(result, runes[i])
}
return string(result)
}

View File

@@ -0,0 +1,78 @@
package envprovider
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/config"
)
func TestGetWithStrings(t *testing.T) {
t.Setenv("SIGNOZ_K1_K2", "string")
t.Setenv("SIGNOZ_K3__K4", "string")
t.Setenv("SIGNOZ_K5__K6_K7__K8", "string")
t.Setenv("SIGNOZ_K9___K10", "string")
t.Setenv("SIGNOZ_K11____K12", "string")
expected := map[string]any{
"k1::k2": "string",
"k3_k4": "string",
"k5_k6::k7_k8": "string",
"k9_::k10": "string",
"k11__k12": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("env:"))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypes(t *testing.T) {
t.Setenv("SIGNOZ_BOOL", "true")
t.Setenv("SIGNOZ_STRING", "string")
t.Setenv("SIGNOZ_INT", "1")
t.Setenv("SIGNOZ_SLICE", "[1,2]")
expected := map[string]any{
"bool": "true",
"int": "1",
"slice": "[1,2]",
"string": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("env:"))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypesWithUnmarshal(t *testing.T) {
t.Setenv("SIGNOZ_BOOL", "true")
t.Setenv("SIGNOZ_STRING", "string")
t.Setenv("SIGNOZ_INT", "1")
type test struct {
Bool bool `mapstructure:"bool"`
String string `mapstructure:"string"`
Int int `mapstructure:"int"`
}
expected := test{
Bool: true,
String: "string",
Int: 1,
}
provider := New(config.ProviderConfig{})
conf, err := provider.Get(context.Background(), config.MustNewUri("env:"))
require.NoError(t, err)
actual := test{}
err = conf.Unmarshal("", &actual)
require.NoError(t, err)
assert.Equal(t, expected, actual)
}

View File

@@ -0,0 +1,34 @@
package fileprovider
import (
"context"
koanfyaml "github.com/knadh/koanf/parsers/yaml"
koanffile "github.com/knadh/koanf/providers/file"
"go.signoz.io/signoz/pkg/config"
)
const (
scheme string = "file"
)
type provider struct{}
func NewFactory() config.ProviderFactory {
return config.NewProviderFactory(New)
}
func New(config config.ProviderConfig) config.Provider {
return &provider{}
}
func (provider *provider) Scheme() string {
return scheme
}
func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) {
conf := config.NewConf()
err := conf.Load(koanffile.Provider(uri.Value()), koanfyaml.Parser())
return conf, err
}

View File

@@ -0,0 +1,68 @@
package fileprovider
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/config"
)
func TestGetWithStrings(t *testing.T) {
expected := map[string]any{
"k1::k2": "string",
"k3_k4": "string",
"k5_k6::k7_k8": "string",
"k9_::k10": "string",
"k11__k12": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "strings.yaml")))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypes(t *testing.T) {
expected := map[string]any{
"bool": true,
"int": 1,
"slice": []any{1, 2},
"string": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml")))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypesWithUnmarshal(t *testing.T) {
type test struct {
Bool bool `mapstructure:"bool"`
String string `mapstructure:"string"`
Int int `mapstructure:"int"`
Slice []any `mapstructure:"slice"`
}
expected := test{
Bool: true,
String: "string",
Int: 1,
Slice: []any{1, 2},
}
provider := New(config.ProviderConfig{})
conf, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml")))
require.NoError(t, err)
actual := test{}
err = conf.Unmarshal("", &actual)
require.NoError(t, err)
assert.Equal(t, expected, actual)
}

View File

@@ -0,0 +1,6 @@
bool: true
string: string
int: 1
slice:
- 1
- 2

View File

@@ -0,0 +1,8 @@
k1:
k2: string
k3_k4: string
k5_k6:
k7_k8: string
k9_:
k10: string
k11__k12: string

View File

@@ -2,51 +2,38 @@ package config
import ( import (
"context" "context"
"fmt"
"go.opentelemetry.io/collector/confmap"
) )
// Provides the configuration for signoz. // NewProviderFunc is a function that creates a new provider.
type NewProviderFunc = func(ProviderConfig) Provider
// ProviderFactory is a factory that creates a new provider.
type ProviderFactory interface {
New(ProviderConfig) Provider
}
// NewProviderFactory creates a new provider factory.
func NewProviderFactory(f NewProviderFunc) ProviderFactory {
return &providerFactory{f: f}
}
// providerFactory is a factory that implements the ProviderFactory interface.
type providerFactory struct {
f NewProviderFunc
}
// New creates a new provider.
func (factory *providerFactory) New(config ProviderConfig) Provider {
return factory.f(config)
}
// ProviderConfig is the configuration for a provider.
type ProviderConfig struct{}
// Provider is an interface that represents a provider.
type Provider interface { type Provider interface {
// Get returns the configuration, or error otherwise. // Get returns the configuration for the given URI.
Get(ctx context.Context) (*Config, error) Get(context.Context, Uri) (*Conf, error)
} // Scheme returns the scheme of the provider.
Scheme() string
type provider struct {
resolver *confmap.Resolver
}
// ProviderSettings are the settings to configure the behavior of the Provider.
type ProviderSettings struct {
// ResolverSettings are the settings to configure the behavior of the confmap.Resolver.
ResolverSettings confmap.ResolverSettings
}
// NewProvider returns a new Provider that provides the entire configuration.
// See https://github.com/open-telemetry/opentelemetry-collector/blob/main/otelcol/configprovider.go for
// more details
func NewProvider(settings ProviderSettings) (Provider, error) {
resolver, err := confmap.NewResolver(settings.ResolverSettings)
if err != nil {
return nil, err
}
return &provider{
resolver: resolver,
}, nil
}
func (provider *provider) Get(ctx context.Context) (*Config, error) {
conf, err := provider.resolver.Resolve(ctx)
if err != nil {
return nil, fmt.Errorf("cannot resolve configuration: %w", err)
}
config, err := unmarshal(conf)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal configuration: %w", err)
}
return config, nil
} }

87
pkg/config/resolver.go Normal file
View File

@@ -0,0 +1,87 @@
package config
import (
"context"
"errors"
"fmt"
)
type ResolverConfig struct {
// Each string or `uri` must follow "<scheme>:<value>" format. This format is compatible with the URI definition
// defined at https://datatracker.ietf.org/doc/html/rfc3986".
// It is required to have at least one uri.
Uris []string
// ProviderFactories is a slice of Provider factories.
// It is required to have at least one factory.
ProviderFactories []ProviderFactory
}
type Resolver struct {
uris []Uri
providers map[string]Provider
}
func NewResolver(config ResolverConfig) (*Resolver, error) {
if len(config.Uris) == 0 {
return nil, errors.New("cannot build resolver, no uris have been provided")
}
if len(config.ProviderFactories) == 0 {
return nil, errors.New("cannot build resolver, no providers have been provided")
}
uris := make([]Uri, len(config.Uris))
for i, inputUri := range config.Uris {
uri, err := NewUri(inputUri)
if err != nil {
return nil, err
}
uris[i] = uri
}
providers := make(map[string]Provider, len(config.ProviderFactories))
for _, factory := range config.ProviderFactories {
provider := factory.New(ProviderConfig{})
scheme := provider.Scheme()
// Check that the scheme is unique.
if _, ok := providers[scheme]; ok {
return nil, fmt.Errorf("cannot build resolver, duplicate scheme %q found", scheme)
}
providers[provider.Scheme()] = provider
}
return &Resolver{
uris: uris,
providers: providers,
}, nil
}
func (resolver *Resolver) Do(ctx context.Context) (*Conf, error) {
conf := NewConf()
for _, uri := range resolver.uris {
currentConf, err := resolver.get(ctx, uri)
if err != nil {
return nil, err
}
if err = conf.Merge(currentConf); err != nil {
return nil, fmt.Errorf("cannot merge config: %w", err)
}
}
return conf, nil
}
func (resolver *Resolver) get(ctx context.Context, uri Uri) (*Conf, error) {
provider, ok := resolver.providers[uri.scheme]
if !ok {
return nil, fmt.Errorf("cannot find provider with schema %q", uri.scheme)
}
return provider.Get(ctx, uri)
}

View File

@@ -1,49 +0,0 @@
package config
import (
"fmt"
"go.opentelemetry.io/collector/confmap"
)
// unmarshal converts a confmap.Conf into a Config struct.
// It splits the input confmap into a map of key-value pairs, fetches the corresponding
// signozconfmap.Config interface by name, merges it with the default config, validates it,
// and then creates a new confmap from the parsed map to unmarshal into the Config struct.
func unmarshal(conf *confmap.Conf) (*Config, error) {
raw := make(map[string]any)
if err := conf.Unmarshal(&raw); err != nil {
return nil, err
}
parsed := make(map[string]any)
// To help the defaults kick in, we need iterate over the default map instead of the raw values
for k, v := range defaults {
sub, err := conf.Sub(k)
if err != nil {
return nil, fmt.Errorf("cannot read config for %q: %w", k, err)
}
d := v.NewWithDefaults()
if err := sub.Unmarshal(&d); err != nil {
return nil, fmt.Errorf("cannot merge config for %q: %w", k, err)
}
err = d.Validate()
if err != nil {
return nil, fmt.Errorf("failed to validate config for for %q: %w", k, err)
}
parsed[k] = d
}
parsedConf := confmap.NewFromStringMap(parsed)
config := new(Config)
err := parsedConf.Unmarshal(config)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal config: %w", err)
}
return config, nil
}

View File

@@ -1,33 +0,0 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.signoz.io/signoz/pkg/instrumentation"
)
func TestUnmarshalForInstrumentation(t *testing.T) {
input := confmap.NewFromStringMap(
map[string]any{
"instrumentation": map[string]any{
"logs": map[string]bool{
"enabled": true,
},
},
},
)
expected := &Config{
Instrumentation: instrumentation.Config{
Logs: instrumentation.LogsConfig{
Enabled: true,
},
},
}
cfg, err := unmarshal(input)
require.NoError(t, err)
assert.Equal(t, expected.Instrumentation, cfg.Instrumentation)
}

46
pkg/config/uri.go Normal file
View File

@@ -0,0 +1,46 @@
package config
import (
"fmt"
"regexp"
)
var (
// uriRegex is a regex that matches the URI format. It complies with the URI definition defined at https://datatracker.ietf.org/doc/html/rfc3986.
// The format is "<scheme>:<value>".
uriRegex = regexp.MustCompile(`(?s:^(?P<Scheme>[A-Za-z][A-Za-z0-9+.-]+):(?P<Value>.*)$)`)
)
type Uri struct {
scheme string
value string
}
func NewUri(input string) (Uri, error) {
submatches := uriRegex.FindStringSubmatch(input)
if len(submatches) != 3 {
return Uri{}, fmt.Errorf("invalid uri: %q", input)
}
return Uri{
scheme: submatches[1],
value: submatches[2],
}, nil
}
func MustNewUri(input string) Uri {
uri, err := NewUri(input)
if err != nil {
panic(err)
}
return uri
}
func (uri Uri) Scheme() string {
return uri.scheme
}
func (uri Uri) Value() string {
return uri.value
}

35
pkg/config/uri_test.go Normal file
View File

@@ -0,0 +1,35 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewUri(t *testing.T) {
testCases := []struct {
input string
expected Uri
pass bool
}{
{input: "file:/path/1", expected: Uri{scheme: "file", value: "/path/1"}, pass: true},
{input: "file:", expected: Uri{scheme: "file", value: ""}, pass: true},
{input: "env:", expected: Uri{scheme: "env", value: ""}, pass: true},
{input: "scheme", expected: Uri{}, pass: false},
}
for _, tc := range testCases {
uri, err := NewUri(tc.input)
if !tc.pass {
assert.Error(t, err)
continue
}
require.NoError(t, err)
assert.NotPanics(t, func() { MustNewUri(tc.input) })
assert.Equal(t, tc.expected, uri)
assert.Equal(t, tc.expected.Scheme(), uri.scheme)
assert.Equal(t, tc.expected.Value(), uri.value)
}
}

View File

@@ -1,9 +0,0 @@
package confmap
// Config is an interface that defines methods for creating and validating configurations.
type Config interface {
// New creates a new instance of the configuration with default values.
NewWithDefaults() Config
// Validate the configuration and returns an error if invalid.
Validate() error
}

View File

@@ -1,3 +0,0 @@
// Package confmap is a wrapper on top of the confmap defined here:
// https://github.com/open-telemetry/opentelemetry-collector/blob/main/otelcol/configprovider.go/
package confmap

View File

@@ -1,94 +0,0 @@
package signozenvprovider
import (
"context"
"fmt"
"os"
"regexp"
"sort"
"strings"
"go.opentelemetry.io/collector/confmap"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
)
const (
schemeName string = "signozenv"
envPrefix string = "signoz"
separator string = "__"
envPrefixWithOneSeparator string = "signoz_"
envRegexString string = `^[a-zA-Z][a-zA-Z0-9_]*$`
)
var (
envRegex = regexp.MustCompile(envRegexString)
)
type provider struct {
logger *zap.Logger
}
// NewFactory returns a factory for a confmap.Provider that reads the configuration from the environment.
// All variables starting with `SIGNOZ__` are read from the environment.
// The separator is `__` (2 underscores) in order to incorporate env variables having keys with a single `_`
func NewFactory() confmap.ProviderFactory {
return confmap.NewProviderFactory(newProvider)
}
func newProvider(settings confmap.ProviderSettings) confmap.Provider {
return &provider{
logger: settings.Logger,
}
}
func (provider *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) {
if !strings.HasPrefix(uri, schemeName+":") {
return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName)
}
// Read and Sort environment variables for consistent output
envvars := os.Environ()
sort.Strings(envvars)
// Create a map m containing key value pairs
m := make(map[string]any)
for _, envvar := range envvars {
parts := strings.SplitN(envvar, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.ToLower(parts[0])
val := parts[1]
if strings.HasPrefix(key, envPrefixWithOneSeparator) {
// Remove the envPrefix from the key
key = strings.Replace(key, envPrefix+separator, "", 1)
// Check whether the resulting key matches with the regex
if !envRegex.MatchString(key) {
provider.logger.Warn("Configuration references invalid environment variable key", zap.String("key", key))
continue
}
// Convert key into yaml format
key = strings.ToLower(strings.ReplaceAll(key, separator, confmap.KeyDelimiter))
m[key] = val
}
}
out, err := yaml.Marshal(m)
if err != nil {
return nil, err
}
return confmap.NewRetrievedFromYAML(out)
}
func (*provider) Scheme() string {
return schemeName
}
func (*provider) Shutdown(context.Context) error {
return nil
}

View File

@@ -1,40 +0,0 @@
package signozenvprovider
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
)
func createProvider() confmap.Provider {
return NewFactory().Create(confmaptest.NewNopProviderSettings())
}
func TestValidateProviderScheme(t *testing.T) {
assert.NoError(t, confmaptest.ValidateProviderScheme(createProvider()))
}
func TestRetrieve(t *testing.T) {
t.Setenv("SIGNOZ__STORAGE__DSN", "localhost:9000")
t.Setenv("SIGNOZ__SIGNOZ_ENABLED", "true")
t.Setenv("SIGNOZ__INSTRUMENTATION__LOGS__ENABLED", "true")
expected := confmap.NewFromStringMap(map[string]any{
"storage::dsn": "localhost:9000",
"signoz_enabled": "true",
"instrumentation::logs::enabled": "true",
})
signoz := createProvider()
retrieved, err := signoz.Retrieve(context.Background(), schemeName+":", nil)
require.NoError(t, err)
actual, err := retrieved.AsConf()
require.NoError(t, err)
assert.Equal(t, expected.ToStringMap(), actual.ToStringMap())
assert.NoError(t, signoz.Shutdown(context.Background()))
}

37
pkg/factory/config.go Normal file
View File

@@ -0,0 +1,37 @@
package factory
// Config is an interface that defines methods for creating and validating configurations.
type Config interface {
// Validate the configuration and returns an error if invalid.
Validate() error
}
// NewConfigFunc is a function that creates a new config.
type NewConfigFunc func() Config
// ConfigFactory is a factory that creates a new config.
type ConfigFactory interface {
Named
New() Config
}
// configFactory is a factory that implements the ConfigFactory interface.
type configFactory struct {
name Name
newConfigFunc NewConfigFunc
}
// New creates a new config.
func (factory *configFactory) Name() Name {
return factory.name
}
// New creates a new config.
func (factory *configFactory) New() Config {
return factory.newConfigFunc()
}
// Creates a new config factory.
func NewConfigFactory(name Name, f NewConfigFunc) ConfigFactory {
return &configFactory{name: name, newConfigFunc: f}
}

View File

@@ -1,12 +1,14 @@
package registry package factorytest
import ( import (
"context" "context"
"net" "net"
"net/http" "net/http"
"go.signoz.io/signoz/pkg/factory"
) )
var _ NamedService = (*httpService)(nil) var _ factory.Service = (*httpService)(nil)
type httpService struct { type httpService struct {
Listener net.Listener Listener net.Listener
@@ -14,15 +16,15 @@ type httpService struct {
name string name string
} }
func newHttpService(name string) (*httpService, error) { func NewHttpService(name string) (*httpService, error) {
return &httpService{ return &httpService{
name: name, name: name,
Server: &http.Server{}, Server: &http.Server{},
}, nil }, nil
} }
func (service *httpService) Name() string { func (service *httpService) Name() factory.Name {
return service.name return factory.MustNewName(service.name)
} }
func (service *httpService) Start(ctx context.Context) error { func (service *httpService) Start(ctx context.Context) error {

38
pkg/factory/name.go Normal file
View File

@@ -0,0 +1,38 @@
package factory
import (
"fmt"
"regexp"
)
var (
// nameRegex is a regex that matches a valid name.
// It must start with a alphabet, and can only contain alphabets, numbers, underscores or hyphens.
nameRegex = regexp.MustCompile(`^[a-z][a-z0-9_-]{0,30}$`)
)
type Name struct {
name string
}
func (n Name) String() string {
return n.name
}
// NewName creates a new name.
func NewName(name string) (Name, error) {
if !nameRegex.MatchString(name) {
return Name{}, fmt.Errorf("invalid factory name %q", name)
}
return Name{name: name}, nil
}
// MustNewName creates a new name.
// It panics if the name is invalid.
func MustNewName(name string) Name {
n, err := NewName(name)
if err != nil {
panic(err)
}
return n
}

64
pkg/factory/named.go Normal file
View File

@@ -0,0 +1,64 @@
package factory
import "fmt"
// Named is implemented by all types of factories.
type Named interface {
Name() Name
}
type NamedMap[T Named] struct {
factories map[Name]T
factoriesInOrder []T
}
func NewNamedMap[T Named](factories ...T) (NamedMap[T], error) {
fmap := make(map[Name]T)
for _, factory := range factories {
if _, ok := fmap[factory.Name()]; ok {
return NamedMap[T]{}, fmt.Errorf("cannot build factory map, duplicate name %q found", factory.Name())
}
fmap[factory.Name()] = factory
}
return NamedMap[T]{factories: fmap, factoriesInOrder: factories}, nil
}
func MustNewNamedMap[T Named](factories ...T) NamedMap[T] {
nm, err := NewNamedMap(factories...)
if err != nil {
panic(err)
}
return nm
}
func (n NamedMap[T]) Get(namestr string) (t T, err error) {
name, err := NewName(namestr)
if err != nil {
return
}
factory, ok := n.factories[name]
if !ok {
err = fmt.Errorf("factory %q not found or not registered", name)
return
}
t = factory
return
}
func (n NamedMap[T]) Add(factory T) (err error) {
name := factory.Name()
if _, ok := n.factories[name]; ok {
return fmt.Errorf("factory %q already exists", name)
}
n.factories[name] = factory
return nil
}
func (n NamedMap[T]) GetInOrder() []T {
return n.factoriesInOrder
}

48
pkg/factory/provider.go Normal file
View File

@@ -0,0 +1,48 @@
package factory
import "context"
type Provider = any
// NewProviderFunc is a function that creates a new Provider.
type NewProviderFunc[P Provider, C Config] func(context.Context, ProviderSettings, C) (P, error)
type ProviderFactory[P Provider, C Config] interface {
Named
New(context.Context, ProviderSettings, C) (P, error)
}
type providerFactory[P Provider, C Config] struct {
name Name
newProviderFunc NewProviderFunc[P, C]
}
func (factory *providerFactory[P, C]) Name() Name {
return factory.name
}
func (factory *providerFactory[P, C]) New(ctx context.Context, settings ProviderSettings, config C) (P, error) {
return factory.newProviderFunc(ctx, settings, config)
}
func NewProviderFactory[P Provider, C Config](name Name, newProviderFunc NewProviderFunc[P, C]) ProviderFactory[P, C] {
return &providerFactory[P, C]{
name: name,
newProviderFunc: newProviderFunc,
}
}
func NewFromFactory[P Provider, C Config](ctx context.Context, settings ProviderSettings, config C, factories NamedMap[ProviderFactory[P, C]], key string) (p P, err error) {
providerFactory, err := factories.Get(key)
if err != nil {
return
}
provider, err := providerFactory.New(ctx, settings, config)
if err != nil {
return
}
p = provider
return
}

View File

@@ -1,4 +1,4 @@
package registry package factory
import "context" import "context"
@@ -8,9 +8,3 @@ type Service interface {
// Stops a service. // Stops a service.
Stop(context.Context) error Stop(context.Context) error
} }
type NamedService interface {
// Identifier of a service. It should be unique across all services.
Name() string
Service
}

58
pkg/factory/setting.go Normal file
View File

@@ -0,0 +1,58 @@
package factory
import (
sdklog "go.opentelemetry.io/otel/log"
sdkmetric "go.opentelemetry.io/otel/metric"
sdktrace "go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
type ProviderSettings struct {
// LoggerProvider is the otel logger.
LoggerProvider sdklog.LoggerProvider
// ZapLogger is the zap logger.
ZapLogger *zap.Logger
// MeterProvider is the meter provider.
MeterProvider sdkmetric.MeterProvider
// TracerProvider is the tracer provider.
TracerProvider sdktrace.TracerProvider
}
type ScopedProviderSettings interface {
Logger() sdklog.Logger
ZapLogger() *zap.Logger
Meter() sdkmetric.Meter
Tracer() sdktrace.Tracer
}
type scoped struct {
logger sdklog.Logger
zapLogger *zap.Logger
meter sdkmetric.Meter
tracer sdktrace.Tracer
}
func NewScopedProviderSettings(settings ProviderSettings, pkgName string) *scoped {
return &scoped{
logger: settings.LoggerProvider.Logger(pkgName),
zapLogger: settings.ZapLogger.Named(pkgName),
meter: settings.MeterProvider.Meter(pkgName),
tracer: settings.TracerProvider.Tracer(pkgName),
}
}
func (s *scoped) Logger() sdklog.Logger {
return s.logger
}
func (s *scoped) ZapLogger() *zap.Logger {
return s.zapLogger
}
func (s *scoped) Meter() sdkmetric.Meter {
return s.meter
}
func (s *scoped) Tracer() sdktrace.Tracer {
return s.tracer
}

View File

@@ -1,12 +1,5 @@
package server package server
import (
"go.signoz.io/signoz/pkg/confmap"
)
// Config satisfies the confmap.Config interface
var _ confmap.Config = (*Config)(nil)
// Config holds the configuration for http. // Config holds the configuration for http.
type Config struct { type Config struct {
//Address specifies the TCP address for the server to listen on, in the form "host:port". //Address specifies the TCP address for the server to listen on, in the form "host:port".
@@ -15,7 +8,7 @@ type Config struct {
Address string `mapstructure:"address"` Address string `mapstructure:"address"`
} }
func (c *Config) NewWithDefaults() confmap.Config { func (c *Config) NewWithDefaults() *Config {
return &Config{ return &Config{
Address: "0.0.0.0:8080", Address: "0.0.0.0:8080",
} }

View File

@@ -6,21 +6,21 @@ import (
"net/http" "net/http"
"time" "time"
"go.signoz.io/signoz/pkg/registry" "go.signoz.io/signoz/pkg/factory"
"go.uber.org/zap" "go.uber.org/zap"
) )
var _ registry.NamedService = (*Server)(nil) var _ factory.Service = (*Server)(nil)
type Server struct { type Server struct {
srv *http.Server srv *http.Server
logger *zap.Logger logger *zap.Logger
handler http.Handler handler http.Handler
cfg Config cfg Config
name string name factory.Name
} }
func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Server, error) { func New(logger *zap.Logger, name factory.Name, cfg Config, handler http.Handler) (*Server, error) {
if handler == nil { if handler == nil {
return nil, fmt.Errorf("cannot build http server, handler is required") return nil, fmt.Errorf("cannot build http server, handler is required")
} }
@@ -29,10 +29,6 @@ func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Se
return nil, fmt.Errorf("cannot build http server, logger is required") return nil, fmt.Errorf("cannot build http server, logger is required")
} }
if name == "" {
return nil, fmt.Errorf("cannot build http server, name is required")
}
srv := &http.Server{ srv := &http.Server{
Addr: cfg.Address, Addr: cfg.Address,
Handler: handler, Handler: handler,
@@ -50,7 +46,7 @@ func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Se
}, nil }, nil
} }
func (server *Server) Name() string { func (server *Server) Name() factory.Name {
return server.name return server.name
} }

View File

@@ -2,13 +2,10 @@ package instrumentation
import ( import (
contribsdkconfig "go.opentelemetry.io/contrib/config" contribsdkconfig "go.opentelemetry.io/contrib/config"
"go.signoz.io/signoz/pkg/confmap" "go.signoz.io/signoz/pkg/factory"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
// Config satisfies the confmap.Config interface
var _ confmap.Config = (*Config)(nil)
// Config holds the configuration for all instrumentation components. // Config holds the configuration for all instrumentation components.
type Config struct { type Config struct {
Logs LogsConfig `mapstructure:"logs"` Logs LogsConfig `mapstructure:"logs"`
@@ -24,39 +21,69 @@ type Resource struct {
// LogsConfig holds the configuration for the logging component. // LogsConfig holds the configuration for the logging component.
type LogsConfig struct { type LogsConfig struct {
Enabled bool `mapstructure:"enabled"` Enabled bool `mapstructure:"enabled"`
Level zapcore.Level `mapstructure:"level"` Level zapcore.Level `mapstructure:"level"`
contribsdkconfig.LoggerProvider `mapstructure:",squash"` Processors LogsProcessors `mapstructure:"processors"`
}
type LogsProcessors struct {
Batch contribsdkconfig.BatchLogRecordProcessor `mapstructure:"batch"`
} }
// TracesConfig holds the configuration for the tracing component. // TracesConfig holds the configuration for the tracing component.
type TracesConfig struct { type TracesConfig struct {
Enabled bool `mapstructure:"enabled"` Enabled bool `mapstructure:"enabled"`
contribsdkconfig.TracerProvider `mapstructure:",squash"` Processors TracesProcessors `mapstructure:"processors"`
Sampler contribsdkconfig.Sampler `mapstructure:"sampler"`
}
type TracesProcessors struct {
Batch contribsdkconfig.BatchSpanProcessor `mapstructure:"batch"`
} }
// MetricsConfig holds the configuration for the metrics component. // MetricsConfig holds the configuration for the metrics component.
type MetricsConfig struct { type MetricsConfig struct {
Enabled bool `mapstructure:"enabled"` Enabled bool `mapstructure:"enabled"`
contribsdkconfig.MeterProvider `mapstructure:",squash"` Readers MetricsReaders `mapstructure:"readers"`
} }
func (c *Config) NewWithDefaults() confmap.Config { type MetricsReaders struct {
return &Config{ Pull contribsdkconfig.PullMetricReader `mapstructure:"pull"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("instrumentation"), newConfig)
}
func newConfig() factory.Config {
host := "0.0.0.0"
port := 9090
return Config{
Logs: LogsConfig{ Logs: LogsConfig{
Enabled: false, Enabled: false,
Level: zapcore.InfoLevel, Level: zapcore.DebugLevel,
}, },
Traces: TracesConfig{ Traces: TracesConfig{
Enabled: false, Enabled: false,
}, },
Metrics: MetricsConfig{ Metrics: MetricsConfig{
Enabled: false, Enabled: true,
Readers: MetricsReaders{
Pull: contribsdkconfig.PullMetricReader{
Exporter: contribsdkconfig.MetricExporter{
Prometheus: &contribsdkconfig.Prometheus{
Host: &host,
Port: &port,
},
},
},
},
}, },
} }
} }
func (c *Config) Validate() error { func (c Config) Validate() error {
return nil return nil
} }

View File

@@ -2,7 +2,6 @@ package instrumentation
import ( import (
"context" "context"
"fmt"
contribsdkconfig "go.opentelemetry.io/contrib/config" contribsdkconfig "go.opentelemetry.io/contrib/config"
sdklog "go.opentelemetry.io/otel/log" sdklog "go.opentelemetry.io/otel/log"
@@ -10,21 +9,31 @@ import (
sdkresource "go.opentelemetry.io/otel/sdk/resource" sdkresource "go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0" semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
sdktrace "go.opentelemetry.io/otel/trace" sdktrace "go.opentelemetry.io/otel/trace"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/version" "go.signoz.io/signoz/pkg/version"
"go.uber.org/zap" "go.uber.org/zap"
) )
// Instrumentation holds the core components for application instrumentation. var _ factory.Service = (*SDK)(nil)
type Instrumentation struct { var _ Instrumentation = (*SDK)(nil)
LoggerProvider sdklog.LoggerProvider
Logger *zap.Logger type Instrumentation interface {
MeterProvider sdkmetric.MeterProvider LoggerProvider() sdklog.LoggerProvider
TracerProvider sdktrace.TracerProvider Logger() *zap.Logger
MeterProvider() sdkmetric.MeterProvider
TracerProvider() sdktrace.TracerProvider
ToProviderSettings() factory.ProviderSettings
}
// SDK holds the core components for application instrumentation.
type SDK struct {
sdk contribsdkconfig.SDK
logger *zap.Logger
} }
// New creates a new Instrumentation instance with configured providers. // New creates a new Instrumentation instance with configured providers.
// It sets up logging, tracing, and metrics based on the provided configuration. // It sets up logging, tracing, and metrics based on the provided configuration.
func New(ctx context.Context, build version.Build, cfg Config) (*Instrumentation, error) { func New(ctx context.Context, build version.Build, cfg Config) (*SDK, error) {
// Set default resource attributes if not provided // Set default resource attributes if not provided
if cfg.Resource.Attributes == nil { if cfg.Resource.Attributes == nil {
cfg.Resource.Attributes = map[string]any{ cfg.Resource.Attributes = map[string]any{
@@ -55,29 +64,86 @@ func New(ctx context.Context, build version.Build, cfg Config) (*Instrumentation
SchemaUrl: &sch, SchemaUrl: &sch,
} }
loggerProvider, err := newLoggerProvider(ctx, cfg, configResource) var loggerProvider *contribsdkconfig.LoggerProvider
if err != nil { if cfg.Logs.Enabled {
return nil, fmt.Errorf("cannot create logger provider: %w", err) loggerProvider = &contribsdkconfig.LoggerProvider{
Processors: []contribsdkconfig.LogRecordProcessor{
{Batch: &cfg.Logs.Processors.Batch},
},
}
} }
tracerProvider, err := newTracerProvider(ctx, cfg, configResource) var tracerProvider *contribsdkconfig.TracerProvider
if err != nil { if cfg.Traces.Enabled {
return nil, fmt.Errorf("cannot create tracer provider: %w", err) tracerProvider = &contribsdkconfig.TracerProvider{
Processors: []contribsdkconfig.SpanProcessor{
{Batch: &cfg.Traces.Processors.Batch},
},
Sampler: &cfg.Traces.Sampler,
}
} }
meterProvider, err := newMeterProvider(ctx, cfg, configResource) var meterProvider *contribsdkconfig.MeterProvider
if err != nil { if cfg.Metrics.Enabled {
return nil, fmt.Errorf("cannot create meter provider: %w", err) meterProvider = &contribsdkconfig.MeterProvider{
Readers: []contribsdkconfig.MetricReader{
{Pull: &cfg.Metrics.Readers.Pull},
},
}
} }
return &Instrumentation{ sdk, err := contribsdkconfig.NewSDK(
LoggerProvider: loggerProvider, contribsdkconfig.WithContext(ctx),
TracerProvider: tracerProvider, contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
MeterProvider: meterProvider, LoggerProvider: loggerProvider,
Logger: newLogger(cfg, loggerProvider), TracerProvider: tracerProvider,
MeterProvider: meterProvider,
Resource: &configResource,
}),
)
if err != nil {
return nil, err
}
return &SDK{
sdk: sdk,
logger: newLogger(cfg, sdk.LoggerProvider()),
}, nil }, nil
} }
func (i *SDK) Start(ctx context.Context) error {
return nil
}
func (i *SDK) Stop(ctx context.Context) error {
return i.sdk.Shutdown(ctx)
}
func (i *SDK) LoggerProvider() sdklog.LoggerProvider {
return i.sdk.LoggerProvider()
}
func (i *SDK) Logger() *zap.Logger {
return i.logger
}
func (i *SDK) MeterProvider() sdkmetric.MeterProvider {
return i.sdk.MeterProvider()
}
func (i *SDK) TracerProvider() sdktrace.TracerProvider {
return i.sdk.TracerProvider()
}
func (i *SDK) ToProviderSettings() factory.ProviderSettings {
return factory.ProviderSettings{
LoggerProvider: i.LoggerProvider(),
ZapLogger: i.Logger(),
MeterProvider: i.MeterProvider(),
TracerProvider: i.TracerProvider(),
}
}
// attributes merges the input attributes with the resource attributes. // attributes merges the input attributes with the resource attributes.
func attributes(input map[string]any, resource *sdkresource.Resource) map[string]any { func attributes(input map[string]any, resource *sdkresource.Resource) map[string]any {
output := make(map[string]any) output := make(map[string]any)

View File

@@ -0,0 +1,54 @@
package instrumentationtest
import (
sdklog "go.opentelemetry.io/otel/log"
nooplog "go.opentelemetry.io/otel/log/noop"
sdkmetric "go.opentelemetry.io/otel/metric"
noopmetric "go.opentelemetry.io/otel/metric/noop"
sdktrace "go.opentelemetry.io/otel/trace"
nooptrace "go.opentelemetry.io/otel/trace/noop"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation"
"go.uber.org/zap"
)
type noopInstrumentation struct {
logger *zap.Logger
loggerProvider sdklog.LoggerProvider
meterProvider sdkmetric.MeterProvider
tracerProvider sdktrace.TracerProvider
}
func New() instrumentation.Instrumentation {
return &noopInstrumentation{
logger: zap.NewNop(),
loggerProvider: nooplog.NewLoggerProvider(),
meterProvider: noopmetric.NewMeterProvider(),
tracerProvider: nooptrace.NewTracerProvider(),
}
}
func (i *noopInstrumentation) LoggerProvider() sdklog.LoggerProvider {
return i.loggerProvider
}
func (i *noopInstrumentation) Logger() *zap.Logger {
return i.logger
}
func (i *noopInstrumentation) MeterProvider() sdkmetric.MeterProvider {
return i.meterProvider
}
func (i *noopInstrumentation) TracerProvider() sdktrace.TracerProvider {
return i.tracerProvider
}
func (i *noopInstrumentation) ToProviderSettings() factory.ProviderSettings {
return factory.ProviderSettings{
LoggerProvider: i.LoggerProvider(),
ZapLogger: i.Logger(),
MeterProvider: i.MeterProvider(),
TracerProvider: i.TracerProvider(),
}
}

View File

@@ -1,38 +1,14 @@
package instrumentation package instrumentation
import ( import (
"context"
"os" "os"
"go.opentelemetry.io/contrib/bridges/otelzap" "go.opentelemetry.io/contrib/bridges/otelzap"
contribsdkconfig "go.opentelemetry.io/contrib/config"
sdklog "go.opentelemetry.io/otel/log" sdklog "go.opentelemetry.io/otel/log"
nooplog "go.opentelemetry.io/otel/log/noop"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
// newLoggerProvider creates a new logger provider based on the configuration.
// If logging is disabled, it returns a no-op logger provider.
func newLoggerProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdklog.LoggerProvider, error) {
if !cfg.Logs.Enabled {
return nooplog.NewLoggerProvider(), nil
}
sdk, err := contribsdkconfig.NewSDK(
contribsdkconfig.WithContext(ctx),
contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
LoggerProvider: &cfg.Logs.LoggerProvider,
Resource: &cfgResource,
}),
)
if err != nil {
return nil, err
}
return sdk.LoggerProvider(), nil
}
// newLogger creates a new Zap logger with the configured level and output. // newLogger creates a new Zap logger with the configured level and output.
// It combines a JSON encoder for stdout and an OpenTelemetry bridge. // It combines a JSON encoder for stdout and an OpenTelemetry bridge.
func newLogger(cfg Config, provider sdklog.LoggerProvider) *zap.Logger { func newLogger(cfg Config, provider sdklog.LoggerProvider) *zap.Logger {

View File

@@ -1,30 +0,0 @@
package instrumentation
import (
"context"
contribsdkconfig "go.opentelemetry.io/contrib/config"
sdkmetric "go.opentelemetry.io/otel/metric"
noopmetric "go.opentelemetry.io/otel/metric/noop"
)
// newMeterProvider creates a new meter provider based on the configuration.
// If metrics are disabled, it returns a no-op meter provider.
func newMeterProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdkmetric.MeterProvider, error) {
if !cfg.Metrics.Enabled {
return noopmetric.NewMeterProvider(), nil
}
sdk, err := contribsdkconfig.NewSDK(
contribsdkconfig.WithContext(ctx),
contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
MeterProvider: &cfg.Metrics.MeterProvider,
Resource: &cfgResource,
}),
)
if err != nil {
return nil, err
}
return sdk.MeterProvider(), nil
}

View File

@@ -1,30 +0,0 @@
package instrumentation
import (
"context"
contribsdkconfig "go.opentelemetry.io/contrib/config"
sdktrace "go.opentelemetry.io/otel/trace"
nooptrace "go.opentelemetry.io/otel/trace/noop"
)
// newTracerProvider creates a new tracer provider based on the configuration.
// If tracing is disabled, it returns a no-op tracer provider.
func newTracerProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdktrace.TracerProvider, error) {
if !cfg.Traces.Enabled {
return nooptrace.NewTracerProvider(), nil
}
sdk, err := contribsdkconfig.NewSDK(
contribsdkconfig.WithContext(ctx),
contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{
TracerProvider: &cfg.Traces.TracerProvider,
Resource: &cfgResource,
}),
)
if err != nil {
return nil, err
}
return sdk.TracerProvider(), nil
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.signoz.io/signoz/pkg/query-service/agentConf/sqlite"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
@@ -19,15 +18,6 @@ type Repo struct {
db *sqlx.DB db *sqlx.DB
} }
func (r *Repo) initDB(engine string) error {
switch engine {
case "sqlite3", "sqlite":
return sqlite.InitDB(r.db)
default:
return fmt.Errorf("unsupported db")
}
}
func (r *Repo) GetConfigHistory( func (r *Repo) GetConfigHistory(
ctx context.Context, typ ElementTypeDef, limit int, ctx context.Context, typ ElementTypeDef, limit int,
) ([]ConfigVersion, *model.ApiError) { ) ([]ConfigVersion, *model.ApiError) {

View File

@@ -39,8 +39,7 @@ type Manager struct {
} }
type ManagerOptions struct { type ManagerOptions struct {
DB *sqlx.DB DB *sqlx.DB
DBEngine string
// When acting as opamp.AgentConfigProvider, agent conf recommendations are // When acting as opamp.AgentConfigProvider, agent conf recommendations are
// applied to the base conf in the order the features have been specified here. // applied to the base conf in the order the features have been specified here.
@@ -66,10 +65,6 @@ func Initiate(options *ManagerOptions) (*Manager, error) {
configSubscribers: map[string]func(){}, configSubscribers: map[string]func(){},
} }
err := m.initDB(options.DBEngine)
if err != nil {
return nil, errors.Wrap(err, "could not init agentConf db")
}
return m, nil return m, nil
} }

View File

@@ -1,65 +0,0 @@
package sqlite
import (
"fmt"
"github.com/pkg/errors"
"github.com/jmoiron/sqlx"
)
func InitDB(db *sqlx.DB) error {
var err error
if db == nil {
return fmt.Errorf("invalid db connection")
}
table_schema := `CREATE TABLE IF NOT EXISTS agent_config_versions(
id TEXT PRIMARY KEY,
created_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
version INTEGER DEFAULT 1,
active int,
is_valid int,
disabled int,
element_type VARCHAR(120) NOT NULL,
deploy_status VARCHAR(80) NOT NULL DEFAULT 'DIRTY',
deploy_sequence INTEGER,
deploy_result TEXT,
last_hash TEXT,
last_config TEXT,
UNIQUE(element_type, version)
);
CREATE UNIQUE INDEX IF NOT EXISTS agent_config_versions_u1
ON agent_config_versions(element_type, version);
CREATE INDEX IF NOT EXISTS agent_config_versions_nu1
ON agent_config_versions(last_hash);
CREATE TABLE IF NOT EXISTS agent_config_elements(
id TEXT PRIMARY KEY,
created_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
element_id TEXT NOT NULL,
element_type VARCHAR(120) NOT NULL,
version_id TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS agent_config_elements_u1
ON agent_config_elements(version_id, element_id, element_type);
`
_, err = db.Exec(table_schema)
if err != nil {
return errors.Wrap(err, "Error in creating agent config tables")
}
return nil
}

View File

@@ -12,7 +12,7 @@ import (
func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) { func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
require := require.New(t) require := require.New(t)
testDB, _ := utils.NewTestSqliteDB(t) testDB := utils.NewQueryServiceDBForTests(t)
controller, err := NewController(testDB) controller, err := NewController(testDB)
require.NoError(err) require.NoError(err)
@@ -56,7 +56,7 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
func TestAgentCheckIns(t *testing.T) { func TestAgentCheckIns(t *testing.T) {
require := require.New(t) require := require.New(t)
testDB, _ := utils.NewTestSqliteDB(t) testDB := utils.NewQueryServiceDBForTests(t)
controller, err := NewController(testDB) controller, err := NewController(testDB)
require.NoError(err) require.NoError(err)
@@ -139,7 +139,7 @@ func TestAgentCheckIns(t *testing.T) {
func TestCantDisconnectNonExistentAccount(t *testing.T) { func TestCantDisconnectNonExistentAccount(t *testing.T) {
require := require.New(t) require := require.New(t)
testDB, _ := utils.NewTestSqliteDB(t) testDB := utils.NewQueryServiceDBForTests(t)
controller, err := NewController(testDB) controller, err := NewController(testDB)
require.NoError(err) require.NoError(err)

View File

@@ -37,42 +37,11 @@ type cloudProviderAccountsRepository interface {
func newCloudProviderAccountsRepository(db *sqlx.DB) ( func newCloudProviderAccountsRepository(db *sqlx.DB) (
*cloudProviderAccountsSQLRepository, error, *cloudProviderAccountsSQLRepository, error,
) { ) {
if err := InitSqliteDBIfNeeded(db); err != nil {
return nil, fmt.Errorf("could not init sqlite DB for cloudintegrations: %w", err)
}
return &cloudProviderAccountsSQLRepository{ return &cloudProviderAccountsSQLRepository{
db: db, db: db,
}, nil }, nil
} }
func InitSqliteDBIfNeeded(db *sqlx.DB) error {
if db == nil {
return fmt.Errorf("db is required")
}
createTablesStatements := `
CREATE TABLE IF NOT EXISTS cloud_integrations_accounts(
cloud_provider TEXT NOT NULL,
id TEXT NOT NULL,
config_json TEXT,
cloud_account_id TEXT,
last_agent_report_json TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
removed_at TIMESTAMP,
UNIQUE(cloud_provider, id)
)
`
_, err := db.Exec(createTablesStatements)
if err != nil {
return fmt.Errorf(
"could not ensure cloud provider integrations schema in sqlite DB: %w", err,
)
}
return nil
}
type cloudProviderAccountsSQLRepository struct { type cloudProviderAccountsSQLRepository struct {
db *sqlx.DB db *sqlx.DB
} }

View File

@@ -35,126 +35,10 @@ var (
) )
// InitDB sets up setting up the connection pool global variable. // InitDB sets up setting up the connection pool global variable.
func InitDB(dataSourceName string) (*sqlx.DB, error) { // @deprecated
var err error func InitDB(inputDB *sqlx.DB) {
db = inputDB
db, err = sqlx.Open("sqlite3", dataSourceName)
if err != nil {
return nil, err
}
table_schema := `CREATE TABLE IF NOT EXISTS dashboards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
data TEXT NOT NULL
);`
_, err = db.Exec(table_schema)
if err != nil {
return nil, fmt.Errorf("error in creating dashboard table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
updated_at datetime NOT NULL,
deleted INTEGER DEFAULT 0,
data TEXT NOT NULL
);`
_, err = db.Exec(table_schema)
if err != nil {
return nil, fmt.Errorf("error in creating rules table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS notification_channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
name TEXT NOT NULL UNIQUE,
type TEXT NOT NULL,
deleted INTEGER DEFAULT 0,
data TEXT NOT NULL
);`
_, err = db.Exec(table_schema)
if err != nil {
return nil, fmt.Errorf("error in creating notification_channles table: %s", err.Error())
}
tableSchema := `CREATE TABLE IF NOT EXISTS planned_maintenance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
alert_ids TEXT,
schedule TEXT NOT NULL,
created_at datetime NOT NULL,
created_by TEXT NOT NULL,
updated_at datetime NOT NULL,
updated_by TEXT NOT NULL
);`
_, err = db.Exec(tableSchema)
if err != nil {
return nil, fmt.Errorf("error in creating planned_maintenance table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS ttl_status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
transaction_id TEXT NOT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
table_name TEXT NOT NULL,
ttl INTEGER DEFAULT 0,
cold_storage_ttl INTEGER DEFAULT 0,
status TEXT NOT NULL
);`
_, err = db.Exec(table_schema)
if err != nil {
return nil, fmt.Errorf("error in creating ttl_status table: %s", err.Error())
}
// sqlite does not support "IF NOT EXISTS"
createdAt := `ALTER TABLE rules ADD COLUMN created_at datetime;`
_, err = db.Exec(createdAt)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
return nil, fmt.Errorf("error in adding column created_at to rules table: %s", err.Error())
}
createdBy := `ALTER TABLE rules ADD COLUMN created_by TEXT;`
_, err = db.Exec(createdBy)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
return nil, fmt.Errorf("error in adding column created_by to rules table: %s", err.Error())
}
updatedBy := `ALTER TABLE rules ADD COLUMN updated_by TEXT;`
_, err = db.Exec(updatedBy)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
return nil, fmt.Errorf("error in adding column updated_by to rules table: %s", err.Error())
}
createdBy = `ALTER TABLE dashboards ADD COLUMN created_by TEXT;`
_, err = db.Exec(createdBy)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
return nil, fmt.Errorf("error in adding column created_by to dashboards table: %s", err.Error())
}
updatedBy = `ALTER TABLE dashboards ADD COLUMN updated_by TEXT;`
_, err = db.Exec(updatedBy)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
return nil, fmt.Errorf("error in adding column updated_by to dashboards table: %s", err.Error())
}
locked := `ALTER TABLE dashboards ADD COLUMN locked INTEGER DEFAULT 0;`
_, err = db.Exec(locked)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
return nil, fmt.Errorf("error in adding column locked to dashboards table: %s", err.Error())
}
telemetry.GetInstance().SetDashboardsInfoCallback(GetDashboardsInfo) telemetry.GetInstance().SetDashboardsInfoCallback(GetDashboardsInfo)
return db, nil
} }
type Dashboard struct { type Dashboard struct {

View File

@@ -33,41 +33,9 @@ type SavedView struct {
ExtraData string `json:"extra_data" db:"extra_data"` ExtraData string `json:"extra_data" db:"extra_data"`
} }
// InitWithDSN sets up setting up the connection pool global variable.
func InitWithDSN(dataSourceName string) (*sqlx.DB, error) {
var err error
db, err = sqlx.Open("sqlite3", dataSourceName)
if err != nil {
return nil, err
}
tableSchema := `CREATE TABLE IF NOT EXISTS saved_views (
uuid TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
created_at datetime NOT NULL,
created_by TEXT,
updated_at datetime NOT NULL,
updated_by TEXT,
source_page TEXT NOT NULL,
tags TEXT,
data TEXT NOT NULL,
extra_data TEXT
);`
_, err = db.Exec(tableSchema)
if err != nil {
return nil, fmt.Errorf("error in creating saved views table: %s", err.Error())
}
telemetry.GetInstance().SetSavedViewsInfoCallback(GetSavedViewsInfo)
return db, nil
}
func InitWithDB(sqlDB *sqlx.DB) { func InitWithDB(sqlDB *sqlx.DB) {
db = sqlDB db = sqlDB
telemetry.GetInstance().SetSavedViewsInfoCallback(GetSavedViewsInfo)
} }
func GetViews() ([]*v3.SavedView, error) { func GetViews() ([]*v3.SavedView, error) {

View File

@@ -123,12 +123,7 @@ type Manager struct {
} }
func NewManager(db *sqlx.DB) (*Manager, error) { func NewManager(db *sqlx.DB) (*Manager, error) {
iiRepo, err := NewInstalledIntegrationsSqliteRepo(db) iiRepo := NewInstalledIntegrationsSqliteRepo(db)
if err != nil {
return nil, fmt.Errorf(
"could not init sqlite DB for installed integrations: %w", err,
)
}
return &Manager{ return &Manager{
availableIntegrationsRepo: &BuiltInIntegrations{}, availableIntegrationsRepo: &BuiltInIntegrations{},

View File

@@ -9,45 +9,14 @@ import (
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
) )
func InitSqliteDBIfNeeded(db *sqlx.DB) error {
if db == nil {
return fmt.Errorf("db is required")
}
createTablesStatements := `
CREATE TABLE IF NOT EXISTS integrations_installed(
integration_id TEXT PRIMARY KEY,
config_json TEXT,
installed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`
_, err := db.Exec(createTablesStatements)
if err != nil {
return fmt.Errorf(
"could not ensure integrations schema in sqlite DB: %w", err,
)
}
return nil
}
type InstalledIntegrationsSqliteRepo struct { type InstalledIntegrationsSqliteRepo struct {
db *sqlx.DB db *sqlx.DB
} }
func NewInstalledIntegrationsSqliteRepo(db *sqlx.DB) ( func NewInstalledIntegrationsSqliteRepo(db *sqlx.DB) *InstalledIntegrationsSqliteRepo {
*InstalledIntegrationsSqliteRepo, error,
) {
err := InitSqliteDBIfNeeded(db)
if err != nil {
return nil, fmt.Errorf(
"couldn't ensure sqlite schema for installed integrations: %w", err,
)
}
return &InstalledIntegrationsSqliteRepo{ return &InstalledIntegrationsSqliteRepo{
db: db, db: db,
}, nil }
} }
func (r *InstalledIntegrationsSqliteRepo) list( func (r *InstalledIntegrationsSqliteRepo) list(

View File

@@ -15,11 +15,7 @@ import (
func NewTestIntegrationsManager(t *testing.T) *Manager { func NewTestIntegrationsManager(t *testing.T) *Manager {
testDB := utils.NewQueryServiceDBForTests(t) testDB := utils.NewQueryServiceDBForTests(t)
installedIntegrationsRepo := NewInstalledIntegrationsSqliteRepo(testDB)
installedIntegrationsRepo, err := NewInstalledIntegrationsSqliteRepo(testDB)
if err != nil {
t.Fatalf("could not init sqlite DB for installed integrations: %v", err)
}
return &Manager{ return &Manager{
availableIntegrationsRepo: &TestAvailableIntegrationsRepo{}, availableIntegrationsRepo: &TestAvailableIntegrationsRepo{},

View File

@@ -27,15 +27,13 @@ type LogParsingPipelineController struct {
func NewLogParsingPipelinesController( func NewLogParsingPipelinesController(
db *sqlx.DB, db *sqlx.DB,
engine string,
getIntegrationPipelines func(context.Context) ([]Pipeline, *model.ApiError), getIntegrationPipelines func(context.Context) ([]Pipeline, *model.ApiError),
) (*LogParsingPipelineController, error) { ) (*LogParsingPipelineController, error) {
repo := NewRepo(db) repo := NewRepo(db)
err := repo.InitDB(engine)
return &LogParsingPipelineController{ return &LogParsingPipelineController{
Repo: repo, Repo: repo,
GetIntegrationPipelines: getIntegrationPipelines, GetIntegrationPipelines: getIntegrationPipelines,
}, err }, nil
} }
// PipelinesResponse is used to prepare http response for pipelines config related requests // PipelinesResponse is used to prepare http response for pipelines config related requests

View File

@@ -9,7 +9,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline/sqlite"
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/model" "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap" "go.uber.org/zap"
@@ -29,15 +28,6 @@ func NewRepo(db *sqlx.DB) Repo {
} }
} }
func (r *Repo) InitDB(engine string) error {
switch engine {
case "sqlite3", "sqlite":
return sqlite.InitDB(r.db)
default:
return fmt.Errorf("unsupported db")
}
}
// insertPipeline stores a given postable pipeline to database // insertPipeline stores a given postable pipeline to database
func (r *Repo) insertPipeline( func (r *Repo) insertPipeline(
ctx context.Context, postable *PostablePipeline, ctx context.Context, postable *PostablePipeline,

View File

@@ -1,35 +0,0 @@
package sqlite
import (
"fmt"
"github.com/pkg/errors"
"github.com/jmoiron/sqlx"
)
func InitDB(db *sqlx.DB) error {
var err error
if db == nil {
return fmt.Errorf("invalid db connection")
}
table_schema := `CREATE TABLE IF NOT EXISTS pipelines(
id TEXT PRIMARY KEY,
order_id INTEGER,
enabled BOOLEAN,
created_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(400) NOT NULL,
alias VARCHAR(20) NOT NULL,
description TEXT,
filter TEXT NOT NULL,
config_json TEXT
);
`
_, err = db.Exec(table_schema)
if err != nil {
return errors.Wrap(err, "Error in creating pipelines table")
}
return nil
}

View File

@@ -166,10 +166,7 @@ type testbed struct {
func newTestbed(t *testing.T) *testbed { func newTestbed(t *testing.T) *testbed {
testDB := utils.NewQueryServiceDBForTests(t) testDB := utils.NewQueryServiceDBForTests(t)
_, err := model.InitDB(testDB) model.InitDB(testDB)
if err != nil {
t.Fatalf("could not init opamp model: %v", err)
}
testConfigProvider := NewMockAgentConfigProvider() testConfigProvider := NewMockAgentConfigProvider()
opampServer := InitializeServer(nil, testConfigProvider) opampServer := InitializeServer(nil, testConfigProvider)

View File

@@ -30,28 +30,15 @@ func (a *Agents) Count() int {
} }
// Initialize the database and create schema if needed // Initialize the database and create schema if needed
func InitDB(qsDB *sqlx.DB) (*sqlx.DB, error) { func InitDB(qsDB *sqlx.DB) *sqlx.DB {
db = qsDB db = qsDB
tableSchema := `CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY UNIQUE,
started_at datetime NOT NULL,
terminated_at datetime,
current_status TEXT NOT NULL,
effective_config TEXT NOT NULL
);`
_, err := db.Exec(tableSchema)
if err != nil {
return nil, fmt.Errorf("error in creating agents table: %s", err.Error())
}
AllAgents = Agents{ AllAgents = Agents{
agentsById: make(map[string]*Agent), agentsById: make(map[string]*Agent),
connections: make(map[types.Connection]map[string]bool), connections: make(map[types.Connection]map[string]bool),
mux: sync.RWMutex{}, mux: sync.RWMutex{},
} }
return db, nil
return qsDB
} }
// RemoveConnection removes the connection all Agent instances associated with the // RemoveConnection removes the connection all Agent instances associated with the

View File

@@ -203,53 +203,8 @@ type UpdatePreference struct {
var db *sqlx.DB var db *sqlx.DB
func InitDB(datasourceName string) error { func InitDB(inputDB *sqlx.DB) {
var err error db = inputDB
db, err = sqlx.Open("sqlite3", datasourceName)
if err != nil {
return err
}
// create the user preference table
tableSchema := `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS user_preference(
preference_id TEXT NOT NULL,
preference_value TEXT,
user_id TEXT NOT NULL,
PRIMARY KEY (preference_id,user_id),
FOREIGN KEY (user_id)
REFERENCES users(id)
ON UPDATE CASCADE
ON DELETE CASCADE
);`
_, err = db.Exec(tableSchema)
if err != nil {
return fmt.Errorf("error in creating user_preference table: %s", err.Error())
}
// create the org preference table
tableSchema = `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS org_preference(
preference_id TEXT NOT NULL,
preference_value TEXT,
org_id TEXT NOT NULL,
PRIMARY KEY (preference_id,org_id),
FOREIGN KEY (org_id)
REFERENCES organizations(id)
ON UPDATE CASCADE
ON DELETE CASCADE
);`
_, err = db.Exec(tableSchema)
if err != nil {
return fmt.Errorf("error in creating org_preference table: %s", err.Error())
}
return nil
} }
// org preference functions // org preference functions

View File

@@ -27,6 +27,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/clickhouseReader" "go.signoz.io/signoz/pkg/query-service/app/clickhouseReader"
"go.signoz.io/signoz/pkg/query-service/app/cloudintegrations" "go.signoz.io/signoz/pkg/query-service/app/cloudintegrations"
"go.signoz.io/signoz/pkg/query-service/app/dashboards" "go.signoz.io/signoz/pkg/query-service/app/dashboards"
"go.signoz.io/signoz/pkg/query-service/app/explorer"
"go.signoz.io/signoz/pkg/query-service/app/integrations" "go.signoz.io/signoz/pkg/query-service/app/integrations"
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline" "go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
"go.signoz.io/signoz/pkg/query-service/app/opamp" "go.signoz.io/signoz/pkg/query-service/app/opamp"
@@ -35,8 +36,8 @@ import (
"go.signoz.io/signoz/pkg/query-service/common" "go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/migrate" "go.signoz.io/signoz/pkg/query-service/migrate"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/query-service/app/explorer"
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/cache" "go.signoz.io/signoz/pkg/query-service/cache"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
@@ -96,24 +97,13 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
} }
// NewServer creates and initializes Server // NewServer creates and initializes Server
func NewServer(serverOptions *ServerOptions) (*Server, error) { func NewServer(serverOptions *ServerOptions, config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
if err := dao.InitDao(signoz.SQLStore.SQLxDB()); err != nil {
if err := dao.InitDao("sqlite", constants.RELATIONAL_DATASOURCE_PATH); err != nil {
return nil, err return nil, err
} }
preferences.InitDB(signoz.SQLStore.SQLxDB())
if err := preferences.InitDB(constants.RELATIONAL_DATASOURCE_PATH); err != nil { dashboards.InitDB(signoz.SQLStore.SQLxDB())
return nil, err explorer.InitWithDB(signoz.SQLStore.SQLxDB())
}
localDB, err := dashboards.InitDB(constants.RELATIONAL_DATASOURCE_PATH)
explorer.InitWithDSN(constants.RELATIONAL_DATASOURCE_PATH)
if err != nil {
return nil, err
}
localDB.SetMaxOpenConns(10)
// initiate feature manager // initiate feature manager
fm := featureManager.StartManager() fm := featureManager.StartManager()
@@ -125,7 +115,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
if storage == "clickhouse" { if storage == "clickhouse" {
zap.L().Info("Using ClickHouse as datastore ...") zap.L().Info("Using ClickHouse as datastore ...")
clickhouseReader := clickhouseReader.NewReader( clickhouseReader := clickhouseReader.NewReader(
localDB, signoz.SQLStore.SQLxDB(),
serverOptions.PromConfigPath, serverOptions.PromConfigPath,
fm, fm,
serverOptions.MaxIdleConns, serverOptions.MaxIdleConns,
@@ -140,7 +130,9 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} else { } else {
return nil, fmt.Errorf("storage type: %s is not supported in query service", storage) return nil, fmt.Errorf("storage type: %s is not supported in query service", storage)
} }
skipConfig := &model.SkipConfig{} skipConfig := &model.SkipConfig{}
var err error
if serverOptions.SkipTopLvlOpsPath != "" { if serverOptions.SkipTopLvlOpsPath != "" {
// read skip config // read skip config
skipConfig, err = model.ReadSkipConfig(serverOptions.SkipTopLvlOpsPath) skipConfig, err = model.ReadSkipConfig(serverOptions.SkipTopLvlOpsPath)
@@ -161,7 +153,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
rm, err := makeRulesManager( rm, err := makeRulesManager(
serverOptions.PromConfigPath, serverOptions.PromConfigPath,
constants.GetAlertManagerApiPrefix(), constants.GetAlertManagerApiPrefix(),
serverOptions.RuleRepoURL, localDB, reader, c, serverOptions.DisableRules, fm, serverOptions.UseLogsNewSchema, serverOptions.UseTraceNewSchema) serverOptions.RuleRepoURL, signoz.SQLStore.SQLxDB(), reader, c, serverOptions.DisableRules, fm, serverOptions.UseLogsNewSchema, serverOptions.UseTraceNewSchema)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -178,18 +170,18 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err return nil, err
} }
integrationsController, err := integrations.NewController(localDB) integrationsController, err := integrations.NewController(signoz.SQLStore.SQLxDB())
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't create integrations controller: %w", err) return nil, fmt.Errorf("couldn't create integrations controller: %w", err)
} }
cloudIntegrationsController, err := cloudintegrations.NewController(localDB) cloudIntegrationsController, err := cloudintegrations.NewController(signoz.SQLStore.SQLxDB())
if err != nil { if err != nil {
return nil, fmt.Errorf("couldn't create cloud provider integrations controller: %w", err) return nil, fmt.Errorf("couldn't create cloud provider integrations controller: %w", err)
} }
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController( logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
localDB, "sqlite", integrationsController.GetPipelinesForInstalledIntegrations, signoz.SQLStore.SQLxDB(), integrationsController.GetPipelinesForInstalledIntegrations,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -241,14 +233,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
s.privateHTTP = privateServer s.privateHTTP = privateServer
_, err = opAmpModel.InitDB(localDB) opAmpModel.InitDB(signoz.SQLStore.SQLxDB())
if err != nil {
return nil, err
}
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{ agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
DB: localDB, DB: signoz.SQLStore.SQLxDB(),
DBEngine: "sqlite",
AgentFeatures: []agentConf.AgentFeature{ AgentFeatures: []agentConf.AgentFeature{
logParsingPipelineController, logParsingPipelineController,
}, },

View File

@@ -1,26 +1,20 @@
package dao package dao
import ( import (
"fmt" "github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.signoz.io/signoz/pkg/query-service/dao/sqlite" "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
) )
var db ModelDao var db ModelDao
func InitDao(engine, path string) error { func InitDao(inputDB *sqlx.DB) error {
var err error var err error
db, err = sqlite.InitDB(inputDB)
switch engine { if err != nil {
case "sqlite": return errors.Wrap(err, "failed to initialize DB")
db, err = sqlite.InitDB(path)
if err != nil {
return errors.Wrap(err, "failed to initialize DB")
}
default:
return fmt.Errorf("RelationalDB type: %s is not supported in query service", engine)
} }
return nil return nil
} }

View File

@@ -2,7 +2,6 @@ package sqlite
import ( import (
"context" "context"
"fmt"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -17,83 +16,8 @@ type ModelDaoSqlite struct {
} }
// InitDB sets up setting up the connection pool global variable. // InitDB sets up setting up the connection pool global variable.
func InitDB(dataSourceName string) (*ModelDaoSqlite, error) { func InitDB(inputDB *sqlx.DB) (*ModelDaoSqlite, error) {
var err error mds := &ModelDaoSqlite{db: inputDB}
db, err := sqlx.Open("sqlite3", dataSourceName)
if err != nil {
return nil, errors.Wrap(err, "failed to Open sqlite3 DB")
}
db.SetMaxOpenConns(10)
table_schema := `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS invites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
token TEXT NOT NULL,
created_at INTEGER NOT NULL,
role TEXT NOT NULL,
org_id TEXT NOT NULL,
FOREIGN KEY(org_id) REFERENCES organizations(id)
);
CREATE TABLE IF NOT EXISTS organizations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at INTEGER NOT NULL,
is_anonymous INTEGER NOT NULL DEFAULT 0 CHECK(is_anonymous IN (0,1)),
has_opted_updates INTEGER NOT NULL DEFAULT 1 CHECK(has_opted_updates IN (0,1))
);
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
created_at INTEGER NOT NULL,
profile_picture_url TEXT,
group_id TEXT NOT NULL,
org_id TEXT NOT NULL,
FOREIGN KEY(group_id) REFERENCES groups(id),
FOREIGN KEY(org_id) REFERENCES organizations(id)
);
CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS reset_password_request (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
token TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS user_flags (
user_id TEXT PRIMARY KEY,
flags TEXT,
FOREIGN KEY(user_id) REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS apdex_settings (
service_name TEXT PRIMARY KEY,
threshold FLOAT NOT NULL,
exclude_status_codes TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS ingestion_keys (
key_id TEXT PRIMARY KEY,
name TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ingestion_key TEXT NOT NULL,
ingestion_url TEXT NOT NULL,
data_region TEXT NOT NULL
);
`
_, err = db.Exec(table_schema)
if err != nil {
return nil, fmt.Errorf("error in creating tables: %v", err.Error())
}
mds := &ModelDaoSqlite{db: db}
ctx := context.Background() ctx := context.Background()
if err := mds.initializeOrgPreferences(ctx); err != nil { if err := mds.initializeOrgPreferences(ctx); err != nil {

View File

@@ -9,25 +9,19 @@ import (
"time" "time"
prommodel "github.com/prometheus/common/model" prommodel "github.com/prometheus/common/model"
"go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/config/envprovider"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/query-service/app" "go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/migrate"
"go.signoz.io/signoz/pkg/query-service/version" "go.signoz.io/signoz/pkg/query-service/version"
"go.signoz.io/signoz/pkg/signoz"
pkgversion "go.signoz.io/signoz/pkg/version"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
func initZapLog() *zap.Logger {
config := zap.NewProductionConfig()
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, _ := config.Build()
return logger
}
func init() { func init() {
prommodel.NameValidationScheme = prommodel.UTF8Validation prommodel.NameValidationScheme = prommodel.UTF8Validation
} }
@@ -67,13 +61,33 @@ func main() {
flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection, only used with clickhouse if not set in ClickHouseUrl env var DSN.)") flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection, only used with clickhouse if not set in ClickHouseUrl env var DSN.)")
flag.Parse() flag.Parse()
loggerMgr := initZapLog() config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{
zap.ReplaceGlobals(loggerMgr) Uris: []string{"env:"},
defer loggerMgr.Sync() // flushes buffer, if any ProviderFactories: []config.ProviderFactory{
envprovider.NewFactory(),
},
})
if err != nil {
zap.L().Fatal("Failed to create config", zap.Error(err))
}
logger := loggerMgr.Sugar() instrumentation, err := instrumentation.New(context.Background(), pkgversion.Build{}, config.Instrumentation)
if err != nil {
zap.L().Fatal("Failed to create instrumentation", zap.Error(err))
}
defer instrumentation.Stop(context.Background())
zap.ReplaceGlobals(instrumentation.Logger())
defer instrumentation.Logger().Sync() // flushes buffer, if any
logger := instrumentation.Logger().Sugar()
version.PrintVersion() version.PrintVersion()
signoz, err := signoz.New(context.Background(), instrumentation, config, signoz.NewProviderFactories())
if err != nil {
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
}
serverOptions := &app.ServerOptions{ serverOptions := &app.ServerOptions{
HTTPHostPort: constants.HTTPHostPort, HTTPHostPort: constants.HTTPHostPort,
PromConfigPath: promConfigPath, PromConfigPath: promConfigPath,
@@ -101,13 +115,7 @@ func main() {
zap.L().Info("JWT secret key set successfully.") zap.L().Info("JWT secret key set successfully.")
} }
if err := migrate.Migrate(constants.RELATIONAL_DATASOURCE_PATH); err != nil { server, err := app.NewServer(serverOptions, config, signoz)
zap.L().Error("Failed to migrate", zap.Error(err))
} else {
zap.L().Info("Migration successful")
}
server, err := app.NewServer(serverOptions)
if err != nil { if err != nil {
logger.Fatal("Failed to create server", zap.Error(err)) logger.Fatal("Failed to create server", zap.Error(err))
} }

View File

@@ -16,22 +16,6 @@ type DataMigration struct {
Succeeded bool `db:"succeeded"` Succeeded bool `db:"succeeded"`
} }
func initSchema(conn *sqlx.DB) error {
tableSchema := `
CREATE TABLE IF NOT EXISTS data_migrations (
id SERIAL PRIMARY KEY,
version VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
succeeded BOOLEAN NOT NULL DEFAULT FALSE
);
`
_, err := conn.Exec(tableSchema)
if err != nil {
return err
}
return nil
}
func getMigrationVersion(conn *sqlx.DB, version string) (*DataMigration, error) { func getMigrationVersion(conn *sqlx.DB, version string) (*DataMigration, error) {
var migration DataMigration var migration DataMigration
err := conn.Get(&migration, "SELECT * FROM data_migrations WHERE version = $1", version) err := conn.Get(&migration, "SELECT * FROM data_migrations WHERE version = $1", version)
@@ -44,18 +28,6 @@ func getMigrationVersion(conn *sqlx.DB, version string) (*DataMigration, error)
return &migration, nil return &migration, nil
} }
func Migrate(dsn string) error {
conn, err := sqlx.Connect("sqlite3", dsn)
if err != nil {
return err
}
if err := initSchema(conn); err != nil {
return err
}
return nil
}
func ClickHouseMigrate(conn driver.Conn, cluster string) error { func ClickHouseMigrate(conn driver.Conn, cluster string) error {
database := "CREATE DATABASE IF NOT EXISTS signoz_analytics ON CLUSTER %s" database := "CREATE DATABASE IF NOT EXISTS signoz_analytics ON CLUSTER %s"

View File

@@ -34,7 +34,7 @@ import (
) )
func TestLogPipelinesLifecycle(t *testing.T) { func TestLogPipelinesLifecycle(t *testing.T) {
testbed := NewLogPipelinesTestBed(t, nil) testbed := NewLogPipelinesTestBed(t, utils.NewQueryServiceDBForTests(t))
require := require.New(t) require := require.New(t)
getPipelinesResp := testbed.GetPipelinesFromQS() getPipelinesResp := testbed.GetPipelinesFromQS()
@@ -461,7 +461,7 @@ func NewTestbedWithoutOpamp(t *testing.T, testDB *sqlx.DB) *LogPipelinesTestBed
} }
controller, err := logparsingpipeline.NewLogParsingPipelinesController( controller, err := logparsingpipeline.NewLogParsingPipelinesController(
testDB, "sqlite", ic.GetPipelinesForInstalledIntegrations, testDB, ic.GetPipelinesForInstalledIntegrations,
) )
if err != nil { if err != nil {
t.Fatalf("could not create a logparsingpipelines controller: %v", err) t.Fatalf("could not create a logparsingpipelines controller: %v", err)
@@ -481,12 +481,10 @@ func NewTestbedWithoutOpamp(t *testing.T, testDB *sqlx.DB) *LogPipelinesTestBed
} }
// Mock an available opamp agent // Mock an available opamp agent
testDB, err = opampModel.InitDB(testDB) _ = opampModel.InitDB(testDB)
require.Nil(t, err, "failed to init opamp model")
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{ agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
DB: testDB, DB: testDB,
DBEngine: "sqlite",
AgentFeatures: []agentConf.AgentFeature{ AgentFeatures: []agentConf.AgentFeature{
apiHandler.LogsParsingPipelineController, apiHandler.LogsParsingPipelineController,
}}) }})

View File

@@ -1,38 +1,70 @@
package utils package utils
import ( import (
"context"
"os" "os"
"testing" "testing"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation/instrumentationtest"
"go.signoz.io/signoz/pkg/query-service/app/dashboards" "go.signoz.io/signoz/pkg/query-service/app/dashboards"
"go.signoz.io/signoz/pkg/query-service/dao" "go.signoz.io/signoz/pkg/query-service/dao"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/sqlstore/sqlitesqlstore"
"go.signoz.io/signoz/pkg/sqlstoremigrator"
"go.signoz.io/signoz/pkg/sqlstoremigrator/migrations"
) )
func NewTestSqliteDB(t *testing.T) (testDB *sqlx.DB, testDBFilePath string) { func NewQueryServiceDBForTests(t *testing.T) (testDB *sqlx.DB) {
testDBFile, err := os.CreateTemp("", "test-signoz-db-*") testDBFile, err := os.CreateTemp("", "test-signoz-db-*")
if err != nil { if err != nil {
t.Fatalf("could not create temp file for test db: %v", err) t.Fatalf("could not create temp file for test db: %v", err)
} }
testDBFilePath = testDBFile.Name() testDBFilePath := testDBFile.Name()
t.Cleanup(func() { os.Remove(testDBFilePath) }) t.Cleanup(func() { os.Remove(testDBFilePath) })
testDBFile.Close() testDBFile.Close()
testDB, err = sqlx.Open("sqlite3", testDBFilePath) config := sqlstore.Config{
if err != nil { Provider: "sqlite",
t.Fatalf("could not open test db sqlite file: %v", err) Sqlite: sqlstore.SqliteConfig{
Path: testDBFilePath,
},
} }
return testDB, testDBFilePath sqlStore, err := factory.NewFromFactory(context.Background(), instrumentationtest.New().ToProviderSettings(), config, factory.MustNewNamedMap(sqlitesqlstore.NewFactory()), "sqlite")
} if err != nil {
t.Fatalf("could not create sqlite provider: %v", err)
func NewQueryServiceDBForTests(t *testing.T) *sqlx.DB { }
testDB, testDBFilePath := NewTestSqliteDB(t)
migrations, err := sqlstoremigrator.NewMigrations(context.Background(), instrumentationtest.New().ToProviderSettings(), config, factory.MustNewNamedMap(
// TODO(Raj): This should not require passing in the DB file path migrations.NewAddDataMigrationsFactory(),
dao.InitDao("sqlite", testDBFilePath) migrations.NewAddOrganizationFactory(),
dashboards.InitDB(testDBFilePath) migrations.NewAddPreferencesFactory(),
migrations.NewAddDashboardsFactory(),
return testDB migrations.NewAddSavedViewsFactory(),
migrations.NewAddAgentsFactory(),
migrations.NewAddPipelinesFactory(),
migrations.NewAddIntegrationsFactory(),
))
if err != nil {
t.Fatalf("could not create migrations: %v", err)
}
sqlStoreMigrator := sqlstoremigrator.New(context.Background(), instrumentationtest.New().ToProviderSettings(), sqlStore, migrations, config)
err = sqlStoreMigrator.Migrate(context.Background())
if err != nil {
t.Fatalf("could not run migrations: %v", err)
}
err = dao.InitDao(sqlStore.SQLxDB())
if err != nil {
t.Fatalf("could not init dao: %v", err)
}
dashboards.InitDB(sqlStore.SQLxDB())
return sqlStore.SQLxDB()
} }

View File

@@ -8,18 +8,19 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"go.signoz.io/signoz/pkg/factory"
"go.uber.org/zap" "go.uber.org/zap"
) )
type Registry struct { type Registry struct {
services []NamedService services []factory.Service
logger *zap.Logger logger *zap.Logger
startCh chan error startCh chan error
stopCh chan error stopCh chan error
} }
// New creates a new registry of services. It needs at least one service in the input. // New creates a new registry of services. It needs at least one service in the input.
func New(logger *zap.Logger, services ...NamedService) (*Registry, error) { func New(logger *zap.Logger, services ...factory.Service) (*Registry, error) {
if logger == nil { if logger == nil {
return nil, fmt.Errorf("cannot build registry, logger is required") return nil, fmt.Errorf("cannot build registry, logger is required")
} }
@@ -38,7 +39,7 @@ func New(logger *zap.Logger, services ...NamedService) (*Registry, error) {
func (r *Registry) Start(ctx context.Context) error { func (r *Registry) Start(ctx context.Context) error {
for _, s := range r.services { for _, s := range r.services {
go func(s Service) { go func(s factory.Service) {
err := s.Start(ctx) err := s.Start(ctx)
r.startCh <- err r.startCh <- err
}(s) }(s)
@@ -66,7 +67,7 @@ func (r *Registry) Wait(ctx context.Context) error {
func (r *Registry) Stop(ctx context.Context) error { func (r *Registry) Stop(ctx context.Context) error {
for _, s := range r.services { for _, s := range r.services {
go func(s Service) { go func(s factory.Service) {
err := s.Stop(ctx) err := s.Stop(ctx)
r.stopCh <- err r.stopCh <- err
}(s) }(s)

View File

@@ -6,14 +6,15 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/factory/factorytest"
"go.uber.org/zap" "go.uber.org/zap"
) )
func TestRegistryWith2HttpServers(t *testing.T) { func TestRegistryWith2HttpServers(t *testing.T) {
http1, err := newHttpService("http1") http1, err := factorytest.NewHttpService("http1")
require.NoError(t, err) require.NoError(t, err)
http2, err := newHttpService("http2") http2, err := factorytest.NewHttpService("http2")
require.NoError(t, err) require.NoError(t, err)
registry, err := New(zap.NewNop(), http1, http2) registry, err := New(zap.NewNop(), http1, http2)
@@ -34,10 +35,10 @@ func TestRegistryWith2HttpServers(t *testing.T) {
} }
func TestRegistryWith2HttpServersWithoutWait(t *testing.T) { func TestRegistryWith2HttpServersWithoutWait(t *testing.T) {
http1, err := newHttpService("http1") http1, err := factorytest.NewHttpService("http1")
require.NoError(t, err) require.NoError(t, err)
http2, err := newHttpService("http2") http2, err := factorytest.NewHttpService("http2")
require.NoError(t, err) require.NoError(t, err)
registry, err := New(zap.NewNop(), http1, http2) registry, err := New(zap.NewNop(), http1, http2)

41
pkg/signoz/config.go Normal file
View File

@@ -0,0 +1,41 @@
package signoz
import (
"context"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/web"
)
// Config defines the entire configuration of signoz.
type Config struct {
Instrumentation instrumentation.Config `mapstructure:"instrumentation"`
Web web.Config `mapstructure:"web"`
Cache cache.Config `mapstructure:"cache"`
SQLStore sqlstore.Config `mapstructure:"sqlstore"`
}
func NewConfig(ctx context.Context, resolverConfig config.ResolverConfig) (Config, error) {
configFactories := []factory.ConfigFactory{
instrumentation.NewConfigFactory(),
web.NewConfigFactory(),
sqlstore.NewConfigFactory(),
cache.NewConfigFactory(),
}
conf, err := config.New(ctx, resolverConfig, configFactories)
if err != nil {
return Config{}, err
}
var config Config
if err := conf.Unmarshal("", &config); err != nil {
return Config{}, err
}
return config, nil
}

47
pkg/signoz/provider.go Normal file
View File

@@ -0,0 +1,47 @@
package signoz
import (
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/cache/memorycache"
"go.signoz.io/signoz/pkg/cache/rediscache"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/sqlstore/sqlitesqlstore"
"go.signoz.io/signoz/pkg/sqlstoremigrator/migrations"
"go.signoz.io/signoz/pkg/web"
"go.signoz.io/signoz/pkg/web/noopweb"
"go.signoz.io/signoz/pkg/web/routerweb"
)
type ProviderFactories struct {
SQLStoreMigrationFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config]]
SQLStoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]]
WebProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]]
CacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]]
}
func NewProviderFactories() ProviderFactories {
return ProviderFactories{
SQLStoreMigrationFactories: factory.MustNewNamedMap(
migrations.NewAddDataMigrationsFactory(),
migrations.NewAddOrganizationFactory(),
migrations.NewAddPreferencesFactory(),
migrations.NewAddDashboardsFactory(),
migrations.NewAddSavedViewsFactory(),
migrations.NewAddAgentsFactory(),
migrations.NewAddPipelinesFactory(),
migrations.NewAddIntegrationsFactory(),
),
SQLStoreProviderFactories: factory.MustNewNamedMap(
sqlitesqlstore.NewFactory(),
),
WebProviderFactories: factory.MustNewNamedMap(
routerweb.NewFactory(),
noopweb.NewFactory(),
),
CacheProviderFactories: factory.MustNewNamedMap(
memorycache.NewFactory(),
rediscache.NewFactory(),
),
}
}

View File

@@ -1,37 +1,57 @@
package signoz package signoz
import ( import (
"context"
"go.signoz.io/signoz/pkg/cache" "go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/cache/strategy/memory" "go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/cache/strategy/redis" "go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/config" "go.signoz.io/signoz/pkg/sqlstoremigrator"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/web" "go.signoz.io/signoz/pkg/web"
"go.uber.org/zap"
) )
type SigNoz struct { type SigNoz struct {
Cache cache.Cache Cache cache.Cache
Web *web.Web Web web.Web
SQLStore sqlstore.SQLStore
SQLStoreMigrator sqlstore.SQLStoreMigrator
} }
func New(config *config.Config, skipWebFrontend bool) (*SigNoz, error) { func New(ctx context.Context, instrumentation instrumentation.Instrumentation, config Config, factories ProviderFactories) (*SigNoz, error) {
var cache cache.Cache providerSettings := instrumentation.ToProviderSettings()
// init for the cache cache, err := factory.NewFromFactory(ctx, providerSettings, config.Cache, factories.CacheProviderFactories, config.Cache.Provider)
switch config.Cache.Provider { if err != nil {
case "memory": return nil, err
cache = memory.New(&config.Cache.Memory)
case "redis":
cache = redis.New(&config.Cache.Redis)
} }
web, err := web.New(zap.L(), config.Web) web, err := factory.NewFromFactory(ctx, providerSettings, config.Web, factories.WebProviderFactories, config.Web.GetProvider())
if err != nil && !skipWebFrontend { if err != nil {
return nil, err
}
sqlStore, err := factory.NewFromFactory(ctx, providerSettings, config.SQLStore, factories.SQLStoreProviderFactories, config.SQLStore.Provider)
if err != nil {
return nil, err
}
migrations, err := sqlstoremigrator.NewMigrations(ctx, providerSettings, config.SQLStore, factories.SQLStoreMigrationFactories)
if err != nil {
return nil, err
}
sqlStoreMigrator := sqlstoremigrator.New(ctx, providerSettings, sqlStore, migrations, config.SQLStore)
err = sqlStoreMigrator.Migrate(ctx)
if err != nil {
return nil, err return nil, err
} }
return &SigNoz{ return &SigNoz{
Cache: cache, Cache: cache,
Web: web, Web: web,
SQLStore: sqlStore,
}, nil }, nil
} }

57
pkg/sqlstore/config.go Normal file
View File

@@ -0,0 +1,57 @@
package sqlstore
import (
"errors"
"time"
"go.signoz.io/signoz/pkg/factory"
)
type Config struct {
Provider string `mapstructure:"provider"`
Connection ConnectionConfig `mapstructure:",squash"`
Migration MigrationConfig `mapstructure:"migration"`
Sqlite SqliteConfig `mapstructure:"sqlite"`
}
type SqliteConfig struct {
Path string `mapstructure:"path"`
}
type ConnectionConfig struct {
MaxOpenConns int `mapstructure:"max_open_conns"`
}
type MigrationConfig struct {
LockTimeout time.Duration `mapstructure:"lock_timeout"`
LockInterval time.Duration `mapstructure:"lock_interval"`
}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("sqlstore"), newConfig)
}
func newConfig() factory.Config {
return &Config{
Provider: "sqlite",
Connection: ConnectionConfig{
MaxOpenConns: 100,
},
Migration: MigrationConfig{
LockTimeout: 2 * time.Minute,
LockInterval: 10 * time.Second,
},
Sqlite: SqliteConfig{
Path: "/var/lib/signoz/signoz.db",
},
}
}
func (c Config) Validate() error {
if c.Migration.LockTimeout < c.Migration.LockInterval {
return errors.New("lock_timeout must be greater than lock_interval")
}
return nil
}

View File

@@ -0,0 +1,63 @@
package sqlitesqlstore
import (
"context"
"database/sql"
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/sqlitedialect"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
"go.uber.org/zap"
)
type provider struct {
sqlDB *sql.DB
bunDB *bun.DB
sqlxDB *sqlx.DB
}
func NewFactory() factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("sqlite"), New)
}
func New(ctx context.Context, settings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) {
if config.Provider != "sqlite" {
return nil, fmt.Errorf("provider %q is not supported by sqlite", config.Provider)
}
sqlDB, err := sql.Open("sqlite3", "file:"+config.Sqlite.Path+"?_foreign_keys=true")
if err != nil {
return nil, err
}
settings.ZapLogger.Info("connected to sqlite", zap.String("path", config.Sqlite.Path))
// Set connection options
sqlDB.SetMaxOpenConns(config.Connection.MaxOpenConns)
// Initialize ORMs
bunDB := bun.NewDB(sqlDB, sqlitedialect.New())
sqlxDB := sqlx.NewDb(sqlDB, "sqlite3")
return &provider{
sqlDB: sqlDB,
bunDB: bunDB,
sqlxDB: sqlxDB,
}, nil
}
func (e *provider) BunDB() *bun.DB {
return e.bunDB
}
func (e *provider) SQLDB() *sql.DB {
return e.sqlDB
}
func (e *provider) SQLxDB() *sqlx.DB {
return e.sqlxDB
}

37
pkg/sqlstore/sqlstore.go Normal file
View File

@@ -0,0 +1,37 @@
package sqlstore
import (
"context"
"database/sql"
"github.com/jmoiron/sqlx"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
// SQLStore is the interface for the SQLStore.
type SQLStore interface {
// SQLDB returns the underlying sql.DB.
SQLDB() *sql.DB
// BunDB returns the underlying bun.DB. This is the recommended way to interact with the database.
BunDB() *bun.DB
// SQLxDB returns the underlying sqlx.DB.
SQLxDB() *sqlx.DB
}
// SQLStoreMigrator is the interface for the SQLStoreMigrator.
type SQLStoreMigrator interface {
// Migrate migrates the database. Migrate acquires a lock on the database and runs the migrations.
Migrate(ctx context.Context) error
// Rollback rolls back the database. Rollback acquires a lock on the database and rolls back the migrations.
Rollback(ctx context.Context) error
}
type SQLStoreMigration interface {
// Register registers the migration with the given migrations.
Register(*migrate.Migrations) error
// Up runs the migration.
Up(context.Context, *bun.DB) error
// Down rolls back the migration.
Down(context.Context, *bun.DB) error
}

View File

@@ -0,0 +1,60 @@
package sqlstoretest
import (
"database/sql"
"fmt"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/sqlitedialect"
"go.signoz.io/signoz/pkg/sqlstore"
)
var _ sqlstore.SQLStore = (*MockSQLStore)(nil)
type MockSQLStore struct {
db *sql.DB
mock sqlmock.Sqlmock
bunDB *bun.DB
sqlxDB *sqlx.DB
}
func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *MockSQLStore {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(matcher))
if err != nil {
panic(err)
}
var bunDB *bun.DB
var sqlxDB *sqlx.DB
if config.Provider == "sqlite" {
bunDB = bun.NewDB(db, sqlitedialect.New())
sqlxDB = sqlx.NewDb(db, "sqlite3")
} else {
panic(fmt.Errorf("provider %q is not supported by mockSQLStore", config.Provider))
}
return &MockSQLStore{
db: db,
mock: mock,
bunDB: bunDB,
sqlxDB: sqlxDB,
}
}
func (s *MockSQLStore) BunDB() *bun.DB {
return s.bunDB
}
func (s *MockSQLStore) SQLDB() *sql.DB {
return s.db
}
func (s *MockSQLStore) SQLxDB() *sqlx.DB {
return s.sqlxDB
}
func (s *MockSQLStore) Mock() sqlmock.Sqlmock {
return s.mock
}

View File

@@ -0,0 +1,65 @@
package sqlstoremigrator
import (
"context"
"database/sql"
"errors"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
var (
ErrNoExecute = errors.New("no execute")
)
func NewMigrations(
ctx context.Context,
settings factory.ProviderSettings,
config sqlstore.Config,
factories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config]],
) (*migrate.Migrations, error) {
migrations := migrate.NewMigrations()
for _, factory := range factories.GetInOrder() {
migration, err := factory.New(ctx, settings, config)
if err != nil {
return nil, err
}
err = migration.Register(migrations)
if err != nil {
return nil, err
}
}
return migrations, nil
}
func WrapIfNotExists(ctx context.Context, db *bun.DB, table string, column string) func(q *bun.AddColumnQuery) *bun.AddColumnQuery {
return func(q *bun.AddColumnQuery) *bun.AddColumnQuery {
if db.Dialect().Name() != dialect.SQLite {
return q.IfNotExists()
}
var result string
err := db.
NewSelect().
ColumnExpr("name").
Table("pragma_table_info").
Where("arg = ?", table).
Where("name = ?", column).
Scan(ctx, &result)
if err != nil {
if err == sql.ErrNoRows {
return q
}
return q.Err(err)
}
return q.Err(ErrNoExecute)
}
}

View File

@@ -0,0 +1,52 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type addDataMigrations struct {
settings factory.ProviderSettings
}
func NewAddDataMigrationsFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_data_migrations"), newAddDataMigrations)
}
func newAddDataMigrations(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addDataMigrations{settings: settings}, nil
}
func (migration *addDataMigrations) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addDataMigrations) Up(ctx context.Context, db *bun.DB) error {
// CREATE TABLE IF NOT EXISTS data_migrations (
// id SERIAL PRIMARY KEY,
// version VARCHAR(255) NOT NULL UNIQUE,
// created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
// succeeded BOOLEAN NOT NULL DEFAULT FALSE
// );
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS data_migrations (
id SERIAL PRIMARY KEY,
version VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
succeeded BOOLEAN NOT NULL DEFAULT FALSE
);`); err != nil {
return err
}
return nil
}
func (migration *addDataMigrations) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,127 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type addOrganization struct {
settings factory.ProviderSettings
}
func NewAddOrganizationFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_organization"), newAddOrganization)
}
func newAddOrganization(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addOrganization{settings: settings}, nil
}
func (migration *addOrganization) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addOrganization) Up(ctx context.Context, db *bun.DB) error {
// table:invites
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS invites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
token TEXT NOT NULL,
created_at INTEGER NOT NULL,
role TEXT NOT NULL,
org_id TEXT NOT NULL,
FOREIGN KEY(org_id) REFERENCES organizations(id)
)`).Exec(ctx); err != nil {
return err
}
// table:organizations
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS organizations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at INTEGER NOT NULL,
is_anonymous INTEGER NOT NULL DEFAULT 0 CHECK(is_anonymous IN (0,1)),
has_opted_updates INTEGER NOT NULL DEFAULT 1 CHECK(has_opted_updates IN (0,1))
)`).Exec(ctx); err != nil {
return err
}
// table:users
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
created_at INTEGER NOT NULL,
profile_picture_url TEXT,
group_id TEXT NOT NULL,
org_id TEXT NOT NULL,
FOREIGN KEY(group_id) REFERENCES groups(id),
FOREIGN KEY(org_id) REFERENCES organizations(id)
)`).Exec(ctx); err != nil {
return err
}
// table:groups
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE
)`).Exec(ctx); err != nil {
return err
}
// table:reset_password_request
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS reset_password_request (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
token TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id)
)`).Exec(ctx); err != nil {
return err
}
// table:user_flags
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS user_flags (
user_id TEXT PRIMARY KEY,
flags TEXT,
FOREIGN KEY(user_id) REFERENCES users(id)
)`).Exec(ctx); err != nil {
return err
}
// table:apdex_settings
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS apdex_settings (
service_name TEXT PRIMARY KEY,
threshold FLOAT NOT NULL,
exclude_status_codes TEXT NOT NULL
)`).Exec(ctx); err != nil {
return err
}
// table:ingestion_keys
if _, err := db.NewRaw(`CREATE TABLE IF NOT EXISTS ingestion_keys (
key_id TEXT PRIMARY KEY,
name TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ingestion_key TEXT NOT NULL,
ingestion_url TEXT NOT NULL,
data_region TEXT NOT NULL
)`).Exec(ctx); err != nil {
return err
}
return nil
}
func (migration *addOrganization) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,60 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type addPreferences struct {
settings factory.ProviderSettings
}
func NewAddPreferencesFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_preferences"), newAddPreferences)
}
func newAddPreferences(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addPreferences{settings: settings}, nil
}
func (migration *addPreferences) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addPreferences) Up(ctx context.Context, db *bun.DB) error {
// table:user_preference
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS user_preference (
preference_id TEXT NOT NULL,
preference_value TEXT,
user_id TEXT NOT NULL,
PRIMARY KEY (preference_id,user_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
)`); err != nil {
return err
}
// table:org_preference
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS org_preference (
preference_id TEXT NOT NULL,
preference_value TEXT,
org_id TEXT NOT NULL,
PRIMARY KEY (preference_id,org_id),
FOREIGN KEY (org_id) REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE
);`); err != nil {
return err
}
return nil
}
func (migration *addPreferences) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,162 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/sqlstoremigrator"
)
type addDashboards struct {
settings factory.ProviderSettings
}
func NewAddDashboardsFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_dashboards"), newAddDashboards)
}
func newAddDashboards(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addDashboards{settings: settings}, nil
}
func (migration *addDashboards) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error {
// table:dashboards
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS dashboards (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
data TEXT NOT NULL
);`); err != nil {
return err
}
// table:rules
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
updated_at datetime NOT NULL,
deleted INTEGER DEFAULT 0,
data TEXT NOT NULL
);`); err != nil {
return err
}
// table:notification_channels
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS notification_channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
name TEXT NOT NULL UNIQUE,
type TEXT NOT NULL,
deleted INTEGER DEFAULT 0,
data TEXT NOT NULL
);`); err != nil {
return err
}
// table:planned_maintenance
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS planned_maintenance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
alert_ids TEXT,
schedule TEXT NOT NULL,
created_at datetime NOT NULL,
created_by TEXT NOT NULL,
updated_at datetime NOT NULL,
updated_by TEXT NOT NULL
);`); err != nil {
return err
}
// table:ttl_status
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS ttl_status (
id INTEGER PRIMARY KEY AUTOINCREMENT,
transaction_id TEXT NOT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
table_name TEXT NOT NULL,
ttl INTEGER DEFAULT 0,
cold_storage_ttl INTEGER DEFAULT 0,
status TEXT NOT NULL
);`); err != nil {
return err
}
// table:rules op:add column created_at
if _, err := db.
NewAddColumn().
Table("rules").
ColumnExpr("created_at datetime").
Apply(sqlstoremigrator.WrapIfNotExists(ctx, db, "rules", "created_at")).
Exec(ctx); err != nil && err != sqlstoremigrator.ErrNoExecute {
return err
}
// table:rules op:add column created_by
if _, err := db.
NewAddColumn().
Table("rules").
ColumnExpr("created_by TEXT").
Apply(sqlstoremigrator.WrapIfNotExists(ctx, db, "rules", "created_by")).
Exec(ctx); err != nil && err != sqlstoremigrator.ErrNoExecute {
return err
}
// table:rules op:add column updated_by
if _, err := db.
NewAddColumn().
Table("rules").
ColumnExpr("updated_by TEXT").
Apply(sqlstoremigrator.WrapIfNotExists(ctx, db, "rules", "updated_by")).
Exec(ctx); err != nil && err != sqlstoremigrator.ErrNoExecute {
return err
}
// table:dashboards op:add column created_by
if _, err := db.
NewAddColumn().
Table("dashboards").
ColumnExpr("created_by TEXT").
Apply(sqlstoremigrator.WrapIfNotExists(ctx, db, "dashboards", "created_by")).
Exec(ctx); err != nil && err != sqlstoremigrator.ErrNoExecute {
return err
}
// table:dashboards op:add column updated_by
if _, err := db.
NewAddColumn().
Table("dashboards").
ColumnExpr("updated_by TEXT").
Apply(sqlstoremigrator.WrapIfNotExists(ctx, db, "dashboards", "updated_by")).
Exec(ctx); err != nil && err != sqlstoremigrator.ErrNoExecute {
return err
}
// table:dashboards op:add column locked
if _, err := db.
NewAddColumn().
Table("dashboards").
ColumnExpr("locked INTEGER DEFAULT 0").
Apply(sqlstoremigrator.WrapIfNotExists(ctx, db, "dashboards", "locked")).
Exec(ctx); err != nil && err != sqlstoremigrator.ErrNoExecute {
return err
}
return nil
}
func (migration *addDashboards) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,55 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type addSavedViews struct {
settings factory.ProviderSettings
}
func NewAddSavedViewsFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_saved_views"), newAddSavedViews)
}
func newAddSavedViews(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addSavedViews{settings: settings}, nil
}
func (migration *addSavedViews) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addSavedViews) Up(ctx context.Context, db *bun.DB) error {
// table:saved_views op:create
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS saved_views (
uuid TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
created_at datetime NOT NULL,
created_by TEXT,
updated_at datetime NOT NULL,
updated_by TEXT,
source_page TEXT NOT NULL,
tags TEXT,
data TEXT NOT NULL,
extra_data TEXT
);`); err != nil {
return err
}
return nil
}
func (migration *addSavedViews) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,94 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type addAgents struct {
settings factory.ProviderSettings
}
func NewAddAgentsFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_agents"), newAddAgents)
}
func newAddAgents(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addAgents{settings: settings}, nil
}
func (migration *addAgents) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addAgents) Up(ctx context.Context, db *bun.DB) error {
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY UNIQUE,
started_at datetime NOT NULL,
terminated_at datetime,
current_status TEXT NOT NULL,
effective_config TEXT NOT NULL
);`); err != nil {
return err
}
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS agent_config_versions(
id TEXT PRIMARY KEY,
created_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
version INTEGER DEFAULT 1,
active int,
is_valid int,
disabled int,
element_type VARCHAR(120) NOT NULL,
deploy_status VARCHAR(80) NOT NULL DEFAULT 'DIRTY',
deploy_sequence INTEGER,
deploy_result TEXT,
last_hash TEXT,
last_config TEXT,
UNIQUE(element_type, version)
);`); err != nil {
return err
}
if _, err := db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS agent_config_versions_u1 ON agent_config_versions(element_type, version);`); err != nil {
return err
}
if _, err := db.ExecContext(ctx, `CREATE INDEX IF NOT EXISTS agent_config_versions_nu1 ON agent_config_versions(last_hash);`); err != nil {
return err
}
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS agent_config_elements(
id TEXT PRIMARY KEY,
created_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_by TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
element_id TEXT NOT NULL,
element_type VARCHAR(120) NOT NULL,
version_id TEXT NOT NULL
);`); err != nil {
return err
}
if _, err := db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS agent_config_elements_u1 ON agent_config_elements(version_id, element_id, element_type);`); err != nil {
return err
}
return nil
}
func (migration *addAgents) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,53 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type addPipelines struct {
settings factory.ProviderSettings
}
func NewAddPipelinesFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_pipelines"), newAddPipelines)
}
func newAddPipelines(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addPipelines{settings: settings}, nil
}
func (migration *addPipelines) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addPipelines) Up(ctx context.Context, db *bun.DB) error {
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS pipelines(
id TEXT PRIMARY KEY,
order_id INTEGER,
enabled BOOLEAN,
created_by TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(400) NOT NULL,
alias VARCHAR(20) NOT NULL,
description TEXT,
filter TEXT NOT NULL,
config_json TEXT
);`); err != nil {
return err
}
return nil
}
func (migration *addPipelines) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,59 @@
package migrations
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
type addIntegrations struct {
settings factory.ProviderSettings
}
func NewAddIntegrationsFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("add_integrations"), newAddIntegrations)
}
func newAddIntegrations(_ context.Context, settings factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &addIntegrations{settings: settings}, nil
}
func (migration *addIntegrations) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addIntegrations) Up(ctx context.Context, db *bun.DB) error {
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS integrations_installed(
integration_id TEXT PRIMARY KEY,
config_json TEXT,
installed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`); err != nil {
return err
}
if _, err := db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS cloud_integrations_accounts(
cloud_provider TEXT NOT NULL,
id TEXT NOT NULL,
config_json TEXT,
cloud_account_id TEXT,
last_agent_report_json TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
removed_at TIMESTAMP,
UNIQUE(cloud_provider, id)
)`); err != nil {
return err
}
return nil
}
func (migration *addIntegrations) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,35 @@
package migrationstest
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
)
func NoopMigrationFactory() factory.ProviderFactory[sqlstore.SQLStoreMigration, sqlstore.Config] {
return factory.NewProviderFactory(factory.MustNewName("noop"), newNoopMigration)
}
func newNoopMigration(_ context.Context, _ factory.ProviderSettings, _ sqlstore.Config) (sqlstore.SQLStoreMigration, error) {
return &noopMigration{}, nil
}
type noopMigration struct{}
func (migration *noopMigration) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *noopMigration) Up(ctx context.Context, db *bun.DB) error {
return nil
}
func (migration *noopMigration) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,108 @@
package sqlstoremigrator
import (
"context"
"errors"
"time"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlstore"
"go.uber.org/zap"
)
var (
migrationTableName = "migration"
migrationLockTableName = "migration_lock"
)
type migrator struct {
migrator *migrate.Migrator
sqlStore sqlstore.SQLStore
settings factory.ScopedProviderSettings
config sqlstore.MigrationConfig
}
func New(ctx context.Context, providerSettings factory.ProviderSettings, sqlstore sqlstore.SQLStore, migrations *migrate.Migrations, config sqlstore.Config) sqlstore.SQLStoreMigrator {
return &migrator{
sqlStore: sqlstore,
migrator: migrate.NewMigrator(sqlstore.BunDB(), migrations, migrate.WithTableName(migrationTableName), migrate.WithLocksTableName(migrationLockTableName)),
settings: factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/signoz/pkg/sqlstore/sqlstoremigrator"),
config: config.Migration,
}
}
func (migrator *migrator) Migrate(ctx context.Context) error {
migrator.settings.ZapLogger().Info("starting sqlstore migrations", zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
if err := migrator.migrator.Init(ctx); err != nil {
return err
}
if err := migrator.Lock(ctx); err != nil {
return err
}
defer migrator.migrator.Unlock(ctx) //nolint:errcheck
group, err := migrator.migrator.Migrate(ctx)
if err != nil {
return err
}
if group.IsZero() {
migrator.settings.ZapLogger().Info("no new migrations to run (database is up to date)", zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
return nil
}
migrator.settings.ZapLogger().Info("migrated to", zap.String("group", group.String()), zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
return nil
}
func (migrator *migrator) Rollback(ctx context.Context) error {
if err := migrator.Lock(ctx); err != nil {
return err
}
defer migrator.migrator.Unlock(ctx) //nolint:errcheck
group, err := migrator.migrator.Rollback(ctx)
if err != nil {
return err
}
if group.IsZero() {
migrator.settings.ZapLogger().Info("no groups to roll back", zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
return nil
}
migrator.settings.ZapLogger().Info("rolled back", zap.String("group", group.String()), zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
return nil
}
func (migrator *migrator) Lock(ctx context.Context) error {
if err := migrator.migrator.Lock(ctx); err == nil {
migrator.settings.ZapLogger().Info("acquired migration lock", zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
return nil
}
timer := time.NewTimer(migrator.config.LockTimeout)
defer timer.Stop()
ticker := time.NewTicker(migrator.config.LockInterval)
defer ticker.Stop()
for {
select {
case <-timer.C:
err := errors.New("timed out waiting for lock")
migrator.settings.ZapLogger().Error("cannot acquire lock", zap.Error(err), zap.Duration("lock_timeout", migrator.config.LockTimeout), zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
return err
case <-ticker.C:
if err := migrator.migrator.Lock(ctx); err == nil {
migrator.settings.ZapLogger().Info("acquired migration lock", zap.String("dialect", migrator.sqlStore.BunDB().Dialect().Name().String()))
return nil
}
case <-ctx.Done():
return ctx.Err()
}
}
}

View File

@@ -0,0 +1,49 @@
package sqlstoremigrator
import (
"context"
"database/sql/driver"
"fmt"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation/instrumentationtest"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/sqlstore/sqlstoretest"
"go.signoz.io/signoz/pkg/sqlstoremigrator/migrations/migrationstest"
)
func TestMigratorWithSqliteAndNoopMigration(t *testing.T) {
ctx := context.Background()
config := sqlstore.Config{Provider: "sqlite", Migration: sqlstore.MigrationConfig{LockTimeout: 10 * time.Second, LockInterval: 1 * time.Second}}
providerSettings := instrumentationtest.New().ToProviderSettings()
sqlStore := sqlstoretest.New(config, sqlmock.QueryMatcherEqual)
migrationFactories := factory.MustNewNamedMap(migrationstest.NoopMigrationFactory())
migrations, err := NewMigrations(ctx, providerSettings, config, migrationFactories)
require.NoError(t, err)
migrator := New(ctx, instrumentationtest.New().ToProviderSettings(), sqlStore, migrations, config)
sqlStore.Mock().ExpectExec(
fmt.Sprintf("CREATE TABLE IF NOT EXISTS migration (%q INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, %q VARCHAR, %q INTEGER, %q TIMESTAMP NOT NULL DEFAULT current_timestamp)", "id", "name", "group_id", "migrated_at"),
).WillReturnResult(driver.ResultNoRows)
sqlStore.Mock().ExpectExec(
fmt.Sprintf("CREATE TABLE IF NOT EXISTS migration_lock (%q INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, %q VARCHAR, UNIQUE (%q))", "id", "table_name", "table_name"),
).WillReturnResult(driver.ResultNoRows)
sqlStore.Mock().ExpectQuery(`INSERT INTO migration_lock ("table_name") VALUES ('migration') RETURNING "id"`).
WillReturnRows(sqlStore.Mock().NewRows([]string{"id"}).AddRow(1))
sqlStore.Mock().ExpectQuery(`SELECT * FROM migration`).
WillReturnRows(sqlStore.Mock().NewRows([]string{"id"}).AddRow(1))
sqlStore.Mock().ExpectQuery(`INSERT INTO migration ("name", "group_id") VALUES ('000', 1) RETURNING "id", "migrated_at"`).
WillReturnRows(sqlStore.Mock().NewRows([]string{"id", "migrated_at"}).AddRow(1, time.Now()))
err = migrator.Migrate(ctx)
require.NoError(t, err)
}

View File

@@ -1,14 +1,13 @@
package web package web
import ( import (
"go.signoz.io/signoz/pkg/confmap" "go.signoz.io/signoz/pkg/factory"
) )
// Config satisfies the confmap.Config interface
var _ confmap.Config = (*Config)(nil)
// Config holds the configuration for web. // Config holds the configuration for web.
type Config struct { type Config struct {
// Whether the web package is enabled.
Enabled bool `mapstructure:"enabled"`
// The prefix to serve the files from // The prefix to serve the files from
Prefix string `mapstructure:"prefix"` Prefix string `mapstructure:"prefix"`
// The directory containing the static build files. The root of this directory should // The directory containing the static build files. The root of this directory should
@@ -16,14 +15,26 @@ type Config struct {
Directory string `mapstructure:"directory"` Directory string `mapstructure:"directory"`
} }
func (c *Config) NewWithDefaults() confmap.Config { func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("web"), newConfig)
}
func newConfig() factory.Config {
return &Config{ return &Config{
Enabled: true,
Prefix: "/", Prefix: "/",
Directory: "/etc/signoz/web", Directory: "/etc/signoz/web",
} }
} }
func (c *Config) Validate() error { func (c Config) Validate() error {
return nil return nil
} }
func (c Config) GetProvider() string {
if c.Enabled {
return "router"
}
return "noop"
}

45
pkg/web/config_test.go Normal file
View File

@@ -0,0 +1,45 @@
package web
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/config/envprovider"
"go.signoz.io/signoz/pkg/factory"
)
func TestNewWithEnvProvider(t *testing.T) {
t.Setenv("SIGNOZ_WEB_PREFIX", "/web")
t.Setenv("SIGNOZ_WEB_ENABLED", "false")
conf, err := config.New(
context.Background(),
config.ResolverConfig{
Uris: []string{"env:"},
ProviderFactories: []config.ProviderFactory{
envprovider.NewFactory(),
},
},
[]factory.ConfigFactory{
NewConfigFactory(),
},
)
require.NoError(t, err)
actual := &Config{}
err = conf.Unmarshal("web", actual)
require.NoError(t, err)
def := NewConfigFactory().New().(*Config)
expected := &Config{
Enabled: false,
Prefix: "/web",
Directory: def.Directory,
}
assert.Equal(t, expected, actual)
}

26
pkg/web/noopweb/noop.go Normal file
View File

@@ -0,0 +1,26 @@
package noopweb
import (
"context"
"net/http"
"github.com/gorilla/mux"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/web"
)
type noop struct{}
func NewFactory() factory.ProviderFactory[web.Web, web.Config] {
return factory.NewProviderFactory(factory.MustNewName("noop"), New)
}
func New(ctx context.Context, settings factory.ProviderSettings, config web.Config) (web.Web, error) {
return &noop{}, nil
}
func (n *noop) AddToRouter(router *mux.Router) error {
return nil
}
func (n *noop) ServeHTTP(w http.ResponseWriter, r *http.Request) {}

View File

@@ -0,0 +1,98 @@
package routerweb
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
"github.com/gorilla/mux"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/http/middleware"
"go.signoz.io/signoz/pkg/web"
)
var _ web.Web = (*router)(nil)
const (
indexFileName string = "index.html"
)
type router struct {
config web.Config
}
func NewFactory() factory.ProviderFactory[web.Web, web.Config] {
return factory.NewProviderFactory(factory.MustNewName("router"), New)
}
func New(ctx context.Context, settings factory.ProviderSettings, config web.Config) (web.Web, error) {
if settings.ZapLogger == nil {
return nil, fmt.Errorf("cannot build web, logger is required")
}
fi, err := os.Stat(config.Directory)
if err != nil {
return nil, fmt.Errorf("cannot access web directory: %w", err)
}
ok := fi.IsDir()
if !ok {
return nil, fmt.Errorf("web directory is not a directory")
}
fi, err = os.Stat(filepath.Join(config.Directory, indexFileName))
if err != nil {
return nil, fmt.Errorf("cannot access %q in web directory: %w", indexFileName, err)
}
if os.IsNotExist(err) || fi.IsDir() {
return nil, fmt.Errorf("%q does not exist", indexFileName)
}
return &router{
config: config,
}, nil
}
func (web *router) AddToRouter(router *mux.Router) error {
cache := middleware.NewCache(7 * 24 * time.Hour)
err := router.PathPrefix(web.config.Prefix).
Handler(
http.StripPrefix(
web.config.Prefix,
cache.Wrap(http.HandlerFunc(web.ServeHTTP)),
),
).GetError()
if err != nil {
return fmt.Errorf("unable to add web to router: %w", err)
}
return nil
}
func (web *router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Join internally call path.Clean to prevent directory traversal
path := filepath.Join(web.config.Directory, req.URL.Path)
// check whether a file exists or is a directory at the given path
fi, err := os.Stat(path)
if os.IsNotExist(err) || fi.IsDir() {
// file does not exist or path is a directory, serve index.html
http.ServeFile(rw, req, filepath.Join(web.config.Directory, indexFileName))
return
}
if err != nil {
// if we got an error (that wasn't that the file doesn't exist) stating the
// file, return a 500 internal server error and stop
// TODO: Put down a crash html page here
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// otherwise, use http.FileServer to serve the static file
http.FileServer(http.Dir(web.config.Directory)).ServeHTTP(rw, req)
}

View File

@@ -1,6 +1,7 @@
package web package routerweb
import ( import (
"context"
"io" "io"
"net" "net"
"net/http" "net/http"
@@ -11,7 +12,8 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap" "go.signoz.io/signoz/pkg/instrumentation/instrumentationtest"
"go.signoz.io/signoz/pkg/web"
) )
func TestServeHttpWithoutPrefix(t *testing.T) { func TestServeHttpWithoutPrefix(t *testing.T) {
@@ -22,7 +24,7 @@ func TestServeHttpWithoutPrefix(t *testing.T) {
expected, err := io.ReadAll(fi) expected, err := io.ReadAll(fi)
require.NoError(t, err) require.NoError(t, err)
web, err := New(zap.NewNop(), Config{Prefix: "/", Directory: filepath.Join("testdata")}) web, err := New(context.Background(), instrumentationtest.New().ToProviderSettings(), web.Config{Prefix: "/", Directory: filepath.Join("testdata")})
require.NoError(t, err) require.NoError(t, err)
router := mux.NewRouter() router := mux.NewRouter()
@@ -87,7 +89,7 @@ func TestServeHttpWithPrefix(t *testing.T) {
expected, err := io.ReadAll(fi) expected, err := io.ReadAll(fi)
require.NoError(t, err) require.NoError(t, err)
web, err := New(zap.NewNop(), Config{Prefix: "/web", Directory: filepath.Join("testdata")}) web, err := New(context.Background(), instrumentationtest.New().ToProviderSettings(), web.Config{Prefix: "/web", Directory: filepath.Join("testdata")})
require.NoError(t, err) require.NoError(t, err)
router := mux.NewRouter() router := mux.NewRouter()

View File

@@ -1,94 +1,15 @@
package web package web
import ( import (
"fmt"
"net/http" "net/http"
"os"
"path/filepath"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"go.signoz.io/signoz/pkg/http/middleware"
"go.uber.org/zap"
) )
var _ http.Handler = (*Web)(nil) // Web is the interface that wraps the methods of the web package.
type Web interface {
const ( // AddToRouter adds the web routes to the router.
indexFileName string = "index.html" AddToRouter(router *mux.Router) error
) // ServeHTTP serves the web routes.
http.Handler
type Web struct {
logger *zap.Logger
cfg Config
}
func New(logger *zap.Logger, cfg Config) (*Web, error) {
if logger == nil {
return nil, fmt.Errorf("cannot build web, logger is required")
}
fi, err := os.Stat(cfg.Directory)
if err != nil {
return nil, fmt.Errorf("cannot access web directory: %w", err)
}
ok := fi.IsDir()
if !ok {
return nil, fmt.Errorf("web directory is not a directory")
}
fi, err = os.Stat(filepath.Join(cfg.Directory, indexFileName))
if err != nil {
return nil, fmt.Errorf("cannot access %q in web directory: %w", indexFileName, err)
}
if os.IsNotExist(err) || fi.IsDir() {
return nil, fmt.Errorf("%q does not exist", indexFileName)
}
return &Web{
logger: logger.Named("go.signoz.io/pkg/web"),
cfg: cfg,
}, nil
}
func (web *Web) AddToRouter(router *mux.Router) error {
cache := middleware.NewCache(7 * 24 * time.Hour)
err := router.PathPrefix(web.cfg.Prefix).
Handler(
http.StripPrefix(
web.cfg.Prefix,
cache.Wrap(http.HandlerFunc(web.ServeHTTP)),
),
).GetError()
if err != nil {
return fmt.Errorf("unable to add web to router: %w", err)
}
return nil
}
func (web *Web) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Join internally call path.Clean to prevent directory traversal
path := filepath.Join(web.cfg.Directory, req.URL.Path)
// check whether a file exists or is a directory at the given path
fi, err := os.Stat(path)
if os.IsNotExist(err) || fi.IsDir() {
// file does not exist or path is a directory, serve index.html
http.ServeFile(rw, req, filepath.Join(web.cfg.Directory, indexFileName))
return
}
if err != nil {
// if we got an error (that wasn't that the file doesn't exist) stating the
// file, return a 500 internal server error and stop
// TODO: Put down a crash html page here
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
// otherwise, use http.FileServer to serve the static file
http.FileServer(http.Dir(web.cfg.Directory)).ServeHTTP(rw, req)
} }