Skip to content

Commit

Permalink
Added support for go-pg v10 (#857)
Browse files Browse the repository at this point in the history
* Added support for v10
  • Loading branch information
macnibblet committed Dec 7, 2020
1 parent b228473 commit 46d8c0a
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 0 deletions.
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
}

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")
}

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

0 comments on commit 46d8c0a

Please sign in to comment.