diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index 369c3caec2..d23833415b 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -8,6 +8,7 @@ import ( "go.signoz.io/signoz/pkg/sqlmigration" "go.signoz.io/signoz/pkg/sqlstore" "go.signoz.io/signoz/pkg/sqlstore/sqlitesqlstore" + "go.signoz.io/signoz/pkg/sqlstore/sqlstorehook" "go.signoz.io/signoz/pkg/telemetrystore" "go.signoz.io/signoz/pkg/telemetrystore/clickhousetelemetrystore" "go.signoz.io/signoz/pkg/telemetrystore/telemetrystorehook" @@ -34,7 +35,6 @@ type ProviderConfig struct { } func NewProviderConfig() ProviderConfig { - hook := telemetrystorehook.NewFactory() return ProviderConfig{ CacheProviderFactories: factory.MustNewNamedMap( memorycache.NewFactory(), @@ -45,7 +45,7 @@ func NewProviderConfig() ProviderConfig { noopweb.NewFactory(), ), SQLStoreProviderFactories: factory.MustNewNamedMap( - sqlitesqlstore.NewFactory(), + sqlitesqlstore.NewFactory(sqlstorehook.NewLoggingFactory()), // postgressqlstore.NewFactory(), ), SQLMigrationProviderFactories: factory.MustNewNamedMap( @@ -62,7 +62,7 @@ func NewProviderConfig() ProviderConfig { sqlmigration.NewModifyDatetimeFactory(), ), TelemetryStoreProviderFactories: factory.MustNewNamedMap( - clickhousetelemetrystore.NewFactory(hook), + clickhousetelemetrystore.NewFactory(telemetrystorehook.NewFactory()), ), } } diff --git a/pkg/sqlstore/bun.go b/pkg/sqlstore/bun.go new file mode 100644 index 0000000000..69c5d4e278 --- /dev/null +++ b/pkg/sqlstore/bun.go @@ -0,0 +1,18 @@ +package sqlstore + +import ( + "database/sql" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/schema" +) + +func NewBunDB(sqldb *sql.DB, dialect schema.Dialect, hooks []SQLStoreHook, opts ...bun.DBOption) *bun.DB { + bunDB := bun.NewDB(sqldb, dialect, opts...) + + for _, hook := range hooks { + bunDB.AddQueryHook(hook) + } + + return bunDB +} diff --git a/pkg/sqlstore/postgressqlstore/provider.go b/pkg/sqlstore/postgressqlstore/provider.go index f18f4b3d3c..146d593c66 100644 --- a/pkg/sqlstore/postgressqlstore/provider.go +++ b/pkg/sqlstore/postgressqlstore/provider.go @@ -20,11 +20,22 @@ type provider struct { sqlxdb *sqlx.DB } -func NewFactory() factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { - return factory.NewProviderFactory(factory.MustNewName("postgres"), New) +func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { + return factory.NewProviderFactory(factory.MustNewName("postgres"), func(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) { + hooks := make([]sqlstore.SQLStoreHook, len(hookFactories)) + for i, hookFactory := range hookFactories { + hook, err := hookFactory.New(ctx, providerSettings, config) + if err != nil { + return nil, err + } + hooks[i] = hook + } + + return New(ctx, providerSettings, config, hooks...) + }) } -func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) { +func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config, hooks ...sqlstore.SQLStoreHook) (sqlstore.SQLStore, error) { settings := factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/signoz/pkg/sqlstore/postgressqlstore") pgConfig, err := pgxpool.ParseConfig(config.Postgres.DSN) @@ -46,7 +57,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config return &provider{ settings: settings, sqldb: sqldb, - bundb: bun.NewDB(sqldb, pgdialect.New()), + bundb: sqlstore.NewBunDB(sqldb, pgdialect.New(), hooks), sqlxdb: sqlx.NewDb(sqldb, "postgres"), }, nil } diff --git a/pkg/sqlstore/sqlitesqlstore/provider.go b/pkg/sqlstore/sqlitesqlstore/provider.go index f68605699b..d34cf15ddf 100644 --- a/pkg/sqlstore/sqlitesqlstore/provider.go +++ b/pkg/sqlstore/sqlitesqlstore/provider.go @@ -19,11 +19,22 @@ type provider struct { sqlxdb *sqlx.DB } -func NewFactory() factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { - return factory.NewProviderFactory(factory.MustNewName("sqlite"), New) +func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { + return factory.NewProviderFactory(factory.MustNewName("sqlite"), func(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) { + hooks := make([]sqlstore.SQLStoreHook, len(hookFactories)) + for i, hookFactory := range hookFactories { + hook, err := hookFactory.New(ctx, providerSettings, config) + if err != nil { + return nil, err + } + hooks[i] = hook + } + + return New(ctx, providerSettings, config, hooks...) + }) } -func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStore, error) { +func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config, hooks ...sqlstore.SQLStoreHook) (sqlstore.SQLStore, error) { settings := factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/signoz/pkg/sqlitesqlstore") sqldb, err := sql.Open("sqlite3", "file:"+config.Sqlite.Path+"?_foreign_keys=true") @@ -36,7 +47,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config return &provider{ settings: settings, sqldb: sqldb, - bundb: bun.NewDB(sqldb, sqlitedialect.New()), + bundb: sqlstore.NewBunDB(sqldb, sqlitedialect.New(), hooks), sqlxdb: sqlx.NewDb(sqldb, "sqlite3"), }, nil } diff --git a/pkg/sqlstore/sqlstore.go b/pkg/sqlstore/sqlstore.go index 7249fc3849..a04baaf8d9 100644 --- a/pkg/sqlstore/sqlstore.go +++ b/pkg/sqlstore/sqlstore.go @@ -16,3 +16,7 @@ type SQLStore interface { // SQLxDB returns an instance of sqlx.DB. SQLxDB() *sqlx.DB } + +type SQLStoreHook interface { + bun.QueryHook +} diff --git a/pkg/sqlstore/sqlstorehook/logging.go b/pkg/sqlstore/sqlstorehook/logging.go new file mode 100644 index 0000000000..21108410ae --- /dev/null +++ b/pkg/sqlstore/sqlstorehook/logging.go @@ -0,0 +1,43 @@ +package sqlstorehook + +import ( + "context" + "log/slog" + "time" + + "github.com/uptrace/bun" + "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/sqlstore" +) + +type logging struct { + bun.QueryHook + logger *slog.Logger + level slog.Level +} + +func NewLoggingFactory() factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config] { + return factory.NewProviderFactory(factory.MustNewName("logging"), NewLogging) +} + +func NewLogging(ctx context.Context, providerSettings factory.ProviderSettings, config sqlstore.Config) (sqlstore.SQLStoreHook, error) { + return &logging{ + logger: factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/signoz/pkg/sqlstore/sqlstorehook").Logger(), + level: slog.LevelDebug, + }, nil +} + +func (logging) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context { + return ctx +} + +func (hook logging) AfterQuery(ctx context.Context, event *bun.QueryEvent) { + hook.logger.Log( + ctx, + hook.level, + "::SQLSTORE-QUERY::", + "db.query.operation", event.Operation(), + "db.query.text", event.Query, + "db.duration", time.Since(event.StartTime).String(), + ) +} diff --git a/pkg/types/alertmanagertypes/channel_test.go b/pkg/types/alertmanagertypes/channel_test.go index 1e44a5fe25..de18ac0232 100644 --- a/pkg/types/alertmanagertypes/channel_test.go +++ b/pkg/types/alertmanagertypes/channel_test.go @@ -99,8 +99,8 @@ func TestNewConfigFromChannels(t *testing.T) { testCases := []struct { name string channels Channels - expectedRoutes string - expectedReceivers string + expectedRoutes []map[string]any + expectedReceivers []map[string]any }{ { name: "OneEmailChannel", @@ -111,8 +111,8 @@ func TestNewConfigFromChannels(t *testing.T) { Data: `{"name":"email-receiver","email_configs":[{"to":"test@example.com"}]}`, }, }, - expectedRoutes: `[{"receiver":"email-receiver","continue":true}]`, - expectedReceivers: `[{"name":"default-receiver"},{"name":"email-receiver","email_configs":[{"send_resolved":false,"to":"test@example.com","from":"alerts@example.com","hello":"localhost","smarthost":"smtp.example.com:587","require_tls":true,"tls_config":{"insecure_skip_verify":false}}]}]`, + expectedRoutes: []map[string]any{{"receiver": "email-receiver", "continue": true}}, + expectedReceivers: []map[string]any{{"name": "default-receiver"}, {"name": "email-receiver", "email_configs": []any{map[string]any{"send_resolved": false, "to": "test@example.com", "from": "alerts@example.com", "hello": "localhost", "smarthost": "smtp.example.com:587", "require_tls": true, "tls_config": map[string]any{"insecure_skip_verify": false}}}}}, }, { name: "OneSlackChannel", @@ -123,8 +123,8 @@ func TestNewConfigFromChannels(t *testing.T) { Data: `{"name":"slack-receiver","slack_configs":[{"channel":"#alerts","api_url":"https://slack.com/api/test","send_resolved":true}]}`, }, }, - expectedRoutes: `[{"receiver":"slack-receiver","continue":true}]`, - expectedReceivers: `[{"name":"default-receiver"},{"name":"slack-receiver","slack_configs":[{"send_resolved":true,"http_config":{"tls_config":{"insecure_skip_verify":false},"follow_redirects":true,"enable_http2":true,"proxy_url":null},"api_url":"https://slack.com/api/test","channel":"#alerts"}]}]`, + expectedRoutes: []map[string]any{{"receiver": "slack-receiver", "continue": true}}, + expectedReceivers: []map[string]any{{"name": "default-receiver"}, {"name": "slack-receiver", "slack_configs": []any{map[string]any{"send_resolved": true, "http_config": map[string]any{"tls_config": map[string]any{"insecure_skip_verify": false}, "follow_redirects": true, "enable_http2": true, "proxy_url": nil}, "api_url": "https://slack.com/api/test", "channel": "#alerts"}}}}, }, { name: "OnePagerdutyChannel", @@ -135,8 +135,8 @@ func TestNewConfigFromChannels(t *testing.T) { Data: `{"name":"pagerduty-receiver","pagerduty_configs":[{"service_key":"test"}]}`, }, }, - expectedRoutes: `[{"receiver":"pagerduty-receiver","continue":true}]`, - expectedReceivers: `[{"name":"default-receiver"},{"name":"pagerduty-receiver","pagerduty_configs":[{"send_resolved":false,"http_config":{"tls_config":{"insecure_skip_verify":false},"follow_redirects":true,"enable_http2":true,"proxy_url":null},"service_key":"test","url":"https://events.pagerduty.com/v2/enqueue"}]}]`, + expectedRoutes: []map[string]any{{"receiver": "pagerduty-receiver", "continue": true}}, + expectedReceivers: []map[string]any{{"name": "default-receiver"}, {"name": "pagerduty-receiver", "pagerduty_configs": []any{map[string]any{"send_resolved": false, "http_config": map[string]any{"tls_config": map[string]any{"insecure_skip_verify": false}, "follow_redirects": true, "enable_http2": true, "proxy_url": nil}, "service_key": "test", "url": "https://events.pagerduty.com/v2/enqueue"}}}}, }, { name: "OnePagerdutyAndOneSlackChannel", @@ -152,8 +152,8 @@ func TestNewConfigFromChannels(t *testing.T) { Data: `{"name":"slack-receiver","slack_configs":[{"channel":"#alerts","api_url":"https://slack.com/api/test","send_resolved":true}]}`, }, }, - expectedRoutes: `[{"receiver":"pagerduty-receiver","continue":true},{"receiver":"slack-receiver","continue":true}]`, - expectedReceivers: `[{"name":"default-receiver"},{"name":"pagerduty-receiver","pagerduty_configs":[{"send_resolved":false,"http_config":{"tls_config":{"insecure_skip_verify":false},"follow_redirects":true,"enable_http2":true,"proxy_url":null},"service_key":"test","url":"https://events.pagerduty.com/v2/enqueue"}]},{"name":"slack-receiver","slack_configs":[{"send_resolved":true,"http_config":{"tls_config":{"insecure_skip_verify":false},"follow_redirects":true,"enable_http2":true,"proxy_url":null},"api_url":"https://slack.com/api/test","channel":"#alerts"}]}]`, + expectedRoutes: []map[string]any{{"receiver": "pagerduty-receiver", "continue": true}, {"receiver": "slack-receiver", "continue": true}}, + expectedReceivers: []map[string]any{{"name": "default-receiver"}, {"name": "pagerduty-receiver", "pagerduty_configs": []any{map[string]any{"send_resolved": false, "http_config": map[string]any{"tls_config": map[string]any{"insecure_skip_verify": false}, "follow_redirects": true, "enable_http2": true, "proxy_url": nil}, "service_key": "test", "url": "https://events.pagerduty.com/v2/enqueue"}}}, {"name": "slack-receiver", "slack_configs": []any{map[string]any{"send_resolved": true, "http_config": map[string]any{"tls_config": map[string]any{"insecure_skip_verify": false}, "follow_redirects": true, "enable_http2": true, "proxy_url": nil}, "api_url": "https://slack.com/api/test", "channel": "#alerts"}}}}, }, } @@ -180,11 +180,17 @@ func TestNewConfigFromChannels(t *testing.T) { routes, err := json.Marshal(c.alertmanagerConfig.Route.Routes) assert.NoError(t, err) - assert.JSONEq(t, tc.expectedRoutes, string(routes)) + var actualRoutes []map[string]any + err = json.Unmarshal(routes, &actualRoutes) + assert.NoError(t, err) + assert.ElementsMatch(t, tc.expectedRoutes, actualRoutes) receivers, err := json.Marshal(c.alertmanagerConfig.Receivers) assert.NoError(t, err) - assert.JSONEq(t, tc.expectedReceivers, string(receivers)) + var actualReceivers []map[string]any + err = json.Unmarshal(receivers, &actualReceivers) + assert.NoError(t, err) + assert.ElementsMatch(t, tc.expectedReceivers, actualReceivers) }) } }