LFG
Some checks failed
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
CI/CD Pipeline / Build Docker Image (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Create Release (push) Has been cancelled

This commit is contained in:
Dev
2025-09-11 18:59:15 +03:00
commit 5440884b85
20 changed files with 3074 additions and 0 deletions

124
internal/logger/logger.go Normal file
View File

@@ -0,0 +1,124 @@
package logger
import (
"log"
"os"
)
// Logger represents the application logger
type Logger struct {
debugLogger *log.Logger
infoLogger *log.Logger
warnLogger *log.Logger
errorLogger *log.Logger
}
// Field represents a log field
type Field struct {
Key string
Value interface{}
}
// Option represents a logger option
type Option func(*Logger)
// NewLogger creates a new logger with default settings
func NewLogger(opts ...Option) *Logger {
logger := &Logger{
debugLogger: log.New(os.Stdout, "DEBUG: ", log.Ldate|log.Ltime|log.Lshortfile),
infoLogger: log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile),
warnLogger: log.New(os.Stdout, "WARN: ", log.Ldate|log.Ltime|log.Lshortfile),
errorLogger: log.New(os.Stdout, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile),
}
// Apply options
for _, opt := range opts {
opt(logger)
}
return logger
}
// Debug logs a debug message
func (l *Logger) Debug(msg string, fields ...Field) {
l.debugLogger.Printf(formatMessage(msg, fields...))
}
// Info logs an info message
func (l *Logger) Info(msg string, fields ...Field) {
l.infoLogger.Printf(formatMessage(msg, fields...))
}
// Warn logs a warning message
func (l *Logger) Warn(msg string, fields ...Field) {
l.warnLogger.Printf(formatMessage(msg, fields...))
}
// Error logs an error message
func (l *Logger) Error(msg string, fields ...Field) {
l.errorLogger.Printf(formatMessage(msg, fields...))
}
// Fatal logs a fatal message and exits
func (l *Logger) Fatal(msg string, fields ...Field) {
l.errorLogger.Printf(formatMessage(msg, fields...))
os.Exit(1)
}
// String creates a string field
func String(key, value string) Field {
return Field{Key: key, Value: value}
}
// Int creates an int field
func Int(key string, value int) Field {
return Field{Key: key, Value: value}
}
// Bool creates a bool field
func Bool(key string, value bool) Field {
return Field{Key: key, Value: value}
}
// Error creates an error field
func Error(err error) Field {
return Field{Key: "error", Value: err}
}
// formatMessage formats a log message with fields
func formatMessage(msg string, fields ...Field) string {
if len(fields) == 0 {
return msg
}
result := msg + " ["
for i, field := range fields {
if i > 0 {
result += ", "
}
result += field.Key + "="
result += toString(field.Value)
}
result += "]"
return result
}
// toString converts a value to string
func toString(value interface{}) string {
switch v := value.(type) {
case string:
return v
case int:
return string(v)
case bool:
if v {
return "true"
}
return "false"
case error:
return v.Error()
default:
return "unknown"
}
}

View File

@@ -0,0 +1,185 @@
package logger
import (
"bytes"
"log"
"os"
"strings"
"testing"
)
func TestNewLogger(t *testing.T) {
logger := NewLogger()
if logger == nil {
t.Error("Expected logger to be created, got nil")
}
}
func TestLoggerDebug(t *testing.T) {
// Create a buffer to capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
logger := NewLogger()
logger.Debug("Debug message", String("key", "value"))
output := buf.String()
if !strings.Contains(output, "DEBUG: Debug message") {
t.Errorf("Expected log output to contain 'DEBUG: Debug message', got %s", output)
}
if !strings.Contains(output, "key=value") {
t.Errorf("Expected log output to contain 'key=value', got %s", output)
}
}
func TestLoggerInfo(t *testing.T) {
// Create a buffer to capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
logger := NewLogger()
logger.Info("Info message", Int("number", 42))
output := buf.String()
if !strings.Contains(output, "INFO: Info message") {
t.Errorf("Expected log output to contain 'INFO: Info message', got %s", output)
}
if !strings.Contains(output, "number=42") {
t.Errorf("Expected log output to contain 'number=42', got %s", output)
}
}
func TestLoggerWarn(t *testing.T) {
// Create a buffer to capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
logger := NewLogger()
logger.Warn("Warning message", Bool("flag", true))
output := buf.String()
if !strings.Contains(output, "WARN: Warning message") {
t.Errorf("Expected log output to contain 'WARN: Warning message', got %s", output)
}
if !strings.Contains(output, "flag=true") {
t.Errorf("Expected log output to contain 'flag=true', got %s", output)
}
}
func TestLoggerError(t *testing.T) {
// Create a buffer to capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
logger := NewLogger()
err := os.ErrNotExist
logger.Error("Error message", Error(err))
output := buf.String()
if !strings.Contains(output, "ERROR: Error message") {
t.Errorf("Expected log output to contain 'ERROR: Error message', got %s", output)
}
if !strings.Contains(output, "error=file does not exist") {
t.Errorf("Expected log output to contain 'error=file does not exist', got %s", output)
}
}
func TestLoggerFatal(t *testing.T) {
// Create a buffer to capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
// Mock os.Exit to prevent the test from exiting
exitCalled := false
exitFunc := func(code int) {
exitCalled = true
}
osExit = exitFunc
defer func() {
osExit = realOsExit
}()
logger := NewLogger()
logger.Fatal("Fatal message", String("reason", "testing"))
output := buf.String()
if !strings.Contains(output, "ERROR: Fatal message") {
t.Errorf("Expected log output to contain 'ERROR: Fatal message', got %s", output)
}
if !strings.Contains(output, "reason=testing") {
t.Errorf("Expected log output to contain 'reason=testing', got %s", output)
}
if !exitCalled {
t.Error("Expected os.Exit to be called")
}
}
func TestLoggerMultipleFields(t *testing.T) {
// Create a buffer to capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
logger := NewLogger()
logger.Info("Multiple fields",
String("string", "value"),
Int("int", 123),
Bool("bool", false))
output := buf.String()
if !strings.Contains(output, "INFO: Multiple fields") {
t.Errorf("Expected log output to contain 'INFO: Multiple fields', got %s", output)
}
if !strings.Contains(output, "string=value") {
t.Errorf("Expected log output to contain 'string=value', got %s", output)
}
if !strings.Contains(output, "int=123") {
t.Errorf("Expected log output to contain 'int=123', got %s", output)
}
if !strings.Contains(output, "bool=false") {
t.Errorf("Expected log output to contain 'bool=false', got %s", output)
}
}
func TestLoggerNoFields(t *testing.T) {
// Create a buffer to capture log output
var buf bytes.Buffer
log.SetOutput(&buf)
defer func() {
log.SetOutput(os.Stderr)
}()
logger := NewLogger()
logger.Info("No fields")
output := buf.String()
if !strings.Contains(output, "INFO: No fields") {
t.Errorf("Expected log output to contain 'INFO: No fields', got %s", output)
}
if strings.Contains(output, "[") {
t.Error("Expected log output to not contain field brackets when no fields are provided")
}
}
// Mock os.Exit for testing
var (
osExit = func(code int) { os.Exit(code) }
realOsExit = osExit
)