Skip to content

Commit

Permalink
Merge pull request #605 from newrelic/develop
Browse files Browse the repository at this point in the history
Release 3.20.1
  • Loading branch information
nr-swilloughby committed Nov 15, 2022
2 parents 38d939b + aad2655 commit e4d1622
Show file tree
Hide file tree
Showing 16 changed files with 1,050 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Expand Up @@ -186,6 +186,8 @@ jobs:
- go-version: 1.17.x
dirs: v3/integrations/nrpq
extratesting: go get -u github.com/lib/pq@master
- go-version: 1.18.x
dirs: v3/integrations/nrpgx5
- go-version: 1.17.x
dirs: v3/integrations/nrpq/example/sqlx
- go-version: 1.17.x
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,25 @@
## 3.20.1

### Added
* New integration `nrpgx5` v1.0.0 to instrument `github.com/jackc/pgx/v5`.

### Changed

* Changed the following `TraceOption` function to be consistent with their usage and other related identifier names. The old names remain for backward compatibility, but new code should use the new names.
* `WithIgnoredPrefix` -> `WithIgnoredPrefixes`
* `WithPathPrefix` -> `WithPathPrefixes`
* Implemented better handling of Code Level Metrics reporting when the data (e.g., function names) are excessively long, so that those attributes are suppressed rather than being reported with truncated names. Specifically:
* Attributes with values longer than 255 characters are dropped.
* No CLM attributes at all will be attached to a trace if the `code.function` attribute is empty or is longer than 255 characters.
* No CLM attributes at all will be attached to a trace if both `code.namespace` and `code.filepath` are longer than 255 characters.

### Support Statement
New Relic recommends that you upgrade the agent regularly to ensure that you’re getting the latest features and performance benefits. Additionally, older releases will no longer be supported when they reach end-of-life.

We also recommend using the latest version of the Go language. At minimum, you should at least be using no version of Go older than what is supported by the Go team themselves.

See the [Go Agent EOL Policy](https://docs.newrelic.com/docs/apm/agents/go-agent/get-started/go-agent-eol-policy/) for details about supported versions of the Go Agent and third-party components.

## 3.20.0

**PLEASE READ** these changes, and verify your config settings to ensure your application behaves how you intend it to. This release changes some default behaviors in the go agent.
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -90,6 +90,7 @@ package primitives can be found [here](GUIDE.md#datastore-segments).
| ------------- | ------------- | - |
| [lib/pq](https://github.com/lib/pq) | [v3/integrations/nrpq](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrpq) | Instrument PostgreSQL driver (`pq` driver for `database/sql`) |
| [jackc/pgx](https://github.com/jackc/pgx) | [v3/integrations/nrpgx](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrpgx) | Instrument PostgreSQL driver (`pgx` driver for `database/sql`)|
| [jackc/pgx/v5](https://github.com/jackc/pgx/v5) | [v3/integrations/nrpgx5](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrpgx5) | Instrument PostgreSQL driver (`pgx/v5` driver for `database/sql`)|
| [go-mssqldb](github.com/denisenkom/go-mssqldb) | [v3/integrations/nrmssql](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrmssql) | Instrument MS SQL driver |
| [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) | [v3/integrations/nrmysql](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrmysql) | Instrument MySQL driver |
| [elastic/go-elasticsearch](https://github.com/elastic/go-elasticsearch) | [v3/integrations/nrelasticsearch-v7](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrelasticsearch-v7) | Instrument Elasticsearch datastore calls |
Expand Down
10 changes: 10 additions & 0 deletions v3/integrations/nrpgx5/README.md
@@ -0,0 +1,10 @@
# v3/integrations/nrpgx5 [![GoDoc](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrpgx5?status.svg)](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrpgx5)

Package `nrpgx` instruments https://github.com/jackc/pgx/v5.

```go
import "github.com/newrelic/go-agent/v3/integrations/nrpgx5"
```

For more information, see
[godocs](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrpgx5).
53 changes: 53 additions & 0 deletions v3/integrations/nrpgx5/example/pgx/main.go
@@ -0,0 +1,53 @@
package main

import (
"context"
"fmt"
"log"
"os"
"time"

"github.com/jackc/pgx/v5"
"github.com/newrelic/go-agent/v3/integrations/nrpgx5"
"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
cfg, err := pgx.ParseConfig("postgres://postgres:postgres@localhost:5432")
if err != nil {
panic(err)
}

cfg.Tracer = nrpgx5.NewTracer()
conn, err := pgx.ConnectConfig(context.Background(), cfg)
if err != nil {
panic(err)
}

app, err := newrelic.NewApplication(
newrelic.ConfigAppName("PostgreSQL App"),
newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
newrelic.ConfigDebugLogger(os.Stdout),
)
if err != nil {
panic(err)
}
//
// N.B.: We do not recommend using app.WaitForConnection in production code.
//
app.WaitForConnection(5 * time.Second)
txn := app.StartTransaction("postgresQuery")

ctx := newrelic.NewContext(context.Background(), txn)
row := conn.QueryRow(ctx, "SELECT count(*) FROM pg_catalog.pg_tables")
count := 0
err = row.Scan(&count)
if err != nil {
log.Println(err)
}

txn.End()
app.Shutdown(5 * time.Second)

fmt.Println("number of entries in pg_catalog.pg_tables", count)
}
53 changes: 53 additions & 0 deletions v3/integrations/nrpgx5/example/pgxpool/main.go
@@ -0,0 +1,53 @@
package main

import (
"context"
"fmt"
"log"
"os"
"time"

"github.com/jackc/pgx/v5/pgxpool"
"github.com/newrelic/go-agent/v3/integrations/nrpgx5"
"github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
cfg, err := pgxpool.ParseConfig("postgres://postgres:postgres@localhost:5432")
if err != nil {
panic(err)
}

cfg.ConnConfig.Tracer = nrpgx5.NewTracer()
db, err := pgxpool.NewWithConfig(context.Background(), cfg)
if err != nil {
panic(err)
}

app, err := newrelic.NewApplication(
newrelic.ConfigAppName("PostgreSQL App"),
newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
newrelic.ConfigDebugLogger(os.Stdout),
)
if err != nil {
panic(err)
}
//
// N.B.: We do not recommend using app.WaitForConnection in production code.
//
app.WaitForConnection(5 * time.Second)
txn := app.StartTransaction("postgresQuery")

ctx := newrelic.NewContext(context.Background(), txn)
row := db.QueryRow(ctx, "SELECT count(*) FROM pg_catalog.pg_tables")
count := 0
err = row.Scan(&count)
if err != nil {
log.Println(err)
}

txn.End()
app.Shutdown(5 * time.Second)

fmt.Println("number of entries in pg_catalog.pg_tables", count)
}
10 changes: 10 additions & 0 deletions v3/integrations/nrpgx5/go.mod
@@ -0,0 +1,10 @@
module github.com/newrelic/go-agent/v3/integrations/nrpgx5

go 1.17

require (
github.com/egon12/pgsnap v0.0.0-20221022154027-2847f0124ed8
github.com/jackc/pgx/v5 v5.0.3
github.com/newrelic/go-agent/v3 v3.20.0
github.com/stretchr/testify v1.8.0
)
177 changes: 177 additions & 0 deletions v3/integrations/nrpgx5/nrpgx5.go
@@ -0,0 +1,177 @@
// Package nrpgx5 instruments https://github.com/jackc/pgx/v5.
//
// Use this package to instrument your PostgreSQL calls using the pgx
// library.
//
// This are the steps to instrument your pgx calls without using `database/sql`:
// if you want to use `database/sql`, you can use `nrpgx` package instead
//
// to instrument your pgx calls:
// you can set the tracer in the pgx.Config like this
// ```go
// import (
// "github.com/jackc/pgx/v5"
// "github.com/newrelic/go-agent/v3/integrations/nrpgx5"
// "github.com/newrelic/go-agent/v3/newrelic"
// )
//
// func main() {
// cfg, err := pgx.ParseConfig("postgres://postgres:postgres@localhost:5432")
// if err != nil {
// panic(err)
// }
//
// cfg.Tracer = nrpgx5.NewTracer()
// conn, err := pgx.ConnectConfig(context.Background(), cfg)
// if err != nil {
// panic(err)
// }
// ...
// ```
// or you can set the tracer in the pgxpool.Config like this
// ```go
// import (
// "github.com/jackc/pgx/v5/pgxpool"
// "github.com/newrelic/go-agent/v3/integrations/nrpgx5"
// "github.com/newrelic/go-agent/v3/newrelic"
// )
//
// func main() {
// cfg, err := pgxpool.ParseConfig("postgres://postgres:postgres@localhost:5432")
// if err != nil {
// panic(err)
// }
//
// cfg.ConnConfig.Tracer = nrpgx5.NewTracer()
// db, err := pgxpool.NewWithConfig(context.Background(), cfg)
// if err != nil {
// panic(err)
// }
// ...
// ```

package nrpgx5

import (
"context"
"strconv"

"github.com/jackc/pgx/v5"
"github.com/newrelic/go-agent/v3/internal"
"github.com/newrelic/go-agent/v3/newrelic"
"github.com/newrelic/go-agent/v3/newrelic/sqlparse"
)

func init() {
internal.TrackUsage("integration", "driver", "nrpgx5")
}

type (
Tracer struct {
BaseSegment newrelic.DatastoreSegment
ParseQuery func(segment *newrelic.DatastoreSegment, query string)
}

nrPgxSegmentType string
)

const (
querySegmentKey nrPgxSegmentType = "nrPgx5Segment"
prepareSegmentKey nrPgxSegmentType = "prepareNrPgx5Segment"
batchSegmentKey nrPgxSegmentType = "batchNrPgx5Segment"
)

func NewTracer() *Tracer {
return &Tracer{
ParseQuery: sqlparse.ParseQuery,
}
}

// TraceConnectStart is called at the beginning of Connect and ConnectConfig calls. The returned context is used for
// the rest of the call and will be passed to TraceConnectEnd. // implement pgx.ConnectTracer
func (t *Tracer) TraceConnectStart(ctx context.Context, data pgx.TraceConnectStartData) context.Context {
t.BaseSegment = newrelic.DatastoreSegment{
Product: newrelic.DatastorePostgres,
Host: data.ConnConfig.Host,
PortPathOrID: strconv.FormatUint(uint64(data.ConnConfig.Port), 10),
DatabaseName: data.ConnConfig.Database,
}

return ctx
}

// TraceConnectEnd method // implement pgx.ConnectTracer
func (Tracer) TraceConnectEnd(ctx context.Context, data pgx.TraceConnectEndData) {}

// TraceQueryStart is called at the beginning of Query, QueryRow, and Exec calls. The returned context is used for the
// rest of the call and will be passed to TraceQueryEnd. //implement pgx.QueryTracer
func (t *Tracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
segment := t.BaseSegment
segment.StartTime = newrelic.FromContext(ctx).StartSegmentNow()
segment.ParameterizedQuery = data.SQL
segment.QueryParameters = t.getQueryParameters(data.Args)

// fill Operation and Collection
t.ParseQuery(&segment, data.SQL)

return context.WithValue(ctx, querySegmentKey, &segment)
}

// TraceQueryEnd method implement pgx.QueryTracer. It will try to get segment from context and end it.
func (t *Tracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
segment, ok := ctx.Value(querySegmentKey).(*newrelic.DatastoreSegment)
if !ok {
return
}
segment.End()
}

func (t *Tracer) getQueryParameters(args []interface{}) map[string]interface{} {
result := map[string]interface{}{}
for i, arg := range args {
result["$"+strconv.Itoa(i)] = arg
}
return result
}

// TraceBatchStart is called at the beginning of SendBatch calls. The returned context is used for the
// rest of the call and will be passed to TraceBatchQuery and TraceBatchEnd. // implement pgx.BatchTracer
func (t *Tracer) TraceBatchStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchStartData) context.Context {
segment := t.BaseSegment
segment.StartTime = newrelic.FromContext(ctx).StartSegmentNow()
segment.Operation = "batch"
segment.Collection = ""

return context.WithValue(ctx, batchSegmentKey, &segment)
}

// TraceBatchQuery implement pgx.BatchTracer. In this method we will get query and store it in segment.
func (t *Tracer) TraceBatchQuery(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchQueryData) {
segment, ok := ctx.Value(batchSegmentKey).(*newrelic.DatastoreSegment)
if !ok {
return
}

segment.ParameterizedQuery += data.SQL + "\n"
}

// TraceBatchEnd implement pgx.BatchTracer. In this method we will get segment from context and fill it with
func (t *Tracer) TraceBatchEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchEndData) {
segment, ok := ctx.Value(batchSegmentKey).(*newrelic.DatastoreSegment)
if !ok {
return
}
segment.End()
}

// TracePrepareStart is called at the beginning of Prepare calls. The returned context is used for the
// rest of the call and will be passed to TracePrepareEnd. // implement pgx.PrepareTracer
// The Query and QueryRow will call prepare. Fill this function will make the datastore segment called twice.
// So this function woudln't do anything and just return the context.
func (t *Tracer) TracePrepareStart(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareStartData) context.Context {
return ctx
}

// TracePrepareEnd implement pgx.PrepareTracer. In this function nothing happens.
func (t *Tracer) TracePrepareEnd(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareEndData) {
}

0 comments on commit e4d1622

Please sign in to comment.