A Go linter that ensures consistent code style when using log/slog.
The log/slog API allows two different types of arguments: key-value pairs and attributes.
While people may have different opinions about which one is better, most seem to agree on one thing: it should be consistent.
With sloglint you can enforce various rules for log/slog based on your preferred code style.
- Enforce not mixing key-value pairs and attributes (default)
- Enforce using either key-value pairs only or attributes only (optional)
- Enforce not using global loggers (optional)
- Enforce using methods that accept a context (optional)
- Enforce using static messages (optional)
- Enforce message style (optional)
- Enforce using constants instead of raw keys (optional)
- Enforce key naming convention (optional)
- Enforce not using specific keys (optional)
- Enforce putting arguments on separate lines (optional)
sloglint is integrated into golangci-lint, and this is the recommended way to use it.
To enable the linter, add the following lines to .golangci.yml:
linters:
enable:
- sloglintAlternatively, you can download a prebuilt binary from the Releases page to use sloglint standalone.
Run golangci-lint with sloglint enabled.
See the list of available options to configure the linter.
When using sloglint standalone, pass the options as flags of the same name.
The no-mixed-args option causes sloglint to report mixing key-values pairs and attributes within a single function call:
slog.Info("a user has logged in", "user_id", 42, slog.String("ip_address", "192.0.2.0")) // sloglint: key-value pairs and attributes should not be mixedIt is enabled by default.
The kv-only option causes sloglint to report any use of attributes:
slog.Info("a user has logged in", slog.Int("user_id", 42)) // sloglint: attributes should not be usedIn contrast, the attr-only option causes sloglint to report any use of key-value pairs:
slog.Info("a user has logged in", "user_id", 42) // sloglint: key-value pairs should not be usedSome projects prefer to pass loggers as explicit dependencies.
The no-global option causes sloglint to report the use of global loggers.
slog.Info("a user has logged in", "user_id", 42) // sloglint: global logger should not be usedPossible values are all (report all global loggers) and default (report only the default slog logger).
Some slog.Handler implementations make use of the given context.Context (e.g. to access context values).
For them to work properly, you need to pass a context to all logger calls.
The context-only option causes sloglint to report the use of methods without a context:
slog.Info("a user has logged in") // sloglint: InfoContext should be used insteadPossible values are all (report all contextless calls) and scope (report only if a context exists in the scope of the outermost function).
To get the most out of structured logging, you may want to require log messages to be static.
The static-msg option causes sloglint to report non-static messages:
slog.Info(fmt.Sprintf("a user with id %d has logged in", 42)) // sloglint: message should be a string literal or a constantThe report can be fixed by moving dynamic values to arguments:
slog.Info("a user has logged in", "user_id", 42)The msg-style option causes sloglint to check log messages for a particular style.
Possible values are lowercased (report messages that begin with an uppercase letter)...
slog.Info("Msg") // sloglint: message should be lowercased...and capitalized (report messages that begin with a lowercase letter):
slog.Info("msg") // sloglint: message should be capitalizedSpecial cases such as acronyms (e.g. HTTP, U.S.) are ignored.
To prevent typos, you may want to forbid the use of raw keys altogether.
The no-raw-keys option causes sloglint to report the use of strings as keys
(including slog.Attr calls, e.g. slog.Int("user_id", 42)):
slog.Info("a user has logged in", "user_id", 42) // sloglint: raw keys should not be usedThis report can be fixed by using either constants...
const UserId = "user_id"
slog.Info("a user has logged in", UserId, 42)...or custom slog.Attr constructors:
func UserId(value int) slog.Attr { return slog.Int("user_id", value) }
slog.Info("a user has logged in", UserId(42))Tip
Such helpers can be automatically generated for you by the sloggen tool. Give it a try too!
To ensure consistency in logs, you may want to enforce a single key naming convention.
The key-naming-case option causes sloglint to report keys written in a case other than the given one:
slog.Info("a user has logged in", "user-id", 42) // sloglint: keys should be written in snake_casePossible values are snake, kebab, camel, or pascal.
To prevent accidental use of reserved log keys, you may want to forbid specific keys altogether.
The forbidden-keys option causes sloglint to report the use of forbidden keys:
slog.Info("a user has logged in", "reserved", 42) // sloglint: "reserved" key is forbidden and should not be usedFor example, when using the standard slog.JSONHandler and slog.TextHandler,
you may want to forbid the time, level, msg, and source keys, as these are used by the handlers.
To improve code readability, you may want to put arguments on separate lines, especially when using key-value pairs.
The args-on-sep-lines option causes sloglint to report 2+ arguments on the same line:
slog.Info("a user has logged in", "user_id", 42, "ip_address", "192.0.2.0") // sloglint: arguments should be put on separate linesThis report can be fixed by reformatting the code:
slog.Info("a user has logged in",
"user_id", 42,
"ip_address", "192.0.2.0",
)