on main help sign in
chore: change config signature
e74e9dfKujtim Hoxha
cmd/logs.go+1−2
@@ -8,7 +8,6 @@ import (
8 "time"
8 "time"
9
9
10 "github.com/charmbracelet/crush/pkg/config"
10 "github.com/charmbracelet/crush/pkg/config"
11− "github.com/charmbracelet/crush/pkg/env"
12 "github.com/charmbracelet/log/v2"
11 "github.com/charmbracelet/log/v2"
13 "github.com/nxadm/tail"
12 "github.com/nxadm/tail"
14 "github.com/spf13/cobra"
13 "github.com/spf13/cobra"
@@ -27,7 +26,7 @@ var logsCmd = &cobra.Command{
27 if err != nil {
26 if err != nil {
28 return fmt.Errorf("failed to get current working directory: %v", err)
27 return fmt.Errorf("failed to get current working directory: %v", err)
29 }
28 }
30− cfg, err := config.Load(cwd, env.New())
29+ cfg, err := config.Load(cwd, false)
31 if err != nil {
30 if err != nil {
32 return fmt.Errorf("failed to load configuration: %v", err)
31 return fmt.Errorf("failed to load configuration: %v", err)
33 }
32 }
pkg/config/config.go+54−5
@@ -1,6 +1,11 @@
1package config
1package config
2
2
3+import (
4+ "slices"
5+ "strings"
6+
3−import "github.com/charmbracelet/crush/internal/fur/provider"
7+ "github.com/charmbracelet/crush/internal/fur/provider"
8+)
4
9
5const (
10const (
6 appName = "crush"
11 appName = "crush"
@@ -70,7 +75,7 @@ const (
70 MCPHttp MCPType = "http"
75 MCPHttp MCPType = "http"
71)
76)
72
77
73−type MCP struct {
78+type MCPConfig struct {
74 Command string `json:"command,omitempty" `
79 Command string `json:"command,omitempty" `
75 Env []string `json:"env,omitempty"`
80 Env []string `json:"env,omitempty"`
76 Args []string `json:"args,omitempty"`
81 Args []string `json:"args,omitempty"`
@@ -103,20 +108,64 @@ type Options struct {
103 DataDirectory string `json:"data_directory,omitempty"`
108 DataDirectory string `json:"data_directory,omitempty"`
104}
109}
105
110
111+type MCPs map[string]MCPConfig
112+
113+type MCP struct {
114+ Name string `json:"name"`
115+ MCP MCPConfig `json:"mcp"`
116+}
117+
118+func (m MCPs) Sorted() []MCP {
119+ sorted := make([]MCP, 0, len(m))
120+ for k, v := range m {
121+ sorted = append(sorted, MCP{
122+ Name: k,
123+ MCP: v,
124+ })
125+ }
126+ slices.SortFunc(sorted, func(a, b MCP) int {
127+ return strings.Compare(a.Name, b.Name)
128+ })
129+ return sorted
130+}
131+
132+type LSPs map[string]LSPConfig
133+
134+type LSP struct {
135+ Name string `json:"name"`
136+ LSP LSPConfig `json:"lsp"`
137+}
138+
139+func (l LSPs) Sorted() []LSP {
140+ sorted := make([]LSP, 0, len(l))
141+ for k, v := range l {
142+ sorted = append(sorted, LSP{
143+ Name: k,
144+ LSP: v,
145+ })
146+ }
147+ slices.SortFunc(sorted, func(a, b LSP) int {
148+ return strings.Compare(a.Name, b.Name)
149+ })
150+ return sorted
151+}
152+
106// Config holds the configuration for crush.
153// Config holds the configuration for crush.
107type Config struct {
154type Config struct {
108− workingDir string `json:"-"`
109 // We currently only support large/small as values here.
155 // We currently only support large/small as values here.
110 Models map[string]SelectedModel `json:"models,omitempty"`
156 Models map[string]SelectedModel `json:"models,omitempty"`
111
157
112 // The providers that are configured
158 // The providers that are configured
113 Providers map[string]ProviderConfig `json:"providers,omitempty"`
159 Providers map[string]ProviderConfig `json:"providers,omitempty"`
114
160
115− MCP map[string]MCP `json:"mcp,omitempty"`
161+ MCP MCPs `json:"mcp,omitempty"`
116
162
117− LSP map[string]LSPConfig `json:"lsp,omitempty"`
163+ LSP LSPs `json:"lsp,omitempty"`
118
164
119 Options *Options `json:"options,omitempty"`
165 Options *Options `json:"options,omitempty"`
166+
167+ // Internal
168+ workingDir string `json:"-"`
120}
169}
121
170
122func (c *Config) WorkingDir() string {
171func (c *Config) WorkingDir() string {
pkg/config/load.go+19−6
@@ -13,6 +13,7 @@ import (
13 "github.com/charmbracelet/crush/internal/fur/client"
13 "github.com/charmbracelet/crush/internal/fur/client"
14 "github.com/charmbracelet/crush/internal/fur/provider"
14 "github.com/charmbracelet/crush/internal/fur/provider"
15 "github.com/charmbracelet/crush/pkg/env"
15 "github.com/charmbracelet/crush/pkg/env"
16+ "github.com/charmbracelet/crush/pkg/log"
16)
17)
17
18
18// LoadReader config via io.Reader.
19// LoadReader config via io.Reader.
@@ -31,7 +32,7 @@ func LoadReader(fd io.Reader) (*Config, error) {
31}
32}
32
33
33// Load loads the configuration from the default paths.
34// Load loads the configuration from the default paths.
34−func Load(workingDir string, env env.Env) (*Config, error) {
35+func Load(workingDir string, debug bool) (*Config, error) {
35 // uses default config paths
36 // uses default config paths
36 configPaths := []string{
37 configPaths := []string{
37 globalConfig(),
38 globalConfig(),
@@ -40,6 +41,17 @@ func Load(workingDir string, env env.Env) (*Config, error) {
40 filepath.Join(workingDir, fmt.Sprintf(".%s.json", appName)),
41 filepath.Join(workingDir, fmt.Sprintf(".%s.json", appName)),
41 }
42 }
42 cfg, err := loadFromConfigPaths(configPaths)
43 cfg, err := loadFromConfigPaths(configPaths)
44+
45+ if debug {
46+ cfg.Options.Debug = true
47+ }
48+
49+ // Init logs
50+ log.Init(
51+ filepath.Join(cfg.Options.DataDirectory, "logs", fmt.Sprintf("%s.log", appName)),
52+ cfg.Options.Debug,
53+ )
54+
43 if err != nil {
55 if err != nil {
44 return nil, fmt.Errorf("failed to load config: %w", err)
56 return nil, fmt.Errorf("failed to load config: %w", err)
45 }
57 }
@@ -47,7 +59,7 @@ func Load(workingDir string, env env.Env) (*Config, error) {
47 // e.x validate the models
59 // e.x validate the models
48 // e.x validate provider config
60 // e.x validate provider config
49
61
50− setDefaults(workingDir, cfg)
62+ cfg.setDefaults(workingDir)
51
63
52 // Load known providers, this loads the config from fur
64 // Load known providers, this loads the config from fur
53 providers, err := LoadProviders(client.New())
65 providers, err := LoadProviders(client.New())
@@ -55,16 +67,17 @@ func Load(workingDir string, env env.Env) (*Config, error) {
55 return nil, fmt.Errorf("failed to load providers: %w", err)
67 return nil, fmt.Errorf("failed to load providers: %w", err)
56 }
68 }
57
69
70+ env := env.New()
58 // Configure providers
71 // Configure providers
59 valueResolver := NewShellVariableResolver(env)
72 valueResolver := NewShellVariableResolver(env)
60− if err := configureProviders(cfg, env, valueResolver, providers); err != nil {
73+ if err := cfg.configureProviders(env, valueResolver, providers); err != nil {
61 return nil, fmt.Errorf("failed to configure providers: %w", err)
74 return nil, fmt.Errorf("failed to configure providers: %w", err)
62 }
75 }
63
76
64 return cfg, nil
77 return cfg, nil
65}
78}
66
79
67−func configureProviders(cfg *Config, env env.Env, resolver VariableResolver, knownProviders []provider.Provider) error {
80+func (cfg *Config) configureProviders(env env.Env, resolver VariableResolver, knownProviders []provider.Provider) error {
68 for _, p := range knownProviders {
81 for _, p := range knownProviders {
69
82
70 config, ok := cfg.Providers[string(p.ID)]
83 config, ok := cfg.Providers[string(p.ID)]
@@ -169,7 +182,7 @@ func hasAWSCredentials(env env.Env) bool {
169 return false
182 return false
170}
183}
171
184
172−func setDefaults(workingDir string, cfg *Config) {
185+func (cfg *Config) setDefaults(workingDir string) {
173 cfg.workingDir = workingDir
186 cfg.workingDir = workingDir
174 if cfg.Options == nil {
187 if cfg.Options == nil {
175 cfg.Options = &Options{}
188 cfg.Options = &Options{}
@@ -190,7 +203,7 @@ func setDefaults(workingDir string, cfg *Config) {
190 cfg.Models = make(map[string]SelectedModel)
203 cfg.Models = make(map[string]SelectedModel)
191 }
204 }
192 if cfg.MCP == nil {
205 if cfg.MCP == nil {
193− cfg.MCP = make(map[string]MCP)
206+ cfg.MCP = make(map[string]MCPConfig)
194 }
207 }
195 if cfg.LSP == nil {
208 if cfg.LSP == nil {
196 cfg.LSP = make(map[string]LSPConfig)
209 cfg.LSP = make(map[string]LSPConfig)
pkg/config/load_test.go+20−19
@@ -27,7 +27,7 @@ func TestConfig_LoadFromReaders(t *testing.T) {
27func TestConfig_setDefaults(t *testing.T) {
27func TestConfig_setDefaults(t *testing.T) {
28 cfg := &Config{}
28 cfg := &Config{}
29
29
30− setDefaults("/tmp", cfg)
30+ cfg.setDefaults("/tmp")
31
31
32 assert.NotNil(t, cfg.Options)
32 assert.NotNil(t, cfg.Options)
33 assert.NotNil(t, cfg.Options.TUI)
33 assert.NotNil(t, cfg.Options.TUI)
@@ -56,12 +56,12 @@ func TestConfig_configureProviders(t *testing.T) {
56 }
56 }
57
57
58 cfg := &Config{}
58 cfg := &Config{}
59− setDefaults("/tmp", cfg)
59+ cfg.setDefaults("/tmp")
60 env := env.NewFromMap(map[string]string{
60 env := env.NewFromMap(map[string]string{
61 "OPENAI_API_KEY": "test-key",
61 "OPENAI_API_KEY": "test-key",
62 })
62 })
63 resolver := NewEnvironmentVariableResolver(env)
63 resolver := NewEnvironmentVariableResolver(env)
64− err := configureProviders(cfg, env, resolver, knownProviders)
64+ err := cfg.configureProviders(env, resolver, knownProviders)
65 assert.NoError(t, err)
65 assert.NoError(t, err)
66 assert.Len(t, cfg.Providers, 1)
66 assert.Len(t, cfg.Providers, 1)
67
67
@@ -98,12 +98,13 @@ func TestConfig_configureProvidersWithOverride(t *testing.T) {
98 },
98 },
99 },
99 },
100 }
100 }
101− setDefaults("/tmp", cfg)
101+ cfg.setDefaults("/tmp")
102+
102 env := env.NewFromMap(map[string]string{
103 env := env.NewFromMap(map[string]string{
103 "OPENAI_API_KEY": "test-key",
104 "OPENAI_API_KEY": "test-key",
104 })
105 })
105 resolver := NewEnvironmentVariableResolver(env)
106 resolver := NewEnvironmentVariableResolver(env)
106− err := configureProviders(cfg, env, resolver, knownProviders)
107+ err := cfg.configureProviders(env, resolver, knownProviders)
107 assert.NoError(t, err)
108 assert.NoError(t, err)
108 assert.Len(t, cfg.Providers, 1)
109 assert.Len(t, cfg.Providers, 1)
109
110
@@ -139,12 +140,12 @@ func TestConfig_configureProvidersWithNewProvider(t *testing.T) {
139 },
140 },
140 },
141 },
141 }
142 }
142− setDefaults("/tmp", cfg)
143+ cfg.setDefaults("/tmp")
143 env := env.NewFromMap(map[string]string{
144 env := env.NewFromMap(map[string]string{
144 "OPENAI_API_KEY": "test-key",
145 "OPENAI_API_KEY": "test-key",
145 })
146 })
146 resolver := NewEnvironmentVariableResolver(env)
147 resolver := NewEnvironmentVariableResolver(env)
147− err := configureProviders(cfg, env, resolver, knownProviders)
148+ err := cfg.configureProviders(env, resolver, knownProviders)
148 assert.NoError(t, err)
149 assert.NoError(t, err)
149 // Should be to because of the env variable
150 // Should be to because of the env variable
150 assert.Len(t, cfg.Providers, 2)
151 assert.Len(t, cfg.Providers, 2)
@@ -171,13 +172,13 @@ func TestConfig_configureProvidersBedrockWithCredentials(t *testing.T) {
171 }
172 }
172
173
173 cfg := &Config{}
174 cfg := &Config{}
174− setDefaults("/tmp", cfg)
175+ cfg.setDefaults("/tmp")
175 env := env.NewFromMap(map[string]string{
176 env := env.NewFromMap(map[string]string{
176 "AWS_ACCESS_KEY_ID": "test-key-id",
177 "AWS_ACCESS_KEY_ID": "test-key-id",
177 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
178 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
178 })
179 })
179 resolver := NewEnvironmentVariableResolver(env)
180 resolver := NewEnvironmentVariableResolver(env)
180− err := configureProviders(cfg, env, resolver, knownProviders)
181+ err := cfg.configureProviders(env, resolver, knownProviders)
181 assert.NoError(t, err)
182 assert.NoError(t, err)
182 assert.Len(t, cfg.Providers, 1)
183 assert.Len(t, cfg.Providers, 1)
183
184
@@ -200,10 +201,10 @@ func TestConfig_configureProvidersBedrockWithoutCredentials(t *testing.T) {
200 }
201 }
201
202
202 cfg := &Config{}
203 cfg := &Config{}
203− setDefaults("/tmp", cfg)
204+ cfg.setDefaults("/tmp")
204 env := env.NewFromMap(map[string]string{})
205 env := env.NewFromMap(map[string]string{})
205 resolver := NewEnvironmentVariableResolver(env)
206 resolver := NewEnvironmentVariableResolver(env)
206− err := configureProviders(cfg, env, resolver, knownProviders)
207+ err := cfg.configureProviders(env, resolver, knownProviders)
207 assert.NoError(t, err)
208 assert.NoError(t, err)
208 // Provider should not be configured without credentials
209 // Provider should not be configured without credentials
209 assert.Len(t, cfg.Providers, 0)
210 assert.Len(t, cfg.Providers, 0)
@@ -222,13 +223,13 @@ func TestConfig_configureProvidersBedrockWithoutUnsupportedModel(t *testing.T) {
222 }
223 }
223
224
224 cfg := &Config{}
225 cfg := &Config{}
225− setDefaults("/tmp", cfg)
226+ cfg.setDefaults("/tmp")
226 env := env.NewFromMap(map[string]string{
227 env := env.NewFromMap(map[string]string{
227 "AWS_ACCESS_KEY_ID": "test-key-id",
228 "AWS_ACCESS_KEY_ID": "test-key-id",
228 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
229 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
229 })
230 })
230 resolver := NewEnvironmentVariableResolver(env)
231 resolver := NewEnvironmentVariableResolver(env)
231− err := configureProviders(cfg, env, resolver, knownProviders)
232+ err := cfg.configureProviders(env, resolver, knownProviders)
232 assert.Error(t, err)
233 assert.Error(t, err)
233}
234}
234
235
@@ -245,14 +246,14 @@ func TestConfig_configureProvidersVertexAIWithCredentials(t *testing.T) {
245 }
246 }
246
247
247 cfg := &Config{}
248 cfg := &Config{}
248− setDefaults("/tmp", cfg)
249+ cfg.setDefaults("/tmp")
249 env := env.NewFromMap(map[string]string{
250 env := env.NewFromMap(map[string]string{
250 "GOOGLE_GENAI_USE_VERTEXAI": "true",
251 "GOOGLE_GENAI_USE_VERTEXAI": "true",
251 "GOOGLE_CLOUD_PROJECT": "test-project",
252 "GOOGLE_CLOUD_PROJECT": "test-project",
252 "GOOGLE_CLOUD_LOCATION": "us-central1",
253 "GOOGLE_CLOUD_LOCATION": "us-central1",
253 })
254 })
254 resolver := NewEnvironmentVariableResolver(env)
255 resolver := NewEnvironmentVariableResolver(env)
255− err := configureProviders(cfg, env, resolver, knownProviders)
256+ err := cfg.configureProviders(env, resolver, knownProviders)
256 assert.NoError(t, err)
257 assert.NoError(t, err)
257 assert.Len(t, cfg.Providers, 1)
258 assert.Len(t, cfg.Providers, 1)
258
259
@@ -277,14 +278,14 @@ func TestConfig_configureProvidersVertexAIWithoutCredentials(t *testing.T) {
277 }
278 }
278
279
279 cfg := &Config{}
280 cfg := &Config{}
280− setDefaults("/tmp", cfg)
281+ cfg.setDefaults("/tmp")
281 env := env.NewFromMap(map[string]string{
282 env := env.NewFromMap(map[string]string{
282 "GOOGLE_GENAI_USE_VERTEXAI": "false",
283 "GOOGLE_GENAI_USE_VERTEXAI": "false",
283 "GOOGLE_CLOUD_PROJECT": "test-project",
284 "GOOGLE_CLOUD_PROJECT": "test-project",
284 "GOOGLE_CLOUD_LOCATION": "us-central1",
285 "GOOGLE_CLOUD_LOCATION": "us-central1",
285 })
286 })
286 resolver := NewEnvironmentVariableResolver(env)
287 resolver := NewEnvironmentVariableResolver(env)
287− err := configureProviders(cfg, env, resolver, knownProviders)
288+ err := cfg.configureProviders(env, resolver, knownProviders)
288 assert.NoError(t, err)
289 assert.NoError(t, err)
289 // Provider should not be configured without proper credentials
290 // Provider should not be configured without proper credentials
290 assert.Len(t, cfg.Providers, 0)
291 assert.Len(t, cfg.Providers, 0)
@@ -303,13 +304,13 @@ func TestConfig_configureProvidersVertexAIMissingProject(t *testing.T) {
303 }
304 }
304
305
305 cfg := &Config{}
306 cfg := &Config{}
306− setDefaults("/tmp", cfg)
307+ cfg.setDefaults("/tmp")
307 env := env.NewFromMap(map[string]string{
308 env := env.NewFromMap(map[string]string{
308 "GOOGLE_GENAI_USE_VERTEXAI": "true",
309 "GOOGLE_GENAI_USE_VERTEXAI": "true",
309 "GOOGLE_CLOUD_LOCATION": "us-central1",
310 "GOOGLE_CLOUD_LOCATION": "us-central1",
310 })
311 })
311 resolver := NewEnvironmentVariableResolver(env)
312 resolver := NewEnvironmentVariableResolver(env)
312− err := configureProviders(cfg, env, resolver, knownProviders)
313+ err := cfg.configureProviders(env, resolver, knownProviders)
313 assert.NoError(t, err)
314 assert.NoError(t, err)
314 // Provider should not be configured without project
315 // Provider should not be configured without project
315 assert.Len(t, cfg.Providers, 0)
316 assert.Len(t, cfg.Providers, 0)
pkg/env/env_test.gonew file+142
@@ -0,0 +1,142 @@
1+package env
2+
3+import (
4+ "os"
5+ "strings"
6+ "testing"
7+
8+ "github.com/stretchr/testify/assert"
9+)
10+
11+func TestOsEnv_Get(t *testing.T) {
12+ env := New()
13+
14+ // Test getting an existing environment variable
15+ os.Setenv("TEST_VAR", "test_value")
16+ defer os.Unsetenv("TEST_VAR")
17+
18+ value := env.Get("TEST_VAR")
19+ assert.Equal(t, "test_value", value)
20+
21+ // Test getting a non-existent environment variable
22+ value = env.Get("NON_EXISTENT_VAR")
23+ assert.Equal(t, "", value)
24+}
25+
26+func TestOsEnv_Env(t *testing.T) {
27+ env := New()
28+
29+ envVars := env.Env()
30+
31+ // Environment should not be empty in normal circumstances
32+ assert.NotNil(t, envVars)
33+ assert.Greater(t, len(envVars), 0)
34+
35+ // Each environment variable should be in key=value format
36+ for _, envVar := range envVars {
37+ assert.Contains(t, envVar, "=")
38+ }
39+}
40+
41+func TestNewFromMap(t *testing.T) {
42+ testMap := map[string]string{
43+ "KEY1": "value1",
44+ "KEY2": "value2",
45+ }
46+
47+ env := NewFromMap(testMap)
48+ assert.NotNil(t, env)
49+ assert.IsType(t, &mapEnv{}, env)
50+}
51+
52+func TestMapEnv_Get(t *testing.T) {
53+ testMap := map[string]string{
54+ "KEY1": "value1",
55+ "KEY2": "value2",
56+ }
57+
58+ env := NewFromMap(testMap)
59+
60+ // Test getting existing keys
61+ assert.Equal(t, "value1", env.Get("KEY1"))
62+ assert.Equal(t, "value2", env.Get("KEY2"))
63+
64+ // Test getting non-existent key
65+ assert.Equal(t, "", env.Get("NON_EXISTENT"))
66+}
67+
68+func TestMapEnv_Env(t *testing.T) {
69+ t.Run("with values", func(t *testing.T) {
70+ testMap := map[string]string{
71+ "KEY1": "value1",
72+ "KEY2": "value2",
73+ }
74+
75+ env := NewFromMap(testMap)
76+ envVars := env.Env()
77+
78+ assert.Len(t, envVars, 2)
79+
80+ // Convert to map for easier testing (order is not guaranteed)
81+ envMap := make(map[string]string)
82+ for _, envVar := range envVars {
83+ parts := strings.SplitN(envVar, "=", 2)
84+ assert.Len(t, parts, 2)
85+ envMap[parts[0]] = parts[1]
86+ }
87+
88+ assert.Equal(t, "value1", envMap["KEY1"])
89+ assert.Equal(t, "value2", envMap["KEY2"])
90+ })
91+
92+ t.Run("empty map", func(t *testing.T) {
93+ env := NewFromMap(map[string]string{})
94+ envVars := env.Env()
95+ assert.Nil(t, envVars)
96+ })
97+
98+ t.Run("nil map", func(t *testing.T) {
99+ env := NewFromMap(nil)
100+ envVars := env.Env()
101+ assert.Nil(t, envVars)
102+ })
103+}
104+
105+func TestMapEnv_GetEmptyValue(t *testing.T) {
106+ testMap := map[string]string{
107+ "EMPTY_KEY": "",
108+ "NORMAL_KEY": "value",
109+ }
110+
111+ env := NewFromMap(testMap)
112+
113+ // Test that empty values are returned correctly
114+ assert.Equal(t, "", env.Get("EMPTY_KEY"))
115+ assert.Equal(t, "value", env.Get("NORMAL_KEY"))
116+}
117+
118+func TestMapEnv_EnvFormat(t *testing.T) {
119+ testMap := map[string]string{
120+ "KEY_WITH_EQUALS": "value=with=equals",
121+ "KEY_WITH_SPACES": "value with spaces",
122+ }
123+
124+ env := NewFromMap(testMap)
125+ envVars := env.Env()
126+
127+ assert.Len(t, envVars, 2)
128+
129+ // Check that the format is correct even with special characters
130+ found := make(map[string]bool)
131+ for _, envVar := range envVars {
132+ if envVar == "KEY_WITH_EQUALS=value=with=equals" {
133+ found["equals"] = true
134+ }
135+ if envVar == "KEY_WITH_SPACES=value with spaces" {
136+ found["spaces"] = true
137+ }
138+ }
139+
140+ assert.True(t, found["equals"], "Should handle values with equals signs")
141+ assert.True(t, found["spaces"], "Should handle values with spaces")
142+}
pkg/log/log.go+3−6
@@ -2,20 +2,17 @@ package log
2
2
3import (
3import (
4 "log/slog"
4 "log/slog"
5− "path/filepath"
6 "sync"
5 "sync"
7
6
8− "github.com/charmbracelet/crush/pkg/config"
9−
10 "gopkg.in/natefinch/lumberjack.v2"
7 "gopkg.in/natefinch/lumberjack.v2"
11)
8)
12
9
13var initOnce sync.Once
10var initOnce sync.Once
14
11
15−func Init(cfg *config.Config) {
12+func Init(logFile string, debug bool) {
16 initOnce.Do(func() {
13 initOnce.Do(func() {
17 logRotator := &lumberjack.Logger{
14 logRotator := &lumberjack.Logger{
18− Filename: filepath.Join(cfg.Options.DataDirectory, "logs", "crush.log"),
15+ Filename: logFile,
19 MaxSize: 10, // Max size in MB
16 MaxSize: 10, // Max size in MB
20 MaxBackups: 0, // Number of backups
17 MaxBackups: 0, // Number of backups
21 MaxAge: 30, // Days
18 MaxAge: 30, // Days
@@ -23,7 +20,7 @@ func Init(cfg *config.Config) {
23 }
20 }
24
21
25 level := slog.LevelInfo
22 level := slog.LevelInfo
26− if cfg.Options.Debug {
23+ if debug {
27 level = slog.LevelDebug
24 level = slog.LevelDebug
28 }
25 }
29
26