Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for go-pg v10 #857

Merged
merged 9 commits into from Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/instrumenting.asciidoc
Expand Up @@ -378,6 +378,31 @@ import (
"go.elastic.co/apm/module/apmgopg"
)

func main() {
db := pg.Connect(&pg.Options{})
apmgopg.Instrument(db)

db.WithContext(ctx).Model(...)
}
----
Spans will be created for queries and other statement executions if the context methods are
used, and the context includes a transaction.

[[builtin-modules-apmgopgv10]]
==== module/apmgopgv10
Package apmgopgv10 provides a means of instrumenting v10 of http://github.com/go-pg/pg[go-pg] database operations.

To trace `go-pg` statements, call `apmgopgv10.Instrument` with the database instance you plan on using and provide
a context that contains an apm transaction.

[source,go]
----
import (
"github.com/go-pg/pg/v10"

"go.elastic.co/apm/module/apmgopgv10"
)

func main() {
db := pg.Connect(&pg.Options{})
apmgopg.Instrument(db)
Expand Down
19 changes: 19 additions & 0 deletions module/apmgopgv10/doc.go
@@ -0,0 +1,19 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 apmgopgv10 provides wrappers for tracing go-pg/v10 operations.
package apmgopgv10 // import "go.elastic.co/apm/module/apmgopgv10"
14 changes: 14 additions & 0 deletions module/apmgopgv10/go.mod
@@ -0,0 +1,14 @@
module go.elastic.co/apm/module/apmgopgv10

require (
github.com/go-pg/pg/v10 v10.7.3
github.com/stretchr/testify v1.6.1
go.elastic.co/apm v1.9.0
go.elastic.co/apm/module/apmsql v1.9.0
)

replace go.elastic.co/apm => ../..

replace go.elastic.co/apm/module/apmsql => ../apmsql

go 1.14
341 changes: 341 additions & 0 deletions module/apmgopgv10/go.sum

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions module/apmgopgv10/hook.go
@@ -0,0 +1,89 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.

// +build go1.14

package apmgopgv10 // import "go.elastic.co/apm/module/apmgopgv10"

import (
"context"

"github.com/go-pg/pg/v10"
"github.com/pkg/errors"

"go.elastic.co/apm"
"go.elastic.co/apm/module/apmsql"
"go.elastic.co/apm/stacktrace"
)

func init() {
stacktrace.RegisterLibraryPackage("github.com/go-pg/pg/v10")
}

// Instrument modifies db such that operations are hooked and reported as spans
// to Elastic APM if they occur within the context of a captured transaction.
//
// If Instrument cannot instrument db, then an error will be returned.
func Instrument(db *pg.DB) error {
db.AddQueryHook(&queryHook{})

return nil
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep this to have the same API as the previous version?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. This minimises the number of changes required to update the instrumentation when upgrading, and leaves the door open for additional checks later.

}

var _ pg.QueryHook = (*queryHook)(nil)

// queryHook is an implementation of pg.QueryHook that reports queries as spans to Elastic APM.
type queryHook struct{}

// BeforeQuery initiates the span for the database query
func (qh *queryHook) BeforeQuery(ctx context.Context, evt *pg.QueryEvent) (context.Context, error) {
var (
database string
user string
)
if db, ok := evt.DB.(*pg.DB); ok {
opts := db.Options()
user = opts.User
database = opts.Database
}

sql, err := evt.UnformattedQuery()
if err != nil {
return ctx, errors.Wrap(err, "failed to generate sql")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@axw I'm not sure about this part, but I don't know what would possibly cause it to fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't either; this looks fine to me.

}

span, ctx := apm.StartSpan(ctx, apmsql.QuerySignature(string(sql)), "db.postgresql.query")
span.Context.SetDatabase(apm.DatabaseSpanContext{
Statement: string(sql),

// Static
Type: "sql",
User: user,
Instance: database,
})

return ctx, nil
}

// AfterQuery ends the initiated span from BeforeQuery
func (qh *queryHook) AfterQuery(ctx context.Context, evt *pg.QueryEvent) error {
if span := apm.SpanFromContext(ctx); span != nil {
span.End()
}

return nil
}
97 changes: 97 additions & 0 deletions module/apmgopgv10/hook_test.go
@@ -0,0 +1,97 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.

// +build go1.14

package apmgopgv10_test

import (
"context"
"fmt"
"os"
"testing"

"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.elastic.co/apm/apmtest"
"go.elastic.co/apm/module/apmgopgv10"
)

type User struct {
TableName struct{} `sql:"users"`

Id int `json:"id"`
Name string `json:"name"`
}

func TestWithContext(t *testing.T) {
if os.Getenv("PGHOST") == "" {
t.Skip("PGHOST env not defined, skipping")
return
}

_, spans, errors := apmtest.WithTransaction(func(ctx context.Context) {
db := pg.Connect(&pg.Options{
Addr: fmt.Sprintf("%s:5432", os.Getenv("PGHOST")),
User: "postgres",
Password: "hunter2",
Database: "test_db",
})
err := apmgopgv10.Instrument(db)
require.NoError(t, err)

_, err = db.Exec("SELECT 1")
require.NoError(t, err)

db.Model(&User{}).DropTable(&orm.DropTableOptions{})
db.Model(&User{}).CreateTable(&orm.CreateTableOptions{})

defer db.Close()
db = db.WithContext(ctx)

_, err = db.Model(&User{Id: 1337, Name: "Antoine Hedgecock"}).Insert()
assert.NoError(t, err)

assert.NoError(t, db.Model(&User{}).Where("id = ?", 1337).Select())

_, err = db.Model(&User{Id: 1337, Name: "new name"}).Column("name").WherePK().Update()
assert.NoError(t, err)
})

require.NotEmpty(t, spans)
assert.Empty(t, errors)

spanNames := make([]string, len(spans))
for i, span := range spans {
spanNames[i] = span.Name
require.NotNil(t, span.Context)
require.NotNil(t, span.Context.Database)
assert.Equal(t, "test_db", span.Context.Database.Instance)
assert.NotEmpty(t, span.Context.Database.Statement)
assert.Equal(t, "sql", span.Context.Database.Type)
assert.Equal(t, "postgres", span.Context.Database.User)
}

assert.Equal(t, []string{
"INSERT INTO users",
"SELECT FROM users",
"UPDATE users",
}, spanNames)
}
2 changes: 2 additions & 0 deletions scripts/Dockerfile-testing
Expand Up @@ -15,6 +15,7 @@ COPY module/apmgocql/go.mod module/apmgocql/go.sum /go/src/go.elastic.co/apm/mod
COPY module/apmgokit/go.mod module/apmgokit/go.sum /go/src/go.elastic.co/apm/module/apmgokit/
COPY module/apmgometrics/go.mod module/apmgometrics/go.sum /go/src/go.elastic.co/apm/module/apmgometrics/
COPY module/apmgopg/go.mod module/apmgopg/go.sum /go/src/go.elastic.co/apm/module/apmgopg/
COPY module/apmgopgv10/go.mod module/apmgopgv10/go.sum /go/src/go.elastic.co/apm/module/apmgopgv10/
COPY module/apmgoredis/go.mod module/apmgoredis/go.sum /go/src/go.elastic.co/apm/module/apmgoredis/
COPY module/apmgoredisv8/go.mod module/apmgoredisv8/go.sum /go/src/go.elastic.co/apm/module/apmgoredisv8/
COPY module/apmgorilla/go.mod module/apmgorilla/go.sum /go/src/go.elastic.co/apm/module/apmgorilla/
Expand Down Expand Up @@ -49,6 +50,7 @@ RUN cd /go/src/go.elastic.co/apm/module/apmgocql && go mod download
RUN cd /go/src/go.elastic.co/apm/module/apmgokit && go mod download
RUN cd /go/src/go.elastic.co/apm/module/apmgometrics && go mod download
RUN cd /go/src/go.elastic.co/apm/module/apmgopg && go mod download
RUN cd /go/src/go.elastic.co/apm/module/apmgopgv10 && go mod download
RUN cd /go/src/go.elastic.co/apm/module/apmgoredis && go mod download
RUN cd /go/src/go.elastic.co/apm/module/apmgoredisv8 && go mod download
RUN cd /go/src/go.elastic.co/apm/module/apmgorilla && go mod download
Expand Down