Skip to content

Commit adc8515

Browse files
committed
feat: forward errors to stderr
1 parent 1be5e2e commit adc8515

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

backend/pkg/logger/trace/trace.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ func InitTrace(traceLevel string) *os.File {
6969
// Human-friendly console writer that prints logs to stdout.
7070
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
7171

72+
// Console writer that prints warn/error/fatal logs to stderr so the
73+
// parent process (Electron) can capture them via the stderr pipe.
74+
stderrConsoleWriter := &levelFilterWriter{
75+
w: zerolog.ConsoleWriter{Out: os.Stderr},
76+
minLevel: zerolog.WarnLevel,
77+
}
78+
7279
// Try to create/open the file for writing logs. On failure, fall back to console only and exit.
7380
file, err := loggerbase.CreateFile(traceDir, Trace, traceFile)
7481
if err != nil {
@@ -78,8 +85,8 @@ func InitTrace(traceLevel string) *os.File {
7885
return nil
7986
}
8087

81-
// Write logs to both the console and the file.
82-
multi := zerolog.MultiLevelWriter(consoleWriter, file)
88+
// Write logs to stdout, stderr (warn+), and the file.
89+
multi := zerolog.MultiLevelWriter(consoleWriter, stderrConsoleWriter, file)
8390

8491
// Create a new logger that includes timestamps and caller information.
8592
trace.Logger = zerolog.New(multi).With().Timestamp().Caller().Logger()
@@ -96,3 +103,24 @@ func InitTrace(traceLevel string) *os.File {
96103

97104
return file
98105
}
106+
107+
// levelFilterWriter is a zerolog.LevelWriter that forwards log entries to w
108+
// only when their level is >= minLevel. This lets us route warn/error/fatal
109+
// to stderr while keeping info/debug on stdout.
110+
type levelFilterWriter struct {
111+
w zerolog.ConsoleWriter
112+
minLevel zerolog.Level
113+
}
114+
115+
// Write satisfies the io.Writer interface required by zerolog.LevelWriter.
116+
// MultiLevelWriter always calls WriteLevel instead.
117+
func (f *levelFilterWriter) Write(p []byte) (int, error) {
118+
return f.w.Write(p)
119+
}
120+
121+
func (f *levelFilterWriter) WriteLevel(l zerolog.Level, p []byte) (int, error) {
122+
if l >= f.minLevel {
123+
return f.w.Write(p)
124+
}
125+
return len(p), nil
126+
}

electron-app/src/processes/backend.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,30 @@ let backendProcess = null;
2828
// Common log window instance for all backend processes
2929
let storedLogWindow = null;
3030

31-
// Store error messages (keep last 10 lines to avoid memory issues)
31+
// Store error messages accumulated from the current process run
3232
let lastBackendError = null;
3333

34+
const ERROR_HINTS = [
35+
{
36+
pattern: /bind: The requested address is not valid/,
37+
message: "Network address unavailable",
38+
advice:
39+
"The configured IP address doesn't exist on this machine. Check your network adapter or ADJ.",
40+
},
41+
{
42+
pattern: /failed to start UDP server/,
43+
message: "UDP server failed to start",
44+
advice: "Another process may already be using this port.",
45+
},
46+
];
47+
48+
function getHint(errorText) {
49+
const match = ERROR_HINTS.find(({ pattern }) => pattern.test(errorText));
50+
return match
51+
? `${match.message}\n\n${match.advice}\n\n${errorText}`
52+
: errorText;
53+
}
54+
3455
/**
3556
* Starts the backend process by spawning the backend binary with the user configuration.
3657
* @returns {void}
@@ -73,6 +94,9 @@ async function startBackend(logWindow = null) {
7394
cwd: workingDir,
7495
});
7596

97+
console.log("[DEBUG] backendProcess.stderr:", backendProcess.stderr);
98+
console.log("[DEBUG] backendProcess.stdout:", backendProcess.stdout);
99+
76100
// Log stdout output from backend
77101
backendProcess.stdout.on("data", (data) => {
78102
const text = data.toString().trim();
@@ -101,6 +125,7 @@ async function startBackend(logWindow = null) {
101125
backendProcess.stderr.on("data", (data) => {
102126
const errorMsg = data.toString().trim();
103127
logger.backend.error(errorMsg);
128+
console.log("[DEBUG stderr chunk]", JSON.stringify(errorMsg));
104129
lastBackendError = errorMsg;
105130

106131
// Send error message to log window
@@ -129,7 +154,8 @@ async function startBackend(logWindow = null) {
129154
let errorMessage = `Backend exited with code ${code}`;
130155

131156
if (lastBackendError) {
132-
errorMessage += `\n\n${lastBackendError}`;
157+
const stripped = lastBackendError.replace(/\x1b\[[0-9;]*m/g, "");
158+
errorMessage += `\n\n${getHint(stripped)}`;
133159
} else {
134160
errorMessage += "\n\n(No error output captured)";
135161
}

0 commit comments

Comments
 (0)