Compare commits

..

3 Commits

Author SHA1 Message Date
bb64c08185 chore(dependencies): updated dependencies
Some checks failed
Gitea Build Check / Build (push) Failing after 30s
2024-08-11 22:17:40 +02:00
a368f59234 feat(telemetry): Implement OpenTelemetry tracing and logging in tag management functions 2024-08-11 22:17:24 +02:00
2d9db01d72 feat(telemetry): Implement telemetry setup in database client
Add OpenTelemetry integration to the database client by creating a `setupTelemetry` function. Initialize a tracer and configure logging with the otellogrus hook. Call this function during the connection process to enable tracing and logging for database operations.
2024-08-11 22:17:00 +02:00
4 changed files with 141 additions and 24 deletions

12
go.mod
View File

@ -9,6 +9,9 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/testcontainers/testcontainers-go v0.32.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0
go.opentelemetry.io/contrib/bridges/otellogrus v0.3.0
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/trace v1.28.0
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.11
)
@ -29,7 +32,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@ -60,12 +63,11 @@ require (
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.opentelemetry.io/otel/log v0.4.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/grpc v1.59.0 // indirect

24
go.sum
View File

@ -36,8 +36,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@ -144,20 +144,24 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/contrib/bridges/otellogrus v0.3.0 h1:QHEj9AK6bEiEA9S5OdDUE9KAx4xp6pRkYMnybHDmjZU=
go.opentelemetry.io/contrib/bridges/otellogrus v0.3.0/go.mod h1:HRlW/1YWrBrbzB6FvHU7jUuz33F74PEvQVBL+b+wUhM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o=
go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -188,8 +192,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -8,13 +8,22 @@ import (
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
migrate "github.com/rubenv/sql-migrate"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/bridges/otellogrus"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const tracingName = "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/database"
var (
//go:embed migrations/*.sql
var embedMigrations embed.FS
var client *gorm.DB
embedMigrations embed.FS
client *gorm.DB
tracer trace.Tracer
logger *log.Logger
)
func Connect(_ context.Context, config models.DatabaseConfig) error {
var localSSL string
@ -32,12 +41,14 @@ func Connect(_ context.Context, config models.DatabaseConfig) error {
return err
}
client = sqlDB
err = migrateDatabase(sqlDB, config)
if err != nil {
return err
}
setupTelemetry()
client = sqlDB
return nil
}
@ -68,6 +79,14 @@ func migrateDatabase(dbPool *gorm.DB, config models.DatabaseConfig) error {
return nil
}
func setupTelemetry() {
tracer = otel.Tracer(tracingName)
hook := otellogrus.NewHook(tracingName)
logger.AddHook(hook)
}
func GetGorm(ctx context.Context) (*gorm.DB, error) {
if client == nil {
return nil, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}

View File

@ -5,12 +5,25 @@ import (
"errors"
otterError "git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/error"
"git.anthrove.art/Anthrove/otter-space-sdk/v2/pkg/models"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute"
"gorm.io/gorm"
)
func CreateTag(ctx context.Context, tagName models.TagName, tagType models.TagType) (models.Tag, error) {
ctx, span := tracer.Start(ctx, "CreateTag")
defer span.End()
span.SetAttributes(
attribute.String("tag_name", string(tagName)),
attribute.String("tag_type", string(tagType)),
)
span.AddEvent("Starting tag creation")
if client == nil {
logger.WithContext(ctx).Error(otterError.DatabaseIsNotConnected)
span.RecordError(&otterError.Database{Reason: otterError.DatabaseIsNotConnected})
return models.Tag{}, &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
@ -19,63 +32,142 @@ func CreateTag(ctx context.Context, tagName models.TagName, tagType models.TagTy
Type: tagType,
}
logger.WithContext(ctx).WithFields(log.Fields{
"name": tagName,
"type": tagType,
}).Debug("attempting to create tag")
result := client.WithContext(ctx).Create(&tag)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
logger.WithContext(ctx).WithFields(log.Fields{
"name": tagName,
"type": tagType,
}).Error(otterError.DuplicateKey)
span.RecordError(&otterError.Database{Reason: otterError.DuplicateKey})
return models.Tag{}, &otterError.Database{Reason: otterError.DuplicateKey}
}
logger.WithContext(ctx).WithFields(log.Fields{
"name": tagName,
"type": tagType,
}).Error(result.Error)
span.RecordError(result.Error)
return models.Tag{}, result.Error
}
logger.WithContext(ctx).WithFields(log.Fields{
"name": tagName,
"type": tagType,
}).Debug("tag created")
span.AddEvent("Tag created successfully")
return tag, nil
}
func CreateTagInBatch(ctx context.Context, tags []models.Tag, batchSize int) error {
ctx, span := tracer.Start(ctx, "CreateTagInBatch")
defer span.End()
span.SetAttributes(
attribute.Int64("batch_size", int64(batchSize)),
attribute.Int64("tag_count", int64(len(tags))),
)
span.AddEvent("Starting batch tag creation")
if client == nil {
logger.WithContext(ctx).Error(otterError.DatabaseIsNotConnected)
span.RecordError(&otterError.Database{Reason: otterError.DatabaseIsNotConnected})
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if tags == nil {
return &otterError.EntityValidationFailed{Reason: otterError.TagListIsEmpty}
}
if len(tags) == 0 {
if tags == nil || len(tags) == 0 {
logger.WithContext(ctx).Error(otterError.TagListIsEmpty)
span.RecordError(&otterError.EntityValidationFailed{Reason: otterError.TagListIsEmpty})
return &otterError.EntityValidationFailed{Reason: otterError.TagListIsEmpty}
}
if batchSize == 0 {
logger.WithContext(ctx).Error(otterError.BatchSizeIsEmpty)
span.RecordError(&otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty})
return &otterError.EntityValidationFailed{Reason: otterError.BatchSizeIsEmpty}
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_length": len(tags),
}).Debug("attempting to create tags")
result := client.WithContext(ctx).CreateInBatches(&tags, batchSize)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
logger.WithContext(ctx).WithFields(log.Fields{
"tag_length": len(tags),
}).Error(otterError.DuplicateKey)
span.RecordError(&otterError.Database{Reason: otterError.DuplicateKey})
return &otterError.Database{Reason: otterError.DuplicateKey}
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_length": len(tags),
}).Error(result.Error)
span.RecordError(result.Error)
return result.Error
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_length": len(tags),
}).Debug("tags created")
span.AddEvent("Batch tags created successfully")
return nil
}
func DeleteTag(ctx context.Context, tagName models.TagName) error {
ctx, span := tracer.Start(ctx, "DeleteTag")
defer span.End()
span.SetAttributes(
attribute.String("tag_name", string(tagName)),
)
span.AddEvent("Starting tag deletion")
var tag models.Tag
if client == nil {
logger.WithContext(ctx).Error(otterError.DatabaseIsNotConnected)
span.RecordError(&otterError.Database{Reason: otterError.DatabaseIsNotConnected})
return &otterError.Database{Reason: otterError.DatabaseIsNotConnected}
}
if len(tagName) == 0 {
logger.WithContext(ctx).Error(otterError.TagNameIsEmpty)
span.RecordError(&otterError.EntityValidationFailed{Reason: otterError.TagNameIsEmpty})
return &otterError.EntityValidationFailed{Reason: otterError.TagNameIsEmpty}
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_name": tagName,
}).Debug("attempting to delete tag")
result := client.WithContext(ctx).Delete(&tag, tagName)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
span.RecordError(&otterError.Database{Reason: otterError.NoDataFound})
return &otterError.Database{Reason: otterError.NoDataFound}
}
span.RecordError(result.Error)
return result.Error
}
logger.WithContext(ctx).WithFields(log.Fields{
"tag_name": tagName,
}).Debug("tag deleted")
span.AddEvent("Tag deleted successfully")
return nil
}