first commit

This commit is contained in:
meninjar
2026-04-14 01:23:34 +00:00
commit edfaa886ff
443 changed files with 1245931 additions and 0 deletions
@@ -0,0 +1,395 @@
// File: /home/meninjar/goprint/service/internal/infrastructure/monitoring/health.go
package monitoring
import (
"context"
"runtime"
"time"
"service/internal/infrastructure/cache"
"service/internal/infrastructure/config"
"service/internal/infrastructure/database"
"service/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
)
// HealthMonitor handles health monitoring and metrics collection
type HealthMonitor struct {
metrics *Metrics
dbService database.Service
cacheManager *cache.Manager
config *config.Config
startTime time.Time
logger logger.Logger
}
// NewHealthMonitor creates a new health monitor
func NewHealthMonitor(metrics *Metrics, dbService database.Service, cacheManager *cache.Manager, config *config.Config) *HealthMonitor {
log := logger.Default().WithFields(
logger.String("component", "health_monitor"),
logger.String("service", "goprint"),
)
return &HealthMonitor{
metrics: metrics,
dbService: dbService,
cacheManager: cacheManager,
config: config,
startTime: time.Now(),
logger: log,
}
}
// Start starts the health monitoring routine
func (h *HealthMonitor) Start(ctx context.Context) {
log := h.logger.WithFields(logger.String("action", "start_health_monitoring"))
log.Info("Starting health monitoring service")
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
// Initial health check
h.performHealthCheck()
for {
select {
case <-ticker.C:
h.performHealthCheck()
case <-ctx.Done():
log.Info("Health monitoring service stopped")
return
}
}
}
// performHealthCheck performs comprehensive health check
func (h *HealthMonitor) performHealthCheck() {
log := h.logger.WithFields(logger.String("action", "health_check"))
// Check all databases
dbHealthy := h.checkAllDatabases()
// Check cache
cacheHealthy := h.checkCache()
// Check external services
externalHealthy := h.checkExternalServices()
// Update service health
overallHealthy := dbHealthy && cacheHealthy
h.metrics.UpdateServiceHealth(overallHealthy)
// Update resource usage
h.updateResourceUsage()
// Update uptime
uptime := time.Since(h.startTime).Seconds()
h.metrics.IncrementUptime(uptime)
log.Info("Health check completed",
logger.Bool("database_healthy", dbHealthy),
logger.Bool("cache_healthy", cacheHealthy),
logger.Bool("external_healthy", externalHealthy),
logger.Bool("overall_healthy", overallHealthy),
logger.Float64("uptime_seconds", uptime),
)
}
// checkAllDatabases checks all configured databases
func (h *HealthMonitor) checkAllDatabases() bool {
log := h.logger.WithFields(logger.String("action", "check_databases"))
dbList := h.dbService.ListDBs()
if len(dbList) == 0 {
log.Warn("No databases configured")
return false
}
allHealthy := true
for _, dbName := range dbList {
if !h.checkDatabase(dbName) {
allHealthy = false
}
}
return allHealthy
}
// checkDatabase checks specific database connectivity
func (h *HealthMonitor) checkDatabase(dbName string) bool {
log := h.logger.WithFields(
logger.String("action", "check_database"),
logger.String("database", dbName),
)
// Get database type
dbType, err := h.dbService.GetDBType(dbName)
if err != nil {
log.Error("Failed to get database type", logger.ErrorField(err))
h.metrics.UpdateExternalServiceStatus(dbName, false)
return false
}
// Check based on database type
switch dbType {
case database.Postgres, database.MySQL, database.SQLServer, database.SQLite:
return h.checkSQLDatabase(dbName, log)
case database.MongoDB:
return h.checkMongoDatabase(dbName, log)
default:
log.Error("Unknown database type", logger.String("type", string(dbType)))
return false
}
}
// checkSQLDatabase checks SQL database connectivity
func (h *HealthMonitor) checkSQLDatabase(dbName string, log logger.Logger) bool {
// Get GORM DB
gormDB, err := h.dbService.GetGormDB(dbName)
if err != nil {
log.Error("Failed to get GORM database", logger.ErrorField(err))
h.metrics.UpdateExternalServiceStatus(dbName, false)
return false
}
// Get SQL DB from GORM
sqlDB, err := gormDB.DB()
if err != nil {
log.Error("Failed to get SQL database from GORM", logger.ErrorField(err))
h.metrics.UpdateExternalServiceStatus(dbName, false)
return false
}
// Test connection with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = sqlDB.PingContext(ctx)
if err != nil {
log.Error("Database ping failed", logger.ErrorField(err))
h.metrics.UpdateExternalServiceStatus(dbName, false)
h.metrics.DbActiveConnections.With(prometheus.Labels{"database": dbName}).Set(0)
return false
}
// Get connection stats
stats := sqlDB.Stats()
h.metrics.DbActiveConnections.With(prometheus.Labels{"database": dbName}).Set(float64(stats.OpenConnections))
log.Info("Database connection healthy",
logger.Int("open_connections", stats.OpenConnections),
logger.Int("idle_connections", stats.Idle),
logger.Int("in_use_connections", stats.InUse),
)
h.metrics.UpdateExternalServiceStatus(dbName, true)
return true
}
// checkMongoDatabase checks MongoDB connectivity
func (h *HealthMonitor) checkMongoDatabase(dbName string, log logger.Logger) bool {
client, err := h.dbService.GetMongoClient(dbName)
if err != nil {
log.Error("Failed to get MongoDB client", logger.ErrorField(err))
h.metrics.UpdateExternalServiceStatus(dbName, false)
return false
}
// Test connection with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = client.Ping(ctx, nil)
if err != nil {
log.Error("MongoDB ping failed", logger.ErrorField(err))
h.metrics.UpdateExternalServiceStatus(dbName, false)
return false
}
log.Info("MongoDB connection healthy")
h.metrics.UpdateExternalServiceStatus(dbName, true)
return true
}
// checkCache checks cache connectivity
func (h *HealthMonitor) checkCache() bool {
log := h.logger.WithFields(logger.String("action", "check_cache"))
if h.cacheManager == nil {
log.Warn("Cache manager not available")
return true // Cache is optional
}
// Test cache health directly using manager
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := h.cacheManager.Health(ctx)
if err != nil {
log.Error("Cache health check failed", logger.ErrorField(err))
h.metrics.UpdateExternalServiceStatus("redis", false)
return false
}
log.Info("Cache connection healthy")
h.metrics.UpdateExternalServiceStatus("redis", true)
return true
}
// checkExternalServices checks external service connectivity
func (h *HealthMonitor) checkExternalServices() bool {
allHealthy := true
// Check BPJS service if configured
if h.config != nil && h.config.Bpjs.BaseURL != "" {
bpjsHealthy := h.checkBPJSService()
if !bpjsHealthy {
allHealthy = false
}
}
// Check SatuSehat service if configured
if h.config != nil && h.config.SatuSehat.BaseURL != "" {
satuSehatHealthy := h.checkSatuSehatService()
if !satuSehatHealthy {
allHealthy = false
}
}
return allHealthy
}
// checkBPJSService checks BPJS service connectivity
func (h *HealthMonitor) checkBPJSService() bool {
log := h.logger.WithFields(
logger.String("action", "check_bpjs"),
logger.String("service", "bpjs"),
)
// Implement BPJS health check logic here
// For now, return true as placeholder
log.Info("BPJS service health check (placeholder)")
h.metrics.UpdateExternalServiceStatus("bpjs", true)
return true
}
// checkSatuSehatService checks SatuSehat service connectivity
func (h *HealthMonitor) checkSatuSehatService() bool {
log := h.logger.WithFields(
logger.String("action", "check_satu_sehat"),
logger.String("service", "satu_sehat"),
)
// Implement SatuSehat health check logic here
// For now, return true as placeholder
log.Info("SatuSehat service health check (placeholder)")
h.metrics.UpdateExternalServiceStatus("satu_sehat", true)
return true
}
// updateResourceUsage updates resource usage metrics
func (h *HealthMonitor) updateResourceUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// Memory usage in MB
memoryMB := float64(m.Alloc) / 1024 / 1024
// Number of goroutines
goroutines := runtime.NumGoroutine()
// CPU usage would require more complex implementation
// For now, we'll set it to 0 as placeholder
cpuUsage := 0.0
h.metrics.UpdateResourceUsage(memoryMB, cpuUsage, goroutines)
}
// RegisterHealthEndpoint registers health check endpoint with metrics
func (h *HealthMonitor) RegisterHealthEndpoint(router *gin.Engine) {
router.GET("/metrics/health", func(c *gin.Context) {
health := gin.H{
"status": "healthy",
"timestamp": time.Now().UTC(),
"uptime": time.Since(h.startTime).String(),
"version": "1.0.0",
"service": "service-general",
}
// Check database
dbHealthy := h.checkAllDatabases()
health["database"] = gin.H{
"status": "healthy",
"connected": dbHealthy,
"databases": h.getDatabaseStatuses(),
}
// Check cache
cacheHealthy := h.checkCache()
health["cache"] = gin.H{
"status": "healthy",
"connected": cacheHealthy,
}
// Check external services
externalHealthy := h.checkExternalServices()
health["external_services"] = gin.H{
"status": "healthy",
"connected": externalHealthy,
"services": h.getExternalServiceStatuses(),
}
// Determine overall status
overallHealthy := dbHealthy && cacheHealthy && externalHealthy
if !overallHealthy {
health["status"] = "unhealthy"
c.JSON(503, health)
return
}
c.JSON(200, health)
})
}
// getDatabaseStatuses returns detailed database statuses
func (h *HealthMonitor) getDatabaseStatuses() map[string]interface{} {
statuses := make(map[string]interface{})
dbList := h.dbService.ListDBs()
for _, dbName := range dbList {
dbType, _ := h.dbService.GetDBType(dbName)
healthy := h.checkDatabase(dbName)
statuses[dbName] = gin.H{
"type": string(dbType),
"connected": healthy,
}
}
return statuses
}
// getExternalServiceStatuses returns external service statuses
func (h *HealthMonitor) getExternalServiceStatuses() map[string]interface{} {
statuses := make(map[string]interface{})
if h.config != nil {
if h.config.Bpjs.BaseURL != "" {
statuses["bpjs"] = gin.H{
"connected": true, // Placeholder
"url": h.config.Bpjs.BaseURL,
}
}
if h.config.SatuSehat.BaseURL != "" {
statuses["satu_sehat"] = gin.H{
"connected": true, // Placeholder
"url": h.config.SatuSehat.BaseURL,
}
}
}
return statuses
}
@@ -0,0 +1,419 @@
// File: /home/meninjar/goprint/service/internal/infrastructure/monitoring/metrics.go
package monitoring
import (
"time"
"service/pkg/logger"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
// Metrics defines all Prometheus metrics for the GoPrint service
type Metrics struct {
// HTTP metrics
HttpRequestsTotal *prometheus.CounterVec
HttpRequestDuration *prometheus.HistogramVec
HttpResponseSize *prometheus.HistogramVec
HttpRequestsInFlight prometheus.Gauge
// Database metrics
DbConnections prometheus.Gauge
DbQueryDuration *prometheus.HistogramVec
DbQueryErrors *prometheus.CounterVec
DbActiveConnections *prometheus.GaugeVec
// Cache metrics
CacheHits prometheus.Counter
CacheMisses prometheus.Counter
CacheOperations *prometheus.CounterVec
CacheDuration *prometheus.HistogramVec
// Business logic metrics
EthnicOperations *prometheus.CounterVec
AuthOperations *prometheus.CounterVec
PersonOperations *prometheus.CounterVec
// Service health metrics
ServiceUptime prometheus.Counter
ServiceHealthStatus prometheus.Gauge
ExternalServiceStatus *prometheus.GaugeVec
// Resource usage metrics
MemoryUsage prometheus.Gauge
CPUUsage prometheus.Gauge
Goroutines prometheus.Gauge
// Logger untuk monitoring
logger logger.Logger
serviceName string
startTime time.Time
}
// NewMetrics creates a new instance of Prometheus metrics
func NewMetrics(serviceName string) *Metrics {
log := logger.Default().WithFields(
logger.String("component", "monitoring"),
logger.String("service", serviceName),
)
labels := []string{"method", "endpoint", "status"}
dbLabels := []string{"operation", "table", "database"}
cacheLabels := []string{"operation", "result"}
businessLabels := []string{"operation", "status", "entity"}
externalLabels := []string{"service", "status"}
metrics := &Metrics{
serviceName: serviceName,
logger: log,
startTime: time.Now(),
// HTTP metrics
HttpRequestsTotal: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
ConstLabels: prometheus.Labels{"service": serviceName},
},
labels,
),
HttpRequestDuration: promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
ConstLabels: prometheus.Labels{"service": serviceName},
Buckets: prometheus.DefBuckets,
},
labels,
),
HttpResponseSize: promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_response_size_bytes",
Help: "HTTP response size in bytes",
ConstLabels: prometheus.Labels{"service": serviceName},
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
},
labels,
),
HttpRequestsInFlight: promauto.NewGauge(
prometheus.GaugeOpts{
Name: "http_requests_in_flight",
Help: "Number of HTTP requests currently being processed",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
// Database metrics
DbConnections: promauto.NewGauge(
prometheus.GaugeOpts{
Name: "db_connections_active",
Help: "Number of active database connections",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
DbActiveConnections: promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "db_connections_active_by_db",
Help: "Number of active database connections by database",
ConstLabels: prometheus.Labels{"service": serviceName},
},
[]string{"database"},
),
DbQueryDuration: promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "db_query_duration_seconds",
Help: "Database query duration in seconds",
ConstLabels: prometheus.Labels{"service": serviceName},
Buckets: prometheus.DefBuckets,
},
dbLabels,
),
DbQueryErrors: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "db_query_errors_total",
Help: "Total number of database query errors",
ConstLabels: prometheus.Labels{"service": serviceName},
},
dbLabels,
),
// Cache metrics
CacheHits: promauto.NewCounter(
prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "Total number of cache hits",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
CacheMisses: promauto.NewCounter(
prometheus.CounterOpts{
Name: "cache_misses_total",
Help: "Total number of cache misses",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
CacheOperations: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_operations_total",
Help: "Total number of cache operations",
ConstLabels: prometheus.Labels{"service": serviceName},
},
cacheLabels,
),
CacheDuration: promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "cache_operation_duration_seconds",
Help: "Cache operation duration in seconds",
ConstLabels: prometheus.Labels{"service": serviceName},
Buckets: prometheus.DefBuckets,
},
cacheLabels,
),
// Business logic metrics
EthnicOperations: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "ethnic_operations_total",
Help: "Total number of ethnic operations",
ConstLabels: prometheus.Labels{"service": serviceName},
},
businessLabels,
),
AuthOperations: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "auth_operations_total",
Help: "Total number of authentication operations",
ConstLabels: prometheus.Labels{"service": serviceName},
},
businessLabels,
),
PersonOperations: promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "person_operations_total",
Help: "Total number of person operations",
ConstLabels: prometheus.Labels{"service": serviceName},
},
businessLabels,
),
// Service health metrics
ServiceUptime: promauto.NewCounter(
prometheus.CounterOpts{
Name: "service_uptime_seconds",
Help: "Service uptime in seconds",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
ServiceHealthStatus: promauto.NewGauge(
prometheus.GaugeOpts{
Name: "service_health_status",
Help: "Service health status (1 = healthy, 0 = unhealthy)",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
ExternalServiceStatus: promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "external_service_status",
Help: "External service status (1 = up, 0 = down)",
ConstLabels: prometheus.Labels{"service": serviceName},
},
externalLabels,
),
// Resource usage metrics
MemoryUsage: promauto.NewGauge(
prometheus.GaugeOpts{
Name: "memory_usage_bytes",
Help: "Memory usage in bytes",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
CPUUsage: promauto.NewGauge(
prometheus.GaugeOpts{
Name: "cpu_usage_percent",
Help: "CPU usage percentage",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
Goroutines: promauto.NewGauge(
prometheus.GaugeOpts{
Name: "goroutines_count",
Help: "Number of goroutines",
ConstLabels: prometheus.Labels{"service": serviceName},
},
),
}
log.Info("Prometheus metrics initialized successfully",
logger.String("service", serviceName),
)
return metrics
}
// RecordHTTPRequest records HTTP request metrics
func (m *Metrics) RecordHTTPRequest(method, endpoint, status string, duration time.Duration, responseSize int64) {
labels := prometheus.Labels{
"method": method,
"endpoint": endpoint,
"status": status,
}
m.HttpRequestsTotal.With(labels).Inc()
m.HttpRequestDuration.With(labels).Observe(duration.Seconds())
m.HttpResponseSize.With(labels).Observe(float64(responseSize))
m.logger.Debug("HTTP request recorded",
logger.String("method", method),
logger.String("endpoint", endpoint),
logger.String("status", status),
logger.Float64("duration_seconds", duration.Seconds()),
logger.Int64("response_size", responseSize),
)
}
// RecordDBQuery records database query metrics
func (m *Metrics) RecordDBQuery(operation, table, database string, duration time.Duration, err error) {
labels := prometheus.Labels{
"operation": operation,
"table": table,
"database": database,
}
m.DbQueryDuration.With(labels).Observe(duration.Seconds())
if err != nil {
m.DbQueryErrors.With(labels).Inc()
m.logger.Error("Database query error recorded",
logger.String("operation", operation),
logger.String("table", table),
logger.String("database", database),
logger.ErrorField(err),
)
} else {
m.logger.Debug("Database query recorded",
logger.String("operation", operation),
logger.String("table", table),
logger.String("database", database),
logger.Float64("duration_seconds", duration.Seconds()),
)
}
}
// RecordCacheOperation records cache operation metrics
func (m *Metrics) RecordCacheOperation(operation string, hit bool, duration time.Duration) {
if hit {
m.CacheHits.Inc()
} else {
m.CacheMisses.Inc()
}
result := "hit"
if !hit {
result = "miss"
}
labels := prometheus.Labels{
"operation": operation,
"result": result,
}
m.CacheOperations.With(labels).Inc()
m.CacheDuration.With(labels).Observe(duration.Seconds())
m.logger.Debug("Cache operation recorded",
logger.String("operation", operation),
logger.String("result", result),
logger.Float64("duration_seconds", duration.Seconds()),
)
}
// RecordBusinessOperation records business logic operation metrics
func (m *Metrics) RecordBusinessOperation(operationType, operation, status string) {
var counter *prometheus.CounterVec
switch operationType {
case "ethnic":
counter = m.EthnicOperations
case "auth":
counter = m.AuthOperations
case "person":
counter = m.PersonOperations
default:
m.logger.Warn("Unknown business operation type",
logger.String("type", operationType),
logger.String("operation", operation),
)
return
}
labels := prometheus.Labels{
"operation": operation,
"status": status,
"entity": operationType,
}
counter.With(labels).Inc()
m.logger.Debug("Business operation recorded",
logger.String("type", operationType),
logger.String("operation", operation),
logger.String("status", status),
)
}
// UpdateServiceHealth updates service health status
func (m *Metrics) UpdateServiceHealth(healthy bool) {
if healthy {
m.ServiceHealthStatus.Set(1)
m.logger.Info("Service health status updated to healthy")
} else {
m.ServiceHealthStatus.Set(0)
m.logger.Error("Service health status updated to unhealthy")
}
}
// UpdateExternalServiceStatus updates external service status
func (m *Metrics) UpdateExternalServiceStatus(service string, status bool) {
value := 0.0
statusStr := "down"
if status {
value = 1.0
statusStr = "up"
}
labels := prometheus.Labels{
"service": service,
"status": statusStr,
}
m.ExternalServiceStatus.With(labels).Set(value)
m.logger.Info("External service status updated",
logger.String("service", service),
logger.String("status", statusStr),
)
}
// UpdateResourceUsage updates resource usage metrics
func (m *Metrics) UpdateResourceUsage(memory, cpu float64, goroutines int) {
m.MemoryUsage.Set(memory)
m.CPUUsage.Set(cpu)
m.Goroutines.Set(float64(goroutines))
m.logger.Debug("Resource usage updated",
logger.Float64("memory_mb", memory),
logger.Float64("cpu_percent", cpu),
logger.Int("goroutines", goroutines),
)
}
// IncrementUptime increments service uptime
func (m *Metrics) IncrementUptime(seconds float64) {
m.ServiceUptime.Add(seconds)
}
// GetUptime returns current uptime in seconds
func (m *Metrics) GetUptime() float64 {
return time.Since(m.startTime).Seconds()
}
@@ -0,0 +1,98 @@
// File: /home/meninjar/goprint/service/internal/infrastructure/monitoring/middleware.go
package monitoring
import (
"strconv"
"time"
"service/pkg/logger"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// MetricsMiddleware creates Gin middleware for collecting HTTP metrics
func MetricsMiddleware(metrics *Metrics) gin.HandlerFunc {
return func(c *gin.Context) {
// Generate request ID if not present
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
c.Header("X-Request-ID", requestID)
}
// Add request ID to context for logging
log := logger.Default().WithFields(
logger.String("request_id", requestID),
logger.String("method", c.Request.Method),
logger.String("path", c.Request.URL.Path),
logger.String("client_ip", c.ClientIP()),
)
// Start timer
start := time.Now()
// Increment requests in flight
metrics.HttpRequestsInFlight.Inc()
defer metrics.HttpRequestsInFlight.Dec()
// Log request start
log.Info("HTTP request started")
// Process request
c.Next()
// Calculate duration
duration := time.Since(start)
// Get response size
responseSize := c.Writer.Size()
if responseSize < 0 {
responseSize = 0
}
// Get status code
status := strconv.Itoa(c.Writer.Status())
// Record metrics
metrics.RecordHTTPRequest(
c.Request.Method,
c.FullPath(),
status,
duration,
int64(responseSize),
)
// Log request completion
log.Info("HTTP request completed",
logger.String("status", status),
logger.Float64("duration_seconds", duration.Seconds()),
logger.Int("response_size", responseSize),
)
// Add request ID to response
c.Header("X-Request-ID", requestID)
}
}
// DatabaseMetricsMiddleware creates middleware for database metrics
func DatabaseMetricsMiddleware(metrics *Metrics) func(operation, table, database string) func(error) {
return func(operation, table, database string) func(error) {
start := time.Now()
return func(err error) {
duration := time.Since(start)
metrics.RecordDBQuery(operation, table, database, duration, err)
}
}
}
// CacheMetricsMiddleware creates middleware for cache metrics
func CacheMetricsMiddleware(metrics *Metrics) func(operation string, hit bool) func() {
return func(operation string, hit bool) func() {
start := time.Now()
return func() {
duration := time.Since(start)
metrics.RecordCacheOperation(operation, hit, duration)
}
}
}
@@ -0,0 +1,184 @@
// File: /home/meninjar/goprint/service/internal/infrastructure/monitoring/repository_wrapper.go
package monitoring
import (
"context"
"time"
"service/pkg/logger"
"gorm.io/gorm"
)
// GormDBWrapper wraps GORM DB with metrics collection
type GormDBWrapper struct {
*gorm.DB
metrics *Metrics
database string
logger logger.Logger
}
// NewGormDBWrapper creates a new GORM wrapper with metrics
func NewGormDBWrapper(db *gorm.DB, metrics *Metrics, database string) *GormDBWrapper {
log := logger.Default().WithFields(
logger.String("component", "gorm_wrapper"),
logger.String("database", database),
)
return &GormDBWrapper{
DB: db,
metrics: metrics,
database: database,
logger: log,
}
}
// Create overrides GORM Create with metrics
func (w *GormDBWrapper) Create(value interface{}) *gorm.DB {
start := time.Now()
result := w.DB.Create(value)
duration := time.Since(start)
table := w.DB.Statement.Table
status := "success"
if result.Error != nil {
status = "error"
}
w.metrics.RecordDBQuery("create", table, w.database, duration, result.Error)
w.logger.Debug("Database create operation",
logger.String("table", table),
logger.String("status", status),
logger.Float64("duration_seconds", duration.Seconds()),
logger.ErrorField(result.Error),
)
return result
}
// Find overrides GORM Find with metrics
func (w *GormDBWrapper) Find(dest interface{}, conds ...interface{}) *gorm.DB {
start := time.Now()
result := w.DB.Find(dest, conds...)
duration := time.Since(start)
table := w.DB.Statement.Table
status := "success"
if result.Error != nil {
status = "error"
}
w.metrics.RecordDBQuery("find", table, w.database, duration, result.Error)
w.logger.Debug("Database find operation",
logger.String("table", table),
logger.String("status", status),
logger.Float64("duration_seconds", duration.Seconds()),
logger.ErrorField(result.Error),
)
return result
}
// First overrides GORM First with metrics
func (w *GormDBWrapper) First(dest interface{}, conds ...interface{}) *gorm.DB {
start := time.Now()
result := w.DB.First(dest, conds...)
duration := time.Since(start)
table := w.DB.Statement.Table
status := "success"
if result.Error != nil {
status = "error"
}
w.metrics.RecordDBQuery("first", table, w.database, duration, result.Error)
w.logger.Debug("Database first operation",
logger.String("table", table),
logger.String("status", status),
logger.Float64("duration_seconds", duration.Seconds()),
logger.ErrorField(result.Error),
)
return result
}
// Update overrides GORM Update with metrics
func (w *GormDBWrapper) Update(column string, value interface{}) *gorm.DB {
start := time.Now()
result := w.DB.Update(column, value)
duration := time.Since(start)
table := w.DB.Statement.Table
status := "success"
if result.Error != nil {
status = "error"
}
w.metrics.RecordDBQuery("update", table, w.database, duration, result.Error)
w.logger.Debug("Database update operation",
logger.String("table", table),
logger.String("status", status),
logger.Float64("duration_seconds", duration.Seconds()),
logger.ErrorField(result.Error),
)
return result
}
// Delete overrides GORM Delete with metrics
func (w *GormDBWrapper) Delete(value interface{}, conds ...interface{}) *gorm.DB {
start := time.Now()
result := w.DB.Delete(value, conds...)
duration := time.Since(start)
table := w.DB.Statement.Table
status := "success"
if result.Error != nil {
status = "error"
}
w.metrics.RecordDBQuery("delete", table, w.database, duration, result.Error)
w.logger.Debug("Database delete operation",
logger.String("table", table),
logger.String("status", status),
logger.Float64("duration_seconds", duration.Seconds()),
logger.ErrorField(result.Error),
)
return result
}
// WithContext overrides GORM WithContext to maintain wrapper
func (w *GormDBWrapper) WithContext(ctx context.Context) *GormDBWrapper {
return &GormDBWrapper{
DB: w.DB.WithContext(ctx),
metrics: w.metrics,
database: w.database,
logger: w.logger,
}
}
// Model overrides GORM Model to maintain wrapper
func (w *GormDBWrapper) Model(value interface{}) *GormDBWrapper {
return &GormDBWrapper{
DB: w.DB.Model(value),
metrics: w.metrics,
database: w.database,
logger: w.logger,
}
}
// Table overrides GORM Table to maintain wrapper
func (w *GormDBWrapper) Table(name string, args ...interface{}) *GormDBWrapper {
return &GormDBWrapper{
DB: w.DB.Table(name, args...),
metrics: w.metrics,
database: w.database,
logger: w.logger,
}
}