Add automatic query logger function with additional execution details.

This commit is contained in:
go-jet 2022-01-12 19:03:50 +01:00
parent 7377e078cd
commit 4955bfc4b5
18 changed files with 266 additions and 59 deletions

View file

@ -1,6 +1,11 @@
package jet
import "context"
import (
"context"
"runtime"
"strings"
"time"
)
// PrintableStatement is a statement which sql query can be logged
type PrintableStatement interface {
@ -8,7 +13,7 @@ type PrintableStatement interface {
DebugSql() (query string)
}
// LoggerFunc is a definition of a function user can implement to support automatic statement logging.
// LoggerFunc is a function user can implement to support automatic statement logging.
type LoggerFunc func(ctx context.Context, statement PrintableStatement)
var logger LoggerFunc
@ -17,3 +22,60 @@ var logger LoggerFunc
func SetLoggerFunc(loggerFunc LoggerFunc) {
logger = loggerFunc
}
func callLogger(ctx context.Context, statement Statement) {
if logger != nil {
logger(ctx, statement)
}
}
// QueryInfo contains information about executed query
type QueryInfo struct {
Statement PrintableStatement
// Depending of statement execution method RowsProcessed is:
// - Number of rows returned for Query() and QueryContext() methods
// - RowsAffected() for Exec() and ExecContext() methods
// - Always 0 for Rows() method.
RowsProcessed int64
Duration time.Duration
Err error
}
// QueryLoggerFunc is a function user can implement to retrieve more information about statement executed.
type QueryLoggerFunc func(ctx context.Context, info QueryInfo)
var queryLoggerFunc QueryLoggerFunc
// SetQueryLoggerFunc sets automatic query logging function.
func SetQueryLoggerFunc(loggerFunc QueryLoggerFunc) {
queryLoggerFunc = loggerFunc
}
func callQueryLoggerFunc(ctx context.Context, info QueryInfo) {
if queryLoggerFunc != nil {
queryLoggerFunc(ctx, info)
}
}
// Caller returns information about statement caller
func (q QueryInfo) Caller() (file string, line int, function string) {
skip := 4
// depending on execution type (Query, QueryContext, Exec, ...) looped once or twice
for {
var pc uintptr
var ok bool
pc, file, line, ok = runtime.Caller(skip)
if !ok {
return
}
funcDetails := runtime.FuncForPC(pc)
if !strings.Contains(funcDetails.Name(), "github.com/go-jet/jet/v2/internal") {
function = funcDetails.Name()
return
}
skip++
}
}

View file

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"github.com/go-jet/jet/v2/qrm"
"time"
)
//Statement is common interface for all statements(SELECT, INSERT, UPDATE, DELETE, LOCK)
@ -21,9 +22,9 @@ type Statement interface {
// Destination can be either pointer to struct or pointer to a slice.
// If destination is pointer to struct and query result set is empty, method returns qrm.ErrNoRows.
QueryContext(ctx context.Context, db qrm.DB, destination interface{}) error
//Exec executes statement over db connection/transaction without returning any rows.
// Exec executes statement over db connection/transaction without returning any rows.
Exec(db qrm.DB) (sql.Result, error)
//Exec executes statement with context over db connection/transaction without returning any rows.
// ExecContext executes statement with context over db connection/transaction without returning any rows.
ExecContext(ctx context.Context, db qrm.DB) (sql.Result, error)
// Rows executes statements over db connection/transaction and returns rows
Rows(ctx context.Context, db qrm.DB) (*Rows, error)
@ -84,12 +85,7 @@ func (s *serializerStatementInterfaceImpl) DebugSql() (query string) {
}
func (s *serializerStatementInterfaceImpl) Query(db qrm.DB, destination interface{}) error {
query, args := s.Sql()
ctx := context.Background()
callLogger(ctx, s)
return qrm.Query(ctx, db, query, args, destination)
return s.QueryContext(context.Background(), db, destination)
}
func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db qrm.DB, destination interface{}) error {
@ -97,15 +93,25 @@ func (s *serializerStatementInterfaceImpl) QueryContext(ctx context.Context, db
callLogger(ctx, s)
return qrm.Query(ctx, db, query, args, destination)
var rowsProcessed int64
var err error
duration := duration(func() {
rowsProcessed, err = qrm.Query(ctx, db, query, args, destination)
})
callQueryLoggerFunc(ctx, QueryInfo{
Statement: s,
RowsProcessed: rowsProcessed,
Duration: duration,
Err: err,
})
return err
}
func (s *serializerStatementInterfaceImpl) Exec(db qrm.DB) (res sql.Result, err error) {
query, args := s.Sql()
callLogger(context.Background(), s)
return db.Exec(query, args...)
return s.ExecContext(context.Background(), db)
}
func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db qrm.DB) (res sql.Result, err error) {
@ -113,7 +119,24 @@ func (s *serializerStatementInterfaceImpl) ExecContext(ctx context.Context, db q
callLogger(ctx, s)
return db.ExecContext(ctx, query, args...)
duration := duration(func() {
res, err = db.ExecContext(ctx, query, args...)
})
var rowsAffected int64
if err == nil {
rowsAffected, _ = res.RowsAffected()
}
callQueryLoggerFunc(ctx, QueryInfo{
Statement: s,
RowsProcessed: rowsAffected,
Duration: duration,
Err: err,
})
return res, err
}
func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.DB) (*Rows, error) {
@ -121,7 +144,18 @@ func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.DB)
callLogger(ctx, s)
rows, err := db.QueryContext(ctx, query, args...)
var rows *sql.Rows
var err error
duration := duration(func() {
rows, err = db.QueryContext(ctx, query, args...)
})
callQueryLoggerFunc(ctx, QueryInfo{
Statement: s,
Duration: duration,
Err: err,
})
if err != nil {
return nil, err
@ -130,10 +164,12 @@ func (s *serializerStatementInterfaceImpl) Rows(ctx context.Context, db qrm.DB)
return &Rows{rows}, nil
}
func callLogger(ctx context.Context, statement Statement) {
if logger != nil {
logger(ctx, statement)
}
func duration(f func()) time.Duration {
start := time.Now()
f()
return time.Now().Sub(start)
}
// ExpressionStatement interfacess