Generate domain-specific wrappers for log/slog
.
When using log/slog
in a production-grade project, it is useful to write helpers to prevent typos in the keys:
slog.Info("a user has logged in", "user_id", 42)
slog.Info("a user has logged out", "user_ip", 42) // oops :(
Depending on your code style, these can be simple constants (if you prefer key-value pairs)...
const UserId = "user_id"
...or custom slog.Attr
constructors (if you're a safety/performance advocate):
func UserId(value int) slog.Attr { return slog.Int("user_id", value) }
sloggen
generates such helpers for you, so you don't have to write them manually.
The default log/slog
levels cover most use cases, but at some point you may want to introduce custom levels that better suit your app.
At first glance, this is as simple as defining a constant:
const LevelAlert = slog.Level(12)
However, custom levels are treated differently than the first-class citizens Debug
/Info
/Warn
/Error
:
slog.Log(nil, LevelAlert, "msg") // want "ALERT msg"; got "ERROR+4 msg"
sloggen
solves this inconvenience by generating not only the levels themselves, but also the necessary helpers.
Unfortunately, the only way to use such levels is the Log
method, which is quite verbose.
sloggen
can generate a custom Logger
type so that custom levels can be used just like the builtin ones:
// before:
logger.Log(nil, LevelAlert, "msg", "key", "value")
// after:
logger.Alert("msg", "key", "value")
Additionally, there are options to choose the API style of the arguments (...any
or ...slog.Attr
) and to add/remove context.Context
as the first parameter.
This allows you to adjust the logging API to your own code style without sacrificing convenience.
Tip
Various API rules for log/slog
can be enforced by the sloglint
linter. Give it a try too!
- Generate key constants and
slog.Attr
constructors - Generate custom levels with helpers for parsing/printing
- Generate a custom
Logger
type with methods for custom levels - Codegen-based, so no runtime dependency introduced
Add the following directive to any .go
file and run go generate ./...
.
//go:generate go run go-simpler.org/sloggen@<version> [flags]
Where <version>
is the version of sloggen
itself (use latest
for automatic updates) and [flags]
is the list of available options.
There are two ways to provide options to sloggen
: CLI flags and a .yml
config file.
The former works best for few options and requires only a single //go:generate
directive.
For many options it may be more convenient to use a config file, since go generate
does not support multiline commands.
The config file can also be reused between several (micro)services if they share the same domain.
To get started, see the example_test.go
file and the example
directory.
The -c
flag (or the consts
field) is used to generate a key constant.
For example, -c=used_id
results in:
const UserId = "user_id"
The -a
flag (or the attrs
field) is used to generate a custom slog.Attr
constructor.
For example, -a=used_id:int
results in:
func UserId(value int) slog.Attr { return slog.Int("user_id", value) }
The -l
flag (or the levels
field) is used to generate a custom slog.Level
.
For example, -l=alert:12
results in:
const LevelAlert = slog.Level(12)
func ParseLevel(s string) (slog.Level, error) {...}
func RenameLevels(_ []string, attr slog.Attr) slog.Attr {...}
The ParseLevel
function should be used to parse the level from a string (e.g. from an environment variable):
level, err := slogx.ParseLevel("ALERT")
The RenameLevels
function should be used as slog.HandlerOptions.ReplaceAttr
to print custom level names correctly:
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
ReplaceAttr: slogx.RenameLevels,
}))
The -logger
flag (or the logger
field) is used to generate a custom Logger
type with methods for custom levels.
The -api
flag (or the logger.api
field) is used to choose the API style of the arguments: any
for ...any
(key-value pairs) and attr
for ...slog.Attr
.
The -ctx
flag (or the logger.ctx
field) is used to add or remove context.Context
as the first parameter.
For example, -l=alert:12 -logger -api=attr -ctx
results in:
type Logger struct{ handler slog.Handler }
func New(h slog.Handler) *Logger { return &Logger{handler: h} }
func (l *Logger) Alert(ctx context.Context, msg string, attrs ...slog.Attr) {...}
The generated Logger
has all the utility methods of the original slog.Logger
, including Enabled()
, With()
and WithGroup()
.
Since Logger
is just a frontend, you can always fall back to slog.Logger
(e.g. to pass it to a library) using the Handler()
method:
slog.New(logger.Handler())
Usage: sloggen [flags]
Flags:
-config <path> read config from the file instead of flags
-dir <path> change the working directory before generating files
-pkg <name> the name for the generated package (default: slogx)
-i <import> add import
-l <name:severity> add level
-c <key> add constant
-a <key:type> add attribute
-logger generate a custom Logger type
-api <any|attr> the API style for the Logger's methods (default: any)
-ctx add context.Context to the Logger's methods
-h, -help print this message and quit
For the description of the config file fields, see .slog.example.yml
.