Skip to content

Commit bd87c92

Browse files
committed
refactor(log): rework logger to have a factory pattern
1 parent a6ff34f commit bd87c92

3 files changed

Lines changed: 132 additions & 368 deletions

File tree

internal/log/handler_test.go

Lines changed: 19 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -9,156 +9,46 @@ import (
99
"time"
1010
)
1111

12-
func TestDualHandlerColoring(t *testing.T) {
12+
func TestDualHandlerColors(t *testing.T) {
1313
tests := []struct {
1414
level slog.Level
1515
expected string
1616
}{
17-
{slog.LevelDebug, "\033[36m"},
1817
{slog.LevelInfo, "\033[32m"},
19-
{slog.LevelWarn, "\033[33m"},
2018
{slog.LevelError, "\033[31m"},
2119
}
2220

2321
for _, tt := range tests {
24-
t.Run(tt.level.String(), func(t *testing.T) {
25-
var buf bytes.Buffer
26-
handler := NewDualHandler(&buf, false, slog.LevelDebug)
22+
var buf bytes.Buffer
23+
handler := NewDualHandler(&buf, false, slog.LevelDebug)
2724

28-
record := slog.NewRecord(time.Now(), tt.level, "test", 0)
29-
_ = handler.Handle(context.Background(), record)
25+
record := slog.NewRecord(time.Now(), tt.level, "test", 0)
26+
handler.Handle(context.Background(), record)
3027

31-
output := buf.String()
32-
if !strings.Contains(output, tt.expected) {
33-
t.Errorf("expected color code %s in output, got: %s", tt.expected, output)
34-
}
35-
})
28+
output := buf.String()
29+
if !strings.Contains(output, tt.expected) {
30+
t.Errorf("expected color %s for %s level", tt.expected, tt.level)
31+
}
3632
}
3733
}
3834

39-
func TestDualHandlerWithAttrs(t *testing.T) {
40-
var buf bytes.Buffer
41-
handler := NewDualHandler(&buf, false, slog.LevelInfo)
42-
43-
// add some attributes to the handler
44-
handlerWithAttrs := handler.WithAttrs([]slog.Attr{
45-
slog.String("service", "test"),
46-
slog.Int("version", 1),
47-
})
48-
49-
record := slog.NewRecord(time.Now(), slog.LevelInfo, "test message", 0)
50-
record.Add("request_id", "123")
51-
52-
_ = handlerWithAttrs.Handle(context.Background(), record)
53-
54-
output := buf.String()
55-
if !strings.Contains(output, "service=test") {
56-
t.Error("should contain handler attribute service=test")
57-
}
58-
if !strings.Contains(output, "version=1") {
59-
t.Error("should contain handler attribute version=1")
60-
}
61-
if !strings.Contains(output, "request_id=123") {
62-
t.Error("should contain record attribute request_id=123")
63-
}
64-
}
65-
66-
func TestDualHandlerWithGroup(t *testing.T) {
67-
var buf bytes.Buffer
68-
handler := NewDualHandler(&buf, false, slog.LevelInfo)
69-
70-
groupHandler := handler.WithGroup("http")
71-
if groupHandler == nil {
72-
t.Error("WithGroup should return a handler")
73-
}
74-
75-
// test that it creates a new handler instance
76-
if groupHandler == handler {
77-
t.Error("WithGroup should return a new handler instance")
78-
}
79-
}
80-
81-
func TestMultiHandlerEnabled(t *testing.T) {
82-
var buf1, buf2 bytes.Buffer
83-
84-
// one handler with INFO level, another with ERROR level
85-
handler1 := NewDualHandler(&buf1, false, slog.LevelInfo)
86-
handler2 := NewDualHandler(&buf2, false, slog.LevelError)
87-
88-
multiHandler := NewMultiHandler(handler1, handler2)
89-
90-
// should be enabled for INFO (handler1 accepts it)
91-
if !multiHandler.Enabled(context.Background(), slog.LevelInfo) {
92-
t.Error("should be enabled for INFO level")
93-
}
94-
95-
// should be enabled for ERROR (both handlers accept it)
96-
if !multiHandler.Enabled(context.Background(), slog.LevelError) {
97-
t.Error("should be enabled for ERROR level")
98-
}
99-
100-
// should not be enabled for DEBUG (neither handler accepts it)
101-
if multiHandler.Enabled(context.Background(), slog.LevelDebug) {
102-
t.Error("should not be enabled for DEBUG level")
103-
}
104-
}
105-
106-
func TestMultiHandlerWithAttrs(t *testing.T) {
35+
func TestMultiHandler(t *testing.T) {
10736
var buf1, buf2 bytes.Buffer
10837

10938
handler1 := NewDualHandler(&buf1, false, slog.LevelInfo)
11039
handler2 := NewDualHandler(&buf2, false, slog.LevelInfo)
111-
11240
multiHandler := NewMultiHandler(handler1, handler2)
11341

114-
// add attributes
115-
handlerWithAttrs := multiHandler.WithAttrs([]slog.Attr{
116-
slog.String("component", "test"),
117-
})
118-
119-
record := slog.NewRecord(time.Now(), slog.LevelInfo, "test", 0)
120-
_ = handlerWithAttrs.Handle(context.Background(), record)
121-
122-
output1 := buf1.String()
123-
output2 := buf2.String()
124-
125-
if !strings.Contains(output1, "component=test") {
126-
t.Error("first handler should contain the attribute")
127-
}
128-
if !strings.Contains(output2, "component=test") {
129-
t.Error("second handler should contain the attribute")
42+
record := slog.NewRecord(time.Now(), slog.LevelInfo, "multi test", 0)
43+
err := multiHandler.Handle(context.Background(), record)
44+
if err != nil {
45+
t.Fatalf("multihandler failed: %v", err)
13046
}
131-
}
132-
133-
func TestMultiHandlerWithGroup(t *testing.T) {
134-
var buf1, buf2 bytes.Buffer
13547

136-
handler1 := NewDualHandler(&buf1, false, slog.LevelInfo)
137-
handler2 := NewDualHandler(&buf2, false, slog.LevelInfo)
138-
139-
multiHandler := NewMultiHandler(handler1, handler2)
140-
groupHandler := multiHandler.WithGroup("database")
141-
142-
if groupHandler == nil {
143-
t.Error("WithGroup should return a handler")
144-
}
145-
if groupHandler == multiHandler {
146-
t.Error("WithGroup should return a new instance")
48+
if !strings.Contains(buf1.String(), "multi test") {
49+
t.Error("first handler should receive message")
14750
}
148-
}
149-
150-
func TestHandlerTimestampFormat(t *testing.T) {
151-
var buf bytes.Buffer
152-
handler := NewDualHandler(&buf, false, slog.LevelInfo)
153-
154-
now := time.Date(2025, 1, 15, 14, 30, 45, 0, time.UTC)
155-
record := slog.NewRecord(now, slog.LevelInfo, "timestamp test", 0)
156-
157-
_ = handler.Handle(context.Background(), record)
158-
159-
output := buf.String()
160-
expected := "2025-01-15 14:30:45"
161-
if !strings.Contains(output, expected) {
162-
t.Errorf("expected timestamp format %s in output: %s", expected, output)
51+
if !strings.Contains(buf2.String(), "multi test") {
52+
t.Error("second handler should receive message")
16353
}
164-
}
54+
}

internal/log/logger.go

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,55 @@ var (
2222
once sync.Once
2323
)
2424

25+
// LoggerFactory creates Logger instances for dependency injection
26+
type LoggerFactory struct{}
27+
28+
// NewLoggerFactory creates a new LoggerFactory
29+
func NewLoggerFactory() *LoggerFactory {
30+
return &LoggerFactory{}
31+
}
32+
33+
// CreateLogger creates a new Logger with the given config
34+
func (f *LoggerFactory) CreateLogger(config Config) *Logger {
35+
var handlers []slog.Handler
36+
37+
var fileLogger *lumberjack.Logger
38+
if config.LogFilePath != "" {
39+
fileLogger = &lumberjack.Logger{
40+
Filename: config.LogFilePath,
41+
MaxSize: 10,
42+
MaxBackups: 2,
43+
MaxAge: 7,
44+
Compress: true,
45+
}
46+
fileHandler := slog.NewJSONHandler(fileLogger, &slog.HandlerOptions{
47+
Level: config.Level,
48+
})
49+
handlers = append(handlers, fileHandler)
50+
}
51+
52+
if config.Verbose {
53+
terminalHandler := NewDualHandler(os.Stderr, false, config.Level)
54+
handlers = append(handlers, terminalHandler)
55+
}
56+
57+
var handler slog.Handler
58+
if len(handlers) == 1 {
59+
handler = handlers[0]
60+
} else if len(handlers) > 1 {
61+
handler = NewMultiHandler(handlers...)
62+
} else {
63+
handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
64+
Level: config.Level,
65+
})
66+
}
67+
68+
return &Logger{
69+
Logger: slog.New(handler),
70+
fileLogger: fileLogger,
71+
}
72+
}
73+
2574
type Config struct {
2675
Level slog.Level
2776
LogFilePath string
@@ -30,43 +79,8 @@ type Config struct {
3079

3180
func Initialize(config Config) {
3281
once.Do(func() {
33-
var handlers []slog.Handler
34-
35-
var fileLogger *lumberjack.Logger
36-
if config.LogFilePath != "" {
37-
fileLogger = &lumberjack.Logger{
38-
Filename: config.LogFilePath,
39-
MaxSize: 10,
40-
MaxBackups: 2,
41-
MaxAge: 7,
42-
Compress: true,
43-
}
44-
fileHandler := slog.NewJSONHandler(fileLogger, &slog.HandlerOptions{
45-
Level: config.Level,
46-
})
47-
handlers = append(handlers, fileHandler)
48-
}
49-
50-
if config.Verbose {
51-
terminalHandler := NewDualHandler(os.Stderr, false, config.Level)
52-
handlers = append(handlers, terminalHandler)
53-
}
54-
55-
var handler slog.Handler
56-
if len(handlers) == 1 {
57-
handler = handlers[0]
58-
} else if len(handlers) > 1 {
59-
handler = NewMultiHandler(handlers...)
60-
} else {
61-
handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
62-
Level: config.Level,
63-
})
64-
}
65-
66-
globalLogger = &Logger{
67-
Logger: slog.New(handler),
68-
fileLogger: fileLogger,
69-
}
82+
factory := NewLoggerFactory()
83+
globalLogger = factory.CreateLogger(config)
7084
})
7185
}
7286

0 commit comments

Comments
 (0)