Init
This commit is contained in:
110
internal/logger/logger.go
Normal file
110
internal/logger/logger.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.benadis.ru/gitops/benadis-rac/internal/constants"
|
||||
)
|
||||
|
||||
// Logger интерфейс для логирования
|
||||
type Logger interface {
|
||||
Debug(msg string, args ...any)
|
||||
Info(msg string, args ...any)
|
||||
Warn(msg string, args ...any)
|
||||
Error(msg string, args ...any)
|
||||
DebugCommand(msg string, command []string)
|
||||
}
|
||||
|
||||
// SlogLogger реализация Logger с использованием slog
|
||||
type SlogLogger struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewLogger создает новый логгер
|
||||
func NewLogger(level string) Logger {
|
||||
var logLevel slog.Level
|
||||
switch strings.ToLower(level) {
|
||||
case "debug":
|
||||
logLevel = slog.LevelDebug
|
||||
case "info":
|
||||
logLevel = slog.LevelInfo
|
||||
case "warn":
|
||||
logLevel = slog.LevelWarn
|
||||
case "error":
|
||||
logLevel = slog.LevelError
|
||||
default:
|
||||
logLevel = slog.LevelInfo
|
||||
}
|
||||
|
||||
l := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
AddSource: true,
|
||||
Level: logLevel,
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
if a.Key == slog.SourceKey {
|
||||
s := a.Value.Any().(*slog.Source)
|
||||
s.File = path.Base(s.File)
|
||||
}
|
||||
return a
|
||||
},
|
||||
}))
|
||||
l = l.With(slog.Group("App info",
|
||||
slog.String("version", constants.AppVersion),
|
||||
))
|
||||
|
||||
return &SlogLogger{logger: l}
|
||||
}
|
||||
|
||||
// Debug логирует отладочное сообщение
|
||||
func (l *SlogLogger) Debug(msg string, args ...any) {
|
||||
l.logger.Debug(msg, args...)
|
||||
}
|
||||
|
||||
// Info логирует информационное сообщение
|
||||
func (l *SlogLogger) Info(msg string, args ...any) {
|
||||
l.logger.Info(msg, args...)
|
||||
}
|
||||
|
||||
// Warn логирует предупреждение
|
||||
func (l *SlogLogger) Warn(msg string, args ...any) {
|
||||
l.logger.Warn(msg, args...)
|
||||
}
|
||||
|
||||
// Error логирует ошибку
|
||||
func (l *SlogLogger) Error(msg string, args ...any) {
|
||||
l.logger.Error(msg, args...)
|
||||
}
|
||||
|
||||
// DebugCommand логирует команду с маскированием паролей
|
||||
func (l *SlogLogger) DebugCommand(msg string, command []string) {
|
||||
maskedCommand := maskPasswords(command)
|
||||
l.logger.Debug(msg, "command", strings.Join(maskedCommand, " "))
|
||||
}
|
||||
|
||||
// maskPasswords маскирует пароли в команде
|
||||
func maskPasswords(command []string) []string {
|
||||
masked := make([]string, len(command))
|
||||
copy(masked, command)
|
||||
|
||||
// Преобразуем строку флагов в массив
|
||||
passwordFlags := strings.Split(constants.PasswordFlags, ",")
|
||||
|
||||
for i, arg := range masked {
|
||||
for _, flag := range passwordFlags {
|
||||
if strings.HasPrefix(arg, flag+"=") {
|
||||
// Формат: --flag=value
|
||||
parts := strings.SplitN(arg, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
masked[i] = parts[0] + "=" + constants.PasswordMask
|
||||
}
|
||||
} else if arg == flag && i+1 < len(masked) {
|
||||
// Формат: --flag value
|
||||
masked[i+1] = constants.PasswordMask
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return masked
|
||||
}
|
||||
238
internal/logger/logger_test.go
Normal file
238
internal/logger/logger_test.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMaskPasswords(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "mask cluster password with equals",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=secret123",
|
||||
"--other-flag=value",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=***",
|
||||
"--other-flag=value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mask infobase password with equals",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--infobase-pwd=secret456",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--infobase-pwd=***",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mask password with space separator",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd", "secret789",
|
||||
"--other-flag", "value",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd", "***",
|
||||
"--other-flag", "value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mask both passwords",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=cluster_secret",
|
||||
"--infobase-pwd=infobase_secret",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=***",
|
||||
"--infobase-pwd=***",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no passwords to mask",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "cluster", "list",
|
||||
"--some-flag=value",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "cluster", "list",
|
||||
"--some-flag=value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mixed format passwords",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=secret1",
|
||||
"--infobase-pwd", "secret2",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "infobase", "update",
|
||||
"--cluster-pwd=***",
|
||||
"--infobase-pwd", "***",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := maskPasswords(tt.command)
|
||||
|
||||
if len(actual) != len(tt.expected) {
|
||||
t.Errorf("Expected length %d, got %d", len(tt.expected), len(actual))
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range tt.expected {
|
||||
if actual[i] != expected {
|
||||
t.Errorf("At index %d: expected '%s', got '%s'", i, expected, actual[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLogger(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
level string
|
||||
expected string // Мы не можем напрямую проверить уровень, но можем проверить что логгер создается
|
||||
}{
|
||||
{"debug level", "debug", "debug"},
|
||||
{"info level", "info", "info"},
|
||||
{"warn level", "warn", "warn"},
|
||||
{"error level", "error", "error"},
|
||||
{"unknown level defaults to info", "unknown", "info"},
|
||||
{"empty level defaults to info", "", "info"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
logger := NewLogger(tt.level)
|
||||
if logger == nil {
|
||||
t.Error("Expected logger to be created, got nil")
|
||||
}
|
||||
|
||||
// Проверяем что логгер реализует интерфейс Logger
|
||||
var _ Logger = logger
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlogLoggerMethods(t *testing.T) {
|
||||
// Создаем логгер для тестирования
|
||||
logger := NewLogger("debug")
|
||||
|
||||
// Тестируем что методы не паникуют
|
||||
t.Run("debug method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Debug method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Debug("test debug message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("info method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Info method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Info("test info message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("warn method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Warn method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Warn("test warn message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("error method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("Error method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
logger.Error("test error message", "key", "value")
|
||||
})
|
||||
|
||||
t.Run("debug command method", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("DebugCommand method panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
command := []string{"rac", "--cluster-pwd=secret", "command"}
|
||||
logger.DebugCommand("test command", command)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMaskPasswordsEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty command",
|
||||
command: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "single element",
|
||||
command: []string{"rac"},
|
||||
expected: []string{"rac"},
|
||||
},
|
||||
{
|
||||
name: "password flag at end without value",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "password flag with empty value",
|
||||
command: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd=",
|
||||
},
|
||||
expected: []string{
|
||||
"rac", "localhost:1545", "--cluster-pwd=***",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := maskPasswords(tt.command)
|
||||
|
||||
if len(actual) != len(tt.expected) {
|
||||
t.Errorf("Expected length %d, got %d", len(tt.expected), len(actual))
|
||||
return
|
||||
}
|
||||
|
||||
for i, expected := range tt.expected {
|
||||
if actual[i] != expected {
|
||||
t.Errorf("At index %d: expected '%s', got '%s'", i, expected, actual[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user