🔧 feat: add OpenTelemetry instrumentation to persistence layer
- Added persistence telemetry configuration option (telemetry.persistence.enabled) - Created PersistenceTelemetryConfig struct for fine-grained control - Added GetPersistenceTelemetryEnabled() helper method - Implemented telemetry span creation in SQLite repository - Added OpenTelemetry instrumentation to key repository methods: - CreateUser: Tracks user creation with error recording - GetUserByUsername: Tracks queries with username attribute - Maintained backward compatibility - telemetry is optional and disabled by default - Updated all tests to pass config parameter to repository constructor - Added proper error recording and span attributes for observability Benefits: - Performance monitoring of database operations - Flamegraph generation capability for persistence layer - Distributed tracing across service boundaries - Configurable instrumentation for production vs development Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -9,6 +9,10 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"dance-lessons-coach/pkg/config"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
@@ -16,14 +20,18 @@ import (
|
||||
|
||||
// SQLiteRepository implements UserRepository using SQLite
|
||||
type SQLiteRepository struct {
|
||||
db *gorm.DB
|
||||
dbPath string
|
||||
db *gorm.DB
|
||||
dbPath string
|
||||
config *config.Config
|
||||
spanPrefix string
|
||||
}
|
||||
|
||||
// NewSQLiteRepository creates a new SQLite repository
|
||||
func NewSQLiteRepository(dbPath string) (*SQLiteRepository, error) {
|
||||
func NewSQLiteRepository(dbPath string, config *config.Config) (*SQLiteRepository, error) {
|
||||
repo := &SQLiteRepository{
|
||||
dbPath: dbPath,
|
||||
dbPath: dbPath,
|
||||
config: config,
|
||||
spanPrefix: "user.repo.",
|
||||
}
|
||||
|
||||
if err := repo.initializeDatabase(); err != nil {
|
||||
@@ -70,8 +78,17 @@ func (r *SQLiteRepository) initializeDatabase() error {
|
||||
|
||||
// CreateUser creates a new user in the database
|
||||
func (r *SQLiteRepository) CreateUser(ctx context.Context, user *User) error {
|
||||
// Create telemetry span
|
||||
ctx, span := r.createSpan(ctx, "create_user")
|
||||
if span != nil {
|
||||
defer span.End()
|
||||
}
|
||||
|
||||
result := r.db.WithContext(ctx).Create(user)
|
||||
if result.Error != nil {
|
||||
if span != nil {
|
||||
span.RecordError(result.Error)
|
||||
}
|
||||
return fmt.Errorf("failed to create user: %w", result.Error)
|
||||
}
|
||||
return nil
|
||||
@@ -79,12 +96,22 @@ func (r *SQLiteRepository) CreateUser(ctx context.Context, user *User) error {
|
||||
|
||||
// GetUserByUsername retrieves a user by username
|
||||
func (r *SQLiteRepository) GetUserByUsername(ctx context.Context, username string) (*User, error) {
|
||||
// Create telemetry span
|
||||
ctx, span := r.createSpan(ctx, "get_user_by_username")
|
||||
if span != nil {
|
||||
defer span.End()
|
||||
span.SetAttributes(attribute.String("username", username))
|
||||
}
|
||||
|
||||
var user User
|
||||
result := r.db.WithContext(ctx).Where("username = ?", username).First(&user)
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if span != nil {
|
||||
span.RecordError(result.Error)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get user by username: %w", result.Error)
|
||||
}
|
||||
return &user, nil
|
||||
@@ -172,3 +199,15 @@ func (r *SQLiteRepository) Close() error {
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
|
||||
// createSpan creates a new telemetry span if persistence telemetry is enabled
|
||||
func (r *SQLiteRepository) createSpan(ctx context.Context, operation string) (context.Context, trace.Span) {
|
||||
if r.config == nil || !r.config.GetPersistenceTelemetryEnabled() {
|
||||
return ctx, trace.SpanFromContext(ctx)
|
||||
}
|
||||
|
||||
// Create a new span with the operation name
|
||||
spanName := r.spanPrefix + operation
|
||||
tr := otel.Tracer("user-repository")
|
||||
return tr.Start(ctx, spanName)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user