Skip to content

Commit 3740aab

Browse files
authored
logging: configure default fallbacks for NewFromEnv (#420)
1 parent ea765ef commit 3740aab

File tree

2 files changed

+102
-22
lines changed

2 files changed

+102
-22
lines changed

logging/logger.go

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -89,39 +89,119 @@ func New(w io.Writer, level slog.Level, format Format, debug bool) *slog.Logger
8989
// - LOG_LEVEL: string representation of the log level. It panics if no such log level exists.
9090
// - LOG_FORMAT: format in which to output logs (e.g. json, text). It panics if no such format exists.
9191
// - LOG_DEBUG: enable the most detailed debug logging. It panics iff the given value is not a valid boolean.
92-
func NewFromEnv(envPrefix string) *slog.Logger {
93-
return newFromEnv(envPrefix, os.Getenv)
92+
//
93+
// You can customize the default values for when no environment variables are
94+
// found using [Option] like [WithDefaultLevel].
95+
func NewFromEnv(envPrefix string, opts ...Option) *slog.Logger {
96+
return newFromEnv(envPrefix, opts...)
97+
}
98+
99+
// options is a holding structure for configurable options.
100+
type options struct {
101+
level slog.Level
102+
format Format
103+
debug bool
104+
target *os.File
105+
getenv func(s string) string
106+
}
107+
108+
// Option represents a configuration function for the logger. It's primarily
109+
// used with [NewFromEnv] to set defaults.
110+
type Option func(o *options) *options
111+
112+
// WithDefaultLevel sets the default level of the logger if no value is set.
113+
func WithDefaultLevel(l slog.Level) Option {
114+
return func(o *options) *options {
115+
o.level = l
116+
return o
117+
}
118+
}
119+
120+
// WithDefaultFormat sets the default format of the logger if no value is set.
121+
func WithDefaultFormat(f Format) Option {
122+
return func(o *options) *options {
123+
o.format = f
124+
return o
125+
}
126+
}
127+
128+
// WithDefaultDebug sets the default debug mode of the logger if no value is set.
129+
func WithDefaultDebug(b bool) Option {
130+
return func(o *options) *options {
131+
o.debug = b
132+
return o
133+
}
134+
}
135+
136+
// WithDefaultTarget sets the default output of the logger if no value is set.
137+
// If you use something other than [os.Stdout] or [os.Stderr], the caller is
138+
// responsible for closing the provided [os.File] handle.
139+
func WithDefaultTarget(t *os.File) Option {
140+
return func(o *options) *options {
141+
o.target = t
142+
return o
143+
}
144+
}
145+
146+
// WithGetenv overrides the function to get envvars. It's primarily used for
147+
// testing.
148+
func WithGetenv(f func(string) string) Option {
149+
return func(o *options) *options {
150+
o.getenv = f
151+
return o
152+
}
94153
}
95154

96155
// newFromEnv is a helper that makes it easier to test [NewFromEnv].
97-
func newFromEnv(envPrefix string, getenvFunc func(string) string) *slog.Logger {
98-
levelEnvVarKey, levelEnvVarValue := multiGetenv(getenvFunc, envPrefix+"LOG_LEVEL", "LOG_LEVEL")
99-
level, err := LookupLevel(levelEnvVarValue)
100-
if err != nil {
101-
panic(fmt.Sprintf("log level: invalid value for %s: %s", levelEnvVarKey, err))
156+
func newFromEnv(envPrefix string, opts ...Option) *slog.Logger {
157+
o := &options{
158+
level: slog.LevelInfo,
159+
format: FormatJSON,
160+
target: os.Stdout,
161+
debug: false,
162+
getenv: os.Getenv,
163+
}
164+
for _, opt := range opts {
165+
o = opt(o)
166+
}
167+
168+
levelEnvVarKey, levelEnvVarValue := multiGetenv(o.getenv, envPrefix+"LOG_LEVEL", "LOG_LEVEL")
169+
if levelEnvVarValue != "" {
170+
level, err := LookupLevel(levelEnvVarValue)
171+
if err != nil {
172+
panic(fmt.Sprintf("log level: invalid value for %s: %s", levelEnvVarKey, err))
173+
}
174+
o.level = level
102175
}
103176

104-
formatEnvVarKey, formatEnvVarValue := multiGetenv(getenvFunc, envPrefix+"LOG_FORMAT", "LOG_FORMAT")
105-
format, err := LookupFormat(formatEnvVarValue)
106-
if err != nil {
107-
panic(fmt.Sprintf("log format: invalid value for %s: %s", formatEnvVarKey, err))
177+
formatEnvVarKey, formatEnvVarValue := multiGetenv(o.getenv, envPrefix+"LOG_FORMAT", "LOG_FORMAT")
178+
if formatEnvVarValue != "" {
179+
format, err := LookupFormat(formatEnvVarValue)
180+
if err != nil {
181+
panic(fmt.Sprintf("log format: invalid value for %s: %s", formatEnvVarKey, err))
182+
}
183+
o.format = format
108184
}
109185

110-
debugEnvVarKey, debugEnvVarValue := multiGetenv(getenvFunc, envPrefix+"LOG_DEBUG", "LOG_DEBUG")
111-
debug, err := strconv.ParseBool(debugEnvVarValue)
112-
if err != nil {
113-
if debugEnvVarValue != "" {
186+
debugEnvVarKey, debugEnvVarValue := multiGetenv(o.getenv, envPrefix+"LOG_DEBUG", "LOG_DEBUG")
187+
if debugEnvVarValue != "" {
188+
debug, err := strconv.ParseBool(debugEnvVarValue)
189+
if err != nil {
114190
panic(fmt.Sprintf("log debug: invalid value for %s: %s", debugEnvVarKey, err))
115191
}
192+
o.debug = debug
116193
}
117194

118-
targetEnvVarKey, targetEnvVarValue := multiGetenv(getenvFunc, envPrefix+"LOG_TARGET", "LOG_TARGET")
119-
target, err := LookupTarget(targetEnvVarValue)
120-
if err != nil {
121-
panic(fmt.Sprintf("log target: invalid value for %s: %s", targetEnvVarKey, err))
195+
targetEnvVarKey, targetEnvVarValue := multiGetenv(o.getenv, envPrefix+"LOG_TARGET", "LOG_TARGET")
196+
if targetEnvVarValue != "" {
197+
target, err := LookupTarget(targetEnvVarValue)
198+
if err != nil {
199+
panic(fmt.Sprintf("log target: invalid value for %s: %s", targetEnvVarKey, err))
200+
}
201+
o.target = target
122202
}
123203

124-
return New(target, level, format, debug)
204+
return New(o.target, o.level, o.format, o.debug)
125205
}
126206

127207
// multiGetenv is a helper function for looking up a collection of environment

logging/logger_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ func TestNewFromEnv(t *testing.T) {
197197
}
198198
}()
199199

200-
logger := newFromEnv(tc.envPrefix, func(k string) string {
200+
logger := newFromEnv(tc.envPrefix, WithGetenv(func(k string) string {
201201
return tc.env[k]
202-
})
202+
}))
203203

204204
if !logger.Handler().Enabled(ctx, tc.wantLevel) {
205205
t.Errorf("expected handler to be at least %s", tc.wantLevel)

0 commit comments

Comments
 (0)