Repository navigation
fix(container): prevent panic on non-positive REMOTE_LOG_FETCH_INTERVAL (#4419) - #4430
Git-rey-08 wants to merge 2 commits into
Conversation
…AL (gofr-dev#4419) - Validate that REMOTE_LOG_FETCH_INTERVAL is positive (>0) in container.go, defaulting to 15s and logging an error if invalid - Avoid starting ticker in dynamic_level_logger.go when levelFetchInterval <= 0 - Add unit tests verifying non-positive and invalid interval handling
NitinKumar004
left a comment
There was a problem hiding this comment.
Thanks for picking this up, @Git-rey-08. I checked it at 4733b084 locally, with a real GoFr app built from development and from this branch.
What I verified
The fix does what #4419 asks. Same tiny app with a remote log server, REMOTE_LOG_FETCH_INTERVAL set three ways:
| interval | process | GET /hi |
"invalid value" log | |
|---|---|---|---|---|
| development | 0 |
dies (panic: non-positive interval for NewTicker) |
– | no |
| development | -10 |
dies (same panic) | – | no |
| this PR | 0 |
up | 200 | yes |
| this PR | -10 |
up | 200 | yes |
| both | 5 |
up | 200 | no |
Build, vet and gofmt are clean, and go test -race on pkg/gofr/container passes. The races -race reports in remotelogger are already on development (14 there, 13 here), so they're not from this PR.
Blocking
- Two new lint findings that the Code Quality job will flag (golangci-lint v2.12.2, same config as CI):
Both need a blank line: one before
pkg/gofr/container/container.go:123:3: missing whitespace above this line (too many statements above if) (wsl_v5) pkg/gofr/logging/remotelogger/dynamic_level_logger_test.go:704:3: missing whitespace above this line (invalid statement above assign) (wsl_v5)isInvalid := ..., and one betweenw.Header().Set(...)andbody := ....
Should fix
-
The new tests can't fail for the bug they're meant to catch.
remotelogger.NewstartsUpdateLogLevelwithgo l.UpdateLogLevel(), so theNewTickerpanic happens on a background goroutine.assert.NotPanicsonly recovers panics on the test's own goroutine. Both tests return before that goroutine gets to the ticker.- I checked this with mutations, running at
-count=1as CI does:- Remove the
if r.levelFetchInterval <= 0 { return }guard:TestRemoteLogger_UpdateLevel_NonPositiveIntervalpasses, and the wholeremoteloggerpackage passes too (5/5 runs). - Remove
|| levelFetchConfig <= 0from the container:TestContainer_RemoteLogFetchIntervalValidationpasses. The logger guard hides the change, and the test doesn't check the log or the interval. - Revert both (i.e.
development): the new container test still passes on its own. The package only fails because the leftover goroutine panics later, in the middle of some other test.
- Remove the
Two deterministic versions that do fail on those mutations. I ran both, including with
-race -count=3:// dynamic_level_logger_test.go: call UpdateLogLevel synchronously, so a NewTicker panic is on the test goroutine. func TestRemoteLogger_UpdateLogLevel_NonPositiveIntervalReturns(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{"data":{"serviceName":"test-service","logLevel":"DEBUG"}}`)) })) defer srv.Close() for _, interval := range []time.Duration{0, -5 * time.Second} { r := &remoteLogger{remoteURL: srv.URL, levelFetchInterval: interval, currentLevel: logging.INFO, Logger: logging.NewLogger(logging.INFO)} assert.NotPanics(t, r.UpdateLogLevel) assert.Equal(t, logging.DEBUG, r.currentLevel, "the initial fetch still runs") } }
// container_test.go: assert the fallback actually happened, using an httptest server instead of 127.0.0.1:8080. func TestContainer_RemoteLogFetchIntervalInvalidLogsDefault(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{"data":{"serviceName":"x","logLevel":"INFO"}}`)) })) defer srv.Close() for _, interval := range []string{"0", "-10", "invalid"} { out := testutil.StderrOutputForFunc(func() { NewContainer(config.NewMockConfig(map[string]string{ "REMOTE_LOG_URL": srv.URL, "REMOTE_LOG_FETCH_INTERVAL": interval, })) }) assert.Contains(t, out, "invalid value for REMOTE_LOG_FETCH_INTERVAL", "interval %q", interval) } }
mutation tests in this PR tests above drop the remotelogger <= 0guardpass fail drop || levelFetchConfig <= 0in the containerpass fail
Smaller notes
- nit: with the guard,
remotelogger.New(..., 0)now means "fetch once, never poll". The container no longer passes0, so only direct callers ofNewsee this, but a one-line comment on the guard would make it clear that it's intentional. - nit:
docs/references/configscould say the value must be a positive number of seconds and that anything else falls back to 15. - nit: the PR description lost its inline code (e.g. "Validates in to ensure it is positive ()"). Probably the backticks got stripped when pasting.
Requesting changes for the lint findings, since Code Quality will fail on them. With those fixed and the tests tightened, this looks good to me.
Umang01-hash
left a comment
There was a problem hiding this comment.
Verified the bug and the fix end-to-end — the two-layer approach is the right shape (container clamps <=0→15s + warns; the UpdateLogLevel guard is the real root-cause fix for every New caller). Two things before this can merge:
1. CI Code Quality (lint) is red — 2 wsl_v5 violations (new issues), please fix:
container.go:123— blank line needed aboveisInvalid :=("too many statements above if").dynamic_level_logger_test.go:704— blank line needed abovebody :=("invalid statement above assign").
2. The new tests don't actually catch this regression. The panic fires in the goroutine New spawns, after an async fetch — assert.NotPanics can't recover another goroutine's panic, and nothing waits for it. I ran both new tests against the un-fixed source and they pass green, so they wouldn't fail if the guard is reverted. Suggest calling UpdateLogLevel() synchronously with a non-positive interval (it returns immediately with the guard; on the old code NewTicker(0) panics in the calling goroutine, so NotPanics fails-on-revert). That gives a real regression guard.
Logic is correct otherwise — build/vet/fmt clean, no breaking API change, scope is tight.
| if c.Logger == nil { | ||
| levelFetchConfig, err := strconv.Atoi(conf.GetOrDefault("REMOTE_LOG_FETCH_INTERVAL", "15")) | ||
| if err != nil { | ||
| isInvalid := err != nil || levelFetchConfig <= 0 |
There was a problem hiding this comment.
wsl_v5 (CI red): "too many statements above if". Add a blank line above this isInvalid := line so only one statement is cuddled with the if.
| func TestRemoteLogger_UpdateLevel_NonPositiveInterval(t *testing.T) { | ||
| mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { | ||
| w.Header().Set("Content-Type", "application/json") | ||
| body := `{ "data": { "serviceName": "test-service","logLevel":"DEBUG" } }` |
There was a problem hiding this comment.
wsl_v5 (CI red): "invalid statement above assign". Add a blank line above body :=.
| })) | ||
| defer mockServer.Close() | ||
|
|
||
| assert.NotPanics(t, func() { |
There was a problem hiding this comment.
This doesn't guard the regression: New panics in a spawned goroutine after an async fetch, and assert.NotPanics only catches panics in the calling goroutine (no wait either). I ran this test against the un-fixed source and it passes. Call UpdateLogLevel() synchronously with a non-positive interval instead — it returns via the new guard, and on the old code NewTicker(0) panics in-line so this would fail-on-revert.
Description
Breaking Changes (if applicable):
None.
Additional Information:
All unit tests pass and code coverage is preserved.
Checklist: