feat(sqlstorehook): add sqlstore logging hook (#7137)

### Summary

add sqlstore logging hook
This commit is contained in:
Vibhu Pandey 2025-02-17 21:13:40 +05:30 committed by GitHub
parent c3951afdfd
commit 2a939e813d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 116 additions and 23 deletions

View File

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

18
pkg/sqlstore/bun.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -16,3 +16,7 @@ type SQLStore interface {
// SQLxDB returns an instance of sqlx.DB.
SQLxDB() *sqlx.DB
}
type SQLStoreHook interface {
bun.QueryHook
}

View File

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

View File

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