mirror of
https://github.com/juanfont/headscale.git
synced 2026-07-08 00:50:20 +09:00
hscontrol/types: silence zerolog by default in tests
Running go test ./... produced an extreme amount of zerolog output because
test binaries inherited the package default of DebugLevel. Per-test mitigations
were scattered across roughly fifty test functions and most of them were
subtly broken: they restored to DebugLevel instead of the prior level, leaking
state into subsequent tests in the same binary.
Add a single init() in hscontrol/types/testlog.go that detects test mode via
testing.Testing() and lowers the global zerolog level to ErrorLevel. The types
package is transitively imported by every test binary that emits zerolog
output, so this one insertion point silences go test ./... without any
per-package boilerplate. Production binaries are unaffected because
testing.Testing() returns false outside of test execution; the same pattern
already exists in hscontrol/db/{users,node}.go.
Set HEADSCALE_TEST_LOG_LEVEL=trace|debug|info|warn|error|disabled to override
when debugging a specific test.
hscontrol/util/zlog/zlog_test.go asserts on Info-level output from a
zerolog.New(&buf) logger, which is also gated by the global level. Add a
defensive init_test.go in that package pinning the global level to TraceLevel
so the assertions stay reliable if a future import chain pulls
hscontrol/types into the zlog test binary.
Document the mechanism, the env var override, the save/restore idiom for
per-test overrides, and the relevant pitfalls under Testing Guidelines in
AGENTS.md.
This commit is contained in:
@@ -447,6 +447,37 @@ All test runs save comprehensive debugging artifacts to `control_logs/TIMESTAMP-
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
### Test Log Output
|
||||
|
||||
By default, `go test ./...` runs with zerolog set to `ErrorLevel`. This is configured centrally by an `init()` in `hscontrol/types/testlog.go`, which uses `testing.Testing()` to detect test mode and only applies to test binaries — production binaries are unaffected. Because `hscontrol/types` is transitively imported by every package that emits zerolog output, this single insertion point silences all `go test` runs without any per-package boilerplate.
|
||||
|
||||
To re-enable verbose logs while debugging a specific test, set the `HEADSCALE_TEST_LOG_LEVEL` environment variable to any zerolog level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`, `disabled`):
|
||||
|
||||
```bash
|
||||
# Show all zerolog output for one package
|
||||
HEADSCALE_TEST_LOG_LEVEL=trace go test -v ./hscontrol/mapper/...
|
||||
|
||||
# See debug and above for a single test
|
||||
HEADSCALE_TEST_LOG_LEVEL=debug go test -v ./hscontrol/db/... -run TestNodeRegistration
|
||||
|
||||
# Make CI fully silent (suppress even errors)
|
||||
HEADSCALE_TEST_LOG_LEVEL=disabled go test ./...
|
||||
```
|
||||
|
||||
**Do not add per-test `zerolog.SetGlobalLevel` calls.** The central mechanism handles the default. If a specific test genuinely needs a different level (e.g., a benchmark that wants to assert on log output, or a test that needs to capture errors), use the save/restore idiom so subsequent tests in the same binary are not affected:
|
||||
|
||||
```go
|
||||
originalLevel := zerolog.GlobalLevel()
|
||||
defer zerolog.SetGlobalLevel(originalLevel)
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
```
|
||||
|
||||
**Pitfalls**:
|
||||
|
||||
- `log.Fatal()` still calls `os.Exit(1)` and `log.Panic()` still panics regardless of level — only the rendered message is suppressed.
|
||||
- Local buffer loggers created via `zerolog.New(&buf)` are also gated by the global level. Tests that assert on log output (currently only `hscontrol/util/zlog`) re-enable trace level via their own `init_test.go`.
|
||||
- Avoid the broken `defer zerolog.SetGlobalLevel(zerolog.DebugLevel)` pattern. It restores to `DebugLevel` instead of the prior level, polluting global state for subsequent tests in the same binary.
|
||||
|
||||
### Integration Test Patterns
|
||||
|
||||
#### **CRITICAL: EventuallyWithT Pattern for External Calls**
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// EnvTestLogLevel overrides the default test log level. Accepts any zerolog
|
||||
// level string: trace, debug, info, warn, error, fatal, panic, disabled.
|
||||
const EnvTestLogLevel = "HEADSCALE_TEST_LOG_LEVEL"
|
||||
|
||||
// init quiets zerolog when this package is loaded inside a test binary.
|
||||
//
|
||||
// hscontrol/types is transitively imported by every test in the repo that
|
||||
// emits zerolog output, so this init() runs once per test binary and is
|
||||
// the only place that needs to know about test logging configuration.
|
||||
//
|
||||
// Default: ErrorLevel (silent in green-path runs, real errors still surface).
|
||||
// Override: HEADSCALE_TEST_LOG_LEVEL=debug (or trace, info, warn, disabled).
|
||||
//
|
||||
// Production binaries are unaffected because testing.Testing() returns false
|
||||
// outside of test execution. The same testing.Testing() pattern is already
|
||||
// used in hscontrol/db/users.go and hscontrol/db/node.go, so importing the
|
||||
// testing package here is consistent with existing project conventions.
|
||||
//
|
||||
// Pitfalls:
|
||||
// - log.Fatal still calls os.Exit and log.Panic still panics regardless of
|
||||
// level — only the rendered message is suppressed.
|
||||
// - Local buffer loggers (zerolog.New(&buf)) are also gated by the global
|
||||
// level. Tests that assert on log output (currently only
|
||||
// hscontrol/util/zlog) re-enable trace level via their own init_test.go.
|
||||
func init() {
|
||||
if !testing.Testing() {
|
||||
return
|
||||
}
|
||||
|
||||
if raw := os.Getenv(EnvTestLogLevel); raw != "" {
|
||||
lvl, err := zerolog.ParseLevel(raw)
|
||||
if err == nil {
|
||||
zerolog.SetGlobalLevel(lvl)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package zlog
|
||||
|
||||
import "github.com/rs/zerolog"
|
||||
|
||||
// init pins zerolog to TraceLevel for the zlog test binary.
|
||||
//
|
||||
// zlog's tests use zerolog.New(&buf) and assert on Info-level output. zerolog's
|
||||
// (*Logger).should() gates emission on the global level, so any global level
|
||||
// above Info would silently break the assertions.
|
||||
//
|
||||
// Today zlog does not transitively import hscontrol/types, so the test
|
||||
// silencing init() in hscontrol/types/testlog.go does not run in this binary.
|
||||
// This init defends against that changing in the future: if a future import
|
||||
// chain pulls in hscontrol/types, this file will still ensure trace-level
|
||||
// output is available for zlog's assertions.
|
||||
func init() {
|
||||
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
||||
}
|
||||
Reference in New Issue
Block a user