Skip to content

Commit 63b1f53

Browse files
authored
Log slow queries locally (#87)
* Log slow queries locally Currently, queries slower than 100ms will be logged. This should not be used in production. * Fix settings functions and change default to 10ms
1 parent 32da9b1 commit 63b1f53

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

internal/cmd/api/api.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ func run(ctx context.Context, cfg *Config, log logrus.FieldLogger) error {
7878
ctx, signalStop := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT)
7979
defer signalStop()
8080

81+
dbSettings := []database.OptFunc{}
8182
if cfg.WithFakeClients {
8283
log.Warn("using fake clients")
84+
dbSettings = append(dbSettings, database.WithSlowQueryLogger(10*time.Millisecond))
8385
}
8486

8587
_, promReg, err := newMeterProvider(ctx)
@@ -88,7 +90,7 @@ func run(ctx context.Context, cfg *Config, log logrus.FieldLogger) error {
8890
}
8991

9092
log.Info("connecting to database")
91-
pool, err := database.New(ctx, cfg.DatabaseConnectionString, log.WithField("subsystem", "database"))
93+
pool, err := database.New(ctx, cfg.DatabaseConnectionString, log.WithField("subsystem", "database"), dbSettings...)
9294
if err != nil {
9395
return fmt.Errorf("setting up database: %w", err)
9496
}

internal/database/database.go

+72-3
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,34 @@ const databaseConnectRetries = 5
2323

2424
var regParseSQLName = regexp.MustCompile(`\-\-\s*name:\s+(\S+)`)
2525

26-
func New(ctx context.Context, dsn string, log logrus.FieldLogger) (*pgxpool.Pool, error) {
27-
conn, err := NewPool(ctx, dsn, log, true)
26+
type settings struct {
27+
slowQueryLogger time.Duration
28+
}
29+
30+
type OptFunc func(*settings)
31+
32+
// WithSlowQueryLogger enables slow query logging
33+
// This exposes attributes of the slow query to the logger, so it should not be used in production
34+
func WithSlowQueryLogger(d time.Duration) OptFunc {
35+
return func(s *settings) {
36+
s.slowQueryLogger = d
37+
}
38+
}
39+
40+
func New(ctx context.Context, dsn string, log logrus.FieldLogger, opts ...OptFunc) (*pgxpool.Pool, error) {
41+
conn, err := NewPool(ctx, dsn, log, true, opts...)
2842
if err != nil {
2943
return nil, fmt.Errorf("failed to connect to the database: %w", err)
3044
}
3145
return conn, nil
3246
}
3347

34-
func NewPool(ctx context.Context, dsn string, log logrus.FieldLogger, migrate bool) (*pgxpool.Pool, error) {
48+
func NewPool(ctx context.Context, dsn string, log logrus.FieldLogger, migrate bool, opts ...OptFunc) (*pgxpool.Pool, error) {
49+
settings := &settings{}
50+
for _, o := range opts {
51+
o(settings)
52+
}
53+
3554
if migrate {
3655
if err := migrateDatabaseSchema("pgx", dsn, log); err != nil {
3756
return nil, err
@@ -55,6 +74,14 @@ func NewPool(ctx context.Context, dsn string, log logrus.FieldLogger, migrate bo
5574
}),
5675
)
5776

77+
if settings.slowQueryLogger > 0 {
78+
config.ConnConfig.Tracer = &slowQueryLogger{
79+
log: log,
80+
sub: config.ConnConfig.Tracer,
81+
duration: settings.slowQueryLogger,
82+
}
83+
}
84+
5885
config.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
5986
t, err := conn.LoadType(ctx, "slug") // slug type
6087
if err != nil {
@@ -113,3 +140,45 @@ func migrateDatabaseSchema(driver, dsn string, log logrus.FieldLogger) error {
113140

114141
return goose.Up(db, "migrations")
115142
}
143+
144+
type slowQueryLogger struct {
145+
log logrus.FieldLogger
146+
sub pgx.QueryTracer
147+
duration time.Duration
148+
}
149+
150+
type sqlCtx int
151+
152+
const sqlCtxKey sqlCtx = 0
153+
154+
type bucket struct {
155+
pgx.TraceQueryStartData
156+
start time.Time
157+
}
158+
159+
func (s *slowQueryLogger) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
160+
ctx = s.sub.TraceQueryStart(ctx, conn, data)
161+
return context.WithValue(ctx, sqlCtxKey, &bucket{
162+
TraceQueryStartData: data,
163+
start: time.Now(),
164+
})
165+
}
166+
167+
func (s *slowQueryLogger) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
168+
s.sub.TraceQueryEnd(ctx, conn, data)
169+
170+
b, ok := ctx.Value(sqlCtxKey).(*bucket)
171+
if !ok {
172+
return
173+
}
174+
175+
dur := time.Since(b.start)
176+
if dur > s.duration {
177+
s.log.WithFields(logrus.Fields{
178+
"sql": b.SQL,
179+
"args": b.Args,
180+
"time": dur,
181+
"err": data.Err,
182+
}).Warn("slow query")
183+
}
184+
}

0 commit comments

Comments
 (0)