Skip to content

Commit

Permalink
Add v5/splunkpgx package to provide support for pgx/v5 (#2409)
Browse files Browse the repository at this point in the history
  • Loading branch information
aliakseip authored Sep 11, 2023
1 parent 5c97f0f commit bd9a611
Show file tree
Hide file tree
Showing 11 changed files with 857 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

- Add the `github.com/signalfx/splunk-otel-go/instrumentation/github.com/jackc/pgx/v5/splunkpgx`
instrumentation for the `github.com/jackc/pgx/v5` package. (#2406)

## [1.7.0] - 2023-07-17

This release is built on top of [OpenTelemetry Go v1.16.0/v0.39.0][otel-v1.16.0]
Expand Down
16 changes: 16 additions & 0 deletions instrumentation/github.com/jackc/pgx/v5/splunkpgx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Splunk Instrumentation for the PostgreSQL Driver Package pgx

[![Go Reference](https://pkg.go.dev/badge/github.com/signalfx/splunk-otel-go/instrumentation/github.com/pgx/v5/jackc/pgx/splunkpgx.svg)](https://pkg.go.dev/github.com/signalfx/splunk-otel-go/instrumentation/github.com/jackc/pgx/v5/pgx/splunkpgx)

This package instruments the
[`github.com/jackc/pgx`](https://github.com/jackc/pgx) package using the
[`splunksql`](../../../../database/sql/splunksql) package.

## Getting Started

This package is design to be a drop-in replacement for the existing use of the
`pgx` package when it is used in conjunction with the `database/sql` package.
The blank identified import of `github.com/jackc/pgx/v5/stdlib` can be replaced
with this package, and the standard library `sql.Open` function can be replaced
with the equivalent `Open` from `splunksql`. An example can be found
[here](example_test.go).
76 changes: 76 additions & 0 deletions instrumentation/github.com/jackc/pgx/v5/splunkpgx/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright Splunk Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package splunkpgx_test

import (
"database/sql"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql"
)

type server struct {
DB *sql.DB
}

func (s *server) listenAndServe() error {
// Requests to /square/n will return the square of n.
http.HandleFunc("/square/", s.handle)
return http.ListenAndServe(":80", nil)
}

func (s *server) handle(w http.ResponseWriter, req *http.Request) {
idx := strings.LastIndex(req.URL.Path, "/")
n, err := strconv.Atoi(req.URL.Path[idx+1:])
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

query := "SELECT squareNumber FROM squarenum WHERE number = ?"
var nSquared int
// Propagate the context to ensure created spans are included in any
// existing trace.
if err := s.DB.QueryRowContext(req.Context(), query, n).Scan(&nSquared); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

fmt.Fprintf(w, "%d", nSquared)
}

func Example() {
// Create a traced connection to the Postgres database.
db, err := splunksql.Open("pgx", "postgres://localhost:5432/dbname")
if err != nil {
panic(err)
}
defer db.Close()

// Validate DSN data by opening a connection. There is no parent context
// to pass here so the span created from this operation will be in its own
// trace.
if err := db.Ping(); err != nil {
panic(err)
}

srv := &server{DB: db}
if err := srv.listenAndServe(); err != nil {
panic(err)
}
}
27 changes: 27 additions & 0 deletions instrumentation/github.com/jackc/pgx/v5/splunkpgx/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module github.com/signalfx/splunk-otel-go/instrumentation/github.com/jackc/pgx/v5/splunkpgx

go 1.19

require (
github.com/jackc/pgx/v5 v5.4.3
github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql v1.7.0
github.com/stretchr/testify v1.8.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/signalfx/splunk-otel-go/instrumentation/internal v1.7.0 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/text v0.9.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
47 changes: 47 additions & 0 deletions instrumentation/github.com/jackc/pgx/v5/splunkpgx/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql v1.7.0 h1:Z71Q/yywQT0/2o1r0Tg+hWZl5mWhR7pUmx3jhqgSkiQ=
github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql v1.7.0/go.mod h1:rpLeZ6TneBpBtLnEW0vcrGoLeCcgMDc23DygNDZoPZQ=
github.com/signalfx/splunk-otel-go/instrumentation/internal v1.7.0 h1:KDjn8Vyaeq8U0gNP/h+1KrmT27fL1m8zzsNaSNokC+4=
github.com/signalfx/splunk-otel-go/instrumentation/internal v1.7.0/go.mod h1:czHqFdQ1id+fKSmlw8A16m6ZRMhh/2K2XFGpKNVgRgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
132 changes: 132 additions & 0 deletions instrumentation/github.com/jackc/pgx/v5/splunkpgx/sql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright Splunk Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package splunkpgx provides instrumentation for the [github.com/jackc/pgx]
// package when using [database/sql].
//
// To use this package, replace any blank identified imports of the
// github.com/jackc/pgx package with an import of this package and
// use the splunksql.Open function as a replacement for any sql.Open function
// use. For example, if your code looks like this to start.
//
// import (
// "database/sql"
// _ "github.com/jackc/pgx/v5/stdlib"
// )
// // ...
// db, err := sql.Open("pgx", "postgres://localhost:5432/dbname")
// // ...
//
// Update to this.
//
// import (
// _ "github.com/signalfx/splunk-otel-go/instrumentation/github.com/jackc/pgx/v5/splunkpgx"
// "github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql"
// )
// // ...
// db, err := splunksql.Open("pgx", "postgres://localhost:5432/dbname")
// // ...
package splunkpgx

import (
"net"
"net/url"
"strings"

pgx "github.com/jackc/pgx/v5"
// Make sure to import this so the instrumented driver is registered.
_ "github.com/jackc/pgx/v5/stdlib"

"github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql"
)

func init() { //nolint: gochecknoinits // register db driver
splunksql.Register("pgx", splunksql.InstrumentationConfig{
DBSystem: splunksql.DBSystemPostgreSQL,
DSNParser: DSNParser,
})
}

// DSNParser parses the data source connection name for a connection to a
// Postgres database using the github.com/jackc/pgx client package.
func DSNParser(dataSourceName string) (splunksql.ConnectionConfig, error) {
var connCfg splunksql.ConnectionConfig
// ParseConfig defaults:
// host: OS specific unix path, `localhost` otherwise
// port: 5432
// user: OS user name
c, err := pgx.ParseConfig(dataSourceName)
if err != nil {
return connCfg, err
}

connCfg.Name = c.Database
connCfg.User = c.User
if c.Host != "" {
connCfg.Host = c.Host
} else {
connCfg.Host = "localhost"
}
if strings.HasPrefix(connCfg.Host, "/") {
connCfg.NetTransport = splunksql.NetTransportPipe
connCfg.NetSockFamily = splunksql.NetSockFamilyUnix
} else {
connCfg.NetTransport = splunksql.NetTransportTCP
if ip := net.ParseIP(connCfg.Host); ip != nil {
if ip.To4() != nil {
connCfg.NetSockFamily = splunksql.NetSockFamilyInet
} else {
connCfg.NetSockFamily = splunksql.NetSockFamilyInet6
}
}
if c.Port > 0 {
connCfg.Port = int(c.Port)
} else {
connCfg.Port = 5432
}
}

if c.Password == "" {
connCfg.ConnectionString = dataSourceName
} else {
connCfg.ConnectionString = redactPassword(dataSourceName)
}

return connCfg, nil
}

// redactPassword returns the dsn with the password field removed.
func redactPassword(dsn string) string {
if u, err := url.Parse(dsn); err == nil && u.Scheme != "" {
if u.User != nil {
u.User = url.User(u.User.Username())
}
return u.String()
}

parts := strings.Split(dsn, " ")
width := 2
for i := len(parts) - 1; i >= 0; i-- {
vals := strings.SplitN(parts[i], "=", width)
if len(vals) < width {
continue
}
key := strings.Trim(vals[0], " \t\n\r\v\f")
if key == "password" {
parts = append(parts[:i], parts[i+1:]...)
}
}

return strings.Join(parts, " ")
}
76 changes: 76 additions & 0 deletions instrumentation/github.com/jackc/pgx/v5/splunkpgx/sql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright Splunk Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package splunkpgx_test

import (
"testing"

"github.com/signalfx/splunk-otel-go/instrumentation/github.com/jackc/pgx/v5/splunkpgx"

"github.com/stretchr/testify/assert"

"github.com/signalfx/splunk-otel-go/instrumentation/database/sql/splunksql"
)

func TestDSNParser(t *testing.T) {
testcases := []struct {
name string
dsn string
connCfg splunksql.ConnectionConfig
errStr string
}{
{
name: "invalid dsn",
dsn: "invalid dsn",
errStr: "cannot parse `invalid dsn`: failed to parse as DSN (invalid dsn)",
},
{
name: "url: tcp address",
dsn: "postgres://user:password@localhost:8080/testdb",
connCfg: splunksql.ConnectionConfig{
Name: "testdb",
ConnectionString: "postgres://user@localhost:8080/testdb",
User: "user",
Host: "localhost",
Port: 8080,
NetTransport: splunksql.NetTransportTCP,
},
},
{
name: "params: unix socket",
dsn: "user=user password=password host=/tmp/pgdb dbname=testdb",
connCfg: splunksql.ConnectionConfig{
Name: "testdb",
ConnectionString: "user=user host=/tmp/pgdb dbname=testdb",
User: "user",
Host: "/tmp/pgdb",
NetTransport: splunksql.NetTransportPipe,
NetSockFamily: splunksql.NetSockFamilyUnix,
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
got, err := splunkpgx.DSNParser(tc.dsn)
if tc.errStr != "" {
assert.EqualError(t, err, tc.errStr)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.connCfg, got)
})
}
}
Loading

0 comments on commit bd9a611

Please sign in to comment.