aws-xray-sdk-go icon indicating copy to clipboard operation
aws-xray-sdk-go copied to clipboard

How to use aws-xray-sdk-go with gorm

Open momotaro98 opened this issue 4 years ago • 13 comments

I'm trying to use aws-xray-sdk-go to capture SQL query issued by gorm, famous ORM in Go community.

It seems that I need to create a sub-segment before issuing SQL. Is there any better way in aws-xray-sdk-go?

momotaro98 avatar Aug 24 '20 11:08 momotaro98

Hi @momotaro98 ,

I'm not very much familiar with gorm. Looks like X-Ray Go SDK does not support gorm so it won't create automatic subsegment for any calls. So, you will have to manually create subsegment for this in order to trace any calls. However, If this a major use case for you and if you're interested then I can work with you to add a support for this. We love to review/comments/suggestions pull requests on any new framework which X-Ray SDK doesn't support.

bhautikpip avatar Aug 24 '20 16:08 bhautikpip

It might actually be possible now with Gorm v2 released today (2020-08-28)!

To my understanding the issue with X-Ray + Gorm v1 was that X-Ray requires access to sql.Open (or rather that the database needs to be opened with xray.SQLContext) which is not possible with Gorm v1 as it does not support context. Related issues:

  • https://github.com/go-gorm/gorm/issues/2288
  • https://github.com/go-gorm/gorm/issues/1807

Luckily Gorm v2 is now released and according to the main developer it supports context and so it seems to be according to v2 release notes and documentation.

I haven't yet tested this out, but according to Gorm v2 docs, you can use existing connection:

import (
  "database/sql"
  "gorm.io/gorm"
)

sqlDB, err := sql.Open("mysql", "mydb_dsn")
gormDB, err := gorm.Open(mysql.New(mysql.Config{
  Conn: sqlDB,
}), &gorm.Config{})

Then with X-Ray it theoretically should work with:

instrumentedDB, err := xray.SQLContext("postgres", "postgres://user:password@host:port/db")
// Handle err

db, err := gorm.Open(postgres.New(postgres.Config{
  Conn: instrumentedDB,
}), &gorm.Config{})

… if so, I think this should be verified by folks at AWS (ping @bhautikpip) and documented into the X-Ray SDK Go website (under “Tracing SQL queries”-section).

aripalo avatar Aug 28 '20 18:08 aripalo

Hi @aripalo ,

Thanks for this suggestions. I looked into the code you posted. I believe we can use the below code for opening up a connection and that looks fine to me.

instrumentedDB, err := xray.SQLContext("postgres", "postgres://user:password@host:port/db")
// Handle err

db, err := gorm.Open(postgres.New(postgres.Config{
  Conn: instrumentedDB,
}), &gorm.Config{})

The only issue I'm concerned about is how to propagate context for any subsequent gorm API calls. I am assuming that we will be using db to call different gorm APIs right? So, In order to trace all the calls made with gorm I believe we must propagate the context. Here's an example of how we're propagating context. Let me know your thoughts.

bhautikpip avatar Aug 28 '20 19:08 bhautikpip

Thanks for the hint to configure Gorm using the instrumentedDB.

FYI: As for propagating context, the following works for me. Maybe this code helps others, too.

// FindSomeObject loads some object from the database.
func FindSomeObject(ctx context.Context, id string) (*SomeObject, error) {
	var item SomeObject
	err := myGormClient.Perform(ctx, "my subsegment name, e.g. FindSomeObject", func(ctx context.Context, db *gorm.DB) error {
		err := db.Where("...").Take(&item).Error
		// error handling
		return nil
	}
	return &SomeObject{}, nil
}

// My Gorm Client Wrapper (with a `DB *gorm.DB` field):
func (c *GormClient) Perform(ctx context.Context, name string, fn func(ctx context.Context, db *gorm.DB) error) error {
	return xray.Capture(ctx, name, func(ctx2 context.Context) error {
		return fn(ctx2, c.DB.WithContext(ctx2))
	})
}

Calling FindSomeObject (with a ctx that contains an already opened segment) results in a subsegment for FindSomeObject and nested subsegments for SQL related things.

The SQL details are viewable in the X-Ray console, but not in the CloudWatch ServiceLens Traces console, though.

thomasritz avatar Dec 07 '20 18:12 thomasritz

Hi there, i got in stuck with the problem to connect with MySQL.

  • go.mod
go 1.16
require (
	github.com/aws/aws-xray-sdk-go v1.6.0
	gorm.io/driver/mysql v1.1.1
	gorm.io/gorm v1.21.12
)
  • My code
// xray SDK says following the format: `<schema>://<user>:<password>@<host>:<port>/<database>`
// it's different with https://github.com/go-sql-driver/mysql#dsn-data-source-name
// my DSN setting equals "dbuser:dbpass@tcp(localhost:3306)/app?parseTime=true"
instrumentedDB, err := xray.SQLContext("mysql", DSN)
if err != nil {
  fmt.Println("Error: xray.SQLContext. ", DSN)
}

dialector := mysql.New(mysql.Config{
  Conn: instrumentedDB,
})
db, err := gorm.Open(dialector, &gorm.Config{})
  if err != nil {
  fmt.Println("Error: No database connection established.", DSN)
}

When app called gorm.Open, following error occurred.

panic: failed to begin subsegment named 'app': segment cannot be found.

I guess I should use context got from xray.BeginSegment defined as BeginSegment(ctx context.Context, name string) (context.Context, *Segment). Tracing error stack, got it mysql driver uses new context in their Initialize func like below. https://github.com/go-gorm/mysql/blob/master/mysql.go#L63

Does anyone know how to resolve this problem or is there better way to use AWS xray w/ GORM?

Thank u.

horsewin avatar Aug 01 '21 07:08 horsewin

Hi @horsewin AWS X-Ray Go SDK does not support gorm instrumentation officially. I think this thread has some workarounds to trace calls with gorm. Looks like you're having issues with propagating the trace context and hence resulted in segment can not be found error. Unfortunately, there are not other workarounds AFAIK. Have you tried @thomasritz suggestion?

bhautikpip avatar Aug 09 '21 22:08 bhautikpip

Any ETA on this ? GORM is one of the most widely used libraries in Go Eco-system, so it would be valuable to have a connector for X-Ray

driverpt avatar Sep 13 '21 08:09 driverpt

any news for this bug?

dasuma avatar May 24 '22 20:05 dasuma

Hi everyone,

There are currently no plans to support GORM in the X-Ray SDK. OpenTelemetry Go supports instrumenting GORM with a 3rd party instrumentation package: https://github.com/uptrace/opentelemetry-go-extra/tree/main/otelgorm

You can see OpenTelemetry-approved instrumentation packages here: https://opentelemetry.io/registry/?s=gorm&component=&language=

X-Ray provides first-class support for OpenTelemetry instrumentation, you can read getting started with OpenTelemetry Go for X-Ray here: https://aws-otel.github.io/docs/getting-started/go-sdk

willarmiros avatar Jun 06 '22 21:06 willarmiros

I managed to solve this issue with adding

`func openDbConnection(dsn string) *gorm.DB { instrumentedDB, err := xray.SQLContext("postgres", dsn)

if err != nil {
	log.Printf("Can not create instrumented db %+v", err.Error())
}

db, err := gorm.Open(postgres.New(postgres.Config{
	DriverName: "lib/pq",
	Conn:       instrumentedDB,
}), &gorm.Config{
	Logger: logger.Default.LogMode(logger.Info),
})

if err != nil {
	panic("failed to connect database")
}

return db

}`

and executing the query with, where ctx is a param in the handler function

err := repo.db. WithContext(ctx). Model(&SomeModel{}). Find(&array). Limit(100). Error

hatrixdamjan avatar Aug 18 '22 12:08 hatrixdamjan

any news on this one?

jledesma-opsguru avatar Apr 06 '23 21:04 jledesma-opsguru

Thanks for the hint to configure Gorm using the instrumentedDB.

FYI: As for propagating context, the following works for me. Maybe this code helps others, too.

// FindSomeObject loads some object from the database.
func FindSomeObject(ctx context.Context, id string) (*SomeObject, error) {
	var item SomeObject
	err := myGormClient.Perform(ctx, "my subsegment name, e.g. FindSomeObject", func(ctx context.Context, db *gorm.DB) error {
		err := db.Where("...").Take(&item).Error
		// error handling
		return nil
	}
	return &SomeObject{}, nil
}

// My Gorm Client Wrapper (with a `DB *gorm.DB` field):
func (c *GormClient) Perform(ctx context.Context, name string, fn func(ctx context.Context, db *gorm.DB) error) error {
	return xray.Capture(ctx, name, func(ctx2 context.Context) error {
		return fn(ctx2, c.DB.WithContext(ctx2))
	})
}

Calling FindSomeObject (with a ctx that contains an already opened segment) results in a subsegment for FindSomeObject and nested subsegments for SQL related things.

The SQL details are viewable in the X-Ray console, but not in the CloudWatch ServiceLens Traces console, though.

@thomasritz have you found any better way to implement this on GORM v2?

jledesma-opsguru avatar Apr 06 '23 21:04 jledesma-opsguru

@hatrixdamjan solution actually works for me. Thanks :)

jledesma-opsguru avatar Apr 11 '23 16:04 jledesma-opsguru