Compare commits

..

5 Commits
main ... main

Author SHA1 Message Date
9121da9aae Merge pull request 'fix: JSONHandler deadlock from recursive log.Println (closes #3)' (#4) from clawbot/simplelog:fix/json-handler-deadlock into main
Reviewed-on: sneak/simplelog#4
2026-02-08 18:29:55 +01:00
74ce052b77 Merge branch 'main' into fix/json-handler-deadlock 2026-02-08 18:29:12 +01:00
1eef38a5fa Merge pull request 'test: add deadlock regression test for JSONHandler (issue #3)' (#7) from clawbot/simplelog:test/jsonhandler-deadlock into main
Reviewed-on: sneak/simplelog#7
2026-02-08 18:27:15 +01:00
97a82e9b2c test: add deadlock regression test for JSONHandler
Reproduces issue #3 — JSONHandler.Handle() calling log.Println() causes
a deadlock when slog.SetDefault redirects log output back through slog.

This test hangs/fails on main and should pass once #4 is merged.
2026-02-08 09:21:08 -08:00
user
869b7ca4c3 fix: replace log.Println with fmt.Fprintln in JSONHandler to prevent deadlock 2026-02-08 09:15:17 -08:00
2 changed files with 36 additions and 2 deletions

View File

@ -3,8 +3,9 @@ package simplelog
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"log" "fmt"
"log/slog" "log/slog"
"os"
) )
type JSONHandler struct{} type JSONHandler struct{}
@ -15,7 +16,7 @@ func NewJSONHandler() *JSONHandler {
func (j *JSONHandler) Handle(ctx context.Context, record slog.Record) error { func (j *JSONHandler) Handle(ctx context.Context, record slog.Record) error {
jsonData, _ := json.Marshal(record) jsonData, _ := json.Marshal(record)
log.Println(string(jsonData)) fmt.Fprintln(os.Stdout, string(jsonData))
return nil return nil
} }

33
json_handler_test.go Normal file
View File

@ -0,0 +1,33 @@
package simplelog
import (
"log/slog"
"testing"
"time"
)
// TestJSONHandlerDeadlock verifies that JSONHandler.Handle does not deadlock
// when the default slog handler routes log.Println back through slog.
// On the unfixed code this test will hang (deadlock); with the fix it completes.
func TestJSONHandlerDeadlock(t *testing.T) {
handler := NewJSONHandler()
// Set our handler as the default so log.Println routes through slog
logger := slog.New(handler)
slog.SetDefault(logger)
done := make(chan struct{})
go func() {
// This call deadlocks on unfixed code because Handle() calls
// log.Println() which re-enters slog → Handle() → log.Println() …
slog.Info("test message")
close(done)
}()
select {
case <-done:
// success
case <-time.After(5 * time.Second):
t.Fatal("JSONHandler.Handle deadlocked: timed out after 5 seconds")
}
}