feat(.): initialize all factories (#6844)

### Summary

feat(.): initialize all factories

#### Related Issues / PR's

Removed all redundant commits of https://github.com/SigNoz/signoz/pull/6843
Closes https://github.com/SigNoz/signoz/pull/6782
This commit is contained in:
Vibhu Pandey 2025-01-20 17:45:33 +05:30 committed by GitHub
parent 644135a933
commit 0cf9003e3a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1201 additions and 660 deletions

View File

@ -1,32 +0,0 @@
##################### SigNoz Configuration Defaults #####################
#
# Do not modify this file
#
##################### Web #####################
web:
# The prefix to serve web on
prefix: /
# The directory containing the static build files.
directory: /etc/signoz/web
##################### Cache #####################
cache:
# specifies the caching provider to use.
provider: memory
# memory: Uses in-memory caching.
memory:
# Time-to-live for cache entries in memory. Specify the duration in ns
ttl: 60000000000
# The interval at which the cache will be cleaned up
cleanupInterval:
# redis: Uses Redis as the caching backend.
redis:
# The hostname or IP address of the Redis server.
host: localhost
# The port on which the Redis server is running. Default is usually 6379.
port: 6379
# The password for authenticating with the Redis server, if required.
password:
# The Redis database number to use
db: 0

70
conf/example.yaml Normal file
View File

@ -0,0 +1,70 @@
##################### SigNoz Configuration Example #####################
#
# 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:
# Whether to enable the web frontend
enabled: true
# The prefix to serve web on
prefix: /
# The directory containing the static build files.
directory: /etc/signoz/web
##################### Cache #####################
cache:
# specifies the caching provider to use.
provider: memory
# memory: Uses in-memory caching.
memory:
# Time-to-live for cache entries in memory. Specify the duration in ns
ttl: 60000000000
# The interval at which the cache will be cleaned up
cleanupInterval: 1m
# redis: Uses Redis as the caching backend.
redis:
# The hostname or IP address of the Redis server.
host: localhost
# The port on which the Redis server is running. Default is usually 6379.
port: 6379
# The password for authenticating with the Redis server, if required.
password:
# The Redis database number to use
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

@ -81,7 +81,6 @@ type ServerOptions struct {
GatewayUrl string
UseLogsNewSchema bool
UseTraceNewSchema bool
SkipWebFrontend bool
}
// Server runs HTTP api service
@ -388,11 +387,9 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
handler = handlers.CompressHandler(handler)
if !s.serverOptions.SkipWebFrontend {
err := web.AddToRouter(r)
if err != nil {
return nil, err
}
err := web.AddToRouter(r)
if err != nil {
return nil, err
}
return &http.Server{

View File

@ -10,12 +10,12 @@ import (
"syscall"
"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"
signozconfig "go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider"
"go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/config/envprovider"
"go.signoz.io/signoz/pkg/config/fileprovider"
"go.signoz.io/signoz/pkg/query-service/auth"
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/migrate"
@ -108,7 +108,6 @@ func main() {
var dialTimeout time.Duration
var gatewayUrl string
var useLicensesV3 bool
var skipWebFrontend bool
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")
@ -126,7 +125,6 @@ func main() {
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
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()
loggerMgr := initZapLog(enableQueryServiceLogOTLPExport)
@ -136,19 +134,18 @@ func main() {
version.PrintVersion()
config, err := signozconfig.New(context.Background(), signozconfig.ProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{"signozenv:"},
ProviderFactories: []confmap.ProviderFactory{
signozenvprovider.NewFactory(),
},
config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{
Uris: []string{"env:"},
ProviderFactories: []config.ProviderFactory{
envprovider.NewFactory(),
fileprovider.NewFactory(),
},
})
if err != nil {
zap.L().Fatal("Failed to create config", zap.Error(err))
}
signoz, err := signoz.New(config, skipWebFrontend)
signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig())
if err != nil {
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
}
@ -171,7 +168,6 @@ func main() {
GatewayUrl: gatewayUrl,
UseLogsNewSchema: useLogsNewSchema,
UseTraceNewSchema: useTraceNewSchema,
SkipWebFrontend: skipWebFrontend,
}
// Read the jwt secret key

5
go.mod
View File

@ -20,6 +20,7 @@ require (
github.com/go-kit/log v0.2.1
github.com/go-redis/redis/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/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.1
@ -29,6 +30,7 @@ require (
github.com/jmoiron/sqlx v1.3.4
github.com/json-iterator/go v1.1.12
github.com/knadh/koanf v1.5.0
github.com/knadh/koanf/v2 v2.1.1
github.com/mailru/easyjson v0.7.7
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/oklog/oklog v0.3.2
@ -101,6 +103,7 @@ require (
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/felixge/httpsnoop v1.0.4 // 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/errors v0.7.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
@ -108,7 +111,6 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // 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/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
@ -129,7 +131,6 @@ require (
github.com/jpillora/backoff v1.0.0 // indirect
github.com/jtolds/gls v4.20.0+incompatible // 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/leodido/go-syslog/v4 v4.2.0 // indirect
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect

14
pkg/cache/config.go vendored
View File

@ -4,12 +4,9 @@ import (
"time"
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 {
TTL time.Duration `mapstructure:"ttl"`
CleanupInterval time.Duration `mapstructure:"cleanupInterval"`
@ -28,7 +25,11 @@ type Config struct {
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{
Provider: "memory",
Memory: Memory{
@ -42,8 +43,9 @@ func (c *Config) NewWithDefaults() confmap.Config {
DB: 0,
},
}
}
func (c *Config) Validate() error {
func (c Config) Validate() error {
return nil
}

View File

@ -7,15 +7,20 @@ import (
"time"
go_cache "github.com/patrickmn/go-cache"
_cache "go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/factory"
)
type provider struct {
cc *go_cache.Cache
}
func New(opts *_cache.Memory) *provider {
return &provider{cc: go_cache.New(opts.TTL, opts.CleanupInterval)}
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 &provider{cc: go_cache.New(config.Memory.TTL, config.Memory.CleanupInterval)}, nil
}
// Connect does nothing
@ -24,11 +29,11 @@ func (c *provider) Connect(_ context.Context) error {
}
// Store stores the data in the cache
func (c *provider) Store(_ context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error {
func (c *provider) 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")
return cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory")
}
c.cc.Set(cacheKey, data, ttl)
@ -36,32 +41,32 @@ func (c *provider) Store(_ context.Context, cacheKey string, data _cache.Cacheab
}
// Retrieve retrieves the data from the cache
func (c *provider) Retrieve(_ context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) {
func (c *provider) 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")
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())
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
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")
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
return cache.RetrieveStatusHit, nil
}
// SetTTL sets the TTL for the cache entry
@ -91,6 +96,6 @@ func (c *provider) Close(_ context.Context) error {
}
// Configuration returns the cache configuration
func (c *provider) Configuration() *_cache.Memory {
func (c *provider) Configuration() *cache.Memory {
return nil
}

View File

@ -7,18 +7,21 @@ import (
"time"
"github.com/stretchr/testify/assert"
_cache "go.signoz.io/signoz/pkg/cache"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/factory/providertest"
)
// TestNew tests the New function
func TestNew(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
assert.NotNil(t, c)
assert.NotNil(t, c.cc)
assert.NotNil(t, c.(*provider).cc)
assert.NoError(t, c.Connect(context.Background()))
}
@ -53,32 +56,35 @@ func (dce DCacheableEntity) UnmarshalBinary(data []byte) error {
// TestStore tests the Store function
// this should fail because of nil pointer error
func TestStoreWithNilPointer(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
var storeCacheableEntity *CacheableEntity
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
}
// this should fail because of no pointer error
func TestStoreWithStruct(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
var storeCacheableEntity CacheableEntity
assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
}
func TestStoreWithNonNilPointer(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -89,11 +95,12 @@ func TestStoreWithNonNilPointer(t *testing.T) {
// TestRetrieve tests the Retrieve function
func TestRetrieveWithNilPointer(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -105,15 +112,16 @@ func TestRetrieveWithNilPointer(t *testing.T) {
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.Error(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
}
func TestRetrieveWitNonPointer(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -125,15 +133,16 @@ func TestRetrieveWitNonPointer(t *testing.T) {
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.Error(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
}
func TestRetrieveWithDifferentTypes(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -144,15 +153,16 @@ func TestRetrieveWithDifferentTypes(t *testing.T) {
retrieveCacheableEntity := new(DCacheableEntity)
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.Error(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusError)
}
func TestRetrieveWithSameTypes(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -163,13 +173,14 @@ func TestRetrieveWithSameTypes(t *testing.T) {
retrieveCacheableEntity := new(CacheableEntity)
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.NoError(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
}
// TestSetTTL tests the SetTTL function
func TestSetTTL(t *testing.T) {
c := New(&_cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second})
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second}})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -180,7 +191,7 @@ func TestSetTTL(t *testing.T) {
time.Sleep(3 * time.Second)
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.NoError(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 2*time.Second))
@ -188,17 +199,18 @@ func TestSetTTL(t *testing.T) {
time.Sleep(3 * time.Second)
retrieveStatus, err = c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.NoError(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
assert.Equal(t, retrieveCacheableEntity, storeCacheableEntity)
}
// TestRemove tests the Remove function
func TestRemove(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -210,17 +222,18 @@ func TestRemove(t *testing.T) {
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.NoError(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
}
// TestBulkRemove tests the BulkRemove function
func TestBulkRemove(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -233,22 +246,23 @@ func TestBulkRemove(t *testing.T) {
retrieveStatus, err := c.Retrieve(context.Background(), "key1", retrieveCacheableEntity, false)
assert.NoError(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
retrieveStatus, err = c.Retrieve(context.Background(), "key2", retrieveCacheableEntity, false)
assert.NoError(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss)
assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity)
}
// TestCache tests the cache
func TestCache(t *testing.T) {
opts := &_cache.Memory{
opts := cache.Memory{
TTL: 10 * time.Second,
CleanupInterval: 10 * time.Second,
}
c := New(opts)
c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts})
require.NoError(t, err)
storeCacheableEntity := &CacheableEntity{
Key: "some-random-key",
Value: 1,
@ -258,7 +272,7 @@ func TestCache(t *testing.T) {
assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second))
retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false)
assert.NoError(t, err)
assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit)
assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit)
assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity)
c.Remove(context.Background(), "key")
}

View File

@ -7,17 +7,22 @@ import (
"time"
"github.com/go-redis/redis/v8"
_cache "go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/factory"
"go.uber.org/zap"
)
type provider struct {
client *redis.Client
opts *_cache.Redis
opts cache.Redis
}
func New(opts *_cache.Redis) *provider {
return &provider{opts: opts}
func NewFactory() factory.ProviderFactory[cache.Cache, cache.Config] {
return factory.NewProviderFactory(factory.MustNewName("redis"), New)
}
func New(ctx context.Context, settings factory.ProviderSettings, config cache.Config) (cache.Cache, error) {
return &provider{opts: config.Redis}, nil
}
// WithClient creates a new cache with the given client
@ -36,20 +41,20 @@ func (c *provider) Connect(_ context.Context) error {
}
// Store stores the data in the cache
func (c *provider) Store(ctx context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error {
func (c *provider) Store(ctx context.Context, cacheKey string, data cache.CacheableEntity, ttl time.Duration) error {
return c.client.Set(ctx, cacheKey, data, ttl).Err()
}
// Retrieve retrieves the data from the cache
func (c *provider) Retrieve(ctx context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) {
func (c *provider) Retrieve(ctx context.Context, cacheKey string, dest cache.CacheableEntity, allowExpired bool) (cache.RetrieveStatus, error) {
err := c.client.Get(ctx, cacheKey).Scan(dest)
if err != nil {
if errors.Is(err, redis.Nil) {
return _cache.RetrieveStatusKeyMiss, nil
return cache.RetrieveStatusKeyMiss, nil
}
return _cache.RetrieveStatusError, err
return cache.RetrieveStatusError, err
}
return _cache.RetrieveStatusHit, nil
return cache.RetrieveStatusHit, nil
}
// SetTTL sets the TTL for the cache entry
@ -87,11 +92,6 @@ func (c *provider) GetClient() *redis.Client {
return c.client
}
// GetOptions returns the options
func (c *provider) GetOptions() *_cache.Redis {
return c.opts
}
// GetTTL returns the TTL for the cache entry
func (c *provider) GetTTL(ctx context.Context, cacheKey string) time.Duration {
ttl, err := c.client.TTL(ctx, cacheKey).Result()

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,32 +3,34 @@ package config
import (
"context"
"go.signoz.io/signoz/pkg/cache"
signozconfmap "go.signoz.io/signoz/pkg/confmap"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/web"
"go.signoz.io/signoz/pkg/factory"
)
// This map contains the default values of all config structs
var (
defaults = map[string]signozconfmap.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)
func New(ctx context.Context, resolverConfig ResolverConfig, configFactories []factory.ConfigFactory) (*Conf, error) {
// Get the config from the resolver
resolver, err := NewResolver(resolverConfig)
if err != nil {
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,90 @@
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 TestGetWithNoPrefix(t *testing.T) {
t.Setenv("K1_K2", "string")
t.Setenv("K3_K4", "string")
expected := map[string]any{}
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 (
"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 configuration provider.
type Provider interface {
// Get returns the configuration, or error otherwise.
Get(ctx context.Context) (*Config, error)
}
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
// Get returns the configuration for the given URI.
Get(context.Context, Uri) (*Conf, error)
// Scheme returns the scheme of the provider.
Scheme() string
}

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
}

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()))
}

View File

@ -1,6 +1,8 @@
package factory
import "context"
import (
"context"
)
type Provider = any
@ -21,10 +23,17 @@ 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 (factory *providerFactory[P, C]) New(ctx context.Context, settings ProviderSettings, config C) (p P, err error) {
provider, err := factory.newProviderFunc(ctx, settings, config)
if err != nil {
return
}
p = provider
return
}
// NewProviderFactory creates a new provider factory.
func NewProviderFactory[P Provider, C Config](name Name, newProviderFunc NewProviderFunc[P, C]) ProviderFactory[P, C] {
return &providerFactory[P, C]{
name: name,
@ -32,7 +41,8 @@ func NewProviderFactory[P Provider, C Config](name Name, newProviderFunc NewProv
}
}
func NewFromFactory[P Provider, C Config](ctx context.Context, settings ProviderSettings, config C, factories NamedMap[ProviderFactory[P, C]], key string) (p P, err error) {
// NewProviderFromNamedMap creates a new provider from a factory based on the input key.
func NewProviderFromNamedMap[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

View File

@ -32,10 +32,10 @@ func TestNewProviderFactoryFromFactory(t *testing.T) {
m := MustNewNamedMap(pf)
assert.Equal(t, MustNewName("p1"), pf.Name())
p, err := NewFromFactory(context.Background(), ProviderSettings{}, pc1{}, m, "p1")
p, err := NewProviderFromNamedMap(context.Background(), ProviderSettings{}, pc1{}, m, "p1")
assert.NoError(t, err)
assert.IsType(t, p1{}, p)
_, err = NewFromFactory(context.Background(), ProviderSettings{}, pc1{}, m, "p2")
_, err = NewProviderFromNamedMap(context.Background(), ProviderSettings{}, pc1{}, m, "p2")
assert.Error(t, err)
}

View File

@ -0,0 +1,10 @@
package providertest
import (
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation/instrumentationtest"
)
func NewSettings() factory.ProviderSettings {
return instrumentationtest.New().ToProviderSettings()
}

View File

@ -1,12 +1,5 @@
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.
type Config struct {
//Address specifies the TCP address for the server to listen on, in the form "host:port".
@ -14,14 +7,3 @@ type Config struct {
// See net.Dial for details of the address format.
Address string `mapstructure:"address"`
}
func (c *Config) NewWithDefaults() confmap.Config {
return &Config{
Address: "0.0.0.0:8080",
}
}
func (c *Config) Validate() error {
return nil
}

View File

@ -6,21 +6,20 @@ import (
"net/http"
"time"
"go.signoz.io/signoz/pkg/registry"
"go.signoz.io/signoz/pkg/factory"
"go.uber.org/zap"
)
var _ registry.NamedService = (*Server)(nil)
var _ factory.Service = (*Server)(nil)
type Server struct {
srv *http.Server
logger *zap.Logger
handler http.Handler
cfg Config
name string
}
func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Server, error) {
func New(logger *zap.Logger, cfg Config, handler http.Handler) (*Server, error) {
if handler == nil {
return nil, fmt.Errorf("cannot build http server, handler is required")
}
@ -29,10 +28,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")
}
if name == "" {
return nil, fmt.Errorf("cannot build http server, name is required")
}
srv := &http.Server{
Addr: cfg.Address,
Handler: handler,
@ -46,14 +41,9 @@ func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Se
logger: logger.Named("go.signoz.io/pkg/http/server"),
handler: handler,
cfg: cfg,
name: name,
}, nil
}
func (server *Server) Name() string {
return server.name
}
func (server *Server) Start(ctx context.Context) error {
server.logger.Info("starting http server", zap.String("address", server.srv.Addr))
if err := server.srv.ListenAndServe(); err != nil {

View File

@ -34,6 +34,8 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/preferences"
"go.signoz.io/signoz/pkg/query-service/common"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/signoz"
"go.signoz.io/signoz/pkg/web"
"go.signoz.io/signoz/pkg/query-service/app/explorer"
"go.signoz.io/signoz/pkg/query-service/auth"
@ -69,6 +71,7 @@ type ServerOptions struct {
Cluster string
UseLogsNewSchema bool
UseTraceNewSchema bool
SigNoz *signoz.SigNoz
}
// Server runs HTTP, Mux and a grpc server
@ -218,7 +221,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
unavailableChannel: make(chan healthcheck.Status),
}
httpServer, err := s.createPublicServer(apiHandler)
httpServer, err := s.createPublicServer(apiHandler, serverOptions.SigNoz.Web)
if err != nil {
return nil, err
@ -282,7 +285,7 @@ func (s *Server) createPrivateServer(api *APIHandler) (*http.Server, error) {
}, nil
}
func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) {
func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server, error) {
r := NewRouter()
@ -327,6 +330,11 @@ func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) {
handler = handlers.CompressHandler(handler)
err := web.AddToRouter(r)
if err != nil {
return nil, err
}
return &http.Server{
Handler: handler,
}, nil

View File

@ -9,11 +9,15 @@ import (
"time"
prommodel "github.com/prometheus/common/model"
"go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/config/envprovider"
"go.signoz.io/signoz/pkg/config/fileprovider"
"go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/auth"
"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/signoz"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
@ -74,6 +78,22 @@ func main() {
logger := loggerMgr.Sugar()
version.PrintVersion()
config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{
Uris: []string{"env:"},
ProviderFactories: []config.ProviderFactory{
envprovider.NewFactory(),
fileprovider.NewFactory(),
},
})
if err != nil {
zap.L().Fatal("Failed to create config", zap.Error(err))
}
signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig())
if err != nil {
zap.L().Fatal("Failed to create signoz struct", zap.Error(err))
}
serverOptions := &app.ServerOptions{
HTTPHostPort: constants.HTTPHostPort,
PromConfigPath: promConfigPath,
@ -90,6 +110,7 @@ func main() {
Cluster: cluster,
UseLogsNewSchema: useLogsNewSchema,
UseTraceNewSchema: useTraceNewSchema,
SigNoz: signoz,
}
// Read the jwt secret key

View File

@ -8,18 +8,19 @@ import (
"os/signal"
"syscall"
"go.signoz.io/signoz/pkg/factory"
"go.uber.org/zap"
)
type Registry struct {
services []NamedService
services []factory.Service
logger *zap.Logger
startCh chan error
stopCh chan error
}
// 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 {
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 {
for _, s := range r.services {
go func(s Service) {
go func(s factory.Service) {
err := s.Start(ctx)
r.startCh <- err
}(s)
@ -66,7 +67,7 @@ func (r *Registry) Wait(ctx context.Context) error {
func (r *Registry) Stop(ctx context.Context) error {
for _, s := range r.services {
go func(s Service) {
go func(s factory.Service) {
err := s.Stop(ctx)
r.stopCh <- err
}(s)

View File

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

View File

@ -1,16 +0,0 @@
package registry
import "context"
type Service interface {
// Starts a service. The service should return an error if it cannot be started.
Start(context.Context) error
// Stops a service.
Stop(context.Context) error
}
type NamedService interface {
// Identifier of a service. It should be unique across all services.
Name() string
Service
}

View File

@ -1,49 +0,0 @@
package registry
import (
"context"
"net"
"net/http"
)
var _ NamedService = (*httpService)(nil)
type httpService struct {
Listener net.Listener
Server *http.Server
name string
}
func newHttpService(name string) (*httpService, error) {
return &httpService{
name: name,
Server: &http.Server{},
}, nil
}
func (service *httpService) Name() string {
return service.name
}
func (service *httpService) Start(ctx context.Context) error {
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
return err
}
service.Listener = listener
if err := service.Server.Serve(service.Listener); err != nil {
if err != http.ErrServerClosed {
return err
}
}
return nil
}
func (service *httpService) Stop(ctx context.Context) error {
if err := service.Server.Shutdown(ctx); err != nil {
return err
}
return nil
}

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

@ -0,0 +1,53 @@
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/sqlmigrator"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/web"
)
// Config defines the entire input configuration of signoz.
type Config struct {
// Instrumentation config
Instrumentation instrumentation.Config `mapstructure:"instrumentation"`
// Web config
Web web.Config `mapstructure:"web"`
// Cache config
Cache cache.Config `mapstructure:"cache"`
// SQLStore config
SQLStore sqlstore.Config `mapstructure:"sqlstore"`
// SQLMigrator config
SQLMigrator sqlmigrator.Config `mapstructure:"sqlmigrator"`
}
func NewConfig(ctx context.Context, resolverConfig config.ResolverConfig) (Config, error) {
configFactories := []factory.ConfigFactory{
instrumentation.NewConfigFactory(),
web.NewConfigFactory(),
cache.NewConfigFactory(),
sqlstore.NewConfigFactory(),
sqlmigrator.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
}

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

@ -0,0 +1,54 @@
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/sqlmigration"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/sqlstore/sqlitesqlstore"
"go.signoz.io/signoz/pkg/web"
"go.signoz.io/signoz/pkg/web/noopweb"
"go.signoz.io/signoz/pkg/web/routerweb"
)
type ProviderConfig struct {
// Map of all cache provider factories
CacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]]
// Map of all web provider factories
WebProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]]
// Map of all sqlstore provider factories
SQLStoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]]
// Map of all sql migration provider factories
SQLMigrationProviderFactories factory.NamedMap[factory.ProviderFactory[sqlmigration.SQLMigration, sqlmigration.Config]]
}
func NewProviderConfig() ProviderConfig {
return ProviderConfig{
CacheProviderFactories: factory.MustNewNamedMap(
memorycache.NewFactory(),
rediscache.NewFactory(),
),
WebProviderFactories: factory.MustNewNamedMap(
routerweb.NewFactory(),
noopweb.NewFactory(),
),
SQLStoreProviderFactories: factory.MustNewNamedMap(
sqlitesqlstore.NewFactory(),
),
SQLMigrationProviderFactories: factory.MustNewNamedMap(
sqlmigration.NewAddDataMigrationsFactory(),
sqlmigration.NewAddOrganizationFactory(),
sqlmigration.NewAddPreferencesFactory(),
sqlmigration.NewAddDashboardsFactory(),
sqlmigration.NewAddSavedViewsFactory(),
sqlmigration.NewAddAgentsFactory(),
sqlmigration.NewAddPipelinesFactory(),
sqlmigration.NewAddIntegrationsFactory(),
),
}
}

View File

@ -0,0 +1,16 @@
package signoz
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewProviderConfig(t *testing.T) {
// This is a test to ensure that provider factories can be created without panicking since
// we are using the factory.MustNewNamedMap function to initialize the provider factories.
// It also helps us catch these errors during testing instead of runtime.
assert.NotPanics(t, func() {
NewProviderConfig()
})
}

View File

@ -1,13 +1,14 @@
package signoz
import (
"context"
"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/config"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/version"
"go.signoz.io/signoz/pkg/web"
"go.signoz.io/signoz/pkg/web/routerweb"
"go.uber.org/zap"
)
type SigNoz struct {
@ -15,19 +16,41 @@ type SigNoz struct {
Web web.Web
}
func New(config *config.Config, skipWebFrontend bool) (*SigNoz, error) {
var cache cache.Cache
// init for the cache
switch config.Cache.Provider {
case "memory":
cache = memorycache.New(&config.Cache.Memory)
case "redis":
cache = rediscache.New(&config.Cache.Redis)
func New(
ctx context.Context,
config Config,
providerConfig ProviderConfig,
) (*SigNoz, error) {
// Initialize instrumentation
instrumentation, err := instrumentation.New(ctx, version.Build{}, config.Instrumentation)
if err != nil {
return nil, err
}
web, err := routerweb.New(zap.L(), config.Web)
if err != nil && !skipWebFrontend {
// Get the provider settings from instrumentation
providerSettings := instrumentation.ToProviderSettings()
// Initialize cache from the available cache provider factories
cache, err := factory.NewProviderFromNamedMap(
ctx,
providerSettings,
config.Cache,
providerConfig.CacheProviderFactories,
config.Cache.Provider,
)
if err != nil {
return nil, err
}
// Initialize web from the available web provider factories
web, err := factory.NewProviderFromNamedMap(
ctx,
providerSettings,
config.Web,
providerConfig.WebProviderFactories,
config.Web.Provider(),
)
if err != nil {
return nil, err
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addDataMigrations struct{}
func NewAddDataMigrationsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddDataMigrationsFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_data_migrations"), newAddDataMigrations)
}
func newAddDataMigrations(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddDataMigrations(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addDataMigrations{}, nil
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addOrganization struct{}
func NewAddOrganizationFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddOrganizationFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_organization"), newAddOrganization)
}
func newAddOrganization(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddOrganization(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addOrganization{}, nil
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addPreferences struct{}
func NewAddPreferencesFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddPreferencesFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_preferences"), newAddPreferences)
}
func newAddPreferences(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddPreferences(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addPreferences{}, nil
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addDashboards struct{}
func NewAddDashboardsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddDashboardsFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_dashboards"), newAddDashboards)
}
func newAddDashboards(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddDashboards(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addDashboards{}, nil
}
@ -96,8 +95,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error {
NewAddColumn().
Table("rules").
ColumnExpr("created_at datetime").
Apply(sqlmigrator.WrapIfNotExists(ctx, db, "rules", "created_at")).
Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute {
Apply(WrapIfNotExists(ctx, db, "rules", "created_at")).
Exec(ctx); err != nil && err != ErrNoExecute {
return err
}
@ -106,8 +105,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error {
NewAddColumn().
Table("rules").
ColumnExpr("created_by TEXT").
Apply(sqlmigrator.WrapIfNotExists(ctx, db, "rules", "created_by")).
Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute {
Apply(WrapIfNotExists(ctx, db, "rules", "created_by")).
Exec(ctx); err != nil && err != ErrNoExecute {
return err
}
@ -116,8 +115,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error {
NewAddColumn().
Table("rules").
ColumnExpr("updated_by TEXT").
Apply(sqlmigrator.WrapIfNotExists(ctx, db, "rules", "updated_by")).
Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute {
Apply(WrapIfNotExists(ctx, db, "rules", "updated_by")).
Exec(ctx); err != nil && err != ErrNoExecute {
return err
}
@ -126,8 +125,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error {
NewAddColumn().
Table("dashboards").
ColumnExpr("created_by TEXT").
Apply(sqlmigrator.WrapIfNotExists(ctx, db, "dashboards", "created_by")).
Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute {
Apply(WrapIfNotExists(ctx, db, "dashboards", "created_by")).
Exec(ctx); err != nil && err != ErrNoExecute {
return err
}
@ -136,8 +135,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error {
NewAddColumn().
Table("dashboards").
ColumnExpr("updated_by TEXT").
Apply(sqlmigrator.WrapIfNotExists(ctx, db, "dashboards", "updated_by")).
Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute {
Apply(WrapIfNotExists(ctx, db, "dashboards", "updated_by")).
Exec(ctx); err != nil && err != ErrNoExecute {
return err
}
@ -146,8 +145,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error {
NewAddColumn().
Table("dashboards").
ColumnExpr("locked INTEGER DEFAULT 0").
Apply(sqlmigrator.WrapIfNotExists(ctx, db, "dashboards", "locked")).
Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute {
Apply(WrapIfNotExists(ctx, db, "dashboards", "locked")).
Exec(ctx); err != nil && err != ErrNoExecute {
return err
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addSavedViews struct{}
func NewAddSavedViewsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddSavedViewsFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_saved_views"), newAddSavedViews)
}
func newAddSavedViews(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddSavedViews(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addSavedViews{}, nil
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addAgents struct{}
func NewAddAgentsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddAgentsFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_agents"), newAddAgents)
}
func newAddAgents(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddAgents(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addAgents{}, nil
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addPipelines struct{}
func NewAddPipelinesFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddPipelinesFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_pipelines"), newAddPipelines)
}
func newAddPipelines(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddPipelines(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addPipelines{}, nil
}

View File

@ -1,4 +1,4 @@
package migration
package sqlmigration
import (
"context"
@ -6,16 +6,15 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigrator"
)
type addIntegrations struct{}
func NewAddIntegrationsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] {
func NewAddIntegrationsFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_integrations"), newAddIntegrations)
}
func newAddIntegrations(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) {
func newAddIntegrations(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addIntegrations{}, nil
}

View File

@ -0,0 +1,19 @@
package sqlmigration
import (
"go.signoz.io/signoz/pkg/factory"
)
type Config struct{}
func NewConfigFactory() factory.ConfigFactory {
return factory.NewConfigFactory(factory.MustNewName("sqlmigration"), newConfig)
}
func newConfig() factory.Config {
return Config{}
}
func (c Config) Validate() error {
return nil
}

View File

@ -1,4 +1,4 @@
package sqlmigrator
package sqlmigration
import (
"context"
@ -11,11 +11,22 @@ import (
"go.signoz.io/signoz/pkg/factory"
)
// SQLMigration is the interface for a single migration.
type SQLMigration interface {
// Register registers the migration with the given migrations. Each migration needs to be registered
//in a dedicated `*.go` file so that the correct migration semantics can be detected.
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
}
var (
ErrNoExecute = errors.New("no execute")
)
func NewMigrations(
func New(
ctx context.Context,
settings factory.ProviderSettings,
config Config,
@ -38,13 +49,13 @@ func NewMigrations(
return migrations, nil
}
func MustNewMigrations(
func MustNew(
ctx context.Context,
settings factory.ProviderSettings,
config Config,
factories factory.NamedMap[factory.ProviderFactory[SQLMigration, Config]],
) *migrate.Migrations {
migrations, err := NewMigrations(ctx, settings, config, factories)
migrations, err := New(ctx, settings, config, factories)
if err != nil {
panic(err)
}

View File

@ -1,4 +1,4 @@
package sqlmigrator
package sqlmigrationtest
import (
"context"
@ -6,12 +6,13 @@ import (
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/sqlmigration"
)
type noopMigration struct{}
func NoopMigrationFactory() factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("noop"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
func NoopMigrationFactory() factory.ProviderFactory[sqlmigration.SQLMigration, sqlmigration.Config] {
return factory.NewProviderFactory(factory.MustNewName("noop"), func(_ context.Context, _ factory.ProviderSettings, _ sqlmigration.Config) (sqlmigration.SQLMigration, error) {
return &noopMigration{}, nil
})
}

View File

@ -10,6 +10,8 @@ import (
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation/instrumentationtest"
"go.signoz.io/signoz/pkg/sqlmigration"
"go.signoz.io/signoz/pkg/sqlmigration/sqlmigrationtest"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/sqlstore/sqlstoretest"
)
@ -33,7 +35,7 @@ func TestMigratorWithSqliteAndNoopMigration(t *testing.T) {
ctx,
providerSettings,
sqlstore,
MustNewMigrations(ctx, providerSettings, migrationConfig, factory.MustNewNamedMap(NoopMigrationFactory())),
sqlmigration.MustNew(ctx, providerSettings, sqlmigration.Config{}, factory.MustNewNamedMap(sqlmigrationtest.NoopMigrationFactory())),
migrationConfig,
)

View File

@ -2,9 +2,6 @@ package sqlmigrator
import (
"context"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
// SQLMigrator is the interface for the SQLMigrator.
@ -14,14 +11,3 @@ type SQLMigrator interface {
// Rollback rolls back the database. Rollback acquires a lock on the database and rolls back the migrations.
Rollback(context.Context) error
}
// SQLMigration is the interface for a single migration.
type SQLMigration interface {
// Register registers the migration with the given migrations. Each migration needs to be registered
//in a dedicated `*.go` file so that the correct migration semantics can be detected.
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

@ -1,14 +1,13 @@
package web
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.
type Config struct {
// Whether the web package is enabled.
Enabled bool `mapstructure:"enabled"`
// The prefix to serve the files from
Prefix string `mapstructure:"prefix"`
// The directory containing the static build files. The root of this directory should
@ -16,14 +15,26 @@ type Config struct {
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{
Enabled: true,
Prefix: "/",
Directory: "/etc/signoz/web",
}
}
func (c *Config) Validate() error {
func (c Config) Validate() error {
return nil
}
func (c Config) Provider() 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)
}

View File

@ -11,6 +11,10 @@ import (
type provider 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 &provider{}, nil
}

View File

@ -1,6 +1,7 @@
package routerweb
import (
"context"
"fmt"
"net/http"
"os"
@ -8,26 +9,25 @@ import (
"time"
"github.com/gorilla/mux"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/http/middleware"
"go.signoz.io/signoz/pkg/web"
"go.uber.org/zap"
)
const (
indexFileName string = "index.html"
)
type Web struct {
logger *zap.Logger
cfg web.Config
type provider struct {
config web.Config
}
func New(logger *zap.Logger, cfg web.Config) (*Web, error) {
if logger == nil {
return nil, fmt.Errorf("cannot build web, logger is required")
}
func NewFactory() factory.ProviderFactory[web.Web, web.Config] {
return factory.NewProviderFactory(factory.MustNewName("router"), New)
}
fi, err := os.Stat(cfg.Directory)
func New(ctx context.Context, settings factory.ProviderSettings, config web.Config) (web.Web, error) {
fi, err := os.Stat(config.Directory)
if err != nil {
return nil, fmt.Errorf("cannot access web directory: %w", err)
}
@ -37,7 +37,7 @@ func New(logger *zap.Logger, cfg web.Config) (*Web, error) {
return nil, fmt.Errorf("web directory is not a directory")
}
fi, err = os.Stat(filepath.Join(cfg.Directory, indexFileName))
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)
}
@ -46,19 +46,18 @@ func New(logger *zap.Logger, cfg web.Config) (*Web, error) {
return nil, fmt.Errorf("%q does not exist", indexFileName)
}
return &Web{
logger: logger.Named("go.signoz.io/pkg/web"),
cfg: cfg,
return &provider{
config: config,
}, nil
}
func (web *Web) AddToRouter(router *mux.Router) error {
func (provider *provider) AddToRouter(router *mux.Router) error {
cache := middleware.NewCache(7 * 24 * time.Hour)
err := router.PathPrefix(web.cfg.Prefix).
err := router.PathPrefix(provider.config.Prefix).
Handler(
http.StripPrefix(
web.cfg.Prefix,
cache.Wrap(http.HandlerFunc(web.ServeHTTP)),
provider.config.Prefix,
cache.Wrap(http.HandlerFunc(provider.ServeHTTP)),
),
).GetError()
if err != nil {
@ -68,15 +67,15 @@ func (web *Web) AddToRouter(router *mux.Router) error {
return nil
}
func (web *Web) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
func (provider *provider) 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)
path := filepath.Join(provider.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.cfg.Directory, indexFileName))
http.ServeFile(rw, req, filepath.Join(provider.config.Directory, indexFileName))
return
}
@ -89,5 +88,5 @@ func (web *Web) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}
// otherwise, use http.FileServer to serve the static file
http.FileServer(http.Dir(web.cfg.Directory)).ServeHTTP(rw, req)
http.FileServer(http.Dir(provider.config.Directory)).ServeHTTP(rw, req)
}

View File

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