Skip to content

Commit e4d1622

Browse files
Merge pull request #605 from newrelic/develop
Release 3.20.1
2 parents 38d939b + aad2655 commit e4d1622

16 files changed

+1050
-6
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ jobs:
186186
- go-version: 1.17.x
187187
dirs: v3/integrations/nrpq
188188
extratesting: go get -u github.com/lib/pq@master
189+
- go-version: 1.18.x
190+
dirs: v3/integrations/nrpgx5
189191
- go-version: 1.17.x
190192
dirs: v3/integrations/nrpq/example/sqlx
191193
- go-version: 1.17.x

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
## 3.20.1
2+
3+
### Added
4+
* New integration `nrpgx5` v1.0.0 to instrument `github.com/jackc/pgx/v5`.
5+
6+
### Changed
7+
8+
* 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.
9+
* `WithIgnoredPrefix` -> `WithIgnoredPrefixes`
10+
* `WithPathPrefix` -> `WithPathPrefixes`
11+
* 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:
12+
* Attributes with values longer than 255 characters are dropped.
13+
* No CLM attributes at all will be attached to a trace if the `code.function` attribute is empty or is longer than 255 characters.
14+
* No CLM attributes at all will be attached to a trace if both `code.namespace` and `code.filepath` are longer than 255 characters.
15+
16+
### Support Statement
17+
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.
18+
19+
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.
20+
21+
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.
22+
123
## 3.20.0
224

325
**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.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ package primitives can be found [here](GUIDE.md#datastore-segments).
9090
| ------------- | ------------- | - |
9191
| [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`) |
9292
| [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`)|
93+
| [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`)|
9394
| [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 |
9495
| [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 |
9596
| [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 |

v3/integrations/nrpgx5/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 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)
2+
3+
Package `nrpgx` instruments https://github.com/jackc/pgx/v5.
4+
5+
```go
6+
import "github.com/newrelic/go-agent/v3/integrations/nrpgx5"
7+
```
8+
9+
For more information, see
10+
[godocs](https://godoc.org/github.com/newrelic/go-agent/v3/integrations/nrpgx5).
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
"time"
9+
10+
"github.com/jackc/pgx/v5"
11+
"github.com/newrelic/go-agent/v3/integrations/nrpgx5"
12+
"github.com/newrelic/go-agent/v3/newrelic"
13+
)
14+
15+
func main() {
16+
cfg, err := pgx.ParseConfig("postgres://postgres:postgres@localhost:5432")
17+
if err != nil {
18+
panic(err)
19+
}
20+
21+
cfg.Tracer = nrpgx5.NewTracer()
22+
conn, err := pgx.ConnectConfig(context.Background(), cfg)
23+
if err != nil {
24+
panic(err)
25+
}
26+
27+
app, err := newrelic.NewApplication(
28+
newrelic.ConfigAppName("PostgreSQL App"),
29+
newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
30+
newrelic.ConfigDebugLogger(os.Stdout),
31+
)
32+
if err != nil {
33+
panic(err)
34+
}
35+
//
36+
// N.B.: We do not recommend using app.WaitForConnection in production code.
37+
//
38+
app.WaitForConnection(5 * time.Second)
39+
txn := app.StartTransaction("postgresQuery")
40+
41+
ctx := newrelic.NewContext(context.Background(), txn)
42+
row := conn.QueryRow(ctx, "SELECT count(*) FROM pg_catalog.pg_tables")
43+
count := 0
44+
err = row.Scan(&count)
45+
if err != nil {
46+
log.Println(err)
47+
}
48+
49+
txn.End()
50+
app.Shutdown(5 * time.Second)
51+
52+
fmt.Println("number of entries in pg_catalog.pg_tables", count)
53+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"os"
8+
"time"
9+
10+
"github.com/jackc/pgx/v5/pgxpool"
11+
"github.com/newrelic/go-agent/v3/integrations/nrpgx5"
12+
"github.com/newrelic/go-agent/v3/newrelic"
13+
)
14+
15+
func main() {
16+
cfg, err := pgxpool.ParseConfig("postgres://postgres:postgres@localhost:5432")
17+
if err != nil {
18+
panic(err)
19+
}
20+
21+
cfg.ConnConfig.Tracer = nrpgx5.NewTracer()
22+
db, err := pgxpool.NewWithConfig(context.Background(), cfg)
23+
if err != nil {
24+
panic(err)
25+
}
26+
27+
app, err := newrelic.NewApplication(
28+
newrelic.ConfigAppName("PostgreSQL App"),
29+
newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
30+
newrelic.ConfigDebugLogger(os.Stdout),
31+
)
32+
if err != nil {
33+
panic(err)
34+
}
35+
//
36+
// N.B.: We do not recommend using app.WaitForConnection in production code.
37+
//
38+
app.WaitForConnection(5 * time.Second)
39+
txn := app.StartTransaction("postgresQuery")
40+
41+
ctx := newrelic.NewContext(context.Background(), txn)
42+
row := db.QueryRow(ctx, "SELECT count(*) FROM pg_catalog.pg_tables")
43+
count := 0
44+
err = row.Scan(&count)
45+
if err != nil {
46+
log.Println(err)
47+
}
48+
49+
txn.End()
50+
app.Shutdown(5 * time.Second)
51+
52+
fmt.Println("number of entries in pg_catalog.pg_tables", count)
53+
}

v3/integrations/nrpgx5/go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module github.com/newrelic/go-agent/v3/integrations/nrpgx5
2+
3+
go 1.17
4+
5+
require (
6+
github.com/egon12/pgsnap v0.0.0-20221022154027-2847f0124ed8
7+
github.com/jackc/pgx/v5 v5.0.3
8+
github.com/newrelic/go-agent/v3 v3.20.0
9+
github.com/stretchr/testify v1.8.0
10+
)

v3/integrations/nrpgx5/nrpgx5.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Package nrpgx5 instruments https://github.com/jackc/pgx/v5.
2+
//
3+
// Use this package to instrument your PostgreSQL calls using the pgx
4+
// library.
5+
//
6+
// This are the steps to instrument your pgx calls without using `database/sql`:
7+
// if you want to use `database/sql`, you can use `nrpgx` package instead
8+
//
9+
// to instrument your pgx calls:
10+
// you can set the tracer in the pgx.Config like this
11+
// ```go
12+
// import (
13+
// "github.com/jackc/pgx/v5"
14+
// "github.com/newrelic/go-agent/v3/integrations/nrpgx5"
15+
// "github.com/newrelic/go-agent/v3/newrelic"
16+
// )
17+
//
18+
// func main() {
19+
// cfg, err := pgx.ParseConfig("postgres://postgres:postgres@localhost:5432")
20+
// if err != nil {
21+
// panic(err)
22+
// }
23+
//
24+
// cfg.Tracer = nrpgx5.NewTracer()
25+
// conn, err := pgx.ConnectConfig(context.Background(), cfg)
26+
// if err != nil {
27+
// panic(err)
28+
// }
29+
// ...
30+
// ```
31+
// or you can set the tracer in the pgxpool.Config like this
32+
// ```go
33+
// import (
34+
// "github.com/jackc/pgx/v5/pgxpool"
35+
// "github.com/newrelic/go-agent/v3/integrations/nrpgx5"
36+
// "github.com/newrelic/go-agent/v3/newrelic"
37+
// )
38+
//
39+
// func main() {
40+
// cfg, err := pgxpool.ParseConfig("postgres://postgres:postgres@localhost:5432")
41+
// if err != nil {
42+
// panic(err)
43+
// }
44+
//
45+
// cfg.ConnConfig.Tracer = nrpgx5.NewTracer()
46+
// db, err := pgxpool.NewWithConfig(context.Background(), cfg)
47+
// if err != nil {
48+
// panic(err)
49+
// }
50+
// ...
51+
// ```
52+
53+
package nrpgx5
54+
55+
import (
56+
"context"
57+
"strconv"
58+
59+
"github.com/jackc/pgx/v5"
60+
"github.com/newrelic/go-agent/v3/internal"
61+
"github.com/newrelic/go-agent/v3/newrelic"
62+
"github.com/newrelic/go-agent/v3/newrelic/sqlparse"
63+
)
64+
65+
func init() {
66+
internal.TrackUsage("integration", "driver", "nrpgx5")
67+
}
68+
69+
type (
70+
Tracer struct {
71+
BaseSegment newrelic.DatastoreSegment
72+
ParseQuery func(segment *newrelic.DatastoreSegment, query string)
73+
}
74+
75+
nrPgxSegmentType string
76+
)
77+
78+
const (
79+
querySegmentKey nrPgxSegmentType = "nrPgx5Segment"
80+
prepareSegmentKey nrPgxSegmentType = "prepareNrPgx5Segment"
81+
batchSegmentKey nrPgxSegmentType = "batchNrPgx5Segment"
82+
)
83+
84+
func NewTracer() *Tracer {
85+
return &Tracer{
86+
ParseQuery: sqlparse.ParseQuery,
87+
}
88+
}
89+
90+
// TraceConnectStart is called at the beginning of Connect and ConnectConfig calls. The returned context is used for
91+
// the rest of the call and will be passed to TraceConnectEnd. // implement pgx.ConnectTracer
92+
func (t *Tracer) TraceConnectStart(ctx context.Context, data pgx.TraceConnectStartData) context.Context {
93+
t.BaseSegment = newrelic.DatastoreSegment{
94+
Product: newrelic.DatastorePostgres,
95+
Host: data.ConnConfig.Host,
96+
PortPathOrID: strconv.FormatUint(uint64(data.ConnConfig.Port), 10),
97+
DatabaseName: data.ConnConfig.Database,
98+
}
99+
100+
return ctx
101+
}
102+
103+
// TraceConnectEnd method // implement pgx.ConnectTracer
104+
func (Tracer) TraceConnectEnd(ctx context.Context, data pgx.TraceConnectEndData) {}
105+
106+
// TraceQueryStart is called at the beginning of Query, QueryRow, and Exec calls. The returned context is used for the
107+
// rest of the call and will be passed to TraceQueryEnd. //implement pgx.QueryTracer
108+
func (t *Tracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
109+
segment := t.BaseSegment
110+
segment.StartTime = newrelic.FromContext(ctx).StartSegmentNow()
111+
segment.ParameterizedQuery = data.SQL
112+
segment.QueryParameters = t.getQueryParameters(data.Args)
113+
114+
// fill Operation and Collection
115+
t.ParseQuery(&segment, data.SQL)
116+
117+
return context.WithValue(ctx, querySegmentKey, &segment)
118+
}
119+
120+
// TraceQueryEnd method implement pgx.QueryTracer. It will try to get segment from context and end it.
121+
func (t *Tracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
122+
segment, ok := ctx.Value(querySegmentKey).(*newrelic.DatastoreSegment)
123+
if !ok {
124+
return
125+
}
126+
segment.End()
127+
}
128+
129+
func (t *Tracer) getQueryParameters(args []interface{}) map[string]interface{} {
130+
result := map[string]interface{}{}
131+
for i, arg := range args {
132+
result["$"+strconv.Itoa(i)] = arg
133+
}
134+
return result
135+
}
136+
137+
// TraceBatchStart is called at the beginning of SendBatch calls. The returned context is used for the
138+
// rest of the call and will be passed to TraceBatchQuery and TraceBatchEnd. // implement pgx.BatchTracer
139+
func (t *Tracer) TraceBatchStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchStartData) context.Context {
140+
segment := t.BaseSegment
141+
segment.StartTime = newrelic.FromContext(ctx).StartSegmentNow()
142+
segment.Operation = "batch"
143+
segment.Collection = ""
144+
145+
return context.WithValue(ctx, batchSegmentKey, &segment)
146+
}
147+
148+
// TraceBatchQuery implement pgx.BatchTracer. In this method we will get query and store it in segment.
149+
func (t *Tracer) TraceBatchQuery(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchQueryData) {
150+
segment, ok := ctx.Value(batchSegmentKey).(*newrelic.DatastoreSegment)
151+
if !ok {
152+
return
153+
}
154+
155+
segment.ParameterizedQuery += data.SQL + "\n"
156+
}
157+
158+
// TraceBatchEnd implement pgx.BatchTracer. In this method we will get segment from context and fill it with
159+
func (t *Tracer) TraceBatchEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchEndData) {
160+
segment, ok := ctx.Value(batchSegmentKey).(*newrelic.DatastoreSegment)
161+
if !ok {
162+
return
163+
}
164+
segment.End()
165+
}
166+
167+
// TracePrepareStart is called at the beginning of Prepare calls. The returned context is used for the
168+
// rest of the call and will be passed to TracePrepareEnd. // implement pgx.PrepareTracer
169+
// The Query and QueryRow will call prepare. Fill this function will make the datastore segment called twice.
170+
// So this function woudln't do anything and just return the context.
171+
func (t *Tracer) TracePrepareStart(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareStartData) context.Context {
172+
return ctx
173+
}
174+
175+
// TracePrepareEnd implement pgx.PrepareTracer. In this function nothing happens.
176+
func (t *Tracer) TracePrepareEnd(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareEndData) {
177+
}

0 commit comments

Comments
 (0)