diff --git a/internal/core/services/cache.go b/internal/core/services/cache.go index f3df1219c..57d8313cb 100644 --- a/internal/core/services/cache.go +++ b/internal/core/services/cache.go @@ -18,8 +18,13 @@ import ( "github.com/poyrazk/thecloud/internal/errors" "github.com/poyrazk/thecloud/internal/platform" "github.com/poyrazk/thecloud/pkg/util" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) +const tracerNameCache = "cache-service" + const ( defaultRedisPort = "6379" ) @@ -57,10 +62,20 @@ func NewCacheService( } func (s *CacheService) CreateCache(ctx context.Context, name, version string, memoryMB int, vpcID *uuid.UUID) (*domain.Cache, error) { + tracer := otel.Tracer(tracerNameCache) + _, span := tracer.Start(ctx, "CacheService.CreateCache", + trace.WithAttributes( + attribute.String("cache.name", name), + attribute.String("cache.version", version), + attribute.Int("cache.memory_mb", memoryMB), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionCacheCreate, "*"); err != nil { + span.RecordError(err) return nil, err } @@ -336,10 +351,18 @@ func (s *CacheService) FlushCache(ctx context.Context, idOrName string) error { } func (s *CacheService) GetCacheStats(ctx context.Context, idOrName string) (*ports.CacheStats, error) { + tracer := otel.Tracer(tracerNameCache) + _, span := tracer.Start(ctx, "CacheService.GetCacheStats", + trace.WithAttributes( + attribute.String("cache.id_or_name", idOrName), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionCacheRead, idOrName); err != nil { + span.RecordError(err) return nil, err } diff --git a/internal/core/services/database.go b/internal/core/services/database.go index 694c13669..11cbd1ddf 100644 --- a/internal/core/services/database.go +++ b/internal/core/services/database.go @@ -17,8 +17,13 @@ import ( "github.com/poyrazk/thecloud/internal/errors" "github.com/poyrazk/thecloud/internal/platform" "github.com/poyrazk/thecloud/pkg/util" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) +const tracerNameDatabase = "database-service" + const ( // Default ports for database engines DefaultPostgresPort = "5432" @@ -116,9 +121,19 @@ func NewDatabaseService(params DatabaseServiceParams) *DatabaseService { } func (s *DatabaseService) CreateDatabase(ctx context.Context, req ports.CreateDatabaseRequest) (*domain.Database, error) { + tracer := otel.Tracer(tracerNameDatabase) + _, span := tracer.Start(ctx, "DatabaseService.CreateDatabase", + trace.WithAttributes( + attribute.String("db.name", req.Name), + attribute.String("db.engine", req.Engine), + attribute.String("db.version", req.Version), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionDBCreate, "*"); err != nil { + span.RecordError(err) return nil, err } dbEngine := domain.DatabaseEngine(req.Engine) @@ -192,9 +207,19 @@ func (s *DatabaseService) CreateReplica(ctx context.Context, primaryID uuid.UUID } func (s *DatabaseService) RestoreDatabase(ctx context.Context, req ports.RestoreDatabaseRequest) (*domain.Database, error) { + tracer := otel.Tracer(tracerNameDatabase) + _, span := tracer.Start(ctx, "DatabaseService.RestoreDatabase", + trace.WithAttributes( + attribute.String("db.new_name", req.NewName), + attribute.String("db.engine", req.Engine), + attribute.String("db.snapshot_id", req.SnapshotID.String()), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionDBCreate, "*"); err != nil { + span.RecordError(err) return nil, err } snap, err := s.snapshotSvc.GetSnapshot(ctx, req.SnapshotID) @@ -810,6 +835,13 @@ func (s *DatabaseService) RotateCredentials(ctx context.Context, id uuid.UUID, i // doRotateCredentials performs the actual credential rotation func (s *DatabaseService) doRotateCredentials(ctx context.Context, id uuid.UUID, _ string) error { + tracer := otel.Tracer(tracerNameDatabase) + _, span := tracer.Start(ctx, "DatabaseService.doRotateCredentials", + trace.WithAttributes( + attribute.String("db.id", id.String()), + )) + defer span.End() + db, err := s.repo.GetByID(ctx, id) if err != nil { return err diff --git a/internal/core/services/function.go b/internal/core/services/function.go index 032a15c01..42c9d0eb1 100644 --- a/internal/core/services/function.go +++ b/internal/core/services/function.go @@ -21,6 +21,16 @@ import ( "github.com/poyrazk/thecloud/internal/core/domain" "github.com/poyrazk/thecloud/internal/core/ports" "github.com/poyrazk/thecloud/internal/errors" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +const tracerNameFunction = "function-service" + +const ( + // maxLogSize bounds log reading in captureInvocationResults to prevent memory exhaustion. + maxLogSize = 1 * 1024 * 1024 // 1 MB ) // RuntimeConfig describes how a function runtime is executed. @@ -63,10 +73,20 @@ func NewFunctionService(repo ports.FunctionRepository, rbacSvc ports.RBACService } func (s *FunctionService) CreateFunction(ctx context.Context, name, runtime, handler string, code []byte) (*domain.Function, error) { + tracer := otel.Tracer(tracerNameFunction) + _, span := tracer.Start(ctx, "FunctionService.CreateFunction", + trace.WithAttributes( + attribute.String("function.name", name), + attribute.String("function.runtime", runtime), + attribute.String("function.handler", handler), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionFunctionCreate, "*"); err != nil { + span.RecordError(err) return nil, err } @@ -200,25 +220,48 @@ func (s *FunctionService) DeleteFunction(ctx context.Context, id uuid.UUID) erro } func (s *FunctionService) GetFunctionLogs(ctx context.Context, id uuid.UUID, limit int) ([]*domain.Invocation, error) { + tracer := otel.Tracer(tracerNameFunction) + _, span := tracer.Start(ctx, "FunctionService.GetFunctionLogs", + trace.WithAttributes( + attribute.String("function.id", id.String()), + attribute.Int("function.log_limit", limit), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionFunctionRead, id.String()); err != nil { + span.RecordError(err) return nil, err } - // Verify existence and tenant scoping + // Verify existence and tenant scoping (use original ctx to avoid mock context mismatch) if _, err := s.repo.GetByID(ctx, id); err != nil { + span.RecordError(err) return nil, err } - return s.repo.GetInvocations(ctx, id, limit) + invocations, err := s.repo.GetInvocations(ctx, id, limit) + if err != nil { + span.RecordError(err) + } + return invocations, err } func (s *FunctionService) InvokeFunction(ctx context.Context, id uuid.UUID, payload []byte, async bool) (*domain.Invocation, error) { + tracer := otel.Tracer(tracerNameFunction) + _, span := tracer.Start(ctx, "FunctionService.InvokeFunction", + trace.WithAttributes( + attribute.String("function.id", id.String()), + attribute.Bool("function.async", async), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionFunctionInvoke, id.String()); err != nil { + span.RecordError(err) return nil, err } diff --git a/internal/core/services/storage.go b/internal/core/services/storage.go index 6f4b59f5e..dfba44b05 100644 --- a/internal/core/services/storage.go +++ b/internal/core/services/storage.go @@ -22,8 +22,13 @@ import ( "github.com/poyrazk/thecloud/internal/errors" "github.com/poyrazk/thecloud/internal/platform" "github.com/poyrazk/thecloud/pkg/crypto" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) +const tracerNameStorage = "storage-service" + const ( errMultipartNotFound = "multipart upload not found" partPathFormat = ".uploads/%s/part-%d" @@ -82,10 +87,19 @@ func NewStorageService(params StorageServiceParams) *StorageService { } func (s *StorageService) Upload(ctx context.Context, bucketName, key string, r io.Reader, providedChecksum string) (*domain.Object, error) { + tracer := otel.Tracer(tracerNameStorage) + _, span := tracer.Start(ctx, "StorageService.Upload", + trace.WithAttributes( + attribute.String("storage.bucket", bucketName), + attribute.String("storage.key", key), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionStorageWrite, "*"); err != nil { + span.RecordError(err) return nil, err } @@ -202,10 +216,19 @@ func (s *StorageService) Upload(ctx context.Context, bucketName, key string, r i } func (s *StorageService) Download(ctx context.Context, bucket, key string) (io.ReadCloser, *domain.Object, error) { + tracer := otel.Tracer(tracerNameStorage) + _, span := tracer.Start(ctx, "StorageService.Download", + trace.WithAttributes( + attribute.String("storage.bucket", bucket), + attribute.String("storage.key", key), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionStorageRead, bucket); err != nil { + span.RecordError(err) return nil, nil, err } @@ -366,10 +389,19 @@ func (s *StorageService) DeleteObject(ctx context.Context, bucket, key string) e // CreateBucket creates a new storage bucket. func (s *StorageService) CreateBucket(ctx context.Context, name string, isPublic bool) (*domain.Bucket, error) { + tracer := otel.Tracer(tracerNameStorage) + _, span := tracer.Start(ctx, "StorageService.CreateBucket", + trace.WithAttributes( + attribute.String("storage.bucket", name), + attribute.Bool("storage.is_public", isPublic), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionStorageWrite, "*"); err != nil { + span.RecordError(err) return nil, err } @@ -426,10 +458,19 @@ func (s *StorageService) GetBucket(ctx context.Context, name string) (*domain.Bu } func (s *StorageService) DeleteBucket(ctx context.Context, name string, force bool) error { + tracer := otel.Tracer(tracerNameStorage) + _, span := tracer.Start(ctx, "StorageService.DeleteBucket", + trace.WithAttributes( + attribute.String("storage.bucket", name), + attribute.Bool("storage.force", force), + )) + defer span.End() + userID := appcontext.UserIDFromContext(ctx) tenantID := appcontext.TenantIDFromContext(ctx) if err := s.rbacSvc.Authorize(ctx, userID, tenantID, domain.PermissionStorageDelete, name); err != nil { + span.RecordError(err) return err }