-
Notifications
You must be signed in to change notification settings - Fork 25
Always write debug logs to a file #1983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,12 @@ import ( | |
| "log/slog" | ||
| "os" | ||
| "os/signal" | ||
| "path/filepath" | ||
| "runtime/debug" | ||
| "sync" | ||
|
|
||
| "github.com/DefangLabs/defang/src/cmd/cli/command" | ||
| "github.com/DefangLabs/defang/src/pkg/cli/client" | ||
| "github.com/DefangLabs/defang/src/pkg/logs" | ||
| "github.com/DefangLabs/defang/src/pkg/term" | ||
| "github.com/DefangLabs/defang/src/pkg/track" | ||
|
|
@@ -28,6 +31,11 @@ func main() { | |
| setUidGidFromFile(homeDir) | ||
| } | ||
|
|
||
| // Set up always-on debug log file; all log levels are written here. | ||
| // defer handles the normal-exit path; the explicit call handles os.Exit. | ||
| flushDebugLog := setupDebugLog() | ||
| defer flushDebugLog() | ||
|
|
||
| // Handle Ctrl+C so we can exit gracefully | ||
| ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) | ||
|
|
||
|
|
@@ -47,6 +55,7 @@ func main() { | |
| if !ok { | ||
| ec = 1 // should not happen since we always return ExitCode | ||
| } | ||
| flushDebugLog() // os.Exit skips defers, so flush explicitly | ||
| os.Exit(int(ec)) | ||
| } | ||
| } | ||
|
|
@@ -56,3 +65,34 @@ func skipLines(buf []byte, n int) []byte { | |
| lines := bytes.SplitN(buf, []byte{'\n'}, n) | ||
| return lines[len(lines)-1] | ||
| } | ||
|
|
||
| // setupDebugLog creates a temp debug log file, wires it into DefaultTerm, and | ||
| // returns a flush function that closes and atomically renames the file to | ||
| // debug.log. The flush function is idempotent — safe to call multiple times. | ||
| func setupDebugLog() func() { | ||
| stateDir := client.StateDir | ||
| if err := os.MkdirAll(stateDir, 0700); err != nil { | ||
| return func() {} | ||
| } | ||
|
|
||
| finalPath := filepath.Join(stateDir, "debug.log") | ||
| f, err := os.CreateTemp(stateDir, "debug-*.log") | ||
| if err != nil { | ||
| return func() {} | ||
|
lionello marked this conversation as resolved.
|
||
| } | ||
| tmpPath := f.Name() | ||
|
|
||
| term.SetDebugLog(f) | ||
|
|
||
| var once sync.Once | ||
| return func() { | ||
| once.Do(func() { | ||
| term.SetDebugLog(nil) | ||
| f.Close() | ||
| os.Rename(tmpPath, finalPath) //nolint:errcheck | ||
| if term.DoDebug() { | ||
| term.Infof("Debug log written to: %s", finalPath) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels weird: when
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we don't write this message, how will the user know how to read the debug logs? |
||
| } | ||
|
Comment on lines
+90
to
+95
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: On Windows, However, Go explicitly documents that on non-Unix platforms (including Windows) Sources: Check A failed 🤖 Prompt for AI Agents |
||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,9 +66,6 @@ func (h *termHandler) Handle(ctx context.Context, r slog.Record) error { | |
| } | ||
|
|
||
| func (h *termHandler) Enabled(ctx context.Context, level slog.Level) bool { | ||
| if level == slog.LevelDebug { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is why I think we should use the logging infrastructure: the termHandler log-level decides what gets printed, the logger decides what gets logged/written to file. |
||
| return h.t.DoDebug() | ||
| } | ||
| return true | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -74,9 +74,10 @@ func TestAddingPrefix(t *testing.T) { | |||||||||||||||||||||||||||
| t.Cleanup(func() { | ||||||||||||||||||||||||||||
| DefaultTerm = defaultTerm | ||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||
| var stdout, stderr bytes.Buffer | ||||||||||||||||||||||||||||
| var stdout, stderr, debugLog bytes.Buffer | ||||||||||||||||||||||||||||
| DefaultTerm = NewTerm(os.Stdin, &stdout, &stderr) | ||||||||||||||||||||||||||||
| DefaultTerm.SetDebug(true) | ||||||||||||||||||||||||||||
| DefaultTerm.SetDebugLog(&debugLog) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Debug("Hello, World!") | ||||||||||||||||||||||||||||
| Debugf("Hello, %s!", "World") | ||||||||||||||||||||||||||||
|
|
@@ -110,16 +111,28 @@ func TestAddingPrefix(t *testing.T) { | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| expectedErr := []string{ | ||||||||||||||||||||||||||||
| if stderr.String() != "" { | ||||||||||||||||||||||||||||
| t.Errorf("Expected stderr to be empty (debug goes to log file), got %q", stderr.String()) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| expectedLog := []string{ | ||||||||||||||||||||||||||||
| " - Hello, World!", | ||||||||||||||||||||||||||||
| " - Hello, World!", | ||||||||||||||||||||||||||||
| " - Hello, World!", | ||||||||||||||||||||||||||||
| " - Hello, World!", | ||||||||||||||||||||||||||||
| " * Hello, World!", | ||||||||||||||||||||||||||||
| " * Hello, World!", | ||||||||||||||||||||||||||||
| " * Hello, World!", | ||||||||||||||||||||||||||||
| " * Hello, World!", | ||||||||||||||||||||||||||||
| " ! Hello, World!", | ||||||||||||||||||||||||||||
| " ! Hello, World!", | ||||||||||||||||||||||||||||
| " ! Hello, World!", | ||||||||||||||||||||||||||||
| " ! Hello, World!", | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| gotErr := strings.Split(strings.TrimRight(stderr.String(), "\n"), "\n") | ||||||||||||||||||||||||||||
| for i, line := range gotErr { | ||||||||||||||||||||||||||||
| if line != expectedErr[i] { | ||||||||||||||||||||||||||||
| t.Errorf("Expected line %v in stderr to be %q, got %q", i, expectedErr[i], line) | ||||||||||||||||||||||||||||
| gotLog := strings.Split(strings.TrimRight(debugLog.String(), "\n"), "\n") | ||||||||||||||||||||||||||||
| for i, line := range gotLog { | ||||||||||||||||||||||||||||
| if line != expectedLog[i] { | ||||||||||||||||||||||||||||
| t.Errorf("Expected line %v in debug log to be %q, got %q", i, expectedLog[i], line) | ||||||||||||||||||||||||||||
|
Comment on lines
+132
to
+135
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assert the debug-log length before comparing lines. This loop only walks Proposed fix gotLog := strings.Split(strings.TrimRight(debugLog.String(), "\n"), "\n")
-for i, line := range gotLog {
- if line != expectedLog[i] {
- t.Errorf("Expected line %v in debug log to be %q, got %q", i, expectedLog[i], line)
- }
-}
+if len(gotLog) != len(expectedLog) {
+ t.Fatalf("Expected %d lines in debug log, got %d: %q", len(expectedLog), len(gotLog), gotLog)
+}
+for i, expected := range expectedLog {
+ if gotLog[i] != expected {
+ t.Errorf("Expected line %v in debug log to be %q, got %q", i, expected, gotLog[i])
+ }
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Always-on log setup now persists high-risk debug payloads by default.
Because
setupDebugLog()runs for every invocation, the newterm.Debug*dumps insrc/pkg/cli/compose/loader.go,src/pkg/cli/generate.go, andsrc/pkg/cli/composeUp.gonow land on disk even when--debugis off. Those payloads include full compose YAML and generated file contents, so this change silently retains secrets and proprietary source unless those call sites are redacted or kept behind the explicit debug flag.🤖 Prompt for AI Agents