• Stars
    star
    457
  • Rank 92,265 (Top 2 %)
  • Language
    Go
  • License
    MIT License
  • Created over 4 years ago
  • Updated 12 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Interceptors for database/sql

GoDoc

sqlmw

sqlmw provides an absurdly simple API that allows a caller to wrap a database/sql driver with middleware.

This provides an abstraction similar to http middleware or GRPC interceptors but for the database/sql package. This allows a caller to implement observability like tracing and logging easily. More importantly, it also enables powerful possible behaviors like transparently modifying arguments, results or query execution strategy. This power allows programmers to implement functionality like automatic sharding, selective tracing, automatic caching, transparent query mirroring, retries, fail-over in a reuseable way, and more.

Usage

  • Define a new type and embed the sqlmw.NullInterceptor type.
  • Add a method you want to intercept from the sqlmw.Interceptor interface.
  • Wrap the driver with your interceptor with sqlmw.Driver and then install it with sql.Register.
  • Use sql.Open on the new driver string that was passed to register.

Here's a complete example:

func run(dsn string) {
        // install the wrapped driver
        sql.Register("postgres-mw", sqlmw.Driver(pq.Driver{}, 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
}

You may override any subset of methods to intercept in the Interceptor interface (https://godoc.org/github.com/ngrok/sqlmw#Interceptor):

type Interceptor interface {
    // Connection interceptors
    ConnBeginTx(context.Context, driver.ConnBeginTx, driver.TxOptions) (driver.Tx, error)
    ConnPrepareContext(context.Context, driver.ConnPrepareContext, string) (driver.Stmt, error)
    ConnPing(context.Context, driver.Pinger) error
    ConnExecContext(context.Context, driver.ExecerContext, string, []driver.NamedValue) (driver.Result, error)
    ConnQueryContext(context.Context, driver.QueryerContext, string, []driver.NamedValue) (driver.Rows, error)

    // Connector interceptors
    ConnectorConnect(context.Context, driver.Connector) (driver.Conn, error)

    // Results interceptors
    ResultLastInsertId(driver.Result) (int64, error)
    ResultRowsAffected(driver.Result) (int64, error)

    // Rows interceptors
    RowsNext(context.Context, driver.Rows, []driver.Value) error

    // Stmt interceptors
    StmtExecContext(context.Context, driver.StmtExecContext, string, []driver.NamedValue) (driver.Result, error)
    StmtQueryContext(context.Context, driver.StmtQueryContext, string, []driver.NamedValue) (driver.Rows, error)
    StmtClose(context.Context, driver.Stmt) error

    // Tx interceptors
    TxCommit(context.Context, driver.Tx) error
    TxRollback(context.Context, driver.Tx) error
}

Bear in mind that because you are intercepting the calls entirely, that you are responsible for passing control up to the wrapped driver in any function that you override, like so:

func (in *sqlInterceptor) ConnPing(ctx context.Context, conn driver.Pinger) error {
    return conn.Ping(ctx)
}

Examples

Logging

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
}

Tracing

func (in *sqlInterceptor) StmtQueryContext(ctx context.Context, conn driver.StmtQueryContext, query string, args []driver.NamedValue) (driver.Rows, error) {
    span := trace.FromContext(ctx).NewSpan(ctx, "StmtQueryContext")
    span.Tags["query"] = query
    defer span.Finish()
    rows, err := conn.QueryContext(ctx, args)
    if err != nil {
            span.Error(err)
    }
    return rows, err
}

Retries

func (in *sqlInterceptor) StmtQueryContext(ctx context.Context, conn driver.StmtQueryContext, query string, args []driver.NamedValue) (driver.Rows, error) {
    for {
            rows, err := conn.QueryContext(ctx, args)
            if err == nil {
                    return rows, nil
            }
            if err != nil && !isIdempotent(query) {
                    return nil, err
            }
            select {
            case <-ctx.Done():
                    return nil, ctx.Err()
            case <-time.After(time.Second):
            }
    }
}

Comparison with similar projects

There are a number of other packages that allow the programmer to wrap a database/sql/driver.Driver to add logging or tracing.

Examples of tracing packages:

  • github.com/ExpansiveWorlds/instrumentedsql
  • contrib.go.opencensus.io/integrations/ocsql
  • gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql

A few provide a much more flexible setup of arbitrary before/after hooks to facilitate custom observability.

Packages that provide before/after hooks:

  • github.com/gchaincl/sqlhooks
  • github.com/shogo82148/go-sql-proxy

None of these packages provide an interface with sufficient power. sqlmw passes control of executing the sql query to the caller which allows the caller to completely disintermediate the sql calls. This is what provides the power to implement advanced behaviors like caching, sharding, retries, etc.

Go version support

Go versions 1.9 and forward are supported.

Fork

This project began by forking the code in github.com/luna-duclos/instrumentedsql, which itself is a fork of github.com/ExpansiveWorlds/instrumentedsql

More Repositories

1

ngrok-go

Embed ngrok secure ingress into your Go apps as a net.Listener with a single line of code.
Go
597
star
2

ngrok-rust

Embed ngrok secure ingress into your Rust apps with a single line of code.
Rust
271
star
3

kubernetes-ingress-controller

The official ngrok Ingress Controller for Kubernetes
Go
170
star
4

webhooks.fyi

webhooks.fyi site
JavaScript
142
star
5

firewall_toolkit

golang library and tools for managing nftables
Go
80
star
6

ngrok-python

Embed ngrok secure ingress into your Python apps with a single line of code.
Rust
80
star
7

ngrok-javascript

Embed ngrok secure ingress into your Node.js apps with a single line of code.
Rust
75
star
8

docker-ngrok

docker images for the ngrok agent
Nix
47
star
9

ngrok-api-go

ngrok API client library for Golang
Go
41
star
10

ngrok-docs

ngrok's official documentation
JavaScript
35
star
11

ngrok-api-java

ngrok API client library for Java
Java
21
star
12

tableroll

Go
19
star
13

ngrok-java

Embed ngrok secure ingress into your Java apps with a single line of code.
Java
17
star
14

terraform-provider-ngrok

Ngrok exposes local servers behind NATs and firewalls to the public internet over secure tunnels. This provider helps you manage those resources via Terraform.
Go
17
star
15

ngrok-webhook-nodejs-sample

Sample webhook listener using NodeJS and ExpressJS
JavaScript
16
star
16

ngrok-api-python

ngrok API client library for Python
Python
15
star
17

ngrok-api-ruby

ngrok API client library for Ruby
Ruby
13
star
18

ngrok-api-typescript

ngrok API client library for Typescript and Javascript
TypeScript
7
star
19

ngrok-api-dotnet

ngrok API client library for .NET
C#
7
star
20

ngrok-api-scala

ngrok API client library for Scala
Scala
5
star
21

ngrok-docker-extension

ngrok Docker Desktop Extension
TypeScript
5
star
22

ngrok-systemd

5
star
23

muxado-go

Stream multiplexing for Go
Go
4
star
24

ngrok-tutorial

This will help us learn ngrok
JavaScript
4
star
25

choco-ngrok

ngrok agent chocolatey package
PowerShell
3
star
26

ngrok-api-rs

ngrok API client library for Rust
Rust
3
star
27

ngrok-java-demo

Demonstrates how to use ngrok-java
Java
2
star
28

ngrok-ipaas-example

Example of a Remote API for integration with iPaaS solutions
JavaScript
1
star
29

mantle

@ngrok/mantle ui component library
TypeScript
1
star
30

ngrok-nix

Nix
1
star
31

homebrew-ngrok

ngrok agent homebrew tap
Ruby
1
star
32

ngrok-sdk-serverless-example

An example of using the ngrok-js NodeJS SDK in a serverless AWS App Runner application.
JavaScript
1
star
33

examples

Sample Project using ngrok
Go
1
star