1
0
mirror of https://github.com/redis/go-redis.git synced 2025-10-20 09:52:25 +03:00

feat: RESP3 notifications support & Hitless notifications handling [CAE-1088] & [CAE-1072] (#3418)

- Adds support for handling push notifications with RESP3. 
- Using this support adds handlers for hitless upgrades.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Hristo Temelski <hristo.temelski@redis.com>
This commit is contained in:
Nedyalko Dyakov
2025-09-10 22:18:01 +03:00
committed by GitHub
parent 2da6ca07c0
commit 0ef6d0727d
70 changed files with 11668 additions and 596 deletions

121
logging/logging.go Normal file
View File

@@ -0,0 +1,121 @@
// Package logging provides logging level constants and utilities for the go-redis library.
// This package centralizes logging configuration to ensure consistency across all components.
package logging
import (
"context"
"fmt"
"strings"
"github.com/redis/go-redis/v9/internal"
)
// LogLevel represents the logging level
type LogLevel int
// Log level constants for the entire go-redis library
const (
LogLevelError LogLevel = iota // 0 - errors only
LogLevelWarn // 1 - warnings and errors
LogLevelInfo // 2 - info, warnings, and errors
LogLevelDebug // 3 - debug, info, warnings, and errors
)
// String returns the string representation of the log level
func (l LogLevel) String() string {
switch l {
case LogLevelError:
return "ERROR"
case LogLevelWarn:
return "WARN"
case LogLevelInfo:
return "INFO"
case LogLevelDebug:
return "DEBUG"
default:
return "UNKNOWN"
}
}
// IsValid returns true if the log level is valid
func (l LogLevel) IsValid() bool {
return l >= LogLevelError && l <= LogLevelDebug
}
func (l LogLevel) WarnOrAbove() bool {
return l >= LogLevelWarn
}
func (l LogLevel) InfoOrAbove() bool {
return l >= LogLevelInfo
}
func (l LogLevel) DebugOrAbove() bool {
return l >= LogLevelDebug
}
// VoidLogger is a logger that does nothing.
// Used to disable logging and thus speed up the library.
type VoidLogger struct{}
func (v *VoidLogger) Printf(_ context.Context, _ string, _ ...interface{}) {
// do nothing
}
// Disable disables logging by setting the internal logger to a void logger.
// This can be used to speed up the library if logging is not needed.
// It will override any custom logger that was set before and set the VoidLogger.
func Disable() {
internal.Logger = &VoidLogger{}
}
// Enable enables logging by setting the internal logger to the default logger.
// This is the default behavior.
// You can use redis.SetLogger to set a custom logger.
//
// NOTE: This function is not thread-safe.
// It will override any custom logger that was set before and set the DefaultLogger.
func Enable() {
internal.Logger = internal.NewDefaultLogger()
}
// NewBlacklistLogger returns a new logger that filters out messages containing any of the substrings.
// This can be used to filter out messages containing sensitive information.
func NewBlacklistLogger(substr []string) internal.Logging {
l := internal.NewDefaultLogger()
return &filterLogger{logger: l, substr: substr, blacklist: true}
}
// NewWhitelistLogger returns a new logger that only logs messages containing any of the substrings.
// This can be used to only log messages related to specific commands or patterns.
func NewWhitelistLogger(substr []string) internal.Logging {
l := internal.NewDefaultLogger()
return &filterLogger{logger: l, substr: substr, blacklist: false}
}
type filterLogger struct {
logger internal.Logging
blacklist bool
substr []string
}
func (l *filterLogger) Printf(ctx context.Context, format string, v ...interface{}) {
msg := fmt.Sprintf(format, v...)
found := false
for _, substr := range l.substr {
if strings.Contains(msg, substr) {
found = true
if l.blacklist {
return
}
}
}
// whitelist, only log if one of the substrings is present
if !l.blacklist && !found {
return
}
if l.logger != nil {
l.logger.Printf(ctx, format, v...)
return
}
}

59
logging/logging_test.go Normal file
View File

@@ -0,0 +1,59 @@
package logging
import "testing"
func TestLogLevel_String(t *testing.T) {
tests := []struct {
level LogLevel
expected string
}{
{LogLevelError, "ERROR"},
{LogLevelWarn, "WARN"},
{LogLevelInfo, "INFO"},
{LogLevelDebug, "DEBUG"},
{LogLevel(99), "UNKNOWN"},
}
for _, test := range tests {
if got := test.level.String(); got != test.expected {
t.Errorf("LogLevel(%d).String() = %q, want %q", test.level, got, test.expected)
}
}
}
func TestLogLevel_IsValid(t *testing.T) {
tests := []struct {
level LogLevel
expected bool
}{
{LogLevelError, true},
{LogLevelWarn, true},
{LogLevelInfo, true},
{LogLevelDebug, true},
{LogLevel(-1), false},
{LogLevel(4), false},
{LogLevel(99), false},
}
for _, test := range tests {
if got := test.level.IsValid(); got != test.expected {
t.Errorf("LogLevel(%d).IsValid() = %v, want %v", test.level, got, test.expected)
}
}
}
func TestLogLevelConstants(t *testing.T) {
// Test that constants have expected values
if LogLevelError != 0 {
t.Errorf("LogLevelError = %d, want 0", LogLevelError)
}
if LogLevelWarn != 1 {
t.Errorf("LogLevelWarn = %d, want 1", LogLevelWarn)
}
if LogLevelInfo != 2 {
t.Errorf("LogLevelInfo = %d, want 2", LogLevelInfo)
}
if LogLevelDebug != 3 {
t.Errorf("LogLevelDebug = %d, want 3", LogLevelDebug)
}
}