144 lines
3.9 KiB
Go
144 lines
3.9 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"go.uber.org/zap"
|
|
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
|
)
|
|
|
|
// silentRedisLogger filters out noisy "maintnotifications" warnings from go-redis
|
|
// This warning occurs when the Redis client tries to use newer Redis 7.2+ features
|
|
// that may not be fully supported by the current Redis version.
|
|
// The client automatically falls back to compatible mode, so this is harmless.
|
|
type silentRedisLogger struct {
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func (l *silentRedisLogger) Printf(ctx context.Context, format string, v ...interface{}) {
|
|
msg := fmt.Sprintf(format, v...)
|
|
|
|
// Filter out harmless compatibility warnings
|
|
if strings.Contains(msg, "maintnotifications disabled") ||
|
|
strings.Contains(msg, "auto mode fallback") {
|
|
return
|
|
}
|
|
|
|
// Log other Redis messages at debug level
|
|
l.logger.Debug(msg)
|
|
}
|
|
|
|
// RedisCacher defines the interface for Redis cache operations
|
|
type RedisCacher interface {
|
|
Shutdown(ctx context.Context)
|
|
Get(ctx context.Context, key string) ([]byte, error)
|
|
Set(ctx context.Context, key string, val []byte) error
|
|
SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error
|
|
Delete(ctx context.Context, key string) error
|
|
}
|
|
|
|
type redisCache struct {
|
|
client *redis.Client
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewRedisCache creates a new Redis cache instance
|
|
func NewRedisCache(cfg *config.Config, logger *zap.Logger) (RedisCacher, error) {
|
|
logger = logger.Named("redis-cache")
|
|
|
|
logger.Info("⏳ Connecting to Redis...",
|
|
zap.String("host", cfg.Cache.Host),
|
|
zap.Int("port", cfg.Cache.Port))
|
|
|
|
// Build Redis URL from config
|
|
redisURL := fmt.Sprintf("redis://:%s@%s:%d/%d",
|
|
cfg.Cache.Password,
|
|
cfg.Cache.Host,
|
|
cfg.Cache.Port,
|
|
cfg.Cache.DB,
|
|
)
|
|
|
|
// If no password, use simpler URL format
|
|
if cfg.Cache.Password == "" {
|
|
redisURL = fmt.Sprintf("redis://%s:%d/%d",
|
|
cfg.Cache.Host,
|
|
cfg.Cache.Port,
|
|
cfg.Cache.DB,
|
|
)
|
|
}
|
|
|
|
opt, err := redis.ParseURL(redisURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse Redis URL: %w", err)
|
|
}
|
|
|
|
// Suppress noisy "maintnotifications" warnings from go-redis
|
|
// Use a custom logger that filters out these harmless compatibility warnings
|
|
redis.SetLogger(&silentRedisLogger{logger: logger.Named("redis-client")})
|
|
|
|
client := redis.NewClient(opt)
|
|
|
|
// Test connection
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if _, err = client.Ping(ctx).Result(); err != nil {
|
|
return nil, fmt.Errorf("failed to connect to Redis: %w", err)
|
|
}
|
|
|
|
logger.Info("✓ Redis connected",
|
|
zap.String("host", cfg.Cache.Host),
|
|
zap.Int("port", cfg.Cache.Port),
|
|
zap.Int("db", cfg.Cache.DB))
|
|
|
|
return &redisCache{
|
|
client: client,
|
|
logger: logger,
|
|
}, nil
|
|
}
|
|
|
|
func (c *redisCache) Shutdown(ctx context.Context) {
|
|
c.logger.Info("shutting down Redis cache")
|
|
if err := c.client.Close(); err != nil {
|
|
c.logger.Error("error closing Redis connection", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func (c *redisCache) Get(ctx context.Context, key string) ([]byte, error) {
|
|
val, err := c.client.Get(ctx, key).Result()
|
|
if errors.Is(err, redis.Nil) {
|
|
// Key doesn't exist - this is not an error
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("redis get failed: %w", err)
|
|
}
|
|
return []byte(val), nil
|
|
}
|
|
|
|
func (c *redisCache) Set(ctx context.Context, key string, val []byte) error {
|
|
if err := c.client.Set(ctx, key, val, 0).Err(); err != nil {
|
|
return fmt.Errorf("redis set failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *redisCache) SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error {
|
|
if err := c.client.Set(ctx, key, val, expiry).Err(); err != nil {
|
|
return fmt.Errorf("redis set with expiry failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *redisCache) Delete(ctx context.Context, key string) error {
|
|
if err := c.client.Del(ctx, key).Err(); err != nil {
|
|
return fmt.Errorf("redis delete failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|