Clear, safe, consistent Go log messages.
Static analysis for log/slog and go.uber.org/zap
Literal log messages are one of the easiest places for drift, inconsistency, and accidental secrets to slip into a Go codebase. logsLinter turns that into an enforceable rule set for log/slog and go.uber.org/zap, with a standalone analyzer, golangci-lint module plugin support, and safe autofix coverage for the simplest lowercase-start violations.
Built on golang.org/x/tools/go/analysis, it is meant to fit into normal Go workflows instead of becoming its own ecosystem.
- Keep log messages consistent across large teams and services.
- Catch obvious sensitive-data wording before it reaches production logs.
- Enforce log quality in CI instead of code review after the fact.
- Run the same analyzer as a standalone binary or through
golangci-lint.
- Log messages must start with a lowercase letter.
- Log messages must contain English ASCII text only.
- Log messages must not contain decorative special characters or emoji.
- Log messages must not contain sensitive keywords such as
password,token, orsecret.
For the lowercase-start rule, logsLinter also emits a safe SuggestedFix when the message is an interpreted string literal whose first rune is an ASCII uppercase letter.
Build the standalone analyzer:
go build -o ./bin/logslinter ./cmd/logslinterRun it directly on packages:
./bin/logslinter ./...Or use it through go vet as a vettool:
go vet -vettool=$(pwd)/bin/logslinter ./...The command exits with a non-zero status when diagnostics are reported or package loading fails.
Task from tech_spec.pdf |
Status |
|---|---|
Support log/slog log calls |
Done |
Support go.uber.org/zap log calls |
Done |
| Enforce lowercase-start log messages | Done |
| Enforce English-only ASCII log messages | Done |
| Reject decorative special characters and emoji | Done |
| Detect potentially sensitive data keywords in log messages | Done |
Integrate as a go/analysis analyzer |
Done |
| Work as a standalone binary | Done |
Work through golangci-lint module plugin flow |
Done |
| Skip dynamic and non-literal messages to avoid false positives | Done |
| Emit precise diagnostics with file position and rule meaning | Done |
Cover behavior with analysistest and fixture-based tests |
Done |
| Provide README build, usage, and integration documentation | Done |
Bonus: rule configuration via .golangci.yml |
Done |
Bonus: safe SuggestedFixes for auto-correctable cases |
Done |
| Bonus: CI/CD pipeline for build, test, and release automation | Done |
Input:
slog.Info("Starting auth token rotation")Reported diagnostics:
internal/service/auth.go:42:18: log message must start with a lowercase letter
internal/service/auth.go:42:18: log message may contain sensitive data
Safe autofix example:
// before
slog.Info("Starting server")
// after SuggestedFix
slog.Info("starting server")log/slogtop-level calls:Debug,Info,Warn,Errorlog/slogcontext variants:DebugContext,InfoContext,WarnContext,ErrorContextlog/slogstructured variants:Log,LogAttrs*slog.Loggermethods, including chainedWith(...)andWithGroup(...)*zap.Loggermethods:Debug,Info,Warn,Error*zap.SugaredLoggermethods with explicit message argument:Debugw,Infow,Warnw,Errorw
- Go 1.24+
The standalone analyzer exposes rule toggles and custom sensitive keywords:
./bin/logslinter \
-lowercase-start=true \
-english-ascii-only=true \
-no-special-chars-or-emoji=true \
-no-sensitive-data=true \
-additional-sensitive-keywords=credential,session_id \
./...The repository includes example module-plugin configuration:
.custom-gcl.ymlbuilds a customgolangci-lintbinary withlogsLinterlinked in..golangci.ymlenables the custom linter and shows analyzer settings.
Build the custom binary:
golangci-lint customWith the example config in this repository, the binary is written to ./bin/custom-golangci-lint.
Run it:
./bin/custom-golangci-lint run ./...Minimal local-path plugin config:
version: v2.8.0
name: custom-golangci-lint
destination: ./bin
plugins:
- module: github.com/rTexty/logsLinter
import: github.com/rTexty/logsLinter/plugin
path: .Analyzer settings example:
version: "2"
linters:
default: none
enable:
- logslinter
settings:
custom:
logslinter:
type: module
description: Validate literal slog and zap log messages with logsLinter.
settings:
rules:
lowercase-start: true
english-ascii-only: true
no-special-chars-or-emoji: true
no-sensitive-data: true
sensitive-data:
additional-keywords:
- credential
- session_idAvailable plugin settings:
rules.lowercase-startrules.english-ascii-onlyrules.no-special-chars-or-emojirules.no-sensitive-datasensitive-data.additional-keywords
Common commands:
# Build
go build ./...
# Build standalone analyzer binary
go build -o ./bin/logslinter ./cmd/logslinter
# Test
go test ./... -race -count=1
# Format
gofmt -w .
# Tidy dependencies
go mod tidy- Unit coverage exists for rule evaluation, extraction, diagnostics, and logger call inspection.
- Integration coverage runs through
analysistestfixtures forslog,zap, and mixed edge cases. - Configurable rule toggles and additional sensitive keywords are covered in analyzer and plugin tests.
- Current local verification baseline is
go test ./....
Skipped cases:
- Non-literal messages such as variables, function calls, and
fmt.Sprintf(...) - Literal plus variable concatenation such as
"password: " + secret zap.SugaredLoggerprint-style methods such asInfo(...),Warn(...),Error(...)zap.SugaredLoggerformat-style methods such asInfof(...),Warnf(...),Errorf(...)
Known limitations:
- Only string literals and literal-only concatenations are analyzed.
- Dynamic messages such as variables,
fmt.Sprintf(...), and mixed literal-plus-variable expressions are intentionally skipped. zap.SugaredLoggerprint-style and format-style methods stay out of scope in the initial release.- The lowercase-start
SuggestedFixis intentionally limited to interpreted string literals with an ASCII uppercase first rune.
If golangci-lint custom fails, make sure the version in .custom-gcl.yml matches the version used in CI and your local build flow. This repository currently pins the custom binary workflow to v2.8.0 for compatibility with the current release setup.
If the standalone analyzer returns a non-zero exit code, that is expected when diagnostics are found. It does not automatically mean package loading failed.
Recommended release checklist:
gofmt -l .go test ./... -race -count=1go build ./...go build -o ./bin/logslinter ./cmd/logslintergolangci-lint custom -v./bin/custom-golangci-lint run ./...- Verify standalone output on a sample package or repository
Create and push the first release tag:
git tag v0.1.0
git push origin v0.1.0The release workflow will:
- run tests before packaging
- build
logslinterfor Linux, macOS, and Windows - upload
.tar.gzand.zipartifacts - publish a GitHub Release with generated notes and
SHA256SUMS.txt
Release notes and checklist policy are documented in CHANGELOG.md, docs/release-policy.md, and .github/release.yml.
- GitHub Actions CI runs formatting checks,
go vet, tests, build, and repository linting. - CodeQL runs on pull requests and on a weekly schedule.
- Dependency review runs on pull requests.
- Dependabot tracks both Go modules and GitHub Actions.
- Releases are built from Git tags matching
v*and publish packaged binaries plusSHA256SUMS.txt. - Recommended GitHub branch ruleset settings are documented in
docs/github-ruleset.md.