Improve logging testing
diff --git a/cmd/korap-mcp/main.go b/cmd/korap-mcp/main.go
index 0cf7fb3..2b67475 100644
--- a/cmd/korap-mcp/main.go
+++ b/cmd/korap-mcp/main.go
@@ -13,6 +13,7 @@
 func (c *CLI) Run() error {
 	// Handle version flag
 	if c.Version {
+		// For version output, we use stdout directly since logging isn't set up yet
 		fmt.Printf("%s version %s\n", c.GetServerName(), c.GetServerVersion())
 		os.Exit(0)
 	}
@@ -75,6 +76,7 @@
 	// Setup CLI and parser
 	cli, parser, err := SetupCLI()
 	if err != nil {
+		// Before logging is set up, we use stderr directly for critical errors
 		fmt.Fprintf(os.Stderr, "Failed to setup CLI: %v\n", err)
 		os.Exit(1)
 	}
diff --git a/logger/logger_test.go b/logger/logger_test.go
new file mode 100644
index 0000000..06bc660
--- /dev/null
+++ b/logger/logger_test.go
@@ -0,0 +1,203 @@
+package logger
+
+import (
+	"bytes"
+	"encoding/json"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"github.com/korap/korap-mcp/config"
+	"github.com/rs/zerolog"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSetupLogger(t *testing.T) {
+	t.Run("should setup logger with default configuration", func(t *testing.T) {
+		cfg := &config.LoggingConfig{
+			Level:  "info",
+			Format: "json",
+		}
+
+		logger, err := SetupLogger(cfg)
+		assert.NoError(t, err)
+		assert.NotNil(t, logger)
+	})
+
+	t.Run("should setup logger with text format", func(t *testing.T) {
+		cfg := &config.LoggingConfig{
+			Level:  "debug",
+			Format: "text",
+		}
+
+		logger, err := SetupLogger(cfg)
+		assert.NoError(t, err)
+		assert.NotNil(t, logger)
+	})
+
+	t.Run("should setup logger with file output", func(t *testing.T) {
+		// Create temporary file
+		tmpDir := t.TempDir()
+		logFile := filepath.Join(tmpDir, "test.log")
+
+		cfg := &config.LoggingConfig{
+			Level:  "warn",
+			Format: "json",
+			File:   logFile,
+		}
+
+		logger, err := SetupLogger(cfg)
+		assert.NoError(t, err)
+		assert.NotNil(t, logger)
+
+		// Test logging to file
+		logger.Info().Msg("test message")
+
+		// Verify file was created
+		assert.FileExists(t, logFile)
+	})
+
+	t.Run("should return error for invalid log level", func(t *testing.T) {
+		cfg := &config.LoggingConfig{
+			Level:  "invalid",
+			Format: "json",
+		}
+
+		_, err := SetupLogger(cfg)
+		assert.Error(t, err)
+		assert.Contains(t, strings.ToLower(err.Error()), "unknown level")
+	})
+
+	t.Run("should return error for invalid file path", func(t *testing.T) {
+		cfg := &config.LoggingConfig{
+			Level:  "info",
+			Format: "json",
+			File:   "/invalid/path/test.log",
+		}
+
+		_, err := SetupLogger(cfg)
+		assert.Error(t, err)
+	})
+}
+
+func TestGetLogger(t *testing.T) {
+	t.Run("should return configured logger", func(t *testing.T) {
+		cfg := &config.LoggingConfig{
+			Level:  "debug",
+			Format: "json",
+		}
+
+		logger := GetLogger(cfg)
+		assert.NotNil(t, logger)
+	})
+
+	t.Run("should return fallback logger on configuration error", func(t *testing.T) {
+		cfg := &config.LoggingConfig{
+			Level:  "invalid",
+			Format: "json",
+		}
+
+		logger := GetLogger(cfg)
+		assert.NotNil(t, logger)
+	})
+}
+
+func TestLoggerOutput(t *testing.T) {
+	t.Run("should produce structured JSON output", func(t *testing.T) {
+		var buf bytes.Buffer
+
+		// Create a logger that writes to our buffer
+		logger := zerolog.New(&buf).With().Timestamp().Logger()
+
+		// Log a message
+		logger.Info().Str("test", "value").Msg("test message")
+
+		// Parse the JSON output
+		var logEntry map[string]interface{}
+		err := json.Unmarshal(buf.Bytes(), &logEntry)
+		assert.NoError(t, err)
+
+		// Verify log structure
+		assert.Equal(t, "info", logEntry["level"])
+		assert.Equal(t, "test message", logEntry["message"])
+		assert.Equal(t, "value", logEntry["test"])
+		assert.Contains(t, logEntry, "time")
+	})
+
+	t.Run("should respect log levels", func(t *testing.T) {
+		var buf bytes.Buffer
+
+		// Set global level to warn
+		zerolog.SetGlobalLevel(zerolog.WarnLevel)
+		logger := zerolog.New(&buf).With().Timestamp().Logger()
+
+		// Try to log at different levels
+		logger.Debug().Msg("debug message")
+		logger.Info().Msg("info message")
+		logger.Warn().Msg("warn message")
+
+		// Only warn message should be present
+		output := buf.String()
+		assert.NotContains(t, output, "debug message")
+		assert.NotContains(t, output, "info message")
+		assert.Contains(t, output, "warn message")
+
+		// Reset global level
+		zerolog.SetGlobalLevel(zerolog.InfoLevel)
+	})
+}
+
+func TestLoggerIntegration(t *testing.T) {
+	t.Run("should work with all supported log levels", func(t *testing.T) {
+		levels := []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}
+
+		for _, level := range levels {
+			cfg := &config.LoggingConfig{
+				Level:  level,
+				Format: "json",
+			}
+
+			logger, err := SetupLogger(cfg)
+			if level == "panic" {
+				// panic level might not be supported in all contexts
+				continue
+			}
+			assert.NoError(t, err, "Failed to setup logger with level: %s", level)
+			assert.NotNil(t, logger, "Logger should not be nil for level: %s", level)
+		}
+	})
+
+	t.Run("should work with different output formats", func(t *testing.T) {
+		formats := []string{"json", "text"}
+
+		for _, format := range formats {
+			cfg := &config.LoggingConfig{
+				Level:  "info",
+				Format: format,
+			}
+
+			logger, err := SetupLogger(cfg)
+			assert.NoError(t, err, "Failed to setup logger with format: %s", format)
+			assert.NotNil(t, logger, "Logger should not be nil for format: %s", format)
+		}
+	})
+}
+
+func TestLoggerErrorHandling(t *testing.T) {
+	t.Run("should handle errors gracefully in GetLogger", func(t *testing.T) {
+		// Test with various invalid configurations
+		invalidConfigs := []*config.LoggingConfig{
+			{Level: "invalid-level", Format: "json"},
+			{Level: "info", Format: "json", File: "/dev/null/invalid"},
+		}
+
+		for _, cfg := range invalidConfigs {
+			logger := GetLogger(cfg)
+			// Should not panic and should return a valid logger
+			assert.NotNil(t, logger)
+
+			// Should be able to log
+			logger.Info().Msg("test message")
+		}
+	})
+}