Skip to content

Commit 0578d20

Browse files
committed
Change os.Stderr reassign for Windows service
Previously we were reassigning os.Stderr to the panic.log file we create when getting asked to run Containerd as a Windows service. The panic.log file was used as a means to easily collect panic stacks as Windows services don't have regular standard IO, and the usual recommendation is to either write to the event log or just to a file in the case of running as a service. One place where this panic.log flow was biting us was with shim logging, which is forwarded from the shim and copied to os.Stderr directly which was causing shim logs to get forwarded to this panic.log file instead of just panics. We expose an additional `--log-file` flag if you ask to run a windows service which is the main way you'd get Containerd logs, and with this change all of the shim logging which would today end up in panic.log will now also go to this log file. Signed-off-by: Daniel Canter <[email protected]> (cherry picked from commit ee0f2e9) Signed-off-by: Daniel Canter <[email protected]>
1 parent 7438c0a commit 0578d20

1 file changed

Lines changed: 40 additions & 13 deletions

File tree

cmd/containerd/command/service_windows.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package command
1818

1919
import (
2020
"fmt"
21-
"io"
2221
"log"
2322
"os"
2423
"path/filepath"
@@ -214,7 +213,6 @@ func unregisterService() error {
214213
// to handle (un-)registering against Windows Service Control Manager (SCM).
215214
// It returns an indication to stop on successful SCM operation, and an error.
216215
func registerUnregisterService(root string) (bool, error) {
217-
218216
if unregisterServiceFlag {
219217
if registerServiceFlag {
220218
return true, fmt.Errorf("--register-service and --unregister-service cannot be used together: %w", errdefs.ErrInvalidArgument)
@@ -248,22 +246,57 @@ func registerUnregisterService(root string) (bool, error) {
248246
return true, err
249247
}
250248

251-
logOutput := io.Discard
249+
// The usual advice for Windows services is to either write to a log file or to the windows event
250+
// log, the former of which we've exposed here via a --log-file flag. We additionally write panic
251+
// stacks to a panic.log file to diagnose crashes. Below details the two different outcomes if
252+
// --log-file is specified or not:
253+
//
254+
// --log-file is *not* specified.
255+
// -------------------------------
256+
// -logrus, the stdlibs logging package and os.Stderr output will go to
257+
// NUL (Windows' /dev/null equivalent).
258+
// -Panics will write their stack trace to the panic.log file.
259+
// -Writing to the handle returned from GetStdHandle(STD_ERROR_HANDLE) will write
260+
// to the panic.log file as the underlying handle itself has been redirected.
261+
//
262+
// --log-file *is* specified
263+
// -------------------------------
264+
// -Logging to logrus, the stdlibs logging package or directly to
265+
// os.Stderr will all go to the log file specified.
266+
// -Panics will write their stack trace to the panic.log file.
267+
// -Writing to the handle returned from GetStdHandle(STD_ERROR_HANDLE) will write
268+
// to the panic.log file as the underlying handle itself has been redirected.
269+
var f *os.File
252270
if logFileFlag != "" {
253-
f, err := os.OpenFile(logFileFlag, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
271+
f, err = os.OpenFile(logFileFlag, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
254272
if err != nil {
255273
return true, fmt.Errorf("open log file %q: %w", logFileFlag, err)
256274
}
257-
logOutput = f
275+
} else {
276+
// Windows services start with NULL stdio handles, and thus os.Stderr and friends will be
277+
// backed by an os.File with a NULL handle. This means writes to os.Stderr will fail, which
278+
// isn't a huge issue as we want output to be discarded if the user doesn't ask for the log
279+
// file. However, writes succeeding but just going to the ether is a much better construct
280+
// so use devnull instead of relying on writes failing. We use devnull instead of io.Discard
281+
// as os.Stderr is an os.File and can't be assigned to io.Discard.
282+
f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
283+
if err != nil {
284+
return true, err
285+
}
258286
}
259-
logrus.SetOutput(logOutput)
287+
// Reassign os.Stderr to the log file or NUL. Shim logs are copied to os.Stderr
288+
// directly so this ensures those will end up in the log file as well if specified.
289+
os.Stderr = f
290+
// Assign the stdlibs log package in case of any miscellaneous uses by
291+
// dependencies.
292+
log.SetOutput(f)
293+
logrus.SetOutput(f)
260294
}
261295
return false, nil
262296
}
263297

264298
// launchService is the entry point for running the daemon under SCM.
265299
func launchService(s *server.Server, done chan struct{}) error {
266-
267300
if !runServiceFlag {
268301
return nil
269302
}
@@ -359,12 +392,6 @@ func initPanicFile(path string) error {
359392
return err
360393
}
361394

362-
// Reset os.Stderr to the panic file (so fmt.Fprintf(os.Stderr,...) actually gets redirected)
363-
os.Stderr = os.NewFile(panicFile.Fd(), "/dev/stderr")
364-
365-
// Force threads that panic to write to stderr (the panicFile handle now), otherwise it will go into the ether
366-
log.SetOutput(os.Stderr)
367-
368395
return nil
369396
}
370397

0 commit comments

Comments
 (0)