Skip to content

Commit 05e1c8c

Browse files
authored
Merge pull request #275 from majiayu000/fix-250-need-a-method-to-avoid-the-pan-0101-1659
fix: add nil check for os.Stdout to prevent panic on Windows services
2 parents bd819a5 + 3a0d3b0 commit 05e1c8c

File tree

3 files changed

+72
-6
lines changed

3 files changed

+72
-6
lines changed

color.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ var (
1919
// set (regardless of its value). This is a global option and affects all
2020
// colors. For more control over each color block use the methods
2121
// DisableColor() individually.
22-
NoColor = noColorIsSet() || os.Getenv("TERM") == "dumb" ||
23-
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
22+
NoColor = noColorIsSet() || os.Getenv("TERM") == "dumb" || !stdoutIsTerminal()
2423

2524
// Output defines the standard output of the print functions. By default,
26-
// os.Stdout is used.
27-
Output = colorable.NewColorableStdout()
25+
// stdOut() is used.
26+
Output = stdOut()
2827

29-
// Error defines a color supporting writer for os.Stderr.
30-
Error = colorable.NewColorableStderr()
28+
// Error defines the standard error of the print functions. By default,
29+
// stdErr() is used.
30+
Error = stdErr()
3131

3232
// colorsCache is used to reduce the count of created Color objects and
3333
// allows to reuse already created objects with required Attribute.
@@ -40,6 +40,33 @@ func noColorIsSet() bool {
4040
return os.Getenv("NO_COLOR") != ""
4141
}
4242

43+
// stdoutIsTerminal returns true if os.Stdout is a terminal.
44+
// Returns false if os.Stdout is nil (e.g., when running as a Windows service).
45+
func stdoutIsTerminal() bool {
46+
if os.Stdout == nil {
47+
return false
48+
}
49+
return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())
50+
}
51+
52+
// stdOut returns a writer for color output.
53+
// Returns io.Discard if os.Stdout is nil (e.g., when running as a Windows service).
54+
func stdOut() io.Writer {
55+
if os.Stdout == nil {
56+
return io.Discard
57+
}
58+
return colorable.NewColorableStdout()
59+
}
60+
61+
// stdErr returns a writer for color error output.
62+
// Returns io.Discard if os.Stderr is nil (e.g., when running as a Windows service).
63+
func stdErr() io.Writer {
64+
if os.Stderr == nil {
65+
return io.Discard
66+
}
67+
return colorable.NewColorableStderr()
68+
}
69+
4370
// Color defines a custom color object which is defined by SGR parameters.
4471
type Color struct {
4572
params []Attribute

color_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,42 @@ func Test_noColorIsSet(t *testing.T) {
236236
}
237237
}
238238

239+
func TestStdoutIsTerminal_NilStdout(t *testing.T) {
240+
stdout := os.Stdout
241+
os.Stdout = nil
242+
t.Cleanup(func() {
243+
os.Stdout = stdout
244+
})
245+
246+
if stdoutIsTerminal() {
247+
t.Fatal("stdoutIsTerminal() = true, want false")
248+
}
249+
}
250+
251+
func TestStdOut_NilStdout(t *testing.T) {
252+
stdout := os.Stdout
253+
os.Stdout = nil
254+
t.Cleanup(func() {
255+
os.Stdout = stdout
256+
})
257+
258+
if got := stdOut(); got != io.Discard {
259+
t.Fatalf("stdOut() = %v, want %v", got, io.Discard)
260+
}
261+
}
262+
263+
func TestStdErr_NilStderr(t *testing.T) {
264+
stderr := os.Stderr
265+
os.Stderr = nil
266+
t.Cleanup(func() {
267+
os.Stderr = stderr
268+
})
269+
270+
if got := stdErr(); got != io.Discard {
271+
t.Fatalf("stdErr() = %v, want %v", got, io.Discard)
272+
}
273+
}
274+
239275
func TestColorVisual(t *testing.T) {
240276
// First Visual Test
241277
Output = colorable.NewColorableStdout()

color_windows.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
func init() {
1010
// Opt-in for ansi color support for current process.
1111
// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences
12+
if os.Stdout == nil {
13+
return
14+
}
1215
var outMode uint32
1316
out := windows.Handle(os.Stdout.Fd())
1417
if err := windows.GetConsoleMode(out, &outMode); err != nil {

0 commit comments

Comments
 (0)