227 lines
7.1 KiB
Plaintext
227 lines
7.1 KiB
Plaintext
// internal/infrastructure/database/database.go
|
|
package database
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"service-general/internal/infrastructure/config"
|
|
"service-general/pkg/logger" // Import logger Anda
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/stdlib"
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/lib/pq" // PASTIKAN IMPORT INI ADA
|
|
|
|
// Driver GORM di-import secara eksplisit untuk Migration
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/driver/sqlserver"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger" // Import logger dari GORM
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
// ... (tipe DatabaseType dan interface Service tetap sama)
|
|
|
|
type service struct {
|
|
// GORM DB adalah sumber utama
|
|
gormDatabases map[string]*gorm.DB
|
|
// Map untuk menyimpan koneksi mentah yang diekstrak dari GORM
|
|
sqlDatabases map[string]*sql.DB
|
|
sqlxDatabases map[string]*sqlx.DB
|
|
mongoClients map[string]*mongo.Client
|
|
readReplicas map[string][]*sql.DB
|
|
configs map[string]config.DatabaseConfig
|
|
readConfigs map[string][]config.DatabaseConfig
|
|
mu sync.RWMutex
|
|
readBalancer map[string]int
|
|
// PERBAIKAN 1: Kembali ke tipe yang benar untuk listener PostgreSQL
|
|
listeners map[string]*pq.Listener
|
|
listenersMu sync.RWMutex
|
|
}
|
|
|
|
var (
|
|
dbManager *service
|
|
once sync.Once
|
|
)
|
|
|
|
// ... (fungsi New dan loadFromConfig tetap sama)
|
|
|
|
// --- PERBAIKAN 2: Buat Adapter untuk Logger GORM ---
|
|
|
|
// gormLoggerAdapter adalah adapter untuk menghubungkan logger kita dengan GORM
|
|
type gormLoggerAdapter struct {
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewGormLoggerAdapter membuat instance baru dari adapter
|
|
func NewGormLoggerAdapter(l logger.Logger) logger.Interface {
|
|
// Tentukan level log GORM berdasarkan level logger kita
|
|
// Ini adalah implementasi sederhana, Anda bisa membuatnya lebih dinamis
|
|
logLevel := logger.Silent
|
|
if l != nil {
|
|
// Anda bisa menambahkan logika untuk menentukan level dari logger Anda
|
|
// Untuk sekarang, kita hardcode ke Info
|
|
logLevel = logger.Info
|
|
}
|
|
|
|
return &gormLoggerAdapter{
|
|
logger: l,
|
|
// Konfigurasi logger GORM
|
|
Config: logger.Config{
|
|
SlowThreshold: 200 * time.Millisecond,
|
|
LogLevel: logLevel,
|
|
IgnoreRecordNotFoundError: false,
|
|
Colorful: false,
|
|
},
|
|
}
|
|
}
|
|
|
|
// LogMode menetapkan level log
|
|
func (l *gormLoggerAdapter) LogMode(level logger.LogLevel) logger.Interface {
|
|
newLogger := *l
|
|
newLogger.Config.LogLevel = level
|
|
return &newLogger
|
|
}
|
|
|
|
// Info mencatat log info
|
|
func (l *gormLoggerAdapter) Info(ctx context.Context, msg string, data ...interface{}) {
|
|
l.logger.WithContext(ctx).Info(fmt.Sprintf(msg, data...))
|
|
}
|
|
|
|
// Warn mencatat log warning
|
|
func (l *gormLoggerAdapter) Warn(ctx context.Context, msg string, data ...interface{}) {
|
|
l.logger.WithContext(ctx).Warn(fmt.Sprintf(msg, data...))
|
|
}
|
|
|
|
// Error mencatat log error
|
|
func (l *gormLoggerAdapter) Error(ctx context.Context, msg string, data ...interface{}) {
|
|
l.logger.WithContext(ctx).Error(fmt.Sprintf(msg, data...))
|
|
}
|
|
|
|
// Trace mencatat log trace (SQL query)
|
|
func (l *gormLoggerAdapter) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
|
|
elapsed := time.Since(begin)
|
|
sql, rows := fc()
|
|
|
|
fields := map[string]interface{}{
|
|
"duration": elapsed,
|
|
"rows": rows,
|
|
"sql": sql,
|
|
"error": err,
|
|
"function": "database.trace",
|
|
}
|
|
|
|
switch {
|
|
case err != nil && l.Config.LogLevel >= logger.Error:
|
|
l.logger.WithContext(ctx).WithFields(logger.Any("error", err)).Error("Database error", logger.Any("details", fields))
|
|
case elapsed > l.Config.SlowThreshold && l.Config.LogLevel >= logger.Warn:
|
|
l.logger.WithContext(ctx).Warn("Slow database query", logger.Any("details", fields))
|
|
case l.Config.LogLevel == logger.Info:
|
|
l.logger.WithContext(ctx).Debug("Database query", logger.Any("details", fields))
|
|
}
|
|
}
|
|
|
|
// --- Metode Koneksi GORM (diperbaiki untuk menggunakan adapter) ---
|
|
|
|
func (s *service) openPostgresGORM(config config.DatabaseConfig) (*gorm.DB, error) {
|
|
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Jakarta",
|
|
config.Host, config.Username, config.Password, config.Database, config.Port, config.SSLMode)
|
|
|
|
if config.Schema != "" {
|
|
dsn += fmt.Sprintf(" search_path=%s", config.Schema)
|
|
}
|
|
|
|
// PERBAIKAN: Gunakan adapter logger kita
|
|
gormConfig := &gorm.Config{
|
|
Logger: NewGormLoggerAdapter(logger.Default()),
|
|
}
|
|
|
|
if config.RequireSSL {
|
|
// ... (konfigurasi SSL tetap sama)
|
|
}
|
|
|
|
return gorm.Open(postgres.Open(dsn), gormConfig)
|
|
}
|
|
|
|
func (s *service) openMySQLGORM(config config.DatabaseConfig) (*gorm.DB, error) {
|
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
|
config.Username, config.Password, config.Host, config.Port, config.Database)
|
|
|
|
if config.RequireSSL {
|
|
dsn += "&tls=true"
|
|
}
|
|
|
|
// PERBAIKAN: Gunakan adapter logger kita
|
|
gormConfig := &gorm.Config{
|
|
Logger: NewGormLoggerAdapter(logger.Default()),
|
|
}
|
|
|
|
return gorm.Open(mysql.Open(dsn), gormConfig)
|
|
}
|
|
|
|
func (s *service) openSQLServerGORM(config config.DatabaseConfig) (*gorm.DB, error) {
|
|
dsn := fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s",
|
|
config.Username, config.Password, config.Host, config.Port, config.Database)
|
|
|
|
if config.RequireSSL {
|
|
// ... (konfigurasi SSL tetap sama)
|
|
}
|
|
|
|
// PERBAIKAN: Gunakan adapter logger kita
|
|
gormConfig := &gorm.Config{
|
|
Logger: NewGormLoggerAdapter(logger.Default()),
|
|
}
|
|
|
|
return gorm.Open(sqlserver.Open(dsn), gormConfig)
|
|
}
|
|
|
|
func (s *service) openSQLiteGORM(config config.DatabaseConfig) (*gorm.DB, error) {
|
|
// PERBAIKAN: Gunakan adapter logger kita
|
|
gormConfig := &gorm.Config{
|
|
Logger: NewGormLoggerAdapter(logger.Default()),
|
|
}
|
|
|
|
return gorm.Open(sqlite.Open(config.Path), gormConfig)
|
|
}
|
|
|
|
// --- Metode Lainnya ---
|
|
|
|
// ... (semua metode lain seperti GetGormDB, GetDB, Migrate, Health, Close, dll. tidak perlu diubah)
|
|
// ... (kecuali mungkin bagian Close yang membersihkan listeners)
|
|
|
|
// Di fungsi Close, pastikan tipe listener sudah benar
|
|
func (s *service) Close() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
var errs []error
|
|
|
|
for name, listener := range s.listeners {
|
|
if err := listener.Close(); err != nil {
|
|
errs = append(errs, fmt.Errorf("failed to close listener for %s: %w", name, err))
|
|
}
|
|
}
|
|
// ... (sisa kode Close)
|
|
}
|
|
|
|
// Di fungsi ListenForChanges, pastikan Anda menggunakan pq.NewListener
|
|
func (s *service) ListenForChanges(ctx context.Context, dbName string, channels []string, callback func(string, string)) error {
|
|
// ... (kode Anda sudah benar menggunakan pq.NewListener)
|
|
// ...
|
|
} |