From 3da20cab8ad305953d9ccd42716b8f7b0b2a36e9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 23 Feb 2023 13:46:07 +0000 Subject: [PATCH] fix: Use SyscallConn for isTTY which is safe during file close --- internal/entryhuman/entry.go | 23 ++++++++++++++++++++--- internal/entryhuman/entry_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/internal/entryhuman/entry.go b/internal/entryhuman/entry.go index 4e741c9..76b679c 100644 --- a/internal/entryhuman/entry.go +++ b/internal/entryhuman/entry.go @@ -12,6 +12,7 @@ import ( "runtime/debug" "strconv" "strings" + "syscall" "time" "github.com/fatih/color" @@ -163,9 +164,25 @@ func isTTY(w io.Writer) bool { if w == forceColorWriter { return true } - f, ok := w.(interface { - Fd() uintptr - }) + // SyscallConn is safe during file close. + if sc, ok := w.(interface { + SyscallConn() (syscall.RawConn, error) + }); ok { + conn, err := sc.SyscallConn() + if err != nil { + return false + } + var isTerm bool + err = conn.Control(func(fd uintptr) { + isTerm = terminal.IsTerminal(int(fd)) + }) + if err != nil { + return false + } + return isTerm + } + // Fallback to unsafe Fd. + f, ok := w.(interface{ Fd() uintptr }) return ok && terminal.IsTerminal(int(f.Fd())) } diff --git a/internal/entryhuman/entry_test.go b/internal/entryhuman/entry_test.go index f7fc596..8ec0cac 100644 --- a/internal/entryhuman/entry_test.go +++ b/internal/entryhuman/entry_test.go @@ -91,4 +91,32 @@ func TestEntry(t *testing.T) { }) assert.Equal(t, "entry", "\x1b[0m\x1b[0m0001-01-01 00:00:00.000 \x1b[91m[CRITICAL]\x1b[0m\t\x1b[36m<.:0> \x1b[0m\t\"\"\t{\x1b[34m\"hey\"\x1b[0m: \x1b[32m\"hi\"\x1b[0m}", act) }) + + t.Run("isTTY during file close", func(t *testing.T) { + t.Parallel() + + tmpdir := t.TempDir() + f, err := ioutil.TempFile(tmpdir, "slog") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + done := make(chan struct{}, 2) + go func() { + _ = entryhuman.Fmt(f, slog.SinkEntry{ + Level: slog.LevelCritical, + Fields: slog.M( + slog.F("hey", "hi"), + ), + }) + done <- struct{}{} + }() + go func() { + _ = f.Close() + done <- struct{}{} + }() + <-done + <-done + }) }