Go
 
          - Introduction
- Installing it
- Using it
- Enabling OpenCensus
- End to end example
- Examining the traces
- Examining the metrics
- References
Introduction
We have a Go “database/sql” package/wrapper that is instrumented with OpenCensus!
Installing it
To install the ocsql “database/sql” plugin, please run
go get -u -v contrib.go.opencensus.io/integrations/ocsqlUsing it
Given this simple initialization of a database/sql instance in Go:
package main
import (
	"database/sql"
	"log"
)
func main() {
	var ordinaryDriverName string // For example "mysql", "sqlite3" etc.
	db, err := sql.Open(ordinaryDriverName, "resource.db")
	if err != nil {
		log.Fatalf("Failed to open the SQL database: %v", err)
	}
	defer db.Close()
}We can use the OpenCensus instrumented SQL driver wrapper in one of these two ways:
By registration
This mimicks the idiomatic recommendation to use the “database/sql” package in
Go where we pass an implicitly registered driver to sql.Open which returns a
*sql.DB handle
package main
import (
	"database/sql"
	"log"
	"contrib.go.opencensus.io/integrations/ocsql"
)
func main() {
	var ordinaryDriverName string // For example "mysql", "sqlite3" etc.
	// First step is to register the driver and
	// then reuse that driver name while invoking sql.Open
	driverName, err := ocsql.Register(ordinaryDriverName)
	if err != nil {
		log.Fatalf("Failed to register the ocsql driver: %v", err)
	}
	db, err := sql.Open(driverName, "resource.db")
	if err != nil {
		log.Fatalf("Failed to open the SQL database: %v", err)
	}
	defer db.Close()
}By explicitly wrapping your driver
This option is useful if you’d like to be more explicit and if your database package exports its driver implementation.
package main
import (
	"log"
	"database/sql"
	sqlite3 "github.com/mattn/go-sqlite3"
	"contrib.go.opencensus.io/integrations/ocsql"
}
const driverName = "ocsql"
func main() {
	// wrap ocsql around existing database driver
	driver := ocsql.Wrap(&sqlite3.SQLiteDriver{})
	// register our ocsql wrapped driver with database/sql
	sql.Register(driverName, driver)
	// use our ocsql driver
	db, err := sql.Open(driverName, "my-sqlite.db")
	if err != nil {
		log.Fatalf("Failed to open the SQL database: %v", err)
	}
	defer db.Close()OCSQL Trace Options
By default ocsql is conservative with what is exactly traced, e.g. by default
the actual SQL queries and named parameter values are not annotated for security
and verbosity reasons. Both ocsql.Register as well as ocsql.Wrap allow for
setting various functional TraceOptions. To enable all tracing options
including recording sql queries, you can use the WithAllTraceOptions
functional option.
// by registration flow
driverName := ocsql.Register(ordinaryDriverName, ocsql.WithAllTraceOptions())
// by explicit wrapping driver e.g. sqlite3
driver := ocsql.Wrap(&sqlite3.SQLiteDriver{}, ocsql.WithAllTraceOptions())OCSQL Metrics Options
Since Go 1.11 there is support for getting detailed insight into the DB
connection pool as provided by the database/sql package. To enable recording
of DB connection pool metrics at your preferred interval you can use the
ocsql.RecordStats function.
db, err := sql.Open(driverName, "resource.db")
if err != nil {
	log.Fatalf("Failed to open the SQL database: %v", err)
}
// enable periodic recording of sql.DBStats
dbstatsCloser := ocsql.RecordStats(db, 5*time.Second)
defer func() {
	dbstatsCloser()
	db.Close()
}()Enabling OpenCensus
To examine the metrics and traces, we need to hook up our favorite Go exporters as per the Go exporters guides e.g.
func enableOpenCensusObservability(mux *http.ServeMux) (fnStop func(), err error) {
	// enable OpenCensus zPages
	zpages.Handle(mux, "/debug")
	// Enable ocsql metrics with OpenCensus
	ocsql.RegisterAllViews()
	// set up the prometheus exporter
	prometheusExporter, err := prometheus.NewExporter(prometheus.Options{})
	if err == nil {
		// provide /metrics endpoint for Prometheus to scrape from.
		mux.Handle("/metrics", prometheusExporter)
	}
	// For demo purposes, we'll always trace
	trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
	// set up the Zipkin exporter over HTTP transport
	reporter := httpreporter.NewReporter("http://localhost:9411/api/v2/spans")
	localEP, _ := stdzipkin.NewEndpoint(serviceName, listenAddr)
	zipkinExporter := zipkin.NewExporter(reporter, localEP)
	trace.RegisterExporter(zipkinExporter)
	var closeOnce sync.Once
	return func() {
		// flush and shutdown the Zipkin HTTP reporter
		closeOnce.Do(func() {
			reporter.Close()
		})
	}, err
}End to end example
And now to examine the exported stats and traces, let’s make a simple random name service app. For simplicity, we use a sqlite3 database and the following exporters:
- Zipkin for trace exporting
- Prometheus for stats exporting
For assistance setting up any of the exporters, please refer to:
| EXPORTER | URL | 
|---|---|
| Prometheus | Prometheus codelab | 
| Zipkin | Zipkin codelab | 
Please place the code below inside a go-gettable directory and a file
main.go, so
mkdir -p ocsql-e2e && touch main.goSource code
package main
import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"
	"contrib.go.opencensus.io/integrations/ocsql"
	_ "github.com/mattn/go-sqlite3"
	stdzipkin "github.com/openzipkin/zipkin-go"
	httpreporter "github.com/openzipkin/zipkin-go/reporter/http"
	"contrib.go.opencensus.io/exporter/prometheus"
	"contrib.go.opencensus.io/exporter/zipkin"
	"go.opencensus.io/plugin/ochttp"
	"go.opencensus.io/stats/view"
	"go.opencensus.io/trace"
	"go.opencensus.io/zpages"
)
const (
	serviceName = "ocsql-demo"
	listenAddr  = "0.0.0.0:8889"
)
func main() {
	mux := http.NewServeMux()
	ocCloser, err := enableOpenCensusObservability(mux)
	if err != nil {
		log.Fatalf("Failed to enable OpenCensus observability: %v", err)
	}
	defer ocCloser()
	// for this demo enable all ocsql trace options
	driverName, err := ocsql.Register("sqlite3", ocsql.WithAllTraceOptions())
	if err != nil {
		log.Fatalf("Failed to register the ocsql driver: %v", err)
	}
	// allow multiple connections to use same in-memory database
	db, err := sql.Open(driverName, "file::memory:?mode=memory&cache=shared")
	if err != nil {
		log.Fatalf("Failed to open the SQL database: %v", err)
	}
	defer db.Close()
	// record DB connection pool statistics
	dbStatsCloser := ocsql.RecordStats(db, 5*time.Second)
	defer dbStatsCloser()
	// populate our in-memory database
	if err = populateDatabase(context.Background(), db); err != nil {
		log.Printf("Unable to populate database: %v", err)
		return
	}
	// add a HTTP Handler serving a random person lookup
	mux.HandleFunc("/", randomLookup(db))
	// enable ochttp on our HTTP Server
	srv := &http.Server{
		Addr:    listenAddr,
		Handler: &ochttp.Handler{Handler: mux},
	}
	// use interrupt signal for graceful shutdown
	go signalHandler(srv)
	// start HTTP server
	if err := srv.ListenAndServe(); err != nil {
		log.Printf("HTTP server ListenAndServe: %v", err)
	}
}
func populateDatabase(ctx context.Context, db *sql.DB) (err error) {
	ctx, span := trace.StartSpan(context.Background(), "PopulateDatabase")
	defer func() {
		if err != nil {
			span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: err.Error()})
		}
		span.End()
	}()
	if err = createTable(ctx, db); err != nil {
		return err
	}
	if err = insertNames(ctx, db); err != nil {
		return err
	}
	return nil
}
func createTable(ctx context.Context, db *sql.DB) error {
	ctx, span := trace.StartSpan(ctx, "CreateTable")
	defer span.End()
	_, err := db.ExecContext(ctx, `
		CREATE TABLE names(
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			first VARCHAR(256),
			last VARCHAR(256)
		)`,
	)
	if err != nil {
		span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: err.Error()})
		err = errors.New("Unable to create table")
	}
	return nil
}
func insertNames(ctx context.Context, db *sql.DB) error {
	ctx, span := trace.StartSpan(ctx, "InsertNames")
	defer span.End()
	stmt, err := db.PrepareContext(ctx, `INSERT INTO names (first, last) VALUES (?, ?)`)
	if err != nil {
		span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: err.Error()})
		return errors.New("Unable to prepare statement")
	}
	defer stmt.Close()
	for _, person := range []struct {
		first string
		last  string
	}{
		{"Herman", "Fleming"}, {"Katrina", "Schwartz"}, {"Manuel", "Wade"},
		{"Rosa", "Rogers"}, {"Regina", "Rodriquez"}, {"Charles", "Kelly"},
		{"Roosevelt", "Palmer"}, {"Isabel", "Ingram"}, {"Francis", "Drake"},
		{"Amy", "Gibson"}, {"Terry", "Moody"}, {"Iris", "Oliver"},
		{"Karl", "Peterson"}, {"Susie", "Gordon"}, {"Glenda", "Craig"},
		{"Leona", "Wright"}, {"Nadine", "Marsh"}, {"Erma", "Burke"},
		{"Jill", "Horton"}, {"Luther", "Roberson"}, {"Rogelio", "Hunt"},
		{"Brett", "Meyer"}, {"Dave", "Rodgers"}, {"Raymond", "Gonzalez"},
		{"Sheryl", "Hernandez"}, {"Myra", "Bass"}, {"Jonathon", "Pierce"},
		{"Stephen", "Mccarthy"}, {"Marshall", "Vaughn"}, {"Gene", "Weber"},
		{"Pamela", "Lloyd"}, {"Dennis", "Romero"}, {"Julius", "Cruz"},
		{"Alice", "Dean"}, {"Mildred", "Bush"}, {"Amos", "Caldwell"},
		{"Amelia", "Lamb"}, {"Sophie", "Guzman"}, {"Anthony", "Leonard"},
		{"Adam", "Parks"}, {"Arlene", "Reynolds"}, {"Sandy", "Jones"},
		{"Sabrina", "Castro"}, {"Horace", "Fuller"}, {"Kelly", "Owens"},
		{"Alberto", "Sparks"}, {"Monica", "Mendez"}, {"Ernesto", "Wilkins"},
		{"Angela", "Johnson"}, {"Kimberly", "Foster"}, {"Molly", "Higgins"},
		{"Jason", "Mcbride"}, {"Gladys", "Edwards"}, {"Sylvester", "Roberts"},
		{"Aubrey", "Day"}, {"Ed", "Zimmerman"}, {"Bruce", "Carter"},
		{"Tonya", "Fisher"}, {"Rodolfo", "Curry"}, {"Lucille", "Valdez"},
		{"Maryann", "Mathis"}, {"Gilberto", "Miller"}, {"Neil", "Evans"},
		{"Essie", "Hill"}, {"Omar", "Cummings"}, {"Jessica", "Diaz"},
		{"Emma", "Vega"}, {"Jordan", "Silva"}, {"Kendra", "Mcdaniel"},
		{"Dale", "Gomez"}, {"Misty", "Harvey"}, {"Francis", "Ortiz"},
		{"Audrey", "Collins"}, {"Salvatore", "Tucker"}, {"Olga", "Flowers"},
		{"Jeff", "Turner"}, {"Darla", "Hoffman"}, {"Wade", "Dennis"},
		{"Spencer", "Parsons"}, {"Jorge", "Holland"}, {"Vickie", "Martin"},
		{"Myron", "Frazier"}, {"Alejandro", "Snyder"}, {"Daisy", "Flores"},
		{"Christy", "Thompson"}, {"Marcus", "Parker"}, {"Amanda", "Carson"},
		{"Bob", "Walters"}, {"Taylor", "Gregory"}, {"Lauren", "Lambert"},
		{"Ruby", "Gonzales"}, {"Stacey", "Park"}, {"Jo", "Baker"},
		{"Gloria", "Luna"}, {"Raul", "Ryan"}, {"Denise", "Kim"},
		{"Paul", "Black"}, {"Lynette", "Barton"}, {"Evan", "Logan"},
		{"Ryan", "Brady"},
	} {
		rs, err := stmt.ExecContext(ctx, person.first, person.last)
		if err != nil {
			span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: err.Error()})
			return errors.New("Unable to insert person")
		}
		id, err := rs.LastInsertId()
		if err != nil {
			span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: err.Error()})
			return errors.New("Unable to retrieve insert id")
		}
		log.Printf("Successfully inserted #%d\n", id)
	}
	return nil
}
func randomLookup(db *sql.DB) http.HandlerFunc {
	rand.Seed(time.Now().UnixNano())
	type person struct {
		ID    int
		First string
		Last  string
	}
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, span := trace.StartSpan(r.Context(), "Find")
		// we only have 100 records so we'll have some StatusNotFound returns.
		id := rand.Intn(120)
		row := db.QueryRowContext(ctx, `SELECT * from names where id=?`, id)
		span.End()
		var p person
		if err := row.Scan(&p.ID, &p.First, &p.Last); err != nil {
			if err == sql.ErrNoRows {
				http.Error(w, "Record not found", http.StatusNotFound)
				span.SetStatus(trace.Status{Code: trace.StatusCodeNotFound})
				return
			}
			log.Printf("Failed to fetch row: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()})
			return
		}
		fmt.Fprintf(w, "Randomly picked record: %v", p)
	}
}
func enableOpenCensusObservability(mux *http.ServeMux) (fnStop func(), err error) {
	// enable OpenCensus zPages
	zpages.Handle(mux, "/debug")
	// Enable ocsql metrics with OpenCensus
	ocsql.RegisterAllViews()
	// set up the prometheus exporter
	prometheusExporter, err := prometheus.NewExporter(prometheus.Options{})
	if err == nil {
		// provide /metrics endpoint for Prometheus to scrape from.
		mux.Handle("/metrics", prometheusExporter)
	}
	// For demo purposes, we'll always trace
	trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
	// set up the Zipkin exporter over HTTP transport
	reporter := httpreporter.NewReporter("http://localhost:9411/api/v2/spans")
	localEP, _ := stdzipkin.NewEndpoint(serviceName, listenAddr)
	zipkinExporter := zipkin.NewExporter(reporter, localEP)
	trace.RegisterExporter(zipkinExporter)
	var closeOnce sync.Once
	return func() {
		// flush and shutdown the Zipkin HTTP reporter
		closeOnce.Do(func() {
			reporter.Close()
		})
	}, err
}
func signalHandler(srv *http.Server) {
	sigint := make(chan os.Signal, 1)
	signal.Notify(sigint, os.Interrupt)
	<-sigint
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("HTTP server Shutdown: %v", err)
	}
}For Prometheus to scrape the data from your service you will need to update its
config.yaml with something like this:
scrape_configs:
  - job_name: 'ocsqlmetricstutorial'
    scrape_interval: 10s
    static_configs:
      - targets: ['localhost:8889']Running it
Make sure Zipkin is running by either starting the docker container or if running locally doing something like this:
# download latest version of Zipkin
curl -sSL https://zipkin.io/quickstart.sh | bash -s
# run the binary release
java -jar zipkin.jarAlso make sure Prometheus is running with the scrape_config pointing towards the service’s /metrics endpoint.
prometheus --config.file=config.yamlNow you can run the above application
go run main.goThe application will first create a database schema and load up 100 names. To see the names be served up randomly, visit: http://localhost:8889/random

Examining the traces
On visiting http://localhost:9411 we can see something similar to below:

and on clicking to get details about the most recent trace
 

Examining the metrics
With Prometheus running, we can navigate to the Prometheus UI at http://localhost:9090/graph you should be able to see such visuals
- All metrics  
- Latency buckets  
- Calls  
- sql/database connection pool    
References
| Resource | URL | 
|---|---|
| GoDoc | contrib.go.opencensus.io/integrations/ocsql | 
| Medium blogpost | OpenCensus and Go database/sql by Bas van Beek |