Skip to content

Commit 9713948

Browse files
committed
feat(f1): add typed logging configuration options
Add parse helpers and display methods for the typed logging API: - ParseLogLevel(string) (slog.Level, error) — accepts "debug", "info", "warn", "error" (case-insensitive) plus legacy aliases - ParseLogFormat(string) (LogFormat, error) — accepts "text", "json" - LogFormat.String() — returns "text" or "json" These helpers keep string parsing and validation inside f1, so users who receive log level/format from config files or CLI args can convert to typed values without reimplementing the logic. Tests cover valid inputs, case insensitivity, legacy aliases, empty defaults, and invalid input error messages.
1 parent faad580 commit 9713948

File tree

5 files changed

+138
-5
lines changed

5 files changed

+138
-5
lines changed

docs/CODEBASE_REVIEW.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
- **Scenarios:** `WithDescription`, `WithParameter`; `Scenario.RunFn` populated by framework during setup.
5353
- **f1testing.T:** `NewTWithOptions(scenarioName, ...TOption)`; `WithLogger`, `WithIteration`, `WithVUID`; `Error`/`Fatal`/`Log` with `args ...any` (testing.T compatible).
5454
- **Options:** All consolidated in `options.go`; types in `settings.go`. Precedence: programmatic options > env vars > defaults. `WithLogger` takes precedence over log level/format options.
55-
- **Design decisions:** No `Config` struct (bundles unrelated concerns), no `SettingsProvider` (lazy eval adds no benefit). `WithSettings(Settings{})` replaces `WithoutEnvSettings()` — explicit, no order-dependence. Typed logging APIs (`slog.Level`, `LogFormat`) instead of strings.
55+
- **Design decisions:** No `Config` struct (bundles unrelated concerns), no `SettingsProvider` (lazy eval adds no benefit). `WithSettings(Settings{})` replaces `WithoutEnvSettings()` — explicit, no order-dependence. Typed logging APIs (`slog.Level`, `LogFormat`) instead of strings. `ParseLogLevel`/`ParseLogFormat` helpers for users who need string-to-type conversion (e.g., from config files); validation lives inside f1.
5656

5757
### Suggested Improvements
5858

docs/V3_PLAN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ type RunFn func(ctx context.Context, t *T)
9090
- Public types: `Settings`, `PrometheusSettings`, `LoggingSettings`, `LogFormat` (no string-key API)
9191
- `WithSettings(Settings)` replaces baseline (pass `Settings{}` to ignore env vars); replaces `WithoutEnvSettings`
9292
- `WithLogLevel(slog.Level)`, `WithLogFormat(LogFormat)` — strongly typed
93+
- `ParseLogLevel(string)`, `ParseLogFormat(string)` — parse helpers with validation
94+
- `LogFormat.String()` — display helper
9395
- `WithPrometheusPushGateway`, `WithPrometheusNamespace`, `WithPrometheusLabelID`, `WithLogFilePath` — fine-grained overrides
9496
- `DefaultSettings()` — loads from env vars (backward-compat baseline)
9597
- All options consolidated in `options.go`; types in `settings.go`

docs/plan.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ func WithLogFilePath(path string) Option
131131
func WithLogLevel(level slog.Level) Option // typed; compile-time safe
132132
func WithLogFormat(format LogFormat) Option // typed; compile-time safe
133133

134+
// Parse helpers for string-to-typed conversion (validation inside f1):
135+
func ParseLogLevel(string) (slog.Level, error)
136+
func ParseLogFormat(string) (LogFormat, error)
137+
func (LogFormat) String() string // "text" or "json"
138+
134139
func New(opts ...Option) *F1
135140
```
136141

pkg/f1/options_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,80 @@ func TestDefaultSettingsReturnsDefaults(t *testing.T) {
171171
require.Equal(t, slog.LevelInfo, s.Logging.Level)
172172
require.Equal(t, f1.LogFormatText, s.Logging.Format)
173173
}
174+
175+
func TestParseLogLevel(t *testing.T) {
176+
t.Parallel()
177+
178+
tests := []struct {
179+
input string
180+
want slog.Level
181+
}{
182+
{"debug", slog.LevelDebug},
183+
{"DEBUG", slog.LevelDebug},
184+
{"trace", slog.LevelDebug},
185+
{"info", slog.LevelInfo},
186+
{"INFO", slog.LevelInfo},
187+
{"", slog.LevelInfo},
188+
{"warn", slog.LevelWarn},
189+
{"warning", slog.LevelWarn},
190+
{"error", slog.LevelError},
191+
{"fatal", slog.LevelError},
192+
{"panic", slog.LevelError},
193+
}
194+
for _, tt := range tests {
195+
t.Run(tt.input, func(t *testing.T) {
196+
t.Parallel()
197+
198+
got, err := f1.ParseLogLevel(tt.input)
199+
require.NoError(t, err)
200+
require.Equal(t, tt.want, got)
201+
})
202+
}
203+
}
204+
205+
func TestParseLogLevelInvalid(t *testing.T) {
206+
t.Parallel()
207+
208+
_, err := f1.ParseLogLevel("invalid")
209+
require.Error(t, err)
210+
require.ErrorContains(t, err, "unknown log level")
211+
}
212+
213+
func TestParseLogFormat(t *testing.T) {
214+
t.Parallel()
215+
216+
tests := []struct {
217+
input string
218+
want f1.LogFormat
219+
}{
220+
{"text", f1.LogFormatText},
221+
{"TEXT", f1.LogFormatText},
222+
{"", f1.LogFormatText},
223+
{"json", f1.LogFormatJSON},
224+
{"JSON", f1.LogFormatJSON},
225+
}
226+
for _, tt := range tests {
227+
t.Run(tt.input, func(t *testing.T) {
228+
t.Parallel()
229+
230+
got, err := f1.ParseLogFormat(tt.input)
231+
require.NoError(t, err)
232+
require.Equal(t, tt.want, got)
233+
})
234+
}
235+
}
236+
237+
func TestParseLogFormatInvalid(t *testing.T) {
238+
t.Parallel()
239+
240+
_, err := f1.ParseLogFormat("yaml")
241+
require.Error(t, err)
242+
require.ErrorContains(t, err, "unknown log format")
243+
}
244+
245+
func TestLogFormatString(t *testing.T) {
246+
t.Parallel()
247+
248+
require.Equal(t, "text", f1.LogFormatText.String())
249+
require.Equal(t, "json", f1.LogFormatJSON.String())
250+
}

pkg/f1/settings.go

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package f1
22

33
import (
4+
"fmt"
45
"log/slog"
56
"strings"
67

@@ -17,6 +18,54 @@ const (
1718
LogFormatJSON
1819
)
1920

21+
const (
22+
logFormatTextStr = "text"
23+
logFormatJSONStr = "json"
24+
logLevelInfoStr = "info"
25+
)
26+
27+
// String returns "text" or "json".
28+
func (f LogFormat) String() string {
29+
if f == LogFormatJSON {
30+
return logFormatJSONStr
31+
}
32+
33+
return logFormatTextStr
34+
}
35+
36+
// ParseLogLevel parses a log level string into slog.Level.
37+
// Accepted values (case-insensitive): "debug", "info", "warn", "error".
38+
// Also accepts legacy aliases: "trace" (→ Debug), "warning" (→ Warn),
39+
// "fatal"/"panic" (→ Error). Empty string defaults to Info.
40+
func ParseLogLevel(s string) (slog.Level, error) {
41+
switch strings.ToLower(strings.TrimSpace(s)) {
42+
case "debug", "trace":
43+
return slog.LevelDebug, nil
44+
case logLevelInfoStr, "":
45+
return slog.LevelInfo, nil
46+
case "warn", "warning":
47+
return slog.LevelWarn, nil
48+
case "error", "fatal", "panic":
49+
return slog.LevelError, nil
50+
default:
51+
return 0, fmt.Errorf("unknown log level %q: use debug, info, warn, or error", s)
52+
}
53+
}
54+
55+
// ParseLogFormat parses a log format string into LogFormat.
56+
// Accepted values (case-insensitive): "text", "json".
57+
// Empty string defaults to LogFormatText.
58+
func ParseLogFormat(s string) (LogFormat, error) {
59+
switch strings.ToLower(strings.TrimSpace(s)) {
60+
case logFormatTextStr, "":
61+
return LogFormatText, nil
62+
case logFormatJSONStr:
63+
return LogFormatJSON, nil
64+
default:
65+
return 0, fmt.Errorf("unknown log format %q: use %s or %s", s, logFormatTextStr, logFormatJSONStr)
66+
}
67+
}
68+
2069
// LoggingSettings configures the default logger built by f1.
2170
// These settings have no effect when WithLogger is used.
2271
type LoggingSettings struct {
@@ -67,7 +116,7 @@ func DefaultSettings() Settings {
67116
func (s Settings) toInternal() envsettings.Settings {
68117
var format string
69118
if s.Logging.Format == LogFormatJSON {
70-
format = "json"
119+
format = logFormatJSONStr
71120
}
72121

73122
return envsettings.Settings{
@@ -85,7 +134,7 @@ func (s Settings) toInternal() envsettings.Settings {
85134
}
86135

87136
func logFormatFromEnv(s string) LogFormat {
88-
if strings.EqualFold(s, "json") {
137+
if strings.EqualFold(s, logFormatJSONStr) {
89138
return LogFormatJSON
90139
}
91140

@@ -97,12 +146,12 @@ func slogLevelToString(level slog.Level) string {
97146
case slog.LevelDebug:
98147
return "debug"
99148
case slog.LevelInfo:
100-
return "info"
149+
return logLevelInfoStr
101150
case slog.LevelWarn:
102151
return "warn"
103152
case slog.LevelError:
104153
return "error"
105154
default:
106-
return "info"
155+
return logLevelInfoStr
107156
}
108157
}

0 commit comments

Comments
 (0)