From ba68ac6a67c99fcfe92e9de09c3b11f226efdb92 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Fri, 3 Nov 2023 16:13:24 +0000 Subject: [PATCH] feat: add `log-dir` flag to vscodessh for debuggability --- cli/vscodessh.go | 28 +++++++++++++++++++++++----- cli/vscodessh_test.go | 17 ++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/cli/vscodessh.go b/cli/vscodessh.go index 19a836214773f..6b30b9c3c64ec 100644 --- a/cli/vscodessh.go +++ b/cli/vscodessh.go @@ -34,6 +34,7 @@ func (r *RootCmd) vscodeSSH() *clibase.Cmd { var ( sessionTokenFile string urlFile string + logDir string networkInfoDir string networkInfoInterval time.Duration ) @@ -129,13 +130,25 @@ func (r *RootCmd) vscodeSSH() *clibase.Cmd { } } + // The VS Code extension obtains the PID of the SSH process to + // read files to display logs and network info. + // + // We get the parent PID because it's assumed `ssh` is calling this + // command via the ProxyCommand SSH option. + pid := os.Getppid() + var logger slog.Logger - if r.verbose { - logger = slog.Make(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug) + if logDir != "" { + logFilePath := filepath.Join(logDir, fmt.Sprintf("%d.log", pid)) + logFile, err := fs.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY, 0o600) + if err != nil { + return xerrors.Errorf("open log file %q: %w", logFilePath, err) + } + defer logFile.Close() + logger = slog.Make(sloghuman.Sink(logFile)).Leveled(slog.LevelDebug) } - if r.disableDirect { - _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") + logger.Info(ctx, "direct connections disabled") } agentConn, err := client.DialWorkspaceAgent(ctx, agent.ID, &codersdk.DialWorkspaceAgentOptions{ Logger: logger, @@ -166,7 +179,7 @@ func (r *RootCmd) vscodeSSH() *clibase.Cmd { // // We get the parent PID because it's assumed `ssh` is calling this // command via the ProxyCommand SSH option. - networkInfoFilePath := filepath.Join(networkInfoDir, fmt.Sprintf("%d.json", os.Getppid())) + networkInfoFilePath := filepath.Join(networkInfoDir, fmt.Sprintf("%d.json", pid)) statsErrChan := make(chan error, 1) cb := func(start, end time.Time, virtual, _ map[netlogtype.Connection]netlogtype.Counts) { @@ -213,6 +226,11 @@ func (r *RootCmd) vscodeSSH() *clibase.Cmd { Description: "Specifies a directory to write network information periodically.", Value: clibase.StringOf(&networkInfoDir), }, + { + Flag: "log-dir", + Description: "Specifies a directory to write logs to.", + Value: clibase.StringOf(&logDir), + }, { Flag: "session-token-file", Description: "Specifies a file that contains a session token.", diff --git a/cli/vscodessh_test.go b/cli/vscodessh_test.go index dc3e65f5bdb9c..2fc83cdb58d14 100644 --- a/cli/vscodessh_test.go +++ b/cli/vscodessh_test.go @@ -43,6 +43,7 @@ func TestVSCodeSSH(t *testing.T) { "--url-file", "/url", "--session-token-file", "/token", "--network-info-dir", "/net", + "--log-dir", "/log", "--network-info-interval", "25ms", fmt.Sprintf("coder-vscode--%s--%s", user.Username, workspace.Name), ) @@ -50,13 +51,15 @@ func TestVSCodeSSH(t *testing.T) { waiter := clitest.StartWithWaiter(t, inv.WithContext(ctx)) - assert.Eventually(t, func() bool { - entries, err := afero.ReadDir(fs, "/net") - if err != nil { - return false - } - return len(entries) > 0 - }, testutil.WaitLong, testutil.IntervalFast) + for _, dir := range []string{"/net", "/log"} { + assert.Eventually(t, func() bool { + entries, err := afero.ReadDir(fs, dir) + if err != nil { + return false + } + return len(entries) > 0 + }, testutil.WaitLong, testutil.IntervalFast) + } waiter.Cancel() if err := waiter.Wait(); err != nil {