FEAUTRE (logging): Move logger to DI in services and models instead of globlal variables

This commit is contained in:
Rostislav Dugin
2025-06-22 22:11:59 +03:00
parent 01310c4434
commit 39934fbe47
36 changed files with 238 additions and 154 deletions

View File

@@ -14,13 +14,13 @@ require (
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9
github.com/minio/minio-go/v7 v7.0.92
github.com/shirou/gopsutil/v4 v4.25.5
github.com/stretchr/testify v1.10.0
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/swaggo/swag v1.16.4
github.com/testcontainers/testcontainers-go v0.37.0
golang.org/x/crypto v0.39.0
golang.org/x/sys v0.33.0
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.26.1
)
@@ -101,8 +101,6 @@ require (
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shirou/gopsutil/v4 v4.25.5 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tinylib/msgp v1.3.0 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
@@ -120,6 +118,7 @@ require (
golang.org/x/arch v0.17.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.33.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect

View File

@@ -213,8 +213,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=

View File

@@ -111,7 +111,7 @@ func loadEnvVariables() {
log.Info("ENV_MODE loaded", "mode", env.EnvMode)
env.PostgresesInstallDir = filepath.Join(backendRoot, "tools", "postgresql")
tools.VerifyPostgresesInstallation(env.EnvMode, env.PostgresesInstallDir)
tools.VerifyPostgresesInstallation(log, env.EnvMode, env.PostgresesInstallDir)
// Store the data and temp folders one level below the root
// (projectRoot/postgresus-data -> /postgresus-data)

View File

@@ -1,10 +1,10 @@
package backups
import (
"log/slog"
"postgresus-backend/internal/config"
"postgresus-backend/internal/features/databases"
"postgresus-backend/internal/features/storages"
"postgresus-backend/internal/util/logger"
"time"
)
@@ -15,15 +15,14 @@ type BackupBackgroundService struct {
storageService *storages.StorageService
lastBackupTime time.Time
logger *slog.Logger
}
var log = logger.GetLogger()
func (s *BackupBackgroundService) Run() {
s.lastBackupTime = time.Now().UTC()
if err := s.failBackupsInProgress(); err != nil {
log.Error("Failed to fail backups in progress", "error", err)
s.logger.Error("Failed to fail backups in progress", "error", err)
panic(err)
}
@@ -37,11 +36,11 @@ func (s *BackupBackgroundService) Run() {
}
if err := s.cleanOldBackups(); err != nil {
log.Error("Failed to clean old backups", "error", err)
s.logger.Error("Failed to clean old backups", "error", err)
}
if err := s.runPendingBackups(); err != nil {
log.Error("Failed to run pending backups", "error", err)
s.logger.Error("Failed to run pending backups", "error", err)
}
s.lastBackupTime = time.Now().UTC()
@@ -102,7 +101,7 @@ func (s *BackupBackgroundService) cleanOldBackups() error {
dateBeforeBackupsShouldBeDeleted,
)
if err != nil {
log.Error(
s.logger.Error(
"Failed to find old backups for database",
"databaseId",
database.ID,
@@ -115,7 +114,7 @@ func (s *BackupBackgroundService) cleanOldBackups() error {
for _, backup := range oldBackups {
storage, err := s.storageService.GetStorageByID(backup.StorageID)
if err != nil {
log.Error(
s.logger.Error(
"Failed to get storage by ID",
"storageId",
backup.StorageID,
@@ -125,14 +124,18 @@ func (s *BackupBackgroundService) cleanOldBackups() error {
continue
}
storage.DeleteFile(backup.ID)
if err := s.backupRepository.DeleteByID(backup.ID); err != nil {
log.Error("Failed to delete old backup", "backupId", backup.ID, "error", err)
err = storage.DeleteFile(backup.ID)
if err != nil {
s.logger.Error("Failed to delete backup file", "backupId", backup.ID, "error", err)
continue
}
log.Info("Deleted old backup", "backupId", backup.ID, "databaseId", database.ID)
if err := s.backupRepository.DeleteByID(backup.ID); err != nil {
s.logger.Error("Failed to delete old backup", "backupId", backup.ID, "error", err)
continue
}
s.logger.Info("Deleted old backup", "backupId", backup.ID, "databaseId", database.ID)
}
}
@@ -152,7 +155,7 @@ func (s *BackupBackgroundService) runPendingBackups() error {
lastBackup, err := s.backupRepository.FindLastByDatabaseID(database.ID)
if err != nil {
log.Error(
s.logger.Error(
"Failed to get last backup for database",
"databaseId",
database.ID,
@@ -168,7 +171,7 @@ func (s *BackupBackgroundService) runPendingBackups() error {
}
if database.BackupInterval.ShouldTriggerBackup(time.Now().UTC(), lastBackupTime) {
log.Info(
s.logger.Info(
"Triggering scheduled backup",
"databaseId",
database.ID,
@@ -177,7 +180,7 @@ func (s *BackupBackgroundService) runPendingBackups() error {
)
go s.backupService.MakeBackup(database.ID)
log.Info("Successfully triggered scheduled backup", "databaseId", database.ID)
s.logger.Info("Successfully triggered scheduled backup", "databaseId", database.ID)
}
}

View File

@@ -2,25 +2,22 @@ package backups
import (
"postgresus-backend/internal/features/backups/usecases"
usecases_postgresql "postgresus-backend/internal/features/backups/usecases/postgresql"
"postgresus-backend/internal/features/databases"
"postgresus-backend/internal/features/notifiers"
"postgresus-backend/internal/features/storages"
"postgresus-backend/internal/features/users"
"postgresus-backend/internal/util/logger"
"time"
)
var createPostgresqlBackupUsecase = &usecases_postgresql.CreatePostgresqlBackupUsecase{}
var createBackupUseCase = &usecases.CreateBackupUsecase{
CreatePostgresqlBackupUsecase: createPostgresqlBackupUsecase,
}
var backupRepository = &BackupRepository{}
var backupService = &BackupService{
databases.GetDatabaseService(),
storages.GetStorageService(),
backupRepository,
notifiers.GetNotifierService(),
createBackupUseCase,
usecases.GetCreateBackupUsecase(),
logger.GetLogger(),
}
var backupBackgroundService = &BackupBackgroundService{
@@ -29,6 +26,7 @@ var backupBackgroundService = &BackupBackgroundService{
databases.GetDatabaseService(),
storages.GetStorageService(),
time.Now().UTC(),
logger.GetLogger(),
}
var backupController = &BackupController{

View File

@@ -1,6 +1,7 @@
package backups
import (
"log/slog"
"postgresus-backend/internal/features/databases"
"postgresus-backend/internal/features/storages"
"time"
@@ -27,14 +28,14 @@ type Backup struct {
CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"`
}
func (b *Backup) DeleteBackupFromStorage() {
func (b *Backup) DeleteBackupFromStorage(logger *slog.Logger) {
if b.Status != BackupStatusCompleted {
return
}
err := b.Storage.DeleteFile(b.ID)
if err != nil {
log.Error("Failed to delete backup from storage", "error", err)
logger.Error("Failed to delete backup from storage", "error", err)
// we ignore the error, because the access to the storage
// may be lost, file already deleted, etc.
}

View File

@@ -3,6 +3,7 @@ package backups
import (
"errors"
"fmt"
"log/slog"
"postgresus-backend/internal/features/backups/usecases"
"postgresus-backend/internal/features/databases"
"postgresus-backend/internal/features/notifiers"
@@ -21,6 +22,8 @@ type BackupService struct {
notifierService *notifiers.NotifierService
createBackupUseCase *usecases.CreateBackupUsecase
logger *slog.Logger
}
func (s *BackupService) OnBeforeDbStorageChange(
@@ -53,7 +56,7 @@ func (s *BackupService) OnBeforeDbStorageChange(
if err := backup.Storage.DeleteFile(backup.ID); err != nil {
// most likely we cannot do nothing with this,
// so we just remove the backup model
log.Error("Failed to delete backup file", "error", err)
s.logger.Error("Failed to delete backup file", "error", err)
}
if err := s.backupRepository.DeleteByID(backup.ID); err != nil {
@@ -125,7 +128,7 @@ func (s *BackupService) DeleteBackup(
return errors.New("backup is in progress")
}
backup.DeleteBackupFromStorage()
backup.DeleteBackupFromStorage(s.logger)
backup.Status = BackupStatusDeleted
return s.backupRepository.Save(backup)
@@ -134,24 +137,24 @@ func (s *BackupService) DeleteBackup(
func (s *BackupService) MakeBackup(databaseID uuid.UUID) {
database, err := s.databaseService.GetDatabaseByID(databaseID)
if err != nil {
log.Error("Failed to get database by ID", "error", err)
s.logger.Error("Failed to get database by ID", "error", err)
return
}
lastBackup, err := s.backupRepository.FindLastByDatabaseID(databaseID)
if err != nil {
log.Error("Failed to find last backup by database ID", "error", err)
s.logger.Error("Failed to find last backup by database ID", "error", err)
return
}
if lastBackup != nil && lastBackup.Status == BackupStatusInProgress {
log.Error("Backup is in progress")
s.logger.Error("Backup is in progress")
return
}
storage, err := s.storageService.GetStorageByID(database.StorageID)
if err != nil {
log.Error("Failed to get storage by ID", "error", err)
s.logger.Error("Failed to get storage by ID", "error", err)
return
}
@@ -170,7 +173,7 @@ func (s *BackupService) MakeBackup(databaseID uuid.UUID) {
}
if err := s.backupRepository.Save(backup); err != nil {
log.Error("Failed to save backup", "error", err)
s.logger.Error("Failed to save backup", "error", err)
return
}
@@ -183,7 +186,7 @@ func (s *BackupService) MakeBackup(databaseID uuid.UUID) {
backup.BackupDurationMs = time.Since(start).Milliseconds()
if err := s.backupRepository.Save(backup); err != nil {
log.Error("Failed to update backup progress", "error", err)
s.logger.Error("Failed to update backup progress", "error", err)
}
}
@@ -201,7 +204,7 @@ func (s *BackupService) MakeBackup(databaseID uuid.UUID) {
backup.BackupSizeMb = 0
if updateErr := s.databaseService.SetBackupError(databaseID, errMsg); updateErr != nil {
log.Error(
s.logger.Error(
"Failed to update database last backup time",
"databaseId",
databaseID,
@@ -211,7 +214,7 @@ func (s *BackupService) MakeBackup(databaseID uuid.UUID) {
}
if err := s.backupRepository.Save(backup); err != nil {
log.Error("Failed to save backup", "error", err)
s.logger.Error("Failed to save backup", "error", err)
}
s.SendBackupNotification(
@@ -228,14 +231,14 @@ func (s *BackupService) MakeBackup(databaseID uuid.UUID) {
backup.BackupDurationMs = time.Since(start).Milliseconds()
if err := s.backupRepository.Save(backup); err != nil {
log.Error("Failed to save backup", "error", err)
s.logger.Error("Failed to save backup", "error", err)
return
}
// Update database last backup time
now := time.Now().UTC()
if updateErr := s.databaseService.SetLastBackupTime(databaseID, now); updateErr != nil {
log.Error(
s.logger.Error(
"Failed to update database last backup time",
"databaseId",
databaseID,

View File

@@ -0,0 +1,13 @@
package usecases
import (
usecases_postgresql "postgresus-backend/internal/features/backups/usecases/postgresql"
)
var createBackupUsecase = &CreateBackupUsecase{
usecases_postgresql.GetCreatePostgresqlBackupUsecase(),
}
func GetCreateBackupUsecase() *CreateBackupUsecase {
return createBackupUsecase
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"os"
"os/exec"
"path/filepath"
@@ -16,15 +17,14 @@ import (
"postgresus-backend/internal/features/databases"
pgtypes "postgresus-backend/internal/features/databases/databases/postgresql"
"postgresus-backend/internal/features/storages"
"postgresus-backend/internal/util/logger"
"postgresus-backend/internal/util/tools"
"github.com/google/uuid"
)
var log = logger.GetLogger()
type CreatePostgresqlBackupUsecase struct{}
type CreatePostgresqlBackupUsecase struct {
logger *slog.Logger
}
// Execute creates a backup of the database
func (uc *CreatePostgresqlBackupUsecase) Execute(
@@ -35,7 +35,7 @@ func (uc *CreatePostgresqlBackupUsecase) Execute(
completedMBs float64,
),
) error {
log.Info(
uc.logger.Info(
"Creating PostgreSQL backup via pg_dump custom format",
"databaseId",
db.ID,
@@ -85,7 +85,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
db *databases.Database,
backupProgressListener func(completedMBs float64),
) error {
log.Info("Streaming PostgreSQL backup to storage", "pgBin", pgBin, "args", args)
uc.logger.Info("Streaming PostgreSQL backup to storage", "pgBin", pgBin, "args", args)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute)
defer cancel()
@@ -126,7 +126,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
// Verify .pgpass file was created correctly
if info, err := os.Stat(pgpassFile); err == nil {
log.Info("Temporary .pgpass file created successfully",
uc.logger.Info("Temporary .pgpass file created successfully",
"pgpassFile", pgpassFile,
"size", info.Size(),
"mode", info.Mode(),
@@ -136,17 +136,17 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
}
cmd := exec.CommandContext(ctx, pgBin, args...)
log.Info("Executing PostgreSQL backup command", "command", cmd.String())
uc.logger.Info("Executing PostgreSQL backup command", "command", cmd.String())
// Start with system environment variables to preserve Windows PATH, SystemRoot, etc.
cmd.Env = os.Environ()
// Use the .pgpass file for authentication
cmd.Env = append(cmd.Env, "PGPASSFILE="+pgpassFile)
log.Info("Using temporary .pgpass file for authentication", "pgpassFile", pgpassFile)
uc.logger.Info("Using temporary .pgpass file for authentication", "pgpassFile", pgpassFile)
// Debug password setup (without exposing the actual password)
log.Info("Setting up PostgreSQL environment",
uc.logger.Info("Setting up PostgreSQL environment",
"passwordLength", len(password),
"passwordEmpty", password == "",
"pgBin", pgBin,
@@ -170,11 +170,11 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
// Require SSL when explicitly configured
if shouldRequireSSL {
cmd.Env = append(cmd.Env, "PGSSLMODE=require")
log.Info("Using required SSL mode", "configuredHttps", db.Postgresql.IsHttps)
uc.logger.Info("Using required SSL mode", "configuredHttps", db.Postgresql.IsHttps)
} else {
// SSL not explicitly required, but prefer it if available
cmd.Env = append(cmd.Env, "PGSSLMODE=prefer")
log.Info("Using preferred SSL mode", "configuredHttps", db.Postgresql.IsHttps)
uc.logger.Info("Using preferred SSL mode", "configuredHttps", db.Postgresql.IsHttps)
}
// Set other SSL parameters to avoid certificate issues
@@ -220,7 +220,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
// Start streaming into storage in its own goroutine
saveErrCh := make(chan error, 1)
go func() {
saveErrCh <- storage.SaveFile(backupID, storageReader)
saveErrCh <- storage.SaveFile(uc.logger, backupID, storageReader)
}()
// Start pg_dump
@@ -251,7 +251,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
if config.IsShouldShutdown() {
if pipeWriter, ok := countingWriter.writer.(*io.PipeWriter); ok {
if err := pipeWriter.Close(); err != nil {
log.Error("Failed to close counting writer", "error", err)
uc.logger.Error("Failed to close counting writer", "error", err)
}
}
@@ -262,7 +262,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
// Close the pipe writer to signal end of data
if pipeWriter, ok := countingWriter.writer.(*io.PipeWriter); ok {
if err := pipeWriter.Close(); err != nil {
log.Error("Failed to close counting writer", "error", err)
uc.logger.Error("Failed to close counting writer", "error", err)
}
}
@@ -297,7 +297,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
// Enhanced debugging for exit status 1 with empty stderr
if exitCode == 1 && strings.TrimSpace(stderrStr) == "" {
log.Error("pg_dump failed with exit status 1 but no stderr output",
uc.logger.Error("pg_dump failed with exit status 1 but no stderr output",
"pgBin", pgBin,
"args", args,
"env_vars", []string{
@@ -323,7 +323,7 @@ func (uc *CreatePostgresqlBackupUsecase) streamToStorage(
strings.Join(args, " "),
)
} else if exitCode == -1073741819 { // 0xC0000005 in decimal
log.Error("PostgreSQL tool crashed with access violation",
uc.logger.Error("PostgreSQL tool crashed with access violation",
"pgBin", pgBin,
"args", args,
"exitCode", fmt.Sprintf("0x%X", uint32(exitCode)),

View File

@@ -0,0 +1,13 @@
package usecases_postgresql
import (
"postgresus-backend/internal/util/logger"
)
var createPostgresqlBackupUsecase = &CreatePostgresqlBackupUsecase{
logger.GetLogger(),
}
func GetCreatePostgresqlBackupUsecase() *CreatePostgresqlBackupUsecase {
return createPostgresqlBackupUsecase
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"errors"
"fmt"
"postgresus-backend/internal/util/logger"
"log/slog"
"postgresus-backend/internal/util/tools"
"time"
@@ -12,8 +12,6 @@ import (
"github.com/jackc/pgx/v5"
)
var log = logger.GetLogger()
type PostgresqlDatabase struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:gen_random_uuid()"`
@@ -61,15 +59,19 @@ func (p *PostgresqlDatabase) Validate() error {
return nil
}
func (p *PostgresqlDatabase) TestConnection() error {
func (p *PostgresqlDatabase) TestConnection(logger *slog.Logger) error {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
return testSingleDatabaseConnection(ctx, p)
return testSingleDatabaseConnection(logger, ctx, p)
}
// testSingleDatabaseConnection tests connection to a specific database for pg_dump
func testSingleDatabaseConnection(ctx context.Context, p *PostgresqlDatabase) error {
func testSingleDatabaseConnection(
logger *slog.Logger,
ctx context.Context,
p *PostgresqlDatabase,
) error {
// For single database backup, we need to connect to the specific database
if p.Database == nil || *p.Database == "" {
return errors.New("database name is required for single database backup (pg_dump)")
@@ -89,7 +91,7 @@ func testSingleDatabaseConnection(ctx context.Context, p *PostgresqlDatabase) er
}
defer func() {
if closeErr := conn.Close(ctx); closeErr != nil {
log.Error("Failed to close connection", "error", closeErr)
logger.Error("Failed to close connection", "error", closeErr)
}
}()

View File

@@ -1,12 +1,16 @@
package databases
import "postgresus-backend/internal/features/users"
import (
"postgresus-backend/internal/features/users"
"postgresus-backend/internal/util/logger"
)
var databaseRepository = &DatabaseRepository{}
var databaseService = &DatabaseService{
databaseRepository,
nil,
logger.GetLogger(),
}
var databaseController = &DatabaseController{

View File

@@ -1,13 +1,17 @@
package databases
import "github.com/google/uuid"
import (
"log/slog"
"github.com/google/uuid"
)
type DatabaseValidator interface {
Validate() error
}
type DatabaseConnector interface {
TestConnection() error
TestConnection(logger *slog.Logger) error
}
type DatabaseStorageChangeListener interface {

View File

@@ -2,6 +2,7 @@ package databases
import (
"errors"
"log/slog"
"postgresus-backend/internal/features/databases/databases/postgresql"
"postgresus-backend/internal/features/intervals"
"postgresus-backend/internal/features/notifiers"
@@ -102,8 +103,8 @@ func (d *Database) ValidateUpdate(old, new Database) error {
return nil
}
func (d *Database) TestConnection() error {
return d.getSpecificDatabase().TestConnection()
func (d *Database) TestConnection(logger *slog.Logger) error {
return d.getSpecificDatabase().TestConnection(logger)
}
func (d *Database) getSpecificDatabase() DatabaseConnector {

View File

@@ -2,6 +2,7 @@ package databases
import (
"errors"
"log/slog"
users_models "postgresus-backend/internal/features/users/models"
"time"
@@ -11,6 +12,7 @@ import (
type DatabaseService struct {
dbRepository *DatabaseRepository
dbStorageChangeListener DatabaseStorageChangeListener
logger *slog.Logger
}
func (s *DatabaseService) SetDatabaseStorageChangeListener(
@@ -131,7 +133,7 @@ func (s *DatabaseService) TestDatabaseConnection(
return errors.New("you have not access to this database")
}
err = database.TestConnection()
err = database.TestConnection(s.logger)
if err != nil {
lastSaveError := err.Error()
database.LastBackupErrorMessage = &lastSaveError
@@ -146,7 +148,7 @@ func (s *DatabaseService) TestDatabaseConnection(
func (s *DatabaseService) TestDatabaseConnectionDirect(
database *Database,
) error {
return database.TestConnection()
return database.TestConnection(s.logger)
}
func (s *DatabaseService) GetDatabaseByID(

View File

@@ -1,10 +1,14 @@
package notifiers
import "postgresus-backend/internal/features/users"
import (
"postgresus-backend/internal/features/users"
"postgresus-backend/internal/util/logger"
)
var notifierRepository = &NotifierRepository{}
var notifierService = &NotifierService{
notifierRepository,
logger.GetLogger(),
}
var notifierController = &NotifierController{
notifierService,

View File

@@ -1,7 +1,9 @@
package notifiers
import "log/slog"
type NotificationSender interface {
Send(heading string, message string) error
Send(logger *slog.Logger, heading string, message string) error
Validate() error
}

View File

@@ -2,6 +2,7 @@ package notifiers
import (
"errors"
"log/slog"
"postgresus-backend/internal/features/notifiers/notifiers/email_notifier"
telegram_notifier "postgresus-backend/internal/features/notifiers/notifiers/telegram"
webhook_notifier "postgresus-backend/internal/features/notifiers/notifiers/webhook"
@@ -34,8 +35,8 @@ func (n *Notifier) Validate() error {
return n.getSpecificNotifier().Validate()
}
func (n *Notifier) Send(heading string, message string) error {
err := n.getSpecificNotifier().Send(heading, message)
func (n *Notifier) Send(logger *slog.Logger, heading string, message string) error {
err := n.getSpecificNotifier().Send(logger, heading, message)
if err != nil {
lastSendError := err.Error()

View File

@@ -4,6 +4,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net"
"net/smtp"
"time"
@@ -56,7 +57,7 @@ func (e *EmailNotifier) Validate() error {
return nil
}
func (e *EmailNotifier) Send(heading string, message string) error {
func (e *EmailNotifier) Send(logger *slog.Logger, heading string, message string) error {
// Compose email
from := e.SMTPUser
to := []string{e.TargetEmail}

View File

@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"strings"
@@ -33,7 +34,7 @@ func (t *TelegramNotifier) Validate() error {
return nil
}
func (t *TelegramNotifier) Send(heading string, message string) error {
func (t *TelegramNotifier) Send(logger *slog.Logger, heading string, message string) error {
fullMessage := heading
if message != "" {
fullMessage = fmt.Sprintf("%s\n\n%s", heading, message)

View File

@@ -6,15 +6,13 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"postgresus-backend/internal/util/logger"
"github.com/google/uuid"
)
var log = logger.GetLogger()
type WebhookNotifier struct {
NotifierID uuid.UUID `json:"notifierId" gorm:"primaryKey;column:notifier_id"`
WebhookURL string `json:"webhookUrl" gorm:"not null;column:webhook_url"`
@@ -37,7 +35,7 @@ func (t *WebhookNotifier) Validate() error {
return nil
}
func (t *WebhookNotifier) Send(heading string, message string) error {
func (t *WebhookNotifier) Send(logger *slog.Logger, heading string, message string) error {
switch t.WebhookMethod {
case WebhookMethodGET:
reqURL := fmt.Sprintf("%s?heading=%s&message=%s",
@@ -52,7 +50,7 @@ func (t *WebhookNotifier) Send(heading string, message string) error {
}
defer func() {
if cerr := resp.Body.Close(); cerr != nil {
log.Error("failed to close response body", "error", cerr)
logger.Error("failed to close response body", "error", cerr)
}
}()
@@ -85,7 +83,7 @@ func (t *WebhookNotifier) Send(heading string, message string) error {
defer func() {
if cerr := resp.Body.Close(); cerr != nil {
log.Error("failed to close response body", "error", cerr)
logger.Error("failed to close response body", "error", cerr)
}
}()

View File

@@ -2,16 +2,15 @@ package notifiers
import (
"errors"
"log/slog"
users_models "postgresus-backend/internal/features/users/models"
"postgresus-backend/internal/util/logger"
"github.com/google/uuid"
)
var log = logger.GetLogger()
type NotifierService struct {
notifierRepository *NotifierRepository
logger *slog.Logger
}
func (s *NotifierService) SaveNotifier(
@@ -87,7 +86,7 @@ func (s *NotifierService) SendTestNotification(
return errors.New("you have not access to this notifier")
}
err = notifier.Send("Test message", "This is a test message")
err = notifier.Send(s.logger, "Test message", "This is a test message")
if err != nil {
return err
}
@@ -102,7 +101,7 @@ func (s *NotifierService) SendTestNotification(
func (s *NotifierService) SendTestNotificationToNotifier(
notifier *Notifier,
) error {
return notifier.Send("Test message", "This is a test message")
return notifier.Send(s.logger, "Test message", "This is a test message")
}
func (s *NotifierService) SendNotification(
@@ -121,20 +120,20 @@ func (s *NotifierService) SendNotification(
return
}
err = notifiedFromDb.Send(title, message)
err = notifiedFromDb.Send(s.logger, title, message)
if err != nil {
errMsg := err.Error()
notifiedFromDb.LastSendError = &errMsg
err = s.notifierRepository.Save(notifiedFromDb)
if err != nil {
log.Error("Failed to save notifier", "error", err)
s.logger.Error("Failed to save notifier", "error", err)
}
}
notifiedFromDb.LastSendError = nil
err = s.notifierRepository.Save(notifiedFromDb)
if err != nil {
log.Error("Failed to save notifier", "error", err)
s.logger.Error("Failed to save notifier", "error", err)
}
}

View File

@@ -1,19 +1,18 @@
package restores
import (
"log/slog"
"postgresus-backend/internal/features/restores/enums"
"postgresus-backend/internal/util/logger"
)
var log = logger.GetLogger()
type RestoreBackgroundService struct {
restoreRepository *RestoreRepository
logger *slog.Logger
}
func (s *RestoreBackgroundService) Run() {
if err := s.failRestoresInProgress(); err != nil {
log.Error("Failed to fail restores in progress", "error", err)
s.logger.Error("Failed to fail restores in progress", "error", err)
panic(err)
}
}

View File

@@ -5,15 +5,16 @@ import (
"postgresus-backend/internal/features/restores/usecases"
"postgresus-backend/internal/features/storages"
"postgresus-backend/internal/features/users"
"postgresus-backend/internal/util/logger"
)
var restoreBackupUsecase = &usecases.RestoreBackupUsecase{}
var restoreRepository = &RestoreRepository{}
var restoreService = &RestoreService{
backups.GetBackupService(),
restoreRepository,
storages.GetStorageService(),
restoreBackupUsecase,
usecases.GetRestoreBackupUsecase(),
logger.GetLogger(),
}
var restoreController = &RestoreController{
restoreService,
@@ -22,6 +23,7 @@ var restoreController = &RestoreController{
var restoreBackgroundService = &RestoreBackgroundService{
restoreRepository,
logger.GetLogger(),
}
func GetRestoreController() *RestoreController {

View File

@@ -2,6 +2,7 @@ package restores
import (
"errors"
"log/slog"
"postgresus-backend/internal/features/backups"
"postgresus-backend/internal/features/databases"
"postgresus-backend/internal/features/restores/enums"
@@ -19,6 +20,7 @@ type RestoreService struct {
restoreRepository *RestoreRepository
storageService *storages.StorageService
restoreBackupUsecase *usecases.RestoreBackupUsecase
logger *slog.Logger
}
func (s *RestoreService) GetRestores(
@@ -53,7 +55,7 @@ func (s *RestoreService) RestoreBackupWithAuth(
go func() {
if err := s.RestoreBackup(backup, requestDTO); err != nil {
log.Error("Failed to restore backup", "error", err)
s.logger.Error("Failed to restore backup", "error", err)
}
}()

View File

@@ -0,0 +1,13 @@
package usecases
import (
usecases_postgresql "postgresus-backend/internal/features/restores/usecases/postgresql"
)
var restoreBackupUsecase = &RestoreBackupUsecase{
usecases_postgresql.GetRestorePostgresqlBackupUsecase(),
}
func GetRestoreBackupUsecase() *RestoreBackupUsecase {
return restoreBackupUsecase
}

View File

@@ -0,0 +1,13 @@
package usecases_postgresql
import (
"postgresus-backend/internal/util/logger"
)
var restorePostgresqlBackupUsecase = &RestorePostgresqlBackupUsecase{
logger.GetLogger(),
}
func GetRestorePostgresqlBackupUsecase() *RestorePostgresqlBackupUsecase {
return restorePostgresqlBackupUsecase
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"os"
"os/exec"
"path/filepath"
@@ -18,15 +19,14 @@ import (
pgtypes "postgresus-backend/internal/features/databases/databases/postgresql"
"postgresus-backend/internal/features/restores/models"
"postgresus-backend/internal/features/storages"
"postgresus-backend/internal/util/logger"
"postgresus-backend/internal/util/tools"
"github.com/google/uuid"
)
var log = logger.GetLogger()
type RestorePostgresqlBackupUsecase struct{}
type RestorePostgresqlBackupUsecase struct {
logger *slog.Logger
}
func (uc *RestorePostgresqlBackupUsecase) Execute(
restore models.Restore,
@@ -37,7 +37,7 @@ func (uc *RestorePostgresqlBackupUsecase) Execute(
return errors.New("database type not supported")
}
log.Info(
uc.logger.Info(
"Restoring PostgreSQL backup via pg_restore",
"restoreId",
restore.ID,
@@ -96,7 +96,7 @@ func (uc *RestorePostgresqlBackupUsecase) restoreFromStorage(
storage *storages.Storage,
pgConfig *pgtypes.PostgresqlDatabase,
) error {
log.Info(
uc.logger.Info(
"Restoring PostgreSQL backup from storage via temporary file",
"pgBin",
pgBin,
@@ -142,7 +142,7 @@ func (uc *RestorePostgresqlBackupUsecase) restoreFromStorage(
}
if info, err := os.Stat(pgpassFile); err == nil {
log.Info("Temporary .pgpass file created successfully",
uc.logger.Info("Temporary .pgpass file created successfully",
"pgpassFile", pgpassFile,
"size", info.Size(),
"mode", info.Mode(),
@@ -183,7 +183,7 @@ func (uc *RestorePostgresqlBackupUsecase) downloadBackupToTempFile(
tempBackupFile := filepath.Join(tempDir, "backup.dump")
// Get backup data from storage
log.Info(
uc.logger.Info(
"Downloading backup file from storage to temporary file",
"backupId",
backup.ID,
@@ -197,7 +197,7 @@ func (uc *RestorePostgresqlBackupUsecase) downloadBackupToTempFile(
}
defer func() {
if err := backupReader.Close(); err != nil {
log.Error("Failed to close backup reader", "error", err)
uc.logger.Error("Failed to close backup reader", "error", err)
}
}()
@@ -209,7 +209,7 @@ func (uc *RestorePostgresqlBackupUsecase) downloadBackupToTempFile(
}
defer func() {
if err := tempFile.Close(); err != nil {
log.Error("Failed to close temporary file", "error", err)
uc.logger.Error("Failed to close temporary file", "error", err)
}
}()
@@ -226,7 +226,7 @@ func (uc *RestorePostgresqlBackupUsecase) downloadBackupToTempFile(
return "", nil, fmt.Errorf("failed to close temporary backup file: %w", err)
}
log.Info("Backup file written to temporary location", "tempFile", tempBackupFile)
uc.logger.Info("Backup file written to temporary location", "tempFile", tempBackupFile)
return tempBackupFile, cleanupFunc, nil
}
@@ -239,7 +239,7 @@ func (uc *RestorePostgresqlBackupUsecase) executePgRestore(
pgConfig *pgtypes.PostgresqlDatabase,
) error {
cmd := exec.CommandContext(ctx, pgBin, args...)
log.Info("Executing PostgreSQL restore command", "command", cmd.String())
uc.logger.Info("Executing PostgreSQL restore command", "command", cmd.String())
// Setup environment variables
uc.setupPgRestoreEnvironment(cmd, pgpassFile, pgConfig)
@@ -302,7 +302,7 @@ func (uc *RestorePostgresqlBackupUsecase) setupPgRestoreEnvironment(
// Use the .pgpass file for authentication
cmd.Env = append(cmd.Env, "PGPASSFILE="+pgpassFile)
log.Info("Using temporary .pgpass file for authentication", "pgpassFile", pgpassFile)
uc.logger.Info("Using temporary .pgpass file for authentication", "pgpassFile", pgpassFile)
// Add PostgreSQL-specific environment variables
cmd.Env = append(cmd.Env, "PGCLIENTENCODING=UTF8")
@@ -318,10 +318,10 @@ func (uc *RestorePostgresqlBackupUsecase) setupPgRestoreEnvironment(
// Configure SSL settings
if shouldRequireSSL {
cmd.Env = append(cmd.Env, "PGSSLMODE=require")
log.Info("Using required SSL mode", "configuredHttps", pgConfig.IsHttps)
uc.logger.Info("Using required SSL mode", "configuredHttps", pgConfig.IsHttps)
} else {
cmd.Env = append(cmd.Env, "PGSSLMODE=prefer")
log.Info("Using preferred SSL mode", "configuredHttps", pgConfig.IsHttps)
uc.logger.Info("Using preferred SSL mode", "configuredHttps", pgConfig.IsHttps)
}
// Set other SSL parameters to avoid certificate issues

View File

@@ -10,7 +10,7 @@ import (
)
type RestoreBackupUsecase struct {
RestorePostgresqlBackupUsecase *usecases_postgresql.RestorePostgresqlBackupUsecase
restorePostgresqlBackupUsecase *usecases_postgresql.RestorePostgresqlBackupUsecase
}
func (uc *RestoreBackupUsecase) Execute(
@@ -19,7 +19,7 @@ func (uc *RestoreBackupUsecase) Execute(
storage *storages.Storage,
) error {
if restore.Backup.Database.Type == databases.DatabaseTypePostgres {
return uc.RestorePostgresqlBackupUsecase.Execute(restore, backup, storage)
return uc.restorePostgresqlBackupUsecase.Execute(restore, backup, storage)
}
return errors.New("database type not supported")

View File

@@ -2,12 +2,13 @@ package storages
import (
"io"
"log/slog"
"github.com/google/uuid"
)
type StorageFileSaver interface {
SaveFile(fileID uuid.UUID, file io.Reader) error
SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error
GetFile(fileID uuid.UUID) (io.ReadCloser, error)

View File

@@ -3,6 +3,7 @@ package storages
import (
"errors"
"io"
"log/slog"
local_storage "postgresus-backend/internal/features/storages/storages/local"
s3_storage "postgresus-backend/internal/features/storages/storages/s3"
@@ -21,8 +22,8 @@ type Storage struct {
S3Storage *s3_storage.S3Storage `json:"s3Storage" gorm:"foreignKey:StorageID"`
}
func (s *Storage) SaveFile(fileID uuid.UUID, file io.Reader) error {
err := s.getSpecificStorage().SaveFile(fileID, file)
func (s *Storage) SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error {
err := s.getSpecificStorage().SaveFile(logger, fileID, file)
if err != nil {
lastSaveError := err.Error()
s.LastSaveError = &lastSaveError

View File

@@ -9,6 +9,7 @@ import (
"path/filepath"
local_storage "postgresus-backend/internal/features/storages/storages/local"
s3_storage "postgresus-backend/internal/features/storages/storages/s3"
"postgresus-backend/internal/util/logger"
"testing"
"time"
@@ -87,7 +88,7 @@ func Test_Storage_BasicOperations(t *testing.T) {
fileID := uuid.New()
err = tc.storage.SaveFile(fileID, bytes.NewReader(fileData))
err = tc.storage.SaveFile(logger.GetLogger(), fileID, bytes.NewReader(fileData))
require.NoError(t, err, "SaveFile should succeed")
file, err := tc.storage.GetFile(fileID)
@@ -104,7 +105,7 @@ func Test_Storage_BasicOperations(t *testing.T) {
require.NoError(t, err, "Should be able to read test file")
fileID := uuid.New()
err = tc.storage.SaveFile(fileID, bytes.NewReader(fileData))
err = tc.storage.SaveFile(logger.GetLogger(), fileID, bytes.NewReader(fileData))
require.NoError(t, err, "SaveFile should succeed")
err = tc.storage.DeleteFile(fileID)

View File

@@ -3,16 +3,14 @@ package local_storage
import (
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"postgresus-backend/internal/config"
"postgresus-backend/internal/util/logger"
"github.com/google/uuid"
)
var log = logger.GetLogger()
// LocalStorage uses ./postgresus_local_backups folder as a
// directory for backups and ./postgresus_local_temp folder as a
// directory for temp files
@@ -24,20 +22,20 @@ func (l *LocalStorage) TableName() string {
return "local_storages"
}
func (l *LocalStorage) SaveFile(fileID uuid.UUID, file io.Reader) error {
log.Info("Starting to save file to local storage", "fileId", fileID.String())
func (l *LocalStorage) SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error {
logger.Info("Starting to save file to local storage", "fileId", fileID.String())
if err := l.ensureDirectories(); err != nil {
log.Error("Failed to ensure directories", "fileId", fileID.String(), "error", err)
logger.Error("Failed to ensure directories", "fileId", fileID.String(), "error", err)
return err
}
tempFilePath := filepath.Join(config.GetEnv().TempFolder, fileID.String())
log.Debug("Creating temp file", "fileId", fileID.String(), "tempPath", tempFilePath)
logger.Debug("Creating temp file", "fileId", fileID.String(), "tempPath", tempFilePath)
tempFile, err := os.Create(tempFilePath)
if err != nil {
log.Error(
logger.Error(
"Failed to create temp file",
"fileId",
fileID.String(),
@@ -52,26 +50,26 @@ func (l *LocalStorage) SaveFile(fileID uuid.UUID, file io.Reader) error {
_ = tempFile.Close()
}()
log.Debug("Copying file data to temp file", "fileId", fileID.String())
logger.Debug("Copying file data to temp file", "fileId", fileID.String())
_, err = io.Copy(tempFile, file)
if err != nil {
log.Error("Failed to write to temp file", "fileId", fileID.String(), "error", err)
logger.Error("Failed to write to temp file", "fileId", fileID.String(), "error", err)
return fmt.Errorf("failed to write to temp file: %w", err)
}
if err = tempFile.Sync(); err != nil {
log.Error("Failed to sync temp file", "fileId", fileID.String(), "error", err)
logger.Error("Failed to sync temp file", "fileId", fileID.String(), "error", err)
return fmt.Errorf("failed to sync temp file: %w", err)
}
err = tempFile.Close()
if err != nil {
log.Error("Failed to close temp file", "fileId", fileID.String(), "error", err)
logger.Error("Failed to close temp file", "fileId", fileID.String(), "error", err)
return fmt.Errorf("failed to close temp file: %w", err)
}
finalPath := filepath.Join(config.GetEnv().DataFolder, fileID.String())
log.Debug(
logger.Debug(
"Moving file from temp to final location",
"fileId",
fileID.String(),
@@ -81,7 +79,7 @@ func (l *LocalStorage) SaveFile(fileID uuid.UUID, file io.Reader) error {
// Move the file from temp to backups directory
if err = os.Rename(tempFilePath, finalPath); err != nil {
log.Error(
logger.Error(
"Failed to move file from temp to backups",
"fileId",
fileID.String(),
@@ -95,7 +93,7 @@ func (l *LocalStorage) SaveFile(fileID uuid.UUID, file io.Reader) error {
return fmt.Errorf("failed to move file from temp to backups: %w", err)
}
log.Info(
logger.Info(
"Successfully saved file to local storage",
"fileId",
fileID.String(),

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"strings"
"time"
@@ -27,7 +28,7 @@ func (s *S3Storage) TableName() string {
return "s3_storages"
}
func (s *S3Storage) SaveFile(fileID uuid.UUID, file io.Reader) error {
func (s *S3Storage) SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error {
client, err := s.getClient()
if err != nil {
return err

View File

@@ -128,9 +128,13 @@ func testBackupRestoreForVersion(t *testing.T, pgVersion string) {
}
// Make backup
createBackupUC := &usecases_postgresql_backup.CreatePostgresqlBackupUsecase{}
progressTracker := func(completedMBs float64) {}
err = createBackupUC.Execute(backupID, backupDbConfig, storage, progressTracker)
err = usecases_postgresql_backup.GetCreatePostgresqlBackupUsecase().Execute(
backupID,
backupDbConfig,
storage,
progressTracker,
)
assert.NoError(t, err)
// Create new database
@@ -173,7 +177,7 @@ func testBackupRestoreForVersion(t *testing.T, pgVersion string) {
}
// Restore the backup
restoreBackupUC := &usecases_postgresql_restore.RestorePostgresqlBackupUsecase{}
restoreBackupUC := usecases_postgresql_restore.GetRestorePostgresqlBackupUsecase()
err = restoreBackupUC.Execute(restore, completedBackup, storage)
assert.NoError(t, err)

View File

@@ -2,16 +2,14 @@ package tools
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"runtime"
env_utils "postgresus-backend/internal/util/env"
"postgresus-backend/internal/util/logger"
)
var log = logger.GetLogger()
// GetPostgresqlExecutable returns the full path to a specific PostgreSQL executable
// for the given version. Common executables include: pg_dump, psql, etc.
// On Windows, automatically appends .exe extension.
@@ -37,7 +35,11 @@ func GetPostgresqlExecutable(
// client tools (pg_dump, psql) available.
// In development: ./tools/postgresql/postgresql-{VERSION}/bin
// In production: /usr/pgsql-{VERSION}/bin
func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDir string) {
func VerifyPostgresesInstallation(
logger *slog.Logger,
envMode env_utils.EnvMode,
postgresesInstallDir string,
) {
versions := []PostgresqlVersion{
PostgresqlVersion13,
PostgresqlVersion14,
@@ -54,7 +56,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
for _, version := range versions {
binDir := getPostgresqlBasePath(version, envMode, postgresesInstallDir)
log.Info(
logger.Info(
"Verifying PostgreSQL installation",
"version",
string(version),
@@ -64,7 +66,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
if _, err := os.Stat(binDir); os.IsNotExist(err) {
if envMode == env_utils.EnvModeDevelopment {
log.Error(
logger.Error(
"PostgreSQL bin directory not found. Make sure PostgreSQL is installed. Read ./tools/readme.md for details",
"version",
string(version),
@@ -72,7 +74,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
binDir,
)
} else {
log.Error(
logger.Error(
"PostgreSQL bin directory not found. Please ensure PostgreSQL client tools are installed.",
"version",
string(version),
@@ -91,7 +93,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
postgresesInstallDir,
)
log.Info(
logger.Info(
"Checking for PostgreSQL command",
"command",
cmd,
@@ -103,7 +105,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
if _, err := os.Stat(cmdPath); os.IsNotExist(err) {
if envMode == env_utils.EnvModeDevelopment {
log.Error(
logger.Error(
"PostgreSQL command not found. Make sure PostgreSQL is installed. Read ./tools/readme.md for details",
"command",
cmd,
@@ -113,7 +115,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
cmdPath,
)
} else {
log.Error(
logger.Error(
"PostgreSQL command not found. Please ensure PostgreSQL client tools are properly installed.",
"command",
cmd,
@@ -126,7 +128,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
os.Exit(1)
}
log.Info(
logger.Info(
"PostgreSQL command found",
"command",
cmd,
@@ -135,7 +137,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
)
}
log.Info(
logger.Info(
"Installation of PostgreSQL verified",
"version",
string(version),
@@ -144,7 +146,7 @@ func VerifyPostgresesInstallation(envMode env_utils.EnvMode, postgresesInstallDi
)
}
log.Info("All PostgreSQL version-specific client tools verification completed successfully!")
logger.Info("All PostgreSQL version-specific client tools verification completed successfully!")
}
func getPostgresqlBasePath(