Forgot to paste in an example, see below:func run(dsn string) { // install the wrapped driver sql.Register("postgres-mw", sqlmw.Driver(pq.Dirver{}, new(sqlInterceptor))) db, err := sql.Open("postgres-mw", dsn) ... } type sqlInterceptor struct { sqlmw.NullInterceptor } func (in *sqlInterceptor) StmtQueryContext(ctx context.Context, conn driver.StmtQueryContext, query string, args []driver.NamedValue) (driver.Rows, error) { startedAt := time.Now() rows, err := conn.QueryContext(ctx, args) log.Debug("executed sql query", "duration", time.Since(startedAt), "query", query, "args", args, "err", err) return rows, err }
On Thu, Jan 16, 2020 at 5:16 AM Alan Shreve <al...@inconshreveable.com> wrote:Hi golang-nuts -
This is an abstraction that we needed at ngrok. It allows you to intermediate calls to a database/sql Driver just like a client-side grpc interceptor would. This makes it an excellent abstraction layer upon which to build instrumentation and other middleware-like functionality.
There are a number of similar projects which provided instrumentation for logging/tracing and a few others that were aimed solely at providing callbacks for instrumentation, but I realized a more general-purpose abstraction would provide more power to the programmer by providing the ability to intercept and modify calls and return values.
Hope it's helpful to others. Feedback appreciated.
func run(dsn string) {
// install the wrapped driver
sql.Register("postgres-mw", sqlmw.Driver(pq.Dirver{}, new(sqlInterceptor)))
db, err := sql.Open("postgres-mw", dsn)
...
}
type sqlInterceptor struct {
sqlmw.NullInterceptor
}
func (in *sqlInterceptor) StmtQueryContext(ctx context.Context, conn driver.StmtQueryContext, query string, args []driver.NamedValue) (driver.Rows, error) {
startedAt := time.Now()
rows, err := conn.QueryContext(ctx, args)
log.Debug("executed sql query", "duration", time.Since(startedAt), "query", query, "args", args, "err", err)
return rows, err
}